From 30030031b2fb2f9fdf4f0e96bfe9aaac5a61c8ca Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Mon, 24 Feb 2025 20:58:06 +0100 Subject: [PATCH] add checksum to savegame to track if the savegame has been modified Signed-off-by: OMGeeky --- .../AndorsTrail/AndorsTrailApplication.java | 2 +- .../rpg/AndorsTrail/context/WorldContext.java | 10 ++ .../AndorsTrail/model/ChecksumBuilder.java | 115 ++++++++++++++++++ .../rpg/AndorsTrail/model/GameStatistics.java | 45 +++++++ .../rpg/AndorsTrail/model/InterfaceData.java | 8 ++ .../rpg/AndorsTrail/model/ModelContainer.java | 7 ++ .../gpl/rpg/AndorsTrail/model/WorldData.java | 9 ++ .../model/ability/ActorCondition.java | 8 ++ .../rpg/AndorsTrail/model/actor/Monster.java | 40 ++++++ .../rpg/AndorsTrail/model/actor/Player.java | 58 +++++++++ .../rpg/AndorsTrail/model/item/Inventory.java | 18 +++ .../AndorsTrail/model/item/ItemContainer.java | 11 ++ .../gpl/rpg/AndorsTrail/model/item/Loot.java | 9 ++ .../AndorsTrail/model/map/MapCollection.java | 13 ++ .../model/map/MonsterSpawnArea.java | 9 ++ .../AndorsTrail/model/map/PredefinedMap.java | 27 ++++ .../rpg/AndorsTrail/savegames/Savegames.java | 45 +++++-- .../com/gpl/rpg/AndorsTrail/util/Coord.java | 7 ++ .../com/gpl/rpg/AndorsTrail/util/Range.java | 7 ++ 19 files changed, 436 insertions(+), 12 deletions(-) create mode 100644 AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ChecksumBuilder.java diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java index 0d2cba8a1..017abf801 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java @@ -32,7 +32,7 @@ public final class AndorsTrailApplication extends Application { public static final boolean IS_RELEASE_VERSION = !CURRENT_VERSION_DISPLAY.matches(".*[a-d].*"); public static final boolean DEVELOPMENT_INCOMPATIBLE_SAVEGAMES = DEVELOPMENT_DEBUGRESOURCES || DEVELOPMENT_DEBUGBUTTONS || DEVELOPMENT_FASTSPEED || !IS_RELEASE_VERSION; public static final int DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION = 999; - public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION : 80; + public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION : 81; private final AndorsTrailPreferences preferences = new AndorsTrailPreferences(); private WorldContext world = new WorldContext(); diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/context/WorldContext.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/context/WorldContext.java index a3bd99db5..4ebeb5714 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/context/WorldContext.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/context/WorldContext.java @@ -1,5 +1,6 @@ package com.gpl.rpg.AndorsTrail.context; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.model.ModelContainer; import com.gpl.rpg.AndorsTrail.model.ability.ActorConditionTypeCollection; import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection; @@ -13,6 +14,8 @@ import com.gpl.rpg.AndorsTrail.resource.ConversationLoader; import com.gpl.rpg.AndorsTrail.resource.VisualEffectCollection; import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager; +import java.security.DigestException; + public final class WorldContext { //Objectcollections public final ConversationLoader conversationLoader; @@ -62,4 +65,11 @@ public final class WorldContext { public void resetForNewGame() { maps.resetForNewGame(); } + + public byte[] getChecksum() throws DigestException { + ChecksumBuilder checksumBuilder = new ChecksumBuilder(); + model.addToChecksum(checksumBuilder); + maps.addToChecksum(checksumBuilder, this); + return checksumBuilder.build(); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ChecksumBuilder.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ChecksumBuilder.java new file mode 100644 index 000000000..fb430fa8f --- /dev/null +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ChecksumBuilder.java @@ -0,0 +1,115 @@ +package com.gpl.rpg.AndorsTrail.model; + +import android.os.Build; + +import com.gpl.rpg.AndorsTrail.util.L; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class ChecksumBuilder { + + public static final int CHECKSUM_LENGTH = 32;// 256 bits (depends on the hash algorithm) + public static final String CHECKSUM_ALGORITHM = "SHA-256"; //Should be available in all Android versions + private ByteBuffer buffer; + private final MessageDigest digest; + + private ChecksumBuilder(int initialCapacity, ByteOrder byteOrder) { + buffer = ByteBuffer.allocate(initialCapacity); + buffer.order(byteOrder); + try { + digest = MessageDigest.getInstance(CHECKSUM_ALGORITHM); // Or SHA-512 for even stronger hash + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Hash algorithm not found", e); + } + } + + private ChecksumBuilder(int initialCapacity) { + this(initialCapacity, ByteOrder.BIG_ENDIAN); // Default to big-endian + } + + public ChecksumBuilder() { + this(1024*10); // A reasonable default initial capacity + } + + // --- Methods for adding different data types --- + + public ChecksumBuilder add(String value) { + if (value != null) { + byte[] bytes; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + bytes = value.getBytes(StandardCharsets.UTF_8); + } else { + bytes = value.getBytes(); + } + add(bytes); + } else { + add(-1); // Use -1 to represent a null string + } + return this; + } + + public ChecksumBuilder add(byte[] bytes) { + add(bytes.length); // Add length prefix + ensureCapacity(bytes.length + 4); // +4 for length prefix (int) + buffer.put(bytes); + return this; + } + + public ChecksumBuilder add(boolean value) { + ensureCapacity(1); + buffer.put(value ? (byte) 1 : (byte) 0); + return this; + } + + public ChecksumBuilder add(long value) { + ensureCapacity(8); + buffer.putLong(value); + return this; + } + + public ChecksumBuilder add(int value) { + ensureCapacity(4); + buffer.putInt(value); + return this; + } + + public ChecksumBuilder add(float value) { + ensureCapacity(8); + buffer.putFloat(value); + return this; + } + + public ChecksumBuilder add(double value) { + ensureCapacity(4); + buffer.putDouble(value); + return this; + } + + // --- Method to finalize and get the checksum --- + + public byte[] build() throws DigestException { + buffer.flip(); // Prepare for reading + digest.update(buffer);// Only use the actually used part of the buffer + buffer.flip(); // Prepare for further writing + return digest.digest(); + } + + + // --- Utility method to ensure sufficient capacity --- + private void ensureCapacity(int required) { + if (buffer.remaining() < required) { + int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + required); + ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity); + newBuffer.order(buffer.order()); + buffer.flip(); // Prepare for reading + newBuffer.put(buffer); // Copy existing data + buffer = newBuffer; // Assign the new buffer to the field + } + } + +} diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/GameStatistics.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/GameStatistics.java index fc2ed7721..606a2320e 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/GameStatistics.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/GameStatistics.java @@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.model; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -22,6 +23,8 @@ import com.gpl.rpg.AndorsTrail.model.quest.Quest; import com.gpl.rpg.AndorsTrail.util.HashMapHelper; public final class GameStatistics { + private boolean isAlteredSavegame = false; + private byte[] checksum = new byte[ChecksumBuilder.CHECKSUM_LENGTH]; private int deaths = 0; private final HashMap killedMonstersByTypeID = new HashMap(); private final HashMap killedMonstersByName = new HashMap(); @@ -67,6 +70,7 @@ public final class GameStatistics { public boolean hasUnlimitedLives() { return startLives == -1; } public int getStartLives() { return startLives; } + public boolean getIsAlteredSavegame() { return isAlteredSavegame; } public int getLivesLeft() { return hasUnlimitedLives() ? -1 : startLives - deaths; } @@ -158,6 +162,18 @@ public final class GameStatistics { } }; + public void setChecksum(byte[] checksum) { + if (checksum.length != ChecksumBuilder.CHECKSUM_LENGTH) throw new IllegalArgumentException("Invalid checksum length."); + this.checksum = checksum; + } + + public boolean compareChecksum(byte[] checksum) { + return this.checksum.length == checksum.length && MessageDigest.isEqual(this.checksum, checksum); + } + + public void markAsAlteredSavegame() { + isAlteredSavegame = true; + } // ====== PARCELABLE =================================================================== @@ -194,6 +210,11 @@ public final class GameStatistics { this.startLives = src.readInt(); this.unlimitedSaves = src.readBoolean(); + if (fileversion < 81) return; + this.isAlteredSavegame = src.readBoolean(); + final int checksumLength = src.readInt(); + this.checksum = new byte[checksumLength]; + if( src.read(checksum) != checksumLength) throw new IOException("Failed to read full checksum."); } public void writeToParcel(DataOutputStream dest) throws IOException { @@ -213,5 +234,29 @@ public final class GameStatistics { dest.writeInt(spentGold); dest.writeInt(startLives); dest.writeBoolean(unlimitedSaves); + dest.writeBoolean(isAlteredSavegame); + dest.writeInt(checksum.length); + dest.write(checksum); + } + + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(deaths); + Set > set = killedMonstersByTypeID.entrySet(); + builder.add(set.size()); + for (Entry e : set) { + builder.add(e.getKey()); + builder.add(e.getValue()); + } + set = usedItems.entrySet(); + builder.add(set.size()); + for (Entry e : set) { + builder.add(e.getKey()); + builder.add(e.getValue()); + } + builder.add(spentGold); + builder.add(startLives); + builder.add(unlimitedSaves); + builder.add(isAlteredSavegame); } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/InterfaceData.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/InterfaceData.java index b595049a0..2dfd39f30 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/InterfaceData.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/InterfaceData.java @@ -51,4 +51,12 @@ public final class InterfaceData { } dest.writeUTF(selectedTabHeroInfo); } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(isMainActivityVisible); + builder.add(isInCombat); + builder.add(selectedPosition != null); + if (selectedPosition != null) selectedPosition.addToChecksum(builder); + builder.add(selectedTabHeroInfo); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ModelContainer.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ModelContainer.java index 2be421d2a..8e4460e46 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ModelContainer.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ModelContainer.java @@ -49,4 +49,11 @@ public final class ModelContainer { statistics.writeToParcel(dest); worldData.writeToParcel(dest); } + public void addToChecksum(ChecksumBuilder builder){ + player.addToChecksum(builder); + builder.add(currentMaps.map.name); + uiSelections.addToChecksum(builder); + statistics.addToChecksum(builder); + worldData.addToChecksum(builder); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/WorldData.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/WorldData.java index e6cc6fc1a..67be4286b 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/WorldData.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/WorldData.java @@ -114,4 +114,13 @@ public final class WorldData { dest.writeLong(e.getValue()); } } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(worldTime); + builder.add(timers.size()); + for(Map.Entry e : timers.entrySet()) { + builder.add(e.getKey()); + builder.add(e.getValue()); + } + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ability/ActorCondition.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ability/ActorCondition.java index 2ccdefb45..b372c3f0f 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ability/ActorCondition.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/ability/ActorCondition.java @@ -5,6 +5,7 @@ import java.io.DataOutputStream; import java.io.IOException; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; public final class ActorCondition { public static final int MAGNITUDE_REMOVE_ALL = -99; @@ -51,4 +52,11 @@ public final class ActorCondition { dest.writeInt(magnitude); dest.writeInt(duration); } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(conditionType.conditionTypeID); + builder.add(magnitude); + builder.add(duration); + } + } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Monster.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Monster.java index 063394013..28c81cc79 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Monster.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Monster.java @@ -6,6 +6,7 @@ import java.io.IOException; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.controller.Constants; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.model.ability.ActorCondition; import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection; import com.gpl.rpg.AndorsTrail.model.item.DropList; @@ -193,4 +194,43 @@ public final class Monster extends Actor { dest.writeBoolean(false); } } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(getMonsterTypeID()); + if (attackCost == monsterType.attackCost + && attackChance == monsterType.attackChance + && criticalSkill == monsterType.criticalSkill + && criticalMultiplier == monsterType.criticalMultiplier + && damagePotential.equals(monsterType.damagePotential) + && blockChance == monsterType.blockChance + && damageResistance == monsterType.damageResistance + ) { + builder.add(false); + } else { + builder.add(true); + builder.add(attackCost); + builder.add(attackChance); + builder.add(criticalSkill); + builder.add(criticalMultiplier); + damagePotential.addToChecksum(builder); + builder.add(blockChance); + builder.add(damageResistance); + } + ap.addToChecksum(builder); + health.addToChecksum(builder); + position.addToChecksum(builder); + builder.add(conditions.size()); + for (ActorCondition c : conditions) { + c.addToChecksum(builder); + } + builder.add(moveCost); + + builder.add(forceAggressive); + if (shopItems != null) { + builder.add(true); + shopItems.addToChecksum(builder); + } else { + builder.add(false); + } + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Player.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Player.java index 4b1c70951..a99a7e93e 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Player.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/actor/Player.java @@ -19,6 +19,7 @@ import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; import com.gpl.rpg.AndorsTrail.context.ControllerContext; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.controller.Constants; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.model.ability.ActorCondition; import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection; import com.gpl.rpg.AndorsTrail.model.item.DropListCollection; @@ -475,5 +476,62 @@ public final class Player extends Actor { dest.writeUTF(id); dest.writeLong(savedVersion); } + public void addToChecksum(ChecksumBuilder builder) { + //builder.add(baseTraits.iconID);// Do not add to checksum so that it can be changed without invalidating checksums + builder.add(baseTraits.maxAP); + builder.add(baseTraits.maxHP); + //builder.add(name);// Do not add to checksum so that it can be changed without invalidating checksums + builder.add(moveCost); // TODO: Should we really write this? + builder.add(baseTraits.attackCost); + builder.add(baseTraits.attackChance); + builder.add(baseTraits.criticalSkill); + builder.add(baseTraits.criticalMultiplier); + baseTraits.damagePotential.addToChecksum(builder); + builder.add(baseTraits.blockChance); + builder.add(baseTraits.damageResistance); + builder.add(baseTraits.moveCost); + + ap.addToChecksum(builder); + health.addToChecksum(builder); + position.addToChecksum(builder); + builder.add(conditions.size()); + for (ActorCondition c : conditions) { + c.addToChecksum(builder); + } + builder.add(immunities.size()); + for (ActorCondition c : immunities) { + c.addToChecksum(builder); + } + lastPosition.addToChecksum(builder); + nextPosition.addToChecksum(builder); + builder.add(level); + builder.add(totalExperience); + inventory.addToChecksum(builder); + builder.add(baseTraits.useItemCost); + builder.add(baseTraits.reequipCost); + builder.add(skillLevels.size()); + for (int i = 0; i < skillLevels.size(); ++i) { + builder.add(skillLevels.keyAt(i)); + builder.add(skillLevels.valueAt(i)); + } + builder.add(spawnMap); + builder.add(spawnPlace); + builder.add(questProgress.size()); + for(Entry > e : questProgress.entrySet()) { + builder.add(e.getKey()); + builder.add(e.getValue().size()); + for(int progress : e.getValue()) { + builder.add(progress); + } + } + builder.add(availableSkillIncreases); + builder.add(alignments.size()); + for(Entry e : alignments.entrySet()) { + builder.add(e.getKey()); + builder.add(e.getValue()); + } + builder.add(id); + builder.add(savedVersion); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Inventory.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Inventory.java index 25e247a98..d6bd780cb 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Inventory.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Inventory.java @@ -5,6 +5,7 @@ import java.io.DataOutputStream; import java.io.IOException; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForItemContainer; public final class Inventory extends ItemContainer { @@ -208,4 +209,21 @@ public final class Inventory extends ItemContainer { } } } + public void addToChecksum(ChecksumBuilder builder) { + super.addToChecksum(builder); + builder.add(gold); + builder.add(NUM_WORN_SLOTS); + for(int i = 0; i < NUM_WORN_SLOTS; ++i) { + if (wear[i] != null) { + builder.add(wear[i].id); + } + } + builder.add(NUM_QUICK_SLOTS); + for(int i = 0; i < NUM_QUICK_SLOTS; ++i) { + if (quickitem[i] != null) { + builder.add(quickitem[i].id); + } + } + + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/ItemContainer.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/ItemContainer.java index 5e9bf31ef..9fe2cd6d9 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/ItemContainer.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/ItemContainer.java @@ -8,6 +8,7 @@ import java.util.Collections; import java.util.Comparator; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.model.actor.Player; public class ItemContainer { @@ -42,6 +43,10 @@ public class ItemContainer { dest.writeUTF(itemType.id); dest.writeInt(quantity); } + public void addToChecksum(ChecksumBuilder builder) { + builder.add(itemType.id); + builder.add(quantity); + } } public void addItem(ItemType itemType, int quantity) { @@ -280,4 +285,10 @@ public class ItemContainer { e.writeToParcel(dest); } } + public void addToChecksum(ChecksumBuilder builder) { + builder.add(items.size()); + for (ItemEntry e : items) { + e.addToChecksum(builder); + } + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Loot.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Loot.java index 52d6bb0fe..5c5326b1c 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Loot.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/item/Loot.java @@ -5,6 +5,7 @@ import java.io.DataOutputStream; import java.io.IOException; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForItemContainer; import com.gpl.rpg.AndorsTrail.util.Coord; @@ -88,4 +89,12 @@ public final class Loot { position.writeToParcel(dest); dest.writeBoolean(isVisible); } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(exp); + builder.add(gold); + items.addToChecksum(builder); + position.addToChecksum(builder); + builder.add(isVisible); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java index 8f598c1cf..0ab2de987 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java @@ -11,6 +11,7 @@ import java.util.List; import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; import com.gpl.rpg.AndorsTrail.context.ControllerContext; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForMap; import com.gpl.rpg.AndorsTrail.util.L; @@ -99,4 +100,16 @@ public final class MapCollection { map.writeToParcel(dest, world); } } + + public void addToChecksum(ChecksumBuilder checksumBuilder, WorldContext world) { + List mapsToExport = new ArrayList(); + for(PredefinedMap map : getAllMaps()) { + if (shouldSaveMap(world, map)) mapsToExport.add(map); + } + checksumBuilder.add(mapsToExport.size()); + for(PredefinedMap map : mapsToExport) { + checksumBuilder.add(map.name); + map.addToChecksum(checksumBuilder, world); + } + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MonsterSpawnArea.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MonsterSpawnArea.java index 2af96372d..4ed66dbc1 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MonsterSpawnArea.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/MonsterSpawnArea.java @@ -8,6 +8,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.controller.Constants; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.model.actor.Monster; import com.gpl.rpg.AndorsTrail.model.actor.MonsterType; import com.gpl.rpg.AndorsTrail.util.Coord; @@ -140,4 +141,12 @@ public final class MonsterSpawnArea { m.writeToParcel(dest); } } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(isSpawning); + builder.add(monsters.size()); + for (Monster m : monsters) { + m.addToChecksum(builder); + } + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java index 66f352924..78907e6c4 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java @@ -12,6 +12,7 @@ import com.gpl.rpg.AndorsTrail.context.ControllerContext; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.controller.Constants; import com.gpl.rpg.AndorsTrail.controller.VisualEffectController.BloodSplatter; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; import com.gpl.rpg.AndorsTrail.model.actor.Monster; import com.gpl.rpg.AndorsTrail.model.item.ItemType; import com.gpl.rpg.AndorsTrail.model.item.Loot; @@ -398,4 +399,30 @@ public final class PredefinedMap { dest.writeBoolean(visited); dest.writeUTF(lastSeenLayoutHash); } + + public void addToChecksum(ChecksumBuilder builder, WorldContext world) { + if (shouldSaveMapData(world)) { + builder.add(true); + builder.add(spawnAreas.length); + for(MonsterSpawnArea a : spawnAreas) { + builder.add(a.areaID); + a.addToChecksum(builder); + } + builder.add(activeMapObjectGroups.size()); + for(String s : activeMapObjectGroups) { + builder.add(s); + } + builder.add(groundBags.size()); + for(Loot l : groundBags) { + l.addToChecksum(builder); + } + builder.add(currentColorFilter != null); + if (currentColorFilter != null) builder.add(currentColorFilter); + builder.add(lastVisitTime); + } else { + builder.add(false); + } + builder.add(visited); + builder.add(lastSeenLayoutHash); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java index 62d8cca30..b4e34d94a 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.security.DigestException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -79,11 +80,11 @@ public final class Savegames { } return true; - } catch (IOException e) { + } catch (IOException | DigestException e) { L.log("Error saving world: " + e.toString()); return false; } - } + } private static void writeBackup(Context androidContext, byte[] savegame, String playerId) throws IOException { File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); @@ -116,7 +117,7 @@ public final class Savegames { writeCheatCheck(androidContext, DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED, fh.playerId); } return result; - } catch (IOException e) { + } catch (IOException | DigestException e) { if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) { L.log("Error loading world: " + e.toString()); StringWriter sw = new StringWriter(); @@ -126,7 +127,7 @@ public final class Savegames { } return LoadSavegameResult.unknownError; } - } + } private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException { long savedVersionToCheck = 0; @@ -211,20 +212,25 @@ public final class Savegames { } - public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException { + public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException, DigestException { DataOutputStream dest = new DataOutputStream(outStream); FileHeader.writeToParcel(dest, world.model.player.getName(), displayInfo, world.model.player.iconID, world.model.statistics.isDead(), world.model.statistics.hasUnlimitedSaves(), world.model.player.id, - world.model.player.savedVersion); + world.model.player.savedVersion, + world.model.statistics.getIsAlteredSavegame()); + + byte[] checksum = world.getChecksum(); + world.model.statistics.setChecksum(checksum); + world.maps.writeToParcel(dest, world); world.model.writeToParcel(dest); dest.close(); } - public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, Context androidContext, InputStream inState, FileHeader fh) throws IOException { + public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, Context androidContext, InputStream inState, FileHeader fh) throws IOException, DigestException { DataInputStream src = new DataInputStream(inState); final FileHeader header = new FileHeader(src, fh.skipIcon); if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION) @@ -232,9 +238,12 @@ public final class Savegames { world.maps.readFromParcel(src, world, controllers, header.fileversion); world.model = new ModelContainer(src, world, controllers, header.fileversion); - WorldMapController.populateWorldMap(androidContext, world, controllers.getResources()); src.close(); - + if (header.fileversion >= 81) { + checkChecksum(world); + } + WorldMapController.populateWorldMap(androidContext, world, controllers.getResources()); + if (header.fileversion < 45) { LegacySavegamesContentAdaptations.adaptToNewContentForVersion45(world, controllers, res); } @@ -244,6 +253,13 @@ public final class Savegames { return LoadSavegameResult.success; } + private static void checkChecksum(WorldContext world) throws DigestException { + byte[] checksum = world.getChecksum(); + if (!world.model.statistics.compareChecksum(checksum)) { + world.model.statistics.markAsAlteredSavegame(); + } + } + private static void onWorldLoaded(Resources res, WorldContext world, ControllerContext controllers) { controllers.actorStatsController.recalculatePlayerStats(world.model.player); controllers.mapController.resetMapsNotRecentlyVisited(); @@ -331,7 +347,8 @@ public final class Savegames { public final String playerName; public final String displayInfo; public final int iconID; - public boolean skipIcon = false; + public final boolean isAlteredSavegame; + public boolean skipIcon = false; public final boolean isDead; public final boolean hasUnlimitedSaves; public final String playerId; @@ -380,9 +397,14 @@ public final class Savegames { this.playerId = ""; this.savedVersion = 0; } + if(fileversion >= 81){ + this.isAlteredSavegame = src.readBoolean(); + }else{ + this.isAlteredSavegame = false; + } } - public static void writeToParcel(DataOutputStream dest, String playerName, String displayInfo, int iconID, boolean isDead, boolean hasUnlimitedSaves, String playerId, long savedVersion) throws IOException { + public static void writeToParcel(DataOutputStream dest, String playerName, String displayInfo, int iconID, boolean isDead, boolean hasUnlimitedSaves, String playerId, long savedVersion, boolean isAlteredSavegame) throws IOException { dest.writeInt(AndorsTrailApplication.CURRENT_VERSION); dest.writeUTF(playerName); dest.writeUTF(displayInfo); @@ -391,6 +413,7 @@ public final class Savegames { dest.writeBoolean(hasUnlimitedSaves); dest.writeUTF(playerId); dest.writeLong(savedVersion); + dest.writeBoolean(isAlteredSavegame); } } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Coord.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Coord.java index 55ba445fb..278c9993c 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Coord.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Coord.java @@ -1,5 +1,7 @@ package com.gpl.rpg.AndorsTrail.util; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -47,4 +49,9 @@ public final class Coord { dest.writeInt(x); dest.writeInt(y); } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(x); + builder.add(y); + } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Range.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Range.java index fb0229ca1..c7fae5511 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Range.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/Range.java @@ -1,5 +1,7 @@ package com.gpl.rpg.AndorsTrail.util; +import com.gpl.rpg.AndorsTrail.model.ChecksumBuilder; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -99,4 +101,9 @@ public final class Range { dest.writeInt(max); dest.writeInt(current); } + + public void addToChecksum(ChecksumBuilder builder) { + builder.add(max); + builder.add(current); + } }