mirror of
https://github.com/OMGeeky/andors-trail.git
synced 2026-02-23 15:38:29 +01:00
Implement checking for savegame edits with a hash
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Integer> e : killedMonstersByTypeID.entrySet()) {
|
||||
digest.update(ByteUtils.toBytes(e.getKey()));
|
||||
digest.update(ByteUtils.toBytes(e.getValue()));
|
||||
}
|
||||
for (Entry<String, Integer> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Long> e: timers.entrySet() ) {
|
||||
digest.update(ByteUtils.toBytes(e.getKey()));
|
||||
digest.update(ByteUtils.toBytes(e.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<skillLevels.size(); i++){
|
||||
digest.update(ByteUtils.toBytes(skillLevels.keyAt(i)));
|
||||
digest.update(ByteUtils.toBytes(skillLevels.valueAt(i)));
|
||||
}
|
||||
digest.update(ByteUtils.toBytes(spawnMap));
|
||||
digest.update(ByteUtils.toBytes(spawnPlace));
|
||||
for (Entry<String, LinkedHashSet<Integer> > 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<String, Integer> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ItemEntry> items = new ArrayList<ItemEntry>();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PredefinedMap> getAllSavedMaps(WorldContext world) {
|
||||
List<PredefinedMap> mapsToExport = new ArrayList<PredefinedMap>();
|
||||
for(PredefinedMap map : getAllMaps()) {
|
||||
if (shouldSaveMap(world, map)) mapsToExport.add(map);
|
||||
}
|
||||
return mapsToExport;
|
||||
}
|
||||
|
||||
public void writeToParcel(DataOutputStream dest, WorldContext world) throws IOException {
|
||||
List<PredefinedMap> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|| !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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
<string name="savegame_currenthero_displayinfo">level %1$d, %2$d exp, %3$d gold</string>
|
||||
|
||||
<string name="dialog_loading_message">Loading resources…</string>
|
||||
<string name="dialog_loading_warning_title">Loading completed with warnings</string>
|
||||
<string name="dialog_loading_failed_title">Load Failed</string>
|
||||
<string name="dialog_loading_failed_message">Andor\'s Trail was unable to load the savegame file.\n\n:(\n\nThe file may be damaged or incomplete.</string>
|
||||
<string name="dialog_loading_failed_incorrectversion">Andor\'s Trail was unable to load the savegame file. This savegame file is created with a newer version than what is currently running.</string>
|
||||
<string name="dialog_loading_failed_cheat">Andor\'s Trail was unable to load the savegame file. This savegame file has already been continued.</string>
|
||||
<string name="dialog_loading_warning_edit">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.</string>
|
||||
|
||||
<string name="dialog_recenter">Recenter</string>
|
||||
<string name="dialog_close">Close</string>
|
||||
|
||||
Reference in New Issue
Block a user