From 8d80b64bf151474354f88a60f597554e5ceb964f Mon Sep 17 00:00:00 2001 From: Waverunner Date: Fri, 17 Jul 2015 17:48:36 -0400 Subject: [PATCH] Refactored all visitors to use the new format for ClientData classes and significantly improved loading time of client files --- src/resources/client_info/ClientData.java | 5 +- src/resources/client_info/ClientFactory.java | 128 +++++++----- src/resources/client_info/DataFactory.java | 99 ++------- src/resources/client_info/IffNode.java | 191 +++++++++++++++++- src/resources/client_info/SWGFile.java | 162 ++++++++++++++- src/resources/client_info/ServerFactory.java | 5 +- .../visitors/CrcStringTableData.java | 61 +++--- .../client_info/visitors/DatatableData.java | 74 ++++--- .../client_info/visitors/ObjectData.java | 153 +++++++------- .../visitors/PortalLayoutData.java | 113 +++++++---- .../visitors/ProfTemplateData.java | 9 +- .../visitors/SlotArrangementData.java | 9 +- .../visitors/SlotDefinitionData.java | 5 +- .../visitors/SlotDescriptorData.java | 9 +- .../visitors/WorldSnapshotData.java | 128 ++++++++---- .../objects/building/BuildingObject.java | 13 +- .../objects/buildouts/SnapshotRow.java | 4 +- .../buildouts/TerrainSnapshotLoader.java | 28 ++- src/resources/objects/cell/CellObject.java | 2 +- src/services/map/MapService.java | 10 +- src/services/objects/ObjectCreator.java | 14 +- 21 files changed, 812 insertions(+), 410 deletions(-) diff --git a/src/resources/client_info/ClientData.java b/src/resources/client_info/ClientData.java index bfd969cfe..350a23bf5 100644 --- a/src/resources/client_info/ClientData.java +++ b/src/resources/client_info/ClientData.java @@ -27,11 +27,10 @@ package resources.client_info; -import java.nio.ByteBuffer; - /** * Created by Waverunner on 6/9/2015 */ public abstract class ClientData { - public abstract void parse(String node, ByteBuffer data, int size); + public void readIff(SWGFile iff) {} + public void writeIff(SWGFile iff) {} } diff --git a/src/resources/client_info/ClientFactory.java b/src/resources/client_info/ClientFactory.java index 5dbcd2220..46abe47ee 100644 --- a/src/resources/client_info/ClientFactory.java +++ b/src/resources/client_info/ClientFactory.java @@ -27,6 +27,7 @@ ***********************************************************************************/ package resources.client_info; +import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map; @@ -43,7 +44,7 @@ import resources.client_info.visitors.WorldSnapshotData; public class ClientFactory extends DataFactory { private static ClientFactory instance; - private Map dataMap = new HashMap<>(); + private Map > dataMap = new HashMap<>(); private Map typeMap = new HashMap<>(); /** @@ -68,8 +69,42 @@ public class ClientFactory extends DataFactory { * Retrieves information from a client file used by SWG. Parsing of the file is done internally using {@link ClientData} which also * stores the variables and is the returned type. Retrieving info from this file puts a reference of the returned * {@link ClientData} into a {@link HashMap}. Future calls for this file will try and obtain this reference if it's not null to prevent - * the file from being parsed multiple times. - * @param file The SWG file you wish to get information from which resides in the ./DataObject/ folder. + * the file from being parsed multiple times if the save variable is true. + * @param file The SWG file you wish to get information from which resides in the ./clientdata/ folder. + * Example: creation/profession_defaults_combat_brawler.iff + * @return Specific visitor type of {@link ClientData} relating to the chosen file. For example, loading the file + * creation/profession_defaults_combat_brawler.iff would return an instance of {@link ProfTemplateData} extended from {@link ClientData}. + * A null instance of {@link ClientData} means that parsing for the type of file is not done, or a file was entered that doesn't exist on the + * file system. + * @param save Future calls for this file will try and obtain this reference if it's not null to prevent the file from being parsed multiple times + */ + public synchronized static ClientData getInfoFromFile(String file, boolean save) { + ClientFactory factory = ClientFactory.getInstance(); + SoftReference reference = factory.dataMap.get(file); + + if (reference == null) { + ClientData data = factory.readFile(file); + if (data == null) { + return null; + } + + // Soft used over Weak because Weak cleared as soon as the reference was not longer needed, Soft will be cleared when memory is needed by the JVM. + if (save) { + reference = new SoftReference<>(data); + if (reference.get() != null) + factory.dataMap.put(file, reference); + } else { + return data; + } + } + + return reference.get(); + } + + /** + * Retrieves information from a client file used by SWG. Parsing of the file is done internally using {@link ClientData} which also + * stores the variables and is the returned type. + * @param file The SWG file you wish to get information from which resides in the ./clientdata/ folder. * Example: creation/profession_defaults_combat_brawler.iff * @return Specific visitor type of {@link ClientData} relating to the chosen file. For example, loading the file * creation/profession_defaults_combat_brawler.iff would return an instance of {@link ProfTemplateData} extended from {@link ClientData}. @@ -77,24 +112,7 @@ public class ClientFactory extends DataFactory { * file system. */ public synchronized static ClientData getInfoFromFile(String file) { - ClientFactory factory = ClientFactory.getInstance(); - ClientData data = factory.dataMap.get(file); - - if (data == null) { - data = factory.readFile(file); - if (data == null) { - return null; - } - // I believe that this was commented out because it made the references go away while still being used. - // Look into this again further and see what is going wrong. - /*weak = new WeakReference(strong); - - if (weak.get() != null) { - dataMap.put(file, weak); - }*/ - } - - return data; + return getInfoFromFile(file, false); } public synchronized static String formatToSharedFile(String original) { @@ -133,41 +151,41 @@ public class ClientFactory extends DataFactory { // The typeMap is used for determining what DataObject class private void populateTypeMap() { - typeMap.put("ARGDFORM", "SlotArrangementData"); - typeMap.put("0006DATA", "SlotDefinitionData"); - typeMap.put("CSTBFORM", "CrcStringTableData"); - typeMap.put("DTIIFORM", "DatatableData"); - typeMap.put("PRFIFORM", "ProfTemplateData"); - typeMap.put("SLTDFORM", "SlotDescriptorData"); - typeMap.put("WSNPFORM", "WorldSnapshotData"); - typeMap.put("PRTOFORM", "PortalLayoutData"); + typeMap.put("ARGD", "SlotArrangementData"); + typeMap.put("0006", "SlotDefinitionData"); + typeMap.put("CSTB", "CrcStringTableData"); + typeMap.put("DTII", "DatatableData"); + typeMap.put("PRFI", "ProfTemplateData"); + typeMap.put("SLTD", "SlotDescriptorData"); + typeMap.put("WSNP", "WorldSnapshotData"); + typeMap.put("PRTO", "PortalLayoutData"); // Objects - typeMap.put("SBMKFORM", "ObjectData"); // object/battlefield_marker - typeMap.put("SBOTFORM", "ObjectData"); // object/building - typeMap.put("CCLTFORM", "ObjectData"); // object/cell - typeMap.put("SCNCFORM", "ObjectData"); // object/construction_contract - typeMap.put("SCOUFORM", "ObjectData"); // object/counting - typeMap.put("SCOTFORM", "ObjectData"); // object/creature && object/mobile - typeMap.put("SDSCFORM", "ObjectData"); // object/draft_schematic - typeMap.put("SFOTFORM", "ObjectData"); // object/factory - typeMap.put("SGRPFORM", "ObjectData"); // object/group - typeMap.put("SGLDFORM", "ObjectData"); // object/guild - typeMap.put("SIOTFORM", "ObjectData"); // object/installation - typeMap.put("SITNFORM", "ObjectData"); // object/intangible - typeMap.put("SJEDFORM", "ObjectData"); // object/jedi_manager - typeMap.put("SMSCFORM", "ObjectData"); // object/manufacture_schematic - typeMap.put("SMSOFORM", "ObjectData"); // object/mission - typeMap.put("SHOTFORM", "ObjectData"); // object/object - typeMap.put("STOTFORM", "ObjectData"); // object/path_waypoint && object/tangible - typeMap.put("SPLYFORM", "ObjectData"); // object/player - typeMap.put("SPQOFORM", "ObjectData"); // object/player_quest - typeMap.put("RCCTFORM", "ObjectData"); // object/resource_container - typeMap.put("SSHPFORM", "ObjectData"); // object/ship - typeMap.put("STATFORM", "ObjectData"); // object/soundobject && object/static - typeMap.put("STOKFORM", "ObjectData"); // object/token - typeMap.put("SUNIFORM", "ObjectData"); // object/universe - typeMap.put("SWAYFORM", "ObjectData"); // object/waypoint - typeMap.put("SWOTFORM", "ObjectData"); // object/weapon + typeMap.put("SBMK", "ObjectData"); // object/battlefield_marker + typeMap.put("SBOT", "ObjectData"); // object/building + typeMap.put("CCLT", "ObjectData"); // object/cell + typeMap.put("SCNC", "ObjectData"); // object/construction_contract + typeMap.put("SCOU", "ObjectData"); // object/counting + typeMap.put("SCOT", "ObjectData"); // object/creature && object/mobile + typeMap.put("SDSC", "ObjectData"); // object/draft_schematic + typeMap.put("SFOT", "ObjectData"); // object/factory + typeMap.put("SGRP", "ObjectData"); // object/group + typeMap.put("SGLD", "ObjectData"); // object/guild + typeMap.put("SIOT", "ObjectData"); // object/installation + typeMap.put("SITN", "ObjectData"); // object/intangible + typeMap.put("SJED", "ObjectData"); // object/jedi_manager + typeMap.put("SMSC", "ObjectData"); // object/manufacture_schematic + typeMap.put("SMSO", "ObjectData"); // object/mission + typeMap.put("SHOT", "ObjectData"); // object/object + typeMap.put("STOT", "ObjectData"); // object/path_waypoint && object/tangible + typeMap.put("SPLY", "ObjectData"); // object/player + typeMap.put("SPQO", "ObjectData"); // object/player_quest + typeMap.put("RCCT", "ObjectData"); // object/resource_container + typeMap.put("SSHP", "ObjectData"); // object/ship + typeMap.put("STAT", "ObjectData"); // object/soundobject && object/static + typeMap.put("STOK", "ObjectData"); // object/token + typeMap.put("SUNI", "ObjectData"); // object/universe + typeMap.put("SWAY", "ObjectData"); // object/waypoint + typeMap.put("SWOT", "ObjectData"); // object/weapon // } diff --git a/src/resources/client_info/DataFactory.java b/src/resources/client_info/DataFactory.java index 376c742fa..464bf83cc 100644 --- a/src/resources/client_info/DataFactory.java +++ b/src/resources/client_info/DataFactory.java @@ -31,6 +31,7 @@ import utilities.ByteUtilities; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -40,97 +41,27 @@ import java.nio.ByteOrder; */ public abstract class DataFactory { - private ClientData getFileType(ByteBuffer bb) { - bb.position(8); // Skip the first FORM and data size - String type = ByteUtilities.nextString(bb); // Get the specific form for this IFF so we know what visitor to use - - ClientData data = createDataObject(type); - - return data; - } - // readFile only called if dataMap doesn't contain the file as a key or it's value is null protected ClientData readFile(String file) { - FileInputStream stream = null; + if (file == null || file.isEmpty()) { + System.err.println("File cannot be null or empty!"); + return null; + } + + SWGFile swgFile = new SWGFile(); + try { - File f = new File(getFolder() + file); - if (!f.exists()) { - System.out.println(getFolder() + file + " not found!"); - return null; - } - - stream = new FileInputStream(f); - ByteBuffer bb = readIntoBuffer(stream); - - ClientData visitor = getFileType(bb); - if (visitor == null) - return null; - - bb.position(8); // Skip first FORM, some forms only have a node and not a parent form - parseData(bb, visitor); - return visitor; - } catch (Exception e) { + swgFile.read(new File(getFolder() + file)); + } catch (IOException e) { e.printStackTrace(); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - return null; - } - - private void parseData(ByteBuffer bb, ClientData visitor) throws Exception { - String name; - while (bb.hasRemaining()) { - name = ByteUtilities.nextString(bb); - if (name.contains("FORM")) { - parseFolder(bb, visitor); - } else { - parseNode(name, bb, visitor); - } } - } + ClientData clientData = createDataObject(swgFile.getType()); + if (clientData == null) + return null; - private void parseNode(String name, ByteBuffer bb, ClientData visitor) throws Exception { - int size = Integer.reverseBytes(bb.getInt()); // Size of this node/folder - - if (size == 0) - return; - - // Create a new buffer for parsing data specific to this node (excluding name bytes) - visitor.parse(name, getData(bb, size), size); - - } - - private void parseFolder(ByteBuffer bb, ClientData visitor) throws Exception { - ByteBuffer data = getData(bb, Integer.reverseBytes(bb.getInt())); - - // Notify visitor of the Folder? May be needed at some point. - - parseData(data, visitor); - } - - private ByteBuffer readIntoBuffer(FileInputStream stream) throws IOException { - ByteBuffer bb = ByteBuffer.allocate(stream.available()).order(ByteOrder.LITTLE_ENDIAN); - stream.read(bb.array()); - return bb; - } - - // Cut down a ByteBuffer from the current position so we are only working with a certain amount of bytes. Useful when we - // call the ClientData.parse method. - private ByteBuffer getData(ByteBuffer source, int size) { - ByteBuffer data = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); - int offset = source.position(); - data.put(source.array(), offset, size); - source.position(offset+size); - data.flip(); - - return data; + clientData.readIff(swgFile); + return clientData; } protected abstract ClientData createDataObject(String type); diff --git a/src/resources/client_info/IffNode.java b/src/resources/client_info/IffNode.java index 604707d59..83e66b4f2 100644 --- a/src/resources/client_info/IffNode.java +++ b/src/resources/client_info/IffNode.java @@ -1,9 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + *

+ * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on July 7th, 2011 after SOE announced the + * official shutdown of Star Wars Galaxies. Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing it on the final publish of the game prior + * to end-game events. + *

+ * This file is part of Holocore. + *

+ * -------------------------------------------------------------------------------- + *

+ * Holocore 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. + *

+ * Holocore 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 Holocore. If not, see + * + ******************************************************************************/ + package resources.client_info; +import utilities.ByteUtilities; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -13,8 +40,14 @@ public class IffNode { private String tag; private boolean isForm; + private boolean read; + private IffNode parent; private List children; private byte[] chunkData; + private ByteBuffer bb; + + public IffNode() { + } public IffNode(String tag, boolean isForm) { this.tag = tag; @@ -23,16 +56,134 @@ public class IffNode { public void addChild(IffNode child) { if (children == null) - children = new ArrayList<>(); + children = new LinkedList<>(); children.add(child); } + public byte readByte() { + initBuffer(); + return bb.get(); + } + + public short readShort() { + initBuffer(); + return bb.order(ByteOrder.LITTLE_ENDIAN).getShort(); + } + + public int readInt() { + initBuffer(); + return bb.order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + public float readFloat() { + initBuffer(); + return bb.getFloat(); + } + + public long readLong() { + initBuffer(); + return bb.order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + + public int readUInt() { + initBuffer(); + return bb.order(ByteOrder.BIG_ENDIAN).getInt(); + } + + public short readUShort() { + initBuffer(); + return bb.order(ByteOrder.BIG_ENDIAN).getShort(); + } + + public long readULong() { + initBuffer(); + return bb.order(ByteOrder.BIG_ENDIAN).getLong(); + } + + public boolean readBoolean() { + return readByte() == 1; + } + + public void skip(int offset) { + initBuffer(); + bb.position(bb.position() + offset); + } + + public String readString() { + initBuffer(); + String string = ByteUtilities.nextString(bb); + bb.get(); + return string; + } + + public byte[] getBytes() { + return isForm ? createForm() : createChunk(); + } + + public String getTag() { + return tag; + } + + public int getVersionFromTag() { + return Integer.parseInt(tag); + } + + public List getChildren() { + return children; + } + + public boolean isForm() { + return isForm; + } + + public boolean hasBeenRead() { + return read; + } + + public IffNode getNextUnreadChunk() { + for (IffNode child : children) { + if (!child.isForm && !child.hasBeenRead()) + return child; + } + return null; + } + + public IffNode getNextUnreadForm() { + for (IffNode child : children) { + if (child.isForm && !child.hasBeenRead()) + return child; + } + return null; + } + + public void setHasBeenRead(boolean read) { + this.read = read; + } + + public byte[] getChunkData() { + if (chunkData == null) + System.err.println("Null chunk data for " + tag + " Form=" + isForm); + return chunkData; + } + public void setChunkData(ByteBuffer chunkData) { this.chunkData = chunkData.array(); } - public byte[] getBytes() { - return isForm ? createForm() : createChunk(); + public IffNode getParent() { + return parent; + } + + public int readBuffer(ByteBuffer bb) { + String nodeTag = getTag(bb); + if (!nodeTag.equals("FORM")) { + tag = nodeTag; + return 4 + readChunk(bb); + } else return 4 + readForm(bb); + } + + private void initBuffer() { + if (bb == null) bb = ByteBuffer.wrap(getChunkData()); } private byte[] createForm() { @@ -53,6 +204,20 @@ public class IffNode { return bb.array(); } + private int readForm(ByteBuffer bb) { + isForm = true; + int size = bb.getInt(); // Size includes the "FORM" length (4) + tag = getTag(bb); + for (int read = 4; read < size; read++) { + IffNode child = new IffNode(); + child.parent = this; + + read += child.readBuffer(bb); + addChild(child); + } + return 4 + size; + } + private byte[] createChunk() { ByteBuffer byteBuffer = ByteBuffer.allocate(8 + chunkData.length).order(ByteOrder.LITTLE_ENDIAN); byteBuffer.put(tag.getBytes(Charset.forName("US-ASCII"))); @@ -60,4 +225,24 @@ public class IffNode { byteBuffer.put(chunkData); return byteBuffer.array(); } + + private int readChunk(ByteBuffer bb) { + isForm = false; + int chunkSize = bb.getInt(); + chunkData = new byte[chunkSize]; + bb.get(chunkData); + + return 4 + chunkData.length; + } + + private String getTag(ByteBuffer bb) { + byte[] tagBytes = new byte[4]; + bb.get(tagBytes); + return new String(tagBytes); + } + + @Override + public String toString() { + return "IffNode[tag='" + tag + '\'' + ", read=" + read + ", parent=" + parent + ", isForm=" + isForm + "]"; + } } \ No newline at end of file diff --git a/src/resources/client_info/SWGFile.java b/src/resources/client_info/SWGFile.java index dd20320dd..02c9decf7 100644 --- a/src/resources/client_info/SWGFile.java +++ b/src/resources/client_info/SWGFile.java @@ -1,47 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com + * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. + * Our goal is to create an emulator which will provide a server for players to + * continue playing a game similar to the one they used to play. We are basing + * it on the final publish of the game prior to end-game events. + * + * This file is part of Holocore. + * + * -------------------------------------------------------------------------------- + * + * Holocore 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. + * + * Holocore 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 Holocore. If not, see + ******************************************************************************/ + package resources.client_info; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; /** * Created by Waverunner on 6/4/2015 */ public class SWGFile { - private File file; + private String type; private IffNode master; private IffNode currentForm; + private String fileName; + + public SWGFile() {} public SWGFile(String type) { + this.type = type; this.master = new IffNode(type, true); this.currentForm = master; } - public SWGFile(File file, String type) { - this(type); - this.file = file; - } - public void save(File file) throws IOException { FileOutputStream outputStream = new FileOutputStream(file, false); outputStream.write(getData()); outputStream.close(); } - public void save() throws IOException { - save(file); + public void read(File file) throws IOException { + FileInputStream inputStream = new FileInputStream(file); + FileChannel channel = inputStream.getChannel(); + + int size = (int) channel.size(); + ByteBuffer bb = ByteBuffer.allocate((int) channel.size()); + if (channel.read(bb) != size) { + System.err.println("Failed to properly read the bytes in file " + file.getAbsolutePath() + "!"); + return; + } + // Reading will add bytes to the buffer, so we need to flip it before reading the buffer to IffNode's + bb.flip(); + + master = new IffNode("", true); + currentForm = master; + master.readBuffer(bb); + type = master.getTag(); + fileName = file.getAbsolutePath(); } - public void addForm(String tag) { - addForm(tag, true); + public IffNode addForm(String tag) { + return addForm(tag, true); } - public void addForm(String tag, boolean enterForm) { + public IffNode addForm(String tag, boolean enterForm) { IffNode form = new IffNode(tag, true); currentForm.addChild(form); if (enterForm) currentForm = form; + + return form; } public IffNode addChunk(String tag) { @@ -50,7 +97,100 @@ public class SWGFile { return chunk; } + /** + * Enters the next unread form based off of the current form + * @return Entered form + */ + public IffNode enterNextForm() { + IffNode next = currentForm.getNextUnreadForm(); + if (next == null) + return null; + + next.setHasBeenRead(true); + currentForm = next; + + return next; + } + + /** + * Enters the next unread form based off of the current forms children with the given tag + * @param tag Form tag to enter + * @return Entered form + */ + public IffNode enterForm(String tag) { + for (IffNode child : currentForm.getChildren()) { + if (!child.isForm() || child.hasBeenRead()) + continue; + + if (child.getTag().equals(tag) && child != currentForm) { + currentForm = child; + child.setHasBeenRead(true); + return child; + } + } + return null; + } + + /** + * Enters the next unread chunk with the given tag + * @param tag Tag of the chunk to enter + * @return Entered chunk + */ + public IffNode enterChunk(String tag) { + for (IffNode child : currentForm.getChildren()) { + if (child.isForm() || child.hasBeenRead()) + continue; + + if (child.getTag().equals(tag)) { + child.setHasBeenRead(true); + return child; + } + } + return null; + } + + /** + * Enters the next chunk based off of the current forms children + * @return Entered chunk + */ + public IffNode enterNextChunk() { + for (IffNode child : currentForm.getChildren()) { + if (child.isForm() || child.hasBeenRead()) + continue; + + child.setHasBeenRead(true); + return child; + } + return null; + } + + public IffNode exitForm() { + IffNode parent = currentForm.getParent(); + if (parent != null) { +// System.out.println("Exit form " + currentForm.getTag() + " to enter form " + parent.getTag()); + currentForm = parent; + } + + return currentForm; + } + public byte[] getData() { return master.getBytes(); } + + public String getType() { + return type; + } + + public IffNode getMaster() { + return master; + } + + public IffNode getCurrentForm() { + return currentForm; + } + + public String getFileName() { + return fileName; + } } \ No newline at end of file diff --git a/src/resources/client_info/ServerFactory.java b/src/resources/client_info/ServerFactory.java index a0b1074a4..a829705ad 100644 --- a/src/resources/client_info/ServerFactory.java +++ b/src/resources/client_info/ServerFactory.java @@ -44,7 +44,10 @@ public final class ServerFactory extends DataFactory { @Override protected ClientData createDataObject(String type) { - return (type.equals("DTIIFORM") ? new DatatableData() : null); + switch(type) { + case "DTII": return new DatatableData(); + default: return null; + } } @Override diff --git a/src/resources/client_info/visitors/CrcStringTableData.java b/src/resources/client_info/visitors/CrcStringTableData.java index 36612db9a..899105e72 100644 --- a/src/resources/client_info/visitors/CrcStringTableData.java +++ b/src/resources/client_info/visitors/CrcStringTableData.java @@ -33,6 +33,8 @@ import java.util.HashMap; import java.util.Map; import resources.client_info.ClientData; +import resources.client_info.IffNode; +import resources.client_info.SWGFile; import utilities.ByteUtilities; public class CrcStringTableData extends ClientData { @@ -42,37 +44,40 @@ public class CrcStringTableData extends ClientData { private ArrayList stringList = new ArrayList<>(); private Map crcMap = new HashMap<>(); private int count; - + @Override - public void parse(String node, ByteBuffer data, int size) { - switch (node) { - - case "0000DATA": - count = data.getInt(); - break; - - case "CRCT": - for(int i=0; i < count; ++i) { - crcList.add(data.getInt()); + public void readIff(SWGFile iff) { + iff.enterNextForm(); // Version + + IffNode chunk; + while ((chunk = iff.enterNextChunk()) != null) { + switch(chunk.getTag()) { + case "DATA": + count = chunk.readInt(); + break; + + case "CRCT": + for(int i=0; i < count; ++i) { + crcList.add(chunk.readUInt()); + } + break; + + case "STRT": + for(int i=0; i < count; ++i) { + startList.add(chunk.readInt()); + } + break; + + case "STNG": + for(int i=0; i < count; ++i) { + String str = chunk.readString(); + crcMap.put(crcList.get(i), str); + stringList.add(str); + } + break; + default: break; } - break; - - case "STRT": - for(int i=0; i < count; ++i) { - startList.add(data.getInt()); - } - break; - - case "STNG": - for(int i=0; i < count; ++i) { - data.position(startList.get(i)); - String str = ByteUtilities.nextString(data); - crcMap.put(crcList.get(i), str); - stringList.add(str); - } - break; } - } public boolean isValidCrc(int crc) { diff --git a/src/resources/client_info/visitors/DatatableData.java b/src/resources/client_info/visitors/DatatableData.java index c172ffc9f..500e3f5a8 100644 --- a/src/resources/client_info/visitors/DatatableData.java +++ b/src/resources/client_info/visitors/DatatableData.java @@ -27,10 +27,9 @@ ***********************************************************************************/ package resources.client_info.visitors; -import java.nio.ByteBuffer; - import resources.client_info.ClientData; -import utilities.ByteUtilities; +import resources.client_info.IffNode; +import resources.client_info.SWGFile; public class DatatableData extends ClientData { @@ -40,33 +39,34 @@ public class DatatableData extends ClientData { //private Map enums; @Override - public void parse(String node, ByteBuffer data, int size) { + public void readIff(SWGFile iff) { + iff.enterNextForm(); // version form - switch(node) { - - case "0001COLS": - columnNames = new String[data.getInt()]; - - for (int i = 0; i < columnNames.length; i++) { - columnNames[i] = ByteUtilities.nextString(data); - data.get(); // empty separator byte + IffNode chunk; + while((chunk = iff.enterNextChunk()) != null) { + switch(chunk.getTag()) { + case "COLS": + columnNames = new String[chunk.readInt()]; + for (int i = 0; i < columnNames.length; i++) { + columnNames[i] = chunk.readString(); + } + break; + case "TYPE": + parseTypes(chunk); + break; + case "ROWS": + parseRows(chunk); + break; + default: break; } - break; - - case "TYPE": - parseTypes(data); - break; - - case "ROWS": - parseRows(data); } } - private void parseTypes(ByteBuffer data) { + private void parseTypes(IffNode chunk) { columnTypes = new String[columnNames.length]; for (int t = 0; t < columnTypes.length; t++) { - String type = ByteUtilities.nextString(data); + String type = chunk.readString(); if (type.contains("[")) type = type.split("\\[")[0]; @@ -75,8 +75,6 @@ public class DatatableData extends ClientData { type = type.split("\\(")[0]; columnTypes[t] = type; - data.get(); // empty separator byte - /* TODO: Need to come up with a better way of doing enums. Example of what this type looks like: * e(RIFLE=0,CARBINE=1,PISTOL=2,HEAVY=3,1HAND_MELEE=4,2HAND_MELEE=5,UNARMED=6,POLEARM=7,THROWN=8,1HAND_LIGHTSABER=9,2HAND_LIGHTSABER=10,POLEARM_LIGHTSABER=11) if (type.startsWith("e(")) { @@ -85,12 +83,11 @@ public class DatatableData extends ClientData { enumEntries = type.replace("e(", "").replace(")", ""); } */ - } } - private void parseRows(ByteBuffer data) { - int rows = data.getInt(); + private void parseRows(IffNode chunk) { + int rows = chunk.readInt(); table = new Object[rows][columnTypes.length]; for (int r = 0; r < rows; r++) { @@ -101,30 +98,29 @@ public class DatatableData extends ClientData { switch(type) { case "b": // Boolean - table[r][t] = (data.getInt() == 1); + table[r][t] = (chunk.readInt() == 1); break; case "e": // Enumerator - table[r][t] = data.getInt(); + table[r][t] = chunk.readInt(); break; case "f": // Float - table[r][t] = data.getFloat(); + table[r][t] = chunk.readFloat(); break; case "h": // CRC - table[r][t] = data.getInt(); + table[r][t] = chunk.readUInt(); break; case "i": // Integer - table[r][t] = data.getInt(); + table[r][t] = chunk.readInt(); break; case "s": // String - if (data.get() != 0) { - data.position(data.position() - 1); - table[r][t] = ByteUtilities.nextString(data); - data.get(); + if (chunk.readByte() != 0) { + chunk.skip(-1); + table[r][t] = chunk.readString(); } else { table[r][t] = ""; } @@ -133,7 +129,7 @@ public class DatatableData extends ClientData { case "v": // TODO: Datatable Enums - data.getInt(); + chunk.readInt(); break; default: System.err.println("FATAL: Don't know how to decode type " + type + " in row " + r + " column " + t); @@ -144,9 +140,7 @@ public class DatatableData extends ClientData { } public int getRowCount() { - if(table == null) - return 0; - return table.length; + return table == null ? 0 : table.length; } public int getColumnCount() { diff --git a/src/resources/client_info/visitors/ObjectData.java b/src/resources/client_info/visitors/ObjectData.java index 0c5782fc0..2d525cfbe 100644 --- a/src/resources/client_info/visitors/ObjectData.java +++ b/src/resources/client_info/visitors/ObjectData.java @@ -27,7 +27,6 @@ ***********************************************************************************/ package resources.client_info.visitors; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -35,12 +34,13 @@ import java.util.Map; import resources.client_info.ClientFactory; import resources.client_info.ClientData; -import utilities.ByteUtilities; +import resources.client_info.IffNode; +import resources.client_info.SWGFile; public class ObjectData extends ClientData { - private Map attributes = new HashMap(); - private List parsedFiles = new ArrayList(); + private Map attributes = new HashMap<>(); + private List parsedFiles = new ArrayList<>(); public static final String APPEARANCE_FILE = "appearanceFilename"; public static final String ARRANGEMENT_FILE = "arrangementDescriptorFilename"; @@ -52,85 +52,96 @@ public class ObjectData extends ClientData { public static final String SLOT_DESCRIPTOR = "slotDescriptorFilename"; public ObjectData() {} - - @Override - public void parse(String node, ByteBuffer data, int size) { - switch(node) { - - case "DERVXXXX": // Extended attributes - String file = ByteUtilities.nextString(data); - - if (parsedFiles.contains(file)) // some DERVXXX were repeated and we do not want to replace any attributes unless they're overriden by a more specific obj - break; - - ClientData attrData = ClientFactory.getInfoFromFile(file); - if (attrData == null || !(attrData instanceof ObjectData)) { - System.out.println("Could not load attribute data from file " + file + "!"); - return; // break out of whole method as we should only continue if we have all the extended attributes - } - - // Put all the extended attributes in this map so it's accessible. Note that some of these are overridden. - attributes.putAll(((ObjectData)attrData).getAttributes()); - parsedFiles.add(file); - break; - - case "XXXX": - String attribute = ByteUtilities.nextString(data); - - data.get(); // always seems to be 0x00, separator byte after attr name most likely - - // Could put any unknown attributes in the map as a byte[], but most is just for the client which server doesn't need - parseAttribute(attribute, data); - - break; - - default: break; + @Override + public void readIff(SWGFile iff) { + while(iff.enterNextForm() != null) + readNextForm(iff); + } + + private void readNextForm(SWGFile iff) { + IffNode next; + while ((next = iff.enterNextForm()) != null) { + String tag = next.getTag(); + if (tag.equals("DERV")) + readExtendedAttributes(iff); + else if (tag.contains("0")) + readVersionForm(iff); + else if (!tag.isEmpty()) // More than likely to be a template form + readNextForm(iff); } + + iff.exitForm(); + } + + private void readVersionForm(SWGFile iff) { + IffNode attributeChunk; + while ((attributeChunk = iff.enterChunk("XXXX")) != null) { + parseAttributeChunk(attributeChunk); + } + + iff.exitForm(); + } + + private void readExtendedAttributes(SWGFile iff) { + IffNode chunk = iff.enterNextChunk(); + String file = chunk.readString(); + + if (parsedFiles.contains(file)) // some repeated and we do not want to replace any attributes unless they're overriden by a more specific obj + return; + + ClientData attrData = ClientFactory.getInfoFromFile(file, true); + if (attrData == null || !(attrData instanceof ObjectData)) { + System.out.println("Could not load attribute data from file " + file + "!"); + return; // break out of whole method as we should only continue if we have all the extended attributes + } + + // Put all the extended attributes in this map so it's accessible. Note that some of these are overridden. + attributes.putAll(((ObjectData)attrData).getAttributes()); + + parsedFiles.add(file); + + iff.exitForm(); } // Try and parse the attribute to map w/ appropriate Object type. - private boolean parseAttribute(String attr, ByteBuffer data) { + private void parseAttributeChunk(IffNode chunk) { + String attr = chunk.readString(); switch (attr) { - - case "appearanceFilename": putString(attr, data); break; - case "arrangementDescriptorFilename": putString(attr, data); break; - case "containerType": putInt(attr, data); break; - case "containerVolumeLimit": putInt(attr, data); break; - case "detailedDescription": putStfString(attr, data); break; - case "forceNoCollision": putBoolean(attr, data); break; - case "gender": putInt(attr, data); break; - case "objectName": putStfString(attr, data); break; - case "portalLayoutFilename": putString(attr, data); break; - case "slotDescriptorFilename": putString(attr, data); break; - case "structureFootprintFileName": putString(attr, data); break; - case "useStructureFootprintOutline": putBoolean(attr, data); break; - - default: break; + case "appearanceFilename": putString(chunk, attr); break; + case "arrangementDescriptorFilename": putString(chunk,attr); break; + case "containerType": putInt(chunk, attr); break; + case "containerVolumeLimit": putInt(chunk, attr);break; + case "detailedDescription": putStfString(chunk, attr); break; + case "forceNoCollision": putBoolean(chunk, attr); break; + case "gender": putInt(chunk, attr); break; + case "objectName": putStfString(chunk, attr); break; + case "portalLayoutFilename": putString(chunk, attr); break; + case "slotDescriptorFilename": putString(chunk, attr); break; + case "structureFootprintFileName": putString(chunk, attr); break; + case "useStructureFootprintOutline": putBoolean(chunk, attr); break; + default:/*System.out.println("Unknown attribute: " + attr);*/ break; } - - return false; } - private void putStfString(String attr, ByteBuffer data) { - if (data.get() == 0) + private void putStfString(IffNode chunk, String attr) { + if (chunk.readByte() == 0) return; - - data.get(); // 0x01 - String stfFile = ByteUtilities.nextString(data); + + String stfFile = chunk.readString(); if (stfFile.isEmpty()) return; - data.getShort(); // 0x00, 0x01 (Shows up as well even if stfFile prior data.get() is 0) + chunk.readShort(); // 0x00, 0x01 (Shows up as well even if stfFile prior data.get() is 0) - String stfName = ByteUtilities.nextString(data); + String stfName = chunk.readString(); attributes.put(attr, stfFile + ":" + stfName); } - private void putString(String attr, ByteBuffer data) { - if (data.get() != 0) { - String s = ByteUtilities.nextString(data); + private void putString(IffNode chunk, String attr) { + if (chunk.readByte() != 0) { + String s = chunk.readString(); if (s.isEmpty()) return; @@ -138,15 +149,15 @@ public class ObjectData extends ClientData { } } - private void putInt(String attr, ByteBuffer data) { - if (data.get() != 0) { // This should always be 1 if there is an int (note that 0x20 follows after this even if it's 0) - data.get(); // 0x20 byte for all it seems, unsure what it means - attributes.put(attr, data.getInt()); + private void putInt(IffNode chunk, String attr) { + if (chunk.readByte() != 0) { // This should always be 1 if there is an int (note that 0x20 follows after this even if it's 0) + chunk.readByte(); // 0x20 byte for all it seems, unsure what it means + attributes.put(attr, chunk.readInt()); } } - private void putBoolean(String attr, ByteBuffer data) { - attributes.put(attr, (data.get() == 1 ? true : false)); + private void putBoolean(IffNode chunk, String attr) { + attributes.put(attr, (chunk.readByte() == 1)); } public Object getAttribute(String attribute) { diff --git a/src/resources/client_info/visitors/PortalLayoutData.java b/src/resources/client_info/visitors/PortalLayoutData.java index 68c6041a0..f7a7e6046 100644 --- a/src/resources/client_info/visitors/PortalLayoutData.java +++ b/src/resources/client_info/visitors/PortalLayoutData.java @@ -28,10 +28,8 @@ package resources.client_info.visitors; import resources.client_info.ClientData; -import utilities.ByteUtilities; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import resources.client_info.IffNode; +import resources.client_info.SWGFile; import java.util.LinkedList; import java.util.List; @@ -40,47 +38,94 @@ import java.util.List; */ public class PortalLayoutData extends ClientData { - private int cellCount; - private int portalCount; +// private int cellCount; +// private int portalCount; private List cells = new LinkedList<>(); @Override - public void parse(String node, ByteBuffer data, int size) { - System.out.println("Node: " + node); - switch (node) { - case "0003DATA": - portalCount = data.getInt(); // number of portals - cellCount = data.getInt(); // number of cells - break; -/* case "0005DATA": { - - Cell cell = new Cell(); - data.getInt(); // number of portals cell has - - cell.canSeeParentCell = data.get(); - cell.name = data.getString(Charset.forName("US-ASCII").newDecoder().de); - cell.appearance = ByteUtilities.nextString(data); - System.out.println("Name: " + cell.name + " App.: " + cell.appearance); - cell.hasFloor = data.get(); - cell.floor = (cell.hasFloor == 1 ? ByteUtilities.nextString(data) : ""); - cells.add(cell); - - break; - }*/ + public void readIff(SWGFile iff) { + IffNode versionForm = iff.enterNextForm(); + if (versionForm == null) { + System.err.println("Expected version for a POB IFF"); + return; } + + int version = versionForm.getVersionFromTag(); + switch(version) { + case 3: readVersion3(iff); break; + case 4: readVersion3(iff); break; // Seems to be identical + default: System.err.println("Do not know how to handle POB version type " + version + " in file " + iff.getFileName()); + } + } + + private void readVersion3(SWGFile iff) { +/* IffNode dataChunk = iff.enterChunk("DATA"); + cellCount = dataChunk.readInt(); // Seems to be off for quite a few IFFs as displayed in IFF editors + portalCount = dataChunk.readInt();*/ + + iff.enterForm("CELS"); + + while(iff.enterForm("CELL") != null) { + cells.add(new Cell(iff)); + iff.exitForm(); + } + + iff.exitForm(); // Exit CELS form } public List getCells() { return cells; } + public static class Cell extends ClientData { +/* private int cellPortals; + private boolean canSeeParentCell; + private boolean hasFloor; + private String appearance; + private String floor;*/ + private String name; - public static class Cell { + public Cell(SWGFile iff) { + readIff(iff); + } - public byte canSeeParentCell; - public String name; - public String appearance; - public byte hasFloor; - public String floor; + @Override + public void readIff(SWGFile iff) { + IffNode versionForm = iff.enterNextForm(); + if (versionForm == null) { + System.err.println("Expected version for CELL in IFF " + iff.getFileName()); + return; + } + + int version = versionForm.getVersionFromTag(); + switch(version) { + case 3: readVersion3(iff); break; + case 5: readVersion5(iff); break; + default: System.err.println("Don't know how to handle version " + version + " CELL " + iff.getFileName()); + } + + iff.exitForm(); + } + + private void readVersion3(SWGFile iff) { +/* IffNode dataChunk = iff.enterChunk("DATA"); + cellPortals = dataChunk.readInt(); + canSeeParentCell = dataChunk.readBoolean();*/ + } + + private void readVersion5(SWGFile iff) { + IffNode dataChunk = iff.enterChunk("DATA"); +/* cellPortals =*/ dataChunk.readInt(); +/* canSeeParentCell =*/ dataChunk.readBoolean(); + name = dataChunk.readString(); +/* appearance =*/ dataChunk.readString(); +/* hasFloor =*/ dataChunk.readBoolean(); +/* if (hasFloor) + floor = dataChunk.readString();*/ + } + + public String getName() { + return name; + } } } diff --git a/src/resources/client_info/visitors/ProfTemplateData.java b/src/resources/client_info/visitors/ProfTemplateData.java index 3ef7ac267..171ad911d 100644 --- a/src/resources/client_info/visitors/ProfTemplateData.java +++ b/src/resources/client_info/visitors/ProfTemplateData.java @@ -36,7 +36,7 @@ import utilities.ByteUtilities; public class ProfTemplateData extends ClientData { - private List