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 f7c185e93..f0bb239a1 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 : 70; + public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION : 71; private final AndorsTrailPreferences preferences = new AndorsTrailPreferences(); private WorldContext world = new WorldContext(); diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/WorldSetup.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/WorldSetup.java index 4490de782..4fdc22b11 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/WorldSetup.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/WorldSetup.java @@ -134,8 +134,9 @@ public final class WorldSetup { onSceneLoadedListener = null; if (o == null) return; - if (loadResult == Savegames.LoadSavegameResult.success) { - o.onSceneLoaded(); + if (loadResult == Savegames.LoadSavegameResult.success + || loadResult == Savegames.LoadSavegameResult.editDetected) { + o.onSceneLoaded(loadResult); } else { o.onSceneLoadFailed(loadResult); } @@ -161,7 +162,7 @@ public final class WorldSetup { public static interface OnSceneLoadedListener { - void onSceneLoaded(); + void onSceneLoaded(Savegames.LoadSavegameResult loadResult); void onSceneLoadFailed(Savegames.LoadSavegameResult loadResult); } public interface OnResourcesLoadedListener { diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java index a8d9bc232..7ce22615a 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java @@ -141,13 +141,18 @@ public final class LoadingActivity extends AndorsTrailBaseActivity implements On @Override - public void onSceneLoaded() { + public void onSceneLoaded(Savegames.LoadSavegameResult loadResult) { synchronized (semaphore) { if (progressDialog != null) progressDialog.dismiss(); loaded =true; } - startActivity(new Intent(this, MainActivity.class)); - this.finish(); + + if (loadResult == Savegames.LoadSavegameResult.editDetected) { + showLoadingWarnDialog(R.string.dialog_loading_warning_edit); + }else{ + startActivity(new Intent(this, MainActivity.class)); + this.finish(); + } } @Override @@ -165,6 +170,19 @@ public final class LoadingActivity extends AndorsTrailBaseActivity implements On } } + private void showLoadingWarnDialog(int messageResourceID) { + final CustomDialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_loading_warning_title), null, getResources().getString(messageResourceID), null, true); + CustomDialogFactory.addDismissButton(d, android.R.string.ok); + CustomDialogFactory.setDismissListener(d, new OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + + startActivity(new Intent(LoadingActivity.this, MainActivity.class)); + LoadingActivity.this.finish(); + } + }); + CustomDialogFactory.show(d); + } private void showLoadingFailedDialog(int messageResourceID) { final CustomDialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_loading_failed_title), null, getResources().getString(messageResourceID), null, true); CustomDialogFactory.addDismissButton(d, android.R.string.ok); 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..7c87b23c2 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,8 @@ package com.gpl.rpg.AndorsTrail.context; +import android.os.Build; +import android.support.annotation.RequiresApi; + import com.gpl.rpg.AndorsTrail.model.ModelContainer; import com.gpl.rpg.AndorsTrail.model.ability.ActorConditionTypeCollection; import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection; @@ -12,6 +15,10 @@ import com.gpl.rpg.AndorsTrail.model.quest.QuestCollection; import com.gpl.rpg.AndorsTrail.resource.ConversationLoader; import com.gpl.rpg.AndorsTrail.resource.VisualEffectCollection; import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; public final class WorldContext { //Objectcollections @@ -62,4 +69,13 @@ public final class WorldContext { public void resetForNewGame() { maps.resetForNewGame(); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public String createHash() throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("MD5"); + this.maps.createHash(digest); + this.model.createHash(digest); + byte[] hash = digest.digest(); + return ByteUtils.toHexString(hash); + } } 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..35a24ebe5 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; @@ -12,6 +13,8 @@ import java.util.Map.Entry; import java.util.Set; import android.content.res.Resources; +import android.os.Build; +import android.support.annotation.RequiresApi; import com.gpl.rpg.AndorsTrail.R; import com.gpl.rpg.AndorsTrail.context.WorldContext; @@ -19,6 +22,7 @@ import com.gpl.rpg.AndorsTrail.model.actor.MonsterType; import com.gpl.rpg.AndorsTrail.model.item.ItemType; import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; import com.gpl.rpg.AndorsTrail.model.quest.Quest; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.HashMapHelper; public final class GameStatistics { @@ -214,4 +218,21 @@ public final class GameStatistics { dest.writeInt(startLives); dest.writeBoolean(unlimitedSaves); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(deaths)); + + for (Entry e : killedMonstersByTypeID.entrySet()) { + digest.update(ByteUtils.toBytes(e.getKey())); + digest.update(ByteUtils.toBytes(e.getValue())); + } + for (Entry e : usedItems.entrySet()) { + digest.update(ByteUtils.toBytes(e.getKey())); + digest.update(ByteUtils.toBytes(e.getValue())); + } + digest.update(ByteUtils.toBytes(spentGold)); + digest.update(ByteUtils.toBytes(startLives)); + digest.update(ByteUtils.toBytes(unlimitedSaves)); + } } 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..ce00137f2 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 @@ -3,8 +3,10 @@ package com.gpl.rpg.AndorsTrail.model; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import com.gpl.rpg.AndorsTrail.model.actor.Monster; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.Coord; public final class InterfaceData { @@ -51,4 +53,12 @@ public final class InterfaceData { } dest.writeUTF(selectedTabHeroInfo); } + + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(isMainActivityVisible)); + digest.update(ByteUtils.toBytes(isInCombat)); + if(selectedPosition != null){ + selectedPosition.createHash(digest); + } + } } 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..ba0bab824 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 @@ -1,12 +1,17 @@ package com.gpl.rpg.AndorsTrail.model; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import com.gpl.rpg.AndorsTrail.context.ControllerContext; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.model.actor.Player; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; public final class ModelContainer { @@ -49,4 +54,14 @@ public final class ModelContainer { statistics.writeToParcel(dest); worldData.writeToParcel(dest); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + player.createHash(digest); + digest.update(ByteUtils.toBytes(currentMaps.map.name)); + uiSelections.createHash(digest); + statistics.createHash(digest); + worldData.createHash(digest); + + } } 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 f26e61a8b..7b09dc498 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 @@ -1,8 +1,14 @@ package com.gpl.rpg.AndorsTrail.model; +import android.os.Build; +import android.support.annotation.RequiresApi; + +import com.gpl.rpg.AndorsTrail.util.ByteUtils; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; @@ -56,4 +62,13 @@ public final class WorldData { dest.writeLong(e.getValue()); } } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(worldTime)); + for (Map.Entry e: timers.entrySet() ) { + digest.update(ByteUtils.toBytes(e.getKey())); + digest.update(ByteUtils.toBytes(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 94fee6a3c..ed22c12de 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 @@ -1,10 +1,15 @@ package com.gpl.rpg.AndorsTrail.model.ability; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; public final class ActorCondition { public static final int MAGNITUDE_REMOVE_ALL = -99; @@ -45,4 +50,12 @@ public final class ActorCondition { dest.writeInt(magnitude); dest.writeInt(duration); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(conditionType.conditionTypeID)); + digest.update(ByteUtils.toBytes(magnitude)); + digest.update(ByteUtils.toBytes(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..cc7b04116 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 @@ -1,8 +1,12 @@ package com.gpl.rpg.AndorsTrail.model.actor; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.controller.Constants; @@ -13,6 +17,7 @@ import com.gpl.rpg.AndorsTrail.model.item.ItemContainer; import com.gpl.rpg.AndorsTrail.model.item.Loot; import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea; import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForMonster; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.CoordRect; import com.gpl.rpg.AndorsTrail.util.Range; @@ -193,4 +198,28 @@ public final class Monster extends Actor { dest.writeBoolean(false); } } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(getMonsterTypeID())); + digest.update(ByteUtils.toBytes(attackCost)); + digest.update(ByteUtils.toBytes(attackChance)); + digest.update(ByteUtils.toBytes(criticalSkill)); + digest.update(ByteUtils.toBytes(criticalMultiplier)); + damagePotential.createHash(digest); + digest.update(ByteUtils.toBytes(blockChance)); + digest.update(ByteUtils.toBytes(damageResistance)); + ap.createHash(digest); + health.createHash(digest); + position.createHash(digest); + for (ActorCondition c: conditions){ + c.createHash(digest); + } + + digest.update(ByteUtils.toBytes(moveCost)); + digest.update(ByteUtils.toBytes(forceAggressive)); + if (shopItems != null) { + shopItems.createHash(digest); + } + } } 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..4689e9ef7 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 @@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.model.actor; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -13,6 +14,8 @@ import java.util.List; import java.util.Map.Entry; import java.util.UUID; +import android.os.Build; +import android.support.annotation.RequiresApi; import android.util.SparseIntArray; import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; @@ -27,6 +30,7 @@ import com.gpl.rpg.AndorsTrail.model.item.Loot; import com.gpl.rpg.AndorsTrail.model.quest.Quest; import com.gpl.rpg.AndorsTrail.model.quest.QuestProgress; import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForPlayer; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.Range; import com.gpl.rpg.AndorsTrail.util.Size; @@ -55,9 +59,11 @@ public final class Player extends Actor { public String id = UUID.randomUUID().toString(); public long savedVersion = 1; // the version get's increased for cheat detection everytime a player with limited saves is saved + public boolean wasEditingDetected; + public String hash; - // Unequipped stats + // Unequipped stats public static final class PlayerBaseTraits { public int iconID; public int maxAP; @@ -416,6 +422,13 @@ public final class Player extends Actor { this.id = src.readUTF(); this.savedVersion = src.readLong(); } + if (fileversion >= 71){ + this.hash = src.readUTF(); + this.wasEditingDetected = src.readBoolean(); + }else{ + this.hash = ""; + this.wasEditingDetected = false; + } } public void writeToParcel(DataOutputStream dest) throws IOException { @@ -474,6 +487,58 @@ public final class Player extends Actor { } dest.writeUTF(id); dest.writeLong(savedVersion); + dest.writeUTF(hash); + dest.writeBoolean(wasEditingDetected); + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + //skipping icon ID so that it can be changed without hash difference + digest.update(ByteUtils.toBytes(baseTraits.maxAP)); + digest.update(ByteUtils.toBytes(baseTraits.maxHP)); + //skipping name so that it can be changed without hash difference + digest.update(ByteUtils.toBytes(moveCost)); + digest.update(ByteUtils.toBytes(baseTraits.attackCost)); + digest.update(ByteUtils.toBytes(baseTraits.attackChance)); + digest.update(ByteUtils.toBytes(baseTraits.criticalSkill)); + digest.update(ByteUtils.toBytes(baseTraits.criticalMultiplier)); + baseTraits.damagePotential.createHash(digest); + digest.update(ByteUtils.toBytes(baseTraits.blockChance)); + digest.update(ByteUtils.toBytes(baseTraits.damageResistance)); + digest.update(ByteUtils.toBytes(baseTraits.moveCost)); + ap.createHash(digest); + health.createHash(digest); + position.createHash(digest); + for(ActorCondition c : conditions){ + c.createHash(digest); + } + lastPosition.createHash(digest); + nextPosition.createHash(digest); + digest.update(ByteUtils.toBytes(level)); + digest.update(ByteUtils.toBytes(totalExperience)); + inventory.createHash(digest); + digest.update(ByteUtils.toBytes(baseTraits.useItemCost)); + digest.update(ByteUtils.toBytes(baseTraits.reequipCost)); + for (int i = 0; i > e:questProgress.entrySet() ) { + digest.update(ByteUtils.toBytes(e.getKey())); + for(int progress: e.getValue()){ + digest.update(ByteUtils.toBytes(progress)); + } + } + digest.update(ByteUtils.toBytes(availableSkillIncreases)); + for (Entry e:alignments.entrySet() ) { + digest.update(ByteUtils.toBytes(e.getKey())); + digest.update(ByteUtils.toBytes(e.getValue())); + } + digest.update(ByteUtils.toBytes(id)); +// digest.update(ByteUtils.toBytes(savedVersion)); +// digest.update(ByteUtils.toBytes(wasEditingDetected)); } } 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..e4bc298e0 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 @@ -1,14 +1,19 @@ package com.gpl.rpg.AndorsTrail.model.item; +import android.os.Build; +import android.support.annotation.RequiresApi; + 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; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.model.actor.Player; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; public class ItemContainer { public final ArrayList items = new ArrayList(); @@ -23,7 +28,8 @@ public class ItemContainer { return result; } - public static final class ItemEntry { + + public static final class ItemEntry { public final ItemType itemType; public int quantity; public ItemEntry(ItemType itemType, int initialQuantity) { @@ -42,6 +48,12 @@ public class ItemContainer { dest.writeUTF(itemType.id); dest.writeInt(quantity); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(itemType.id)); + digest.update(ByteUtils.toBytes(quantity)); + } } public void addItem(ItemType itemType, int quantity) { @@ -280,4 +292,12 @@ public class ItemContainer { e.writeToParcel(dest); } } + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + for (ItemEntry e : + items) { + e.createHash(digest); + } + + } } 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..95ace4b0a 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 @@ -1,11 +1,16 @@ package com.gpl.rpg.AndorsTrail.model.item; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.savegames.LegacySavegameFormatReaderForItemContainer; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.Coord; public final class Loot { @@ -88,4 +93,13 @@ public final class Loot { position.writeToParcel(dest); dest.writeBoolean(isVisible); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(exp)); + digest.update(ByteUtils.toBytes(exp)); + items.createHash(digest); + position.createHash(digest); + digest.update(ByteUtils.toBytes(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..f6825d38d 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 @@ -1,8 +1,12 @@ package com.gpl.rpg.AndorsTrail.model.map; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -88,15 +92,27 @@ public final class MapCollection { return false; } - public void writeToParcel(DataOutputStream dest, WorldContext world) throws IOException { + private List getAllSavedMaps(WorldContext world) { List mapsToExport = new ArrayList(); for(PredefinedMap map : getAllMaps()) { if (shouldSaveMap(world, map)) mapsToExport.add(map); } + return mapsToExport; + } + + public void writeToParcel(DataOutputStream dest, WorldContext world) throws IOException { + List mapsToExport = getAllSavedMaps(world); dest.writeInt(mapsToExport.size()); for(PredefinedMap map : mapsToExport) { dest.writeUTF(map.name); map.writeToParcel(dest, world); } } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + for (PredefinedMap map : getAllMaps()) { + map.createHash(digest); + } + } } 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 730afd5ac..616dd72f0 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 @@ -1,8 +1,12 @@ package com.gpl.rpg.AndorsTrail.model.map; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -10,6 +14,7 @@ import com.gpl.rpg.AndorsTrail.context.WorldContext; import com.gpl.rpg.AndorsTrail.controller.Constants; import com.gpl.rpg.AndorsTrail.model.actor.Monster; import com.gpl.rpg.AndorsTrail.model.actor.MonsterType; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.CoordRect; import com.gpl.rpg.AndorsTrail.util.Range; @@ -140,4 +145,13 @@ public final class MonsterSpawnArea { m.writeToParcel(dest); } } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(isSpawning)); + for (Monster m : + monsters) { + m.createHash(digest); + } + } } 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..193a80cbe 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 @@ -1,8 +1,12 @@ package com.gpl.rpg.AndorsTrail.model.map; +import android.os.Build; +import android.support.annotation.RequiresApi; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -16,6 +20,7 @@ import com.gpl.rpg.AndorsTrail.model.actor.Monster; import com.gpl.rpg.AndorsTrail.model.item.ItemType; import com.gpl.rpg.AndorsTrail.model.item.Loot; import com.gpl.rpg.AndorsTrail.model.map.MapObject.MapObjectType; +import com.gpl.rpg.AndorsTrail.util.ByteUtils; import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.CoordRect; import com.gpl.rpg.AndorsTrail.util.L; @@ -340,6 +345,7 @@ public final class PredefinedMap { } } } else { + currentColorFilter = null; activeMapObjectGroups.clear(); activeMapObjectGroups.addAll(initiallyActiveMapObjectGroups); activateMapObjects(); @@ -360,16 +366,20 @@ public final class PredefinedMap { } public boolean shouldSaveMapData(WorldContext world) { - if (!hasResetTemporaryData()) return true; if (this == world.model.currentMaps.map) return true; + return mapDataNeedsToBeSaved(); + } + + private boolean mapDataNeedsToBeSaved() { + if (!hasResetTemporaryData()) return true; if (!groundBags.isEmpty()) return true; for (MonsterSpawnArea a : spawnAreas) { if (this.visited && a.isUnique) return true; if (a.isSpawning != a.isSpawningForNewGame) return true; } - if (!activeMapObjectGroups.containsAll(initiallyActiveMapObjectGroups) - || !initiallyActiveMapObjectGroups.containsAll(activeMapObjectGroups)) return true; - if (currentColorFilter != null) return true; + if (!activeMapObjectGroups.containsAll(initiallyActiveMapObjectGroups) + || !initiallyActiveMapObjectGroups.containsAll(activeMapObjectGroups)) return true; + if (currentColorFilter != null && !currentColorFilter.equals(initialColorFilter)) return true; return false; } @@ -398,4 +408,23 @@ public final class PredefinedMap { dest.writeBoolean(visited); dest.writeUTF(lastSeenLayoutHash); } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void createHash(MessageDigest digest) { + if (mapDataNeedsToBeSaved()) { + for (MonsterSpawnArea a : spawnAreas) { + a.createHash(digest); + } + for (String g : activeMapObjectGroups) { + digest.update(ByteUtils.toBytes(g)); + } + for (Loot l : groundBags) { + l.createHash(digest); + } + digest.update(ByteUtils.toBytes(currentColorFilter)); + //skip lastVisitTime since it is too volatile + } + digest.update( ByteUtils.toBytes(visited)); + digest.update( ByteUtils.toBytes(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 caf3567cd..af1d086c9 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.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,6 +21,7 @@ import java.util.regex.Pattern; import android.content.Context; import android.content.res.Resources; +import android.os.Build; import android.os.SystemClock; import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; @@ -42,6 +44,7 @@ public final class Savegames { public static enum LoadSavegameResult { success + , editDetected , unknownError , savegameIsFromAFutureVersion , cheatingDetected @@ -78,7 +81,7 @@ public final class Savegames { } return true; - } catch (IOException e) { + } catch (IOException | NoSuchAlgorithmException e) { L.log("Error saving world: " + e.toString()); return false; } @@ -210,14 +213,18 @@ 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, NoSuchAlgorithmException { DataOutputStream dest = new DataOutputStream(outStream); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + world.model.player.hash = world.createHash(); + } 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.player.wasEditingDetected); world.maps.writeToParcel(dest, world); world.model.writeToParcel(dest); dest.close(); @@ -239,6 +246,15 @@ public final class Savegames { onWorldLoaded(res, world, controllers); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try { + String worldHash = world.createHash(); + if(!worldHash.equals(world.model.player.hash)){ + world.model.player.wasEditingDetected = true; + return LoadSavegameResult.editDetected; + } + } catch (NoSuchAlgorithmException ignored) { } + } return LoadSavegameResult.success; } @@ -334,6 +350,7 @@ public final class Savegames { public final boolean hasUnlimitedSaves; public final String playerId; public final long savedVersion; + public final boolean wasEditingDetected; public String describe() { return (fileversion == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION ? "(D) " : "") + playerName + ", " + displayInfo; @@ -378,9 +395,14 @@ public final class Savegames { this.playerId = ""; this.savedVersion = 0; } + if (fileversion >= 70){ + this.wasEditingDetected = src.readBoolean(); + }else{ + this.wasEditingDetected = 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 wasEditingDetected) throws IOException { dest.writeInt(AndorsTrailApplication.CURRENT_VERSION); dest.writeUTF(playerName); dest.writeUTF(displayInfo); @@ -389,6 +411,7 @@ public final class Savegames { dest.writeBoolean(hasUnlimitedSaves); dest.writeUTF(playerId); dest.writeLong(savedVersion); + dest.writeBoolean(wasEditingDetected); } } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/ByteUtils.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/ByteUtils.java index 2854a9ba7..f8476b28f 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/ByteUtils.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/ByteUtils.java @@ -1,6 +1,17 @@ package com.gpl.rpg.AndorsTrail.util; +import android.os.Build; +import android.support.annotation.RequiresApi; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + public final class ByteUtils { + + private static int bytes; + public static String toHexString(byte[] bytes) { return toHexString(bytes, bytes.length); } public static String toHexString(byte[] bytes, int numBytes) { if (bytes == null) return ""; @@ -21,4 +32,57 @@ public final class ByteUtils { array[i] ^= mask[i]; } } + + public static byte[] toBytes(long l){ + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + bytes = Long.BYTES; + }else{ + bytes = 8; + } + ByteBuffer buffer = ByteBuffer.allocate(bytes); + buffer.putLong(l); + return buffer.array(); + } + + public static byte[] toBytes(int i){ + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + bytes = Integer.BYTES; + }else{ + bytes = 4; + } + ByteBuffer buffer = ByteBuffer.allocate(bytes); + buffer.putInt(i); + return buffer.array(); + } + + public static byte[] toBytes(float f) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + bytes = Float.BYTES; + }else{ + bytes = 4; + } + ByteBuffer buffer = ByteBuffer.allocate(bytes); + buffer.putFloat(f); + return buffer.array(); + } + public static byte[] toBytes(boolean bool){ + byte b; + if(bool){ + b = 0; + }else{ + b = 1; + } + return new byte[] {b}; + } + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static byte[] toBytes(String x) { + if (x == null) { + return new byte[]{}; + } + return x.getBytes(StandardCharsets.UTF_8); + } + + } 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..9cb55bfcb 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 @@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.util; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; public final class Coord { public int x; @@ -47,4 +48,9 @@ public final class Coord { dest.writeInt(x); dest.writeInt(y); } + + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(x)); + digest.update(ByteUtils.toBytes(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..23eea003d 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,8 +1,11 @@ package com.gpl.rpg.AndorsTrail.util; +import com.gpl.rpg.AndorsTrail.context.WorldContext; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.security.MessageDigest; public final class Range { public int max; @@ -99,4 +102,9 @@ public final class Range { dest.writeInt(max); dest.writeInt(current); } + + public void createHash(MessageDigest digest) { + digest.update(ByteUtils.toBytes(max)); + digest.update(ByteUtils.toBytes(current)); + } } diff --git a/AndorsTrail/res/values/strings.xml b/AndorsTrail/res/values/strings.xml index dc2f28116..0e25ea6f4 100644 --- a/AndorsTrail/res/values/strings.xml +++ b/AndorsTrail/res/values/strings.xml @@ -16,10 +16,12 @@ level %1$d, %2$d exp, %3$d gold Loading resources… + Loading completed with warnings Load Failed Andor\'s Trail was unable to load the savegame file.\n\n:(\n\nThe file may be damaged or incomplete. Andor\'s Trail was unable to load the savegame file. This savegame file is created with a newer version than what is currently running. Andor\'s Trail was unable to load the savegame file. This savegame file has already been continued. + Andor\'s Trail detected that this savegame has been edited. Please do not share screenshots of this savegame or at least add a note that it has been edited. Thanks for being fair. Recenter Close