/* TeamFileParser.java * * Created April 8, 2009 * * This file is a part of Shoddy Battle. * Copyright (C) 2009 Catherine Fitzpatrick and Benjamin Gwin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, visit the Free Software Foundation, Inc. * online at http://gnu.org. */ package shoddybattleclient.utils; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.Attributes; import shoddybattleclient.shoddybattle.Generation; import shoddybattleclient.shoddybattle.Pokemon; import shoddybattleclient.shoddybattle.Pokemon.Gender; import shoddybattleclient.shoddybattle.PokemonNature; import shoddybattleclient.shoddybattle.PokemonSpecies; /** * This class parses the XML Shoddy Battle 2 team format, as well as the * terrible Shoddy Battle 1 binary team format crafted by Catherine * * @author ben */ public class TeamFileParser extends DefaultHandler { /** Constants used in the Java file format. **/ private final static short STREAM_MAGIC = (short)0xaced; private final static short STREAM_VERSION = 5; private final static char TC_NULL = (char)0x70; private final static char TC_REFERENCE = (char)0x71; private final static char TC_CLASSDESC = (char)0x72; private final static char TC_OBJECT = (char)0x73; private final static char TC_STRING = (char)0x74; private final static char TC_ARRAY = (char)0x75; private final static char TC_CLASS = (char)0x76; private final static char TC_BLOCKDATA = (char)0x77; private final static char TC_ENDBLOCKDATA = (char)0x78; private final static char TC_RESET = (char)0x79; private final static char TC_BLOCKDATALONG = (char)0x7A; private final static char TC_EXCEPTION = (char)0x7B; private final static char TC_LONGSTRING = (char) 0x7C; private final static char TC_PROXYCLASSDESC = (char) 0x7D; private final static int baseWireHandle = 0x7E0000; private final static char SC_WRITE_METHOD = 0x01; private final static char SC_BLOCK_DATA = 0x08; private final static char SC_SERIALIZABLE = 0x02; private final static char SC_EXTERNALIZABLE = 0x04; private List<Pokemon> m_pokemon = new ArrayList<Pokemon>(); private Pokemon tempPoke; private String tempStr; private int moveIndex; public Pokemon[] parseTeam(String file, Generation generation) { m_pokemon = new ArrayList<Pokemon>(); DataInputStream is = null; try { try { is = new DataInputStream(new FileInputStream(file)); short magic = is.readShort(); boolean sb1 = true; if (magic != STREAM_MAGIC) { sb1 = false; } short version = is.readShort(); if (version != STREAM_VERSION) { sb1 = false; } Pokemon[] team = (sb1) ? parseShoddyBattle1Team(is) : parseShoddyBattle2Team(file); if (team.length == 0) return null; for (Pokemon p : team) { List<String> moves = new ArrayList<String>(); List<Integer> ppUps = new ArrayList<Integer>(); for (int i = 0; i < p.moves.length; i++) { String m = p.moves[i]; if (m != null) { moves.add(m); ppUps.add(p.ppUps[i]); } } p.moves = moves.toArray(new String[moves.size()]); Integer[] temp = ppUps.toArray(new Integer[ppUps.size()]); int[] temp2 = new int[temp.length]; for (int i = 0; i < temp.length; i++) { temp2[i] = temp[i]; } p.ppUps = temp2; PokemonSpecies ps = generation.getSpeciesByName(p.species); // Set the species to the case-sensitive version p.species = ps.getName(); // If the pokemon has no ability put in a default one if (p.ability == null) { p.ability = ps.getAbilities()[0]; } // If the pokemon has no gender put in a default one if (p.gender == null) { if (ps.getGenders() == Gender.GENDER_BOTH) { p.gender = Gender.GENDER_MALE; } else { p.gender = ps.getGenders(); } } } return team; } catch (Exception e) { e.printStackTrace(); return null; } finally { if (is != null) { is.close(); } } } catch (IOException e) { e.printStackTrace(); return null; } } private Pokemon[] parseShoddyBattle2Team(String file) { SAXParserFactory spf = SAXParserFactory.newInstance(); Pokemon[] ret; try { SAXParser sp = spf.newSAXParser(); sp.parse(new File(file), this); } catch (Exception e) { ret = null; } ret = new Pokemon[m_pokemon.size()]; m_pokemon.toArray(ret); return ret; } @Override public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals("pokemon")) { tempPoke = new Pokemon(); tempPoke.species = attributes.getValue("species"); moveIndex = 0; } else if (qName.equals("move")) { try { tempPoke.ppUps[moveIndex] = Integer.parseInt(attributes.getValue("pp-up")); } catch (NumberFormatException e) { tempPoke.ppUps[moveIndex] = 3; } } else if (qName.equals("stat")) { int statIndex = getStatIndex(attributes.getValue("name")); try { tempPoke.ivs[statIndex] = Integer.parseInt(attributes.getValue("iv")); } catch (NumberFormatException e) { tempPoke.ivs[statIndex] = 31; } try { tempPoke.evs[statIndex] = Integer.parseInt(attributes.getValue("ev")); } catch (NumberFormatException e) { tempPoke.evs[statIndex] = 0; } } else if (qName.equals("shiny")) { tempPoke.shiny = true; } tempStr = ""; } @Override public void characters(char[] ch, int start, int length) throws SAXException { String addend = new String(ch, start, length); tempStr += addend; } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (qName.equals("nickname")) { tempPoke.nickname = tempStr; } else if (qName.equals("level")) { try { tempPoke.level = Integer.parseInt(tempStr); } catch (NumberFormatException e) { tempPoke.level = 100; } } else if (qName.equals("gender")) { if (tempStr.equals("Male")) { tempPoke.gender = Gender.GENDER_MALE; } else if (tempStr.equals("Female")) { tempPoke.gender = Gender.GENDER_FEMALE; } else { tempPoke.gender = Gender.GENDER_NONE; } } else if (qName.equals("nature")) { tempPoke.nature = tempStr.trim(); } else if (qName.equals("item")) { tempPoke.item = tempStr.trim(); } else if (qName.equals("ability")) { tempPoke.ability = tempStr.trim(); } else if (qName.equals("move")) { tempPoke.moves[moveIndex] = tempStr.trim(); moveIndex++; } else if (qName.equals("pokemon")) { m_pokemon.add(tempPoke); } else if (qName.equals("happiness")) { try { tempPoke.happiness = Integer.valueOf(tempStr); } catch (NumberFormatException e) { tempPoke.happiness = 255; } } } private int getStatIndex(String s) { for (int i = 0; i < Pokemon.STAT_COUNT; i++) { if (s.equals(Pokemon.getStatName(i))) { return i; } } return -1; } private int m_handle; private Map<Integer, StreamObject> m_objects; //translate between Shoddy Battle 1 and 2 nature IDs (unused) private int[] m_natures = { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 0, 12, 18, 6 }; private Pokemon[] parseShoddyBattle1Team(DataInputStream is) throws IOException { m_handle = -1; m_objects = new HashMap<Integer, StreamObject>(); // uuid string readObject(is); // pokemon array readObject(is); return m_pokemon.toArray(new Pokemon[m_pokemon.size()]); } public class StreamObject { public int type; public int handle; } public class Field { public boolean endBlockData; public boolean object; public char typeCode; public String name; public String type; } public class StreamClassDesc extends StreamObject { public String name; public long uid; public char flags; public List<Field> fields = new ArrayList<Field>(); StreamClassDesc superclass; } public class StreamArray extends StreamObject { public StreamClassDesc pClassDesc; public List<StreamObject> elements = new ArrayList<StreamObject>(); public List<Integer> intElements = new ArrayList<Integer>(); public boolean objects; } public class StreamString extends StreamObject { public String data; } public class StreamNature extends StreamObject { public int nature; } private StreamObject readObject(DataInputStream is) throws IOException { char flag = (char)is.read(); switch (flag) { case TC_OBJECT: return readNewObject(is); case TC_CLASS: break; case TC_ARRAY: StreamClassDesc desc = readClassDesc(is); StreamArray ret = new StreamArray(); ret.type = TC_ARRAY; ret.handle = newHandle(); int size = is.readInt(); char code = desc.name.charAt(1); ret.objects = (code == 'L'); for (int i = 0; i < size; i++) { if (ret.objects) { StreamObject p = readObject(is); ret.elements.add(p); } else if (code == 'I') { int datum = is.readInt(); ret.intElements.add(datum); } } return storeObject(ret); case TC_STRING: StreamString retString = new StreamString(); retString.type = TC_STRING; retString.handle = newHandle(); retString.data = is.readUTF(); return storeObject(retString); case TC_CLASSDESC: return readNewClassDesc(is); case TC_PROXYCLASSDESC: break; case TC_REFERENCE: return readPrevObject(is); case TC_NULL: return null; case TC_BLOCKDATA: size = is.read(); for (int i = 0; i < size; i++) { is.read(); } break; } return null; } private StreamObject readNewObject(DataInputStream is) throws IOException { StreamClassDesc desc = readClassDesc(is); int handle = newHandle(); if (desc != null) { if (desc.name.equals("shoddybattle.Pokemon")) { readPokemon(desc, is); } else if (desc.name.equals("mechanics.AdvanceMechanics") || (desc.name.equals("mechanics.JewelMechanics"))) { readObject(is); } else if (desc.name.equals("java.util.Random")) { readRandomObject(is); } else if (desc.name.equals("mechanics.moves.MoveListEntry")) { StreamObject move = readObject(is); StreamString ret = new StreamString(); ret.handle = handle; ret.type = TC_STRING; if (move.type == TC_STRING) { ret.data = ((StreamString)move).data; } if ((desc.flags & SC_WRITE_METHOD) != 0) { is.read(); } return storeObject(ret); } else if (desc.name.equals("mechanics.PokemonNature")) { int nature = is.readInt(); StreamNature ret = new StreamNature(); ret.handle = handle; ret.type = TC_OBJECT; ret.nature = nature; return storeObject(ret); } } StreamObject ret = new StreamObject(); ret.handle = handle; ret.type = TC_OBJECT; return storeObject(ret); } private StreamClassDesc readClassDesc(DataInputStream is) throws IOException { char flag = (char)is.read(); if (flag == TC_CLASSDESC) { return readNewClassDesc(is); } else if ((flag == TC_PROXYCLASSDESC) || (flag == TC_NULL)) { return null; } else if (flag == TC_REFERENCE) { return (StreamClassDesc)readPrevObject(is); } return null; } private StreamClassDesc readNewClassDesc(DataInputStream is) throws IOException { StreamClassDesc desc = new StreamClassDesc(); desc.name = is.readUTF(); desc.uid = is.readLong(); desc.handle = newHandle(); desc.flags = (char)is.read(); int count = is.readShort(); for (int i = 0; i < count; i++) { Field f = new Field(); f.endBlockData = false; f.typeCode = (char)is.read(); f.name = is.readUTF(); if ((f.typeCode == '[') || (f.typeCode == 'L')) { f.object = true; StreamObject obj = readObject(is); if (obj.type == TC_STRING) { f.type = ((StreamString)obj).data; } } else { f.object = false; } desc.fields.add(f); } is.read(); desc.superclass = readClassDesc(is); if (desc.superclass != null) { List<Field> fields = desc.superclass.fields; for (Field f : desc.fields) { fields.add(f); } desc.fields = fields; } if ((desc.flags & SC_WRITE_METHOD) != 0) { Field field = new Field(); field.endBlockData = true; desc.fields.add(field); } return (StreamClassDesc)storeObject(desc); } private StreamObject readPrevObject(DataInputStream is) throws IOException { is.readShort(); int handle = is.readShort(); return m_objects.get(handle); } private StreamObject readPokemon(StreamClassDesc desc, DataInputStream is) throws IOException { Pokemon p = new Pokemon(); List<Field> fields = desc.fields; for (Field f : fields) { if (f.endBlockData) { is.read(); continue; } if (!f.object) { if (f.name.equals("m_gender")) { p.gender = Gender.getGender(is.readInt()); } else if (f.name.equals("m_level")) { p.level = is.readInt(); } else if (f.name.equals("m_shiny")) { p.shiny = is.readBoolean(); } else { if (f.typeCode == 'I') { is.readInt(); } else if (f.typeCode == 'Z') { is.read(); } } } else { StreamObject obj = readObject(is); if (obj == null) continue; if (obj.type == TC_STRING) { String data = ((StreamString)obj).data; if (f.name.equals("m_name")) { p.species = data; } else if (f.name.equals("m_abilityName")) { p.ability = data; } else if (f.name.equals("m_itemName")) { p.item = data; } else if (f.name.equals("m_nickname")) { p.nickname = data; } } else if (obj.type == TC_ARRAY) { StreamArray arr = (StreamArray)obj; if (!arr.objects) { int length = (f.name.equals("m_ppUp")) ? Pokemon.MOVE_COUNT : Pokemon.STAT_COUNT; int[] vals = new int[length]; for (int i = 0; i < vals.length; i++) { vals[i] = arr.intElements.get(i); } if (f.name.equals("m_iv")) { p.ivs = vals; } else if (f.name.equals("m_ev")) { p.evs = vals; } else if (f.name.equals("m_ppUp")) { p.ppUps = vals; } } else { String[] moves = new String[Pokemon.MOVE_COUNT]; for (int i = 0; i < moves.length; i++) { StreamString str = ((StreamString)arr.elements.get(i)); if (str != null) { moves[i] = str.data; } } p.moves = moves; } } else if (obj.type == TC_OBJECT) { if (f.name.equals("m_nature")) { p.nature = PokemonNature.getNature(((StreamNature)obj).nature).getName(); } } } } p.happiness = 255; m_pokemon.add(p); return null; } private void readRandomObject(DataInputStream is) throws IOException { is.readLong(); is.readLong(); is.read(); is.read(); } private int newHandle() { m_handle++; return m_handle; } private StreamObject storeObject(StreamObject obj) { m_objects.put(obj.handle, obj); return obj; } public static void main(String[] args) { TeamFileParser tfp = new TeamFileParser(); Pokemon[] team = tfp.parseTeam("/Users/ben/Downloads/trickyteam", null); System.out.println("Content-type: text/plain"); System.out.println(); if (team != null) { for (Pokemon p : team) { System.out.println("Level " + p.level + " " + p.species); System.out.println("Gender: " + p.gender); System.out.println("Nickname: " + p.nickname); System.out.println("Ability: " + p.ability); System.out.println("Item: " + p.item); System.out.println("IVs: " + java.util.Arrays.toString(p.ivs)); System.out.println("EVs: " + java.util.Arrays.toString(p.evs)); System.out.println("Moves:"); for (int i = 0; i < p.moves.length; i++) { System.out.println("\t" + p.moves[i] + " (" + p.ppUps[i] + " pp ups)"); } System.out.println(); System.out.println(); } } } }