/** * Copyright (C) 2013 Gundog Studios LLC. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.godsandtowers.core; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.HashMap; import com.godsandtowers.achievements.Achievement; import com.godsandtowers.campaigns.Campaign; import com.godsandtowers.campaigns.CampaignLevel; import com.godsandtowers.sprites.BaseCreature; import com.godsandtowers.sprites.BasePlayer; import com.godsandtowers.sprites.BaseRace; import com.godsandtowers.sprites.BaseSpecial; import com.godsandtowers.sprites.BaseTower; import com.godsandtowers.sprites.Races; import com.godsandtowers.sprites.Upgradeable; public class PlayerSaver { // TODO mask will be a device dependent # and utilize a cipher private static final int mask = 234758624; public static PlayerStats load(InputStream in) throws IOException, ClassNotFoundException { int length = in.read() << (Byte.SIZE * 3); length |= in.read() << (Byte.SIZE * 2); length |= in.read() << (Byte.SIZE); length |= in.read(); byte[] bytes = new byte[length]; in.read(bytes); decrypt(bytes, mask); ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); ObjectInputStream stream = new ObjectInputStream(byteStream); int version = stream.readInt(); if (version < 3) return null; // BasicStats int currentLevel = stream.readInt(); long totalXP = stream.readLong(); long longestGame = stream.readLong(); long shortestGame = stream.readLong(); long highestScore = stream.readLong(); long highestLevelBeaten = stream.readLong(); long wins = stream.readLong(); long losses = stream.readLong(); boolean multipleRacesUnlocked = stream.readBoolean(); if (version < 4) stream.readBoolean(); // CreatePlayer PlayerStats stats = new PlayerStats(totalXP, longestGame, shortestGame, highestScore, highestLevelBeaten, currentLevel, wins, losses, multipleRacesUnlocked); // Races int raceCount = stream.readInt(); int[] races = new int[raceCount]; for (int i = 0; i < raceCount; i++) { races[i] = stream.readInt(); } // RaceXP HashMap<Integer, Long> raceXP = stats.getRaceXP(); for (int i = 0; i < raceCount; i++) { long xp = stream.readLong(); raceXP.put(races[i], xp); } // DefensiveGamesWon HashMap<Integer, Integer> defensiveGamesWon = stats.getDefensiveGamesWon(); for (int i = 0; i < raceCount; i++) { int defenseWins = stream.readInt(); defensiveGamesWon.put(races[i], defenseWins); } // BattleGamesWon HashMap<Integer, Integer> battleGamesWon = stats.getBattleGamesWon(); for (int i = 0; i < raceCount; i++) { int battleWins = stream.readInt(); battleGamesWon.put(races[i], battleWins); } // Campaigns HashMap<Integer, Campaign> campaigns = stats.getCampaigns(); for (int i = 0; i < raceCount; i++) { Campaign campaign = campaigns.get(races[i]); int levelCount = stream.readInt(); CampaignLevel[] levels = campaign.getLevels(); for (int j = 0; j < levelCount; j++) { int stars = stream.readInt(); int level = stream.readInt(); levels[j].update(stars, level); } } // BasePlayer String name = stream.readUTF(); BasePlayer basePlayer = stats.getBasePlayer(); read(stream, basePlayer); // BaseRace name = stream.readUTF(); BaseRace baseRace = stats.getBaseRace(); read(stream, baseRace); // BaseCreatures HashMap<String, BaseCreature> creatures = new HashMap<String, BaseCreature>(); for (BaseCreature creature : stats.getBaseCreatures()) { creatures.put(creature.getName(), creature); } length = stream.readInt(); for (int i = 0; i < length; i++) { boolean unlocked = stream.readBoolean(); name = stream.readUTF(); BaseCreature creature = creatures.get(name); creature.setUnlocked(unlocked); read(stream, creature); } // BaseTowers HashMap<String, BaseTower> towers = new HashMap<String, BaseTower>(); for (BaseTower tower : stats.getBaseTowers()) { towers.put(tower.getName(), tower); } length = stream.readInt(); for (int i = 0; i < length; i++) { boolean unlocked = stream.readBoolean(); name = stream.readUTF(); BaseTower tower = towers.get(name); tower.setUnlocked(unlocked); read(stream, tower); } // Specials HashMap<String, BaseSpecial> specials = new HashMap<String, BaseSpecial>(); for (BaseSpecial special : stats.getSpecials()) { specials.put(special.getName(), special); } length = stream.readInt(); for (int i = 0; i < length; i++) { name = stream.readUTF(); BaseSpecial special = specials.get(name); read(stream, special); } // Achievements HashMap<String, Achievement> achievements = new HashMap<String, Achievement>(); for (Achievement achievement : stats.getAchievements()) { achievements.put(achievement.getName(), achievement); } length = stream.readInt(); for (int i = 0; i < length; i++) { name = stream.readUTF(); Achievement achievement = achievements.get(name); boolean completed = stream.readBoolean(); achievement.setCompleted(completed); } return stats; } private static void read(ObjectInputStream stream, Upgradeable upgradeable) throws IOException { int length = stream.readInt(); for (int i = 0; i < length; i++) { int id = stream.readInt(); int count = stream.readInt(); for (int j = 1; j < count; j++) { upgradeable.upgrade(id); } } } public static void write(PlayerStats stats, OutputStream out) throws IOException { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); ObjectOutputStream stream = new ObjectOutputStream(byteStream); stream.writeInt(PlayerStats.VERSION); // BasicStats stream.writeInt(stats.getCurrentLevel()); stream.writeLong(stats.getTotalXP()); stream.writeLong(stats.getLongestGame()); stream.writeLong(stats.getShortestGame()); stream.writeLong(stats.getHighestScore()); stream.writeLong(stats.getHighestLevelBeaten()); stream.writeLong(stats.getWins()); stream.writeLong(stats.getLosses()); stream.writeBoolean(stats.areMultipleRacesUnlocked()); // Races int[] races = Races.ALL_RACES; stream.writeInt(races.length); for (int race : races) { stream.writeInt(race); } // RaceXP HashMap<Integer, Long> raceXP = stats.getRaceXP(); for (int race : races) { stream.writeLong(raceXP.get(race)); } // DefensiveGamesWon HashMap<Integer, Integer> defensiveGamesWon = stats.getDefensiveGamesWon(); for (int race : races) { stream.writeInt(defensiveGamesWon.get(race)); } // BattleGamesWon HashMap<Integer, Integer> battleGamesWon = stats.getBattleGamesWon(); for (int race : races) { stream.writeInt(battleGamesWon.get(race)); } // Campaigns HashMap<Integer, Campaign> campaigns = stats.getCampaigns(); for (int race : races) { Campaign campaign = campaigns.get(race); CampaignLevel[] levels = campaign.getLevels(); stream.writeInt(levels.length); for (int j = 0; j < levels.length; j++) { stream.writeInt(levels[j].getStars()); stream.writeInt(levels[j].getDifficulty()); } } // BasePlayer BasePlayer basePlayer = stats.getBasePlayer(); write(stream, basePlayer); // BaseRace BaseRace baseRace = stats.getBaseRace(); write(stream, baseRace); // BaseCreatures BaseCreature[] baseCreatures = stats.getBaseCreatures(); stream.writeInt(baseCreatures.length); for (BaseCreature creature : baseCreatures) { stream.writeBoolean(creature.isUnlocked()); write(stream, creature); } // BaseTowers BaseTower[] baseTowers = stats.getBaseTowers(); stream.writeInt(baseTowers.length); for (BaseTower tower : baseTowers) { stream.writeBoolean(tower.isUnlocked()); write(stream, tower); } // Specials BaseSpecial[] specials = stats.getSpecials(); stream.writeInt(specials.length); for (BaseSpecial special : specials) { write(stream, special); } // Achievements Achievement[] achievements = stats.getAchievements(); stream.writeInt(achievements.length); for (Achievement achievement : achievements) { stream.writeUTF(achievement.getName()); stream.writeBoolean(achievement.isCompleted()); } stream.flush(); byteStream.flush(); byte[] bytes = byteStream.toByteArray(); encrypt(bytes, mask); int value = bytes.length; out.write(value >> (Byte.SIZE * 3)); out.write(value >> (Byte.SIZE * 2)); out.write(value >> Byte.SIZE); out.write(value); out.write(bytes); } private static void encrypt(byte[] bytes, int mask) { for (int i = 0; i < bytes.length - 1; i += 2) { bytes[i] = (byte) ~bytes[i]; bytes[i + 1] = (byte) ~bytes[i + 1]; bytes[i] = (byte) (bytes[i] ^ bytes[i + 1]); bytes[i + 1] = (byte) (bytes[i] ^ bytes[i + 1]); bytes[i] = (byte) (bytes[i] ^ bytes[i + 1]); } } private static void decrypt(byte[] bytes, int mask) { for (int i = 0; i < bytes.length - 1; i += 2) { bytes[i] = (byte) ~bytes[i]; bytes[i + 1] = (byte) ~bytes[i + 1]; bytes[i] = (byte) (bytes[i] ^ bytes[i + 1]); bytes[i + 1] = (byte) (bytes[i] ^ bytes[i + 1]); bytes[i] = (byte) (bytes[i] ^ bytes[i + 1]); } } private static void write(ObjectOutputStream stream, Upgradeable upgradeable) throws IOException { stream.writeUTF(upgradeable.getName()); int[] upgradeIDs = upgradeable.getUpgradeIDs(); stream.writeInt(upgradeIDs.length); for (int id : upgradeIDs) { stream.writeInt(id); stream.writeInt(upgradeable.getUpgradeCount(id)); } } }