From 30030031b2fb2f9fdf4f0e96bfe9aaac5a61c8ca Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Mon, 24 Feb 2025 20:58:06 +0100 Subject: [PATCH 1/2] 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); + } } From 31f25a963f3c463c045feae8a50c15943990829d Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Tue, 25 Feb 2025 15:28:52 +0100 Subject: [PATCH 2/2] fix whitespace formatting Signed-off-by: OMGeeky --- .../rpg/AndorsTrail/context/WorldContext.java | 2 +- .../AndorsTrail/model/ChecksumBuilder.java | 166 ++++++------ .../rpg/AndorsTrail/savegames/Savegames.java | 238 +++++++++--------- 3 files changed, 203 insertions(+), 203 deletions(-) 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 4ebeb5714..ed83d2abe 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 @@ -70,6 +70,6 @@ public final class WorldContext { ChecksumBuilder checksumBuilder = new ChecksumBuilder(); model.addToChecksum(checksumBuilder); maps.addToChecksum(checksumBuilder, this); - return checksumBuilder.build(); + 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 index fb430fa8f..570fd1965 100644 --- 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 @@ -13,103 +13,103 @@ 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; + 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, 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 - } + private ChecksumBuilder(int initialCapacity) { + this(initialCapacity, ByteOrder.BIG_ENDIAN); // Default to big-endian + } - public ChecksumBuilder() { - this(1024*10); // A reasonable default initial capacity - } + public ChecksumBuilder() { + this(1024*10); // A reasonable default initial capacity + } - // --- Methods for adding different data types --- + // --- 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(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(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(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(long value) { + ensureCapacity(8); + buffer.putLong(value); + return this; + } - public ChecksumBuilder add(int value) { - ensureCapacity(4); - buffer.putInt(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(float value) { + ensureCapacity(8); + buffer.putFloat(value); + return this; + } - public ChecksumBuilder add(double value) { - ensureCapacity(4); - buffer.putDouble(value); - return this; - } + public ChecksumBuilder add(double value) { + ensureCapacity(4); + buffer.putDouble(value); + return this; + } - // --- Method to finalize and get the checksum --- + // --- 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(); - } + 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 - } - } + // --- 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/savegames/Savegames.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java index b4e34d94a..3f93d88ea 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 @@ -84,11 +84,11 @@ public final class Savegames { 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); - ensureDirExists(cheatDetectionFolder); + ensureDirExists(cheatDetectionFolder); File backupFile = new File(cheatDetectionFolder, playerId + "X"); FileOutputStream fileOutputStream = new FileOutputStream(backupFile); fileOutputStream.write(savegame); @@ -109,8 +109,8 @@ public final class Savegames { LoadSavegameResult result = loadWorld(androidContext.getResources(), world, controllers, androidContext, fos, fh); fos.close(); if (result == LoadSavegameResult.success && slot != SLOT_QUICKSAVE && !world.model.statistics.hasUnlimitedSaves()) { - // save to the quicksave slot before deleting the file - if (!saveWorld(world, androidContext, SLOT_QUICKSAVE)) { + // save to the quicksave slot before deleting the file + if (!saveWorld(world, androidContext, SLOT_QUICKSAVE)) { return LoadSavegameResult.unknownError; } getSlotFile(slot, androidContext).delete(); @@ -127,35 +127,35 @@ public final class Savegames { } return LoadSavegameResult.unknownError; } - } + } - private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException { - long savedVersionToCheck = 0; - File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); - ensureDirExists(cheatDetectionFolder); - File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId); - if (cheatDetectionFile.exists()) { - FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile); - DataInputStream dataInputStream = new DataInputStream(fileInputStream); - final CheatDetection cheatDetection = new CheatDetection(dataInputStream); - savedVersionToCheck = cheatDetection.savedVersion; - dataInputStream.close(); - fileInputStream.close(); - } + private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException { + long savedVersionToCheck = 0; + File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); + ensureDirExists(cheatDetectionFolder); + File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId); + if (cheatDetectionFile.exists()) { + FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile); + DataInputStream dataInputStream = new DataInputStream(fileInputStream); + final CheatDetection cheatDetection = new CheatDetection(dataInputStream); + savedVersionToCheck = cheatDetection.savedVersion; + dataInputStream.close(); + fileInputStream.close(); + } if (savedVersionToCheck == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { return true; } - if (androidContext.getFileStreamPath(fh.playerId).exists()) { - FileInputStream fileInputStream = androidContext.openFileInput(fh.playerId); - DataInputStream dataInputStream = new DataInputStream(fileInputStream); - final CheatDetection cheatDetection = new CheatDetection(dataInputStream); - if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { - savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED; - } else if (cheatDetection.savedVersion > savedVersionToCheck) { - savedVersionToCheck = cheatDetection.savedVersion; - } + if (androidContext.getFileStreamPath(fh.playerId).exists()) { + FileInputStream fileInputStream = androidContext.openFileInput(fh.playerId); + DataInputStream dataInputStream = new DataInputStream(fileInputStream); + final CheatDetection cheatDetection = new CheatDetection(dataInputStream); + if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { + savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED; + } else if (cheatDetection.savedVersion > savedVersionToCheck) { + savedVersionToCheck = cheatDetection.savedVersion; + } if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) { L.log("Internal cheatcheck file savedVersion: " + cheatDetection.savedVersion); @@ -168,48 +168,48 @@ public final class Savegames { return (savedVersionToCheck == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED || fh.savedVersion < savedVersionToCheck); } - private static FileOutputStream getOutputFile(Context androidContext, int slot) throws IOException { - if (slot == SLOT_QUICKSAVE) { - return androidContext.openFileOutput(Constants.FILENAME_SAVEGAME_QUICKSAVE, Context.MODE_PRIVATE); - } else { - ensureSavegameDirectoryExists(androidContext); - return new FileOutputStream(getSlotFile(slot, androidContext)); - } - } + private static FileOutputStream getOutputFile(Context androidContext, int slot) throws IOException { + if (slot == SLOT_QUICKSAVE) { + return androidContext.openFileOutput(Constants.FILENAME_SAVEGAME_QUICKSAVE, Context.MODE_PRIVATE); + } else { + ensureSavegameDirectoryExists(androidContext); + return new FileOutputStream(getSlotFile(slot, androidContext)); + } + } - private static void ensureSavegameDirectoryExists(Context context) { - File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); - ensureDirExists(dir); - } + private static void ensureSavegameDirectoryExists(Context context) { + File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + ensureDirExists(dir); + } - public static boolean ensureDirExists(File dir) { - if (!dir.exists()) { - boolean worked = dir.mkdir(); - return worked; - } - return true; - } + public static boolean ensureDirExists(File dir) { + if (!dir.exists()) { + boolean worked = dir.mkdir(); + return worked; + } + return true; + } - private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException { - if (slot == SLOT_QUICKSAVE) { - return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE); - } else { - return new FileInputStream(getSlotFile(slot, androidContext)); - } - } + private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException { + if (slot == SLOT_QUICKSAVE) { + return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE); + } else { + return new FileInputStream(getSlotFile(slot, androidContext)); + } + } - public static File getSlotFile(int slot, Context context) { - File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); - return getSlotFile(slot, root); - } + public static File getSlotFile(int slot, Context context) { + File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + return getSlotFile(slot, root); + } - public static File getSlotFile(int slot, File directory) { - return new File(directory, getSlotFileName(slot)); - } + public static File getSlotFile(int slot, File directory) { + return new File(directory, getSlotFileName(slot)); + } - public static String getSlotFileName(int slot) { - return Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot; - } + public static String getSlotFileName(int slot) { + return Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot; + } public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException, DigestException { @@ -230,11 +230,11 @@ public final class Savegames { dest.close(); } - 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) - return LoadSavegameResult.savegameIsFromAFutureVersion; + 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) + return LoadSavegameResult.savegameIsFromAFutureVersion; world.maps.readFromParcel(src, world, controllers, header.fileversion); world.model = new ModelContainer(src, world, controllers, header.fileversion); @@ -247,7 +247,7 @@ public final class Savegames { if (header.fileversion < 45) { LegacySavegamesContentAdaptations.adaptToNewContentForVersion45(world, controllers, res); } - + onWorldLoaded(res, world, controllers); return LoadSavegameResult.success; @@ -284,15 +284,15 @@ public final class Savegames { } } - private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException { - File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); - ensureDirExists(cheatDetectionFolder); - File cheatDetectionFile = new File(cheatDetectionFolder, playerId); - FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile); - DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); - CheatDetection.writeToParcel(dataOutputStream, savedVersion); - dataOutputStream.close(); - fileOutputStream.close(); + private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException { + File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); + ensureDirExists(cheatDetectionFolder); + File cheatDetectionFile = new File(cheatDetectionFolder, playerId); + FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile); + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); + CheatDetection.writeToParcel(dataOutputStream, savedVersion); + dataOutputStream.close(); + fileOutputStream.close(); fileOutputStream = androidContext.openFileOutput(playerId, Context.MODE_PRIVATE); dataOutputStream = new DataOutputStream(fileOutputStream); @@ -303,26 +303,26 @@ public final class Savegames { private static final Pattern savegameFilenamePattern = Pattern.compile(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + "(\\d+)"); - public static List getUsedSavegameSlots(Context context) { - try { - final List result = new ArrayList(); - AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY).listFiles(new FilenameFilter() { - @Override - public boolean accept(File f, String filename) { - Matcher m = savegameFilenamePattern.matcher(filename); - if (m != null && m.matches()) { - result.add(Integer.parseInt(m.group(1))); - return true; - } - return false; - } - }); - Collections.sort(result); - return result; - } catch (Exception e) { - return new ArrayList(); - } - } + public static List getUsedSavegameSlots(Context context) { + try { + final List result = new ArrayList(); + AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY).listFiles(new FilenameFilter() { + @Override + public boolean accept(File f, String filename) { + Matcher m = savegameFilenamePattern.matcher(filename); + if (m != null && m.matches()) { + result.add(Integer.parseInt(m.group(1))); + return true; + } + return false; + } + }); + Collections.sort(result); + return result; + } catch (Exception e) { + return new ArrayList(); + } + } private static final class CheatDetection { public final int fileversion; @@ -342,17 +342,17 @@ public final class Savegames { } - public static final class FileHeader { - public final int fileversion; - public final String playerName; - public final String displayInfo; - public final int iconID; + public static final class FileHeader { + public final int fileversion; + public final String playerName; + public final String displayInfo; + public final int iconID; public final boolean isAlteredSavegame; public boolean skipIcon = false; - public final boolean isDead; - public final boolean hasUnlimitedSaves; - public final String playerId; - public final long savedVersion; + public final boolean isDead; + public final boolean hasUnlimitedSaves; + public final String playerId; + public final long savedVersion; public String describe() { return (fileversion == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION ? "(D) " : "") + playerName + ", " + displayInfo; @@ -361,18 +361,18 @@ public final class Savegames { // ====== PARCELABLE =================================================================== - public FileHeader(DataInputStream src, boolean skipIcon) throws IOException { - int fileversion = src.readInt(); - if (fileversion == 11) - fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11. - this.fileversion = fileversion; - if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header. - this.playerName = src.readUTF(); - this.displayInfo = src.readUTF(); - } else { - this.playerName = null; - this.displayInfo = null; - } + public FileHeader(DataInputStream src, boolean skipIcon) throws IOException { + int fileversion = src.readInt(); + if (fileversion == 11) + fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11. + this.fileversion = fileversion; + if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header. + this.playerName = src.readUTF(); + this.displayInfo = src.readUTF(); + } else { + this.playerName = null; + this.displayInfo = null; + } if (fileversion >= 43) { int id = src.readInt();