diff --git a/src/main/java/com/projectswg/common/data/customization/CustomizationString.java b/src/main/java/com/projectswg/common/data/customization/CustomizationString.java index d9233e2..8d2eade 100644 --- a/src/main/java/com/projectswg/common/data/customization/CustomizationString.java +++ b/src/main/java/com/projectswg/common/data/customization/CustomizationString.java @@ -26,6 +26,8 @@ ***********************************************************************************/ package com.projectswg.common.data.customization; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.data.swgfile.ClientFactory; import com.projectswg.common.data.swgfile.visitors.CustomizationIDManagerData; import com.projectswg.common.encoding.Encodable; @@ -45,19 +47,21 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.function.BiConsumer; +import java.util.function.Function; /** * The Customization string is used to set special properties * on objects. This can be lightsaber color, vehicle speed, * facial hair style... */ -public class CustomizationString implements Encodable, Persistable { +public class CustomizationString implements Encodable, Persistable, MongoPersistable { - private CustomizationIDManagerData table = (CustomizationIDManagerData) ClientFactory.getInfoFromFile("customization/customization_id_manager.iff"); - private Map variables; + private final CustomizationIDManagerData table; + private final Map variables; public CustomizationString() { - variables = Collections.synchronizedMap(new LinkedHashMap<>()); // Ordered and synchronized + this.table = (CustomizationIDManagerData) ClientFactory.getInfoFromFile("customization/customization_id_manager.iff"); + this.variables = Collections.synchronizedMap(new LinkedHashMap<>()); // Ordered and synchronized } boolean isEmpty() { @@ -89,6 +93,17 @@ public class CustomizationString implements Encodable, Persistable { }); } + @Override + public void saveMongo(MongoData data) { + data.putMap("variables", variables, Function.identity(), CustomizationVariable::getValue); + } + + @Override + public void readMongo(MongoData data) { + variables.clear(); + variables.putAll(data.getMap("variables", Integer.class, CustomizationVariable::new)); + } + /** * Puts a new {@link CustomizationVariable} in this {@code CustomizationString}. * @param name is the variable to set diff --git a/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java index 280b9bd..909b384 100644 --- a/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java +++ b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoData.java @@ -28,22 +28,30 @@ package com.projectswg.common.data.encodables.mongo; import org.bson.Document; +import org.bson.types.Binary; import org.bson.types.ObjectId; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.time.Instant; import java.util.*; -import java.util.Map.Entry; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; -public class MongoData { +public class MongoData implements Map { private final @NotNull Document doc; public MongoData() { - this(new Document()); + this(0); + } + + public MongoData(int version) { + this(null); + if (version != 0) + doc.put("_ver", version); } public MongoData(Document doc) { @@ -54,10 +62,29 @@ public class MongoData { return new Document(doc); } + @Override + public String toString() { + return doc.toString(); + } + + @Override + public int hashCode() { + return doc.hashCode(); + } + + @Override + public boolean equals(Object o) { + return (o instanceof MongoData && doc.equals(((MongoData) o).doc)) || (o instanceof Document && doc.equals(o)); + } + public boolean containsKey(String key) { return doc.containsKey(key); } + public int version() { + return doc.getInteger("_ver", 0); + } + @Nullable public Integer getInteger(String key) { return doc.getInteger(key); @@ -129,24 +156,60 @@ public class MongoData { return d == null ? null : d.toInstant(); } + public byte [] getByteArray(String key) { + return getByteArray(key, null); + } + + public byte [] getByteArray(String key, byte [] defaultValue) { + Binary b = doc.get(key, Binary.class); + if (b == null) + return defaultValue; + return b.getData(); + } + @NotNull public List getArray(String key, Class klass) { List mdbArray = doc.get(key, List.class); List ret = new ArrayList<>(mdbArray.size()); - if (klass == Instant.class) { - for (Object o : mdbArray) { - if (o == null) - ret.add(null); - else - //noinspection UseOfObsoleteDateTimeApi - ret.add(klass.cast(((Date) o).toInstant())); + for (Object o : mdbArray) { + if (o == null) + ret.add(null); + else if (klass == Instant.class) + //noinspection UseOfObsoleteDateTimeApi + ret.add(klass.cast(((Date) o).toInstant())); + else if (klass == MongoData.class) + ret.add(klass.cast(new MongoData((Document) o))); + else + ret.add(klass.cast(o)); + } + return ret; + } + + @NotNull + public List getArray(String key, Supplier generator) { + List mdbArray = doc.get(key, List.class); + List ret = new ArrayList<>(mdbArray.size()); + for (Object o : mdbArray) { + if (o == null) { + ret.add(null); + } else { + T t = generator.get(); + t.readMongo(new MongoData((Document) o)); + ret.add(t); } - } else { - for (Object o : mdbArray) { - if (o == null) - ret.add(null); - else - ret.add(klass.cast(o)); + } + return ret; + } + + @NotNull + public List getArray(String key, Function generator) { + List mdbArray = doc.get(key, List.class); + List ret = new ArrayList<>(mdbArray.size()); + for (Object o : mdbArray) { + if (o == null) { + ret.add(null); + } else { + ret.add(generator.apply(new MongoData((Document) o))); } } return ret; @@ -168,30 +231,50 @@ public class MongoData { public T getDocument(String key, T dataInstance) { Document doc = this.doc.get(key, Document.class); if (doc != null) - dataInstance.read(new MongoData(doc)); + dataInstance.readMongo(new MongoData(doc)); return dataInstance; } @NotNull public Map getMap(String key, Class klass) { + return getMap(key, klass, Function.identity(), (k, v) -> v); + } + + @NotNull + public Map getMap(String key, Class klass, Supplier supplier) { + return getMap(key, MongoData.class, Function.identity(), (k, v) -> { + T ret = supplier.get(); + ret.readMongo(v); + return ret; + }); + } + + @NotNull + public Map getMap(String key, Class klass, Function valueParser) { + return getMap(key, klass, Function.identity(), valueParser); + } + + @NotNull + public Map getMap(String key, Class klass, Function keyParser, Function valueParser) { + return getMap(key, klass, keyParser, (k, v) -> valueParser.apply(v)); + } + + @NotNull + public Map getMap(String key, Class klass, Function keyParser, BiFunction valueParser) { Document mdbMap = doc.get(key, Document.class); - Map ret = new HashMap<>(mdbMap.size()); - if (klass == Instant.class) { - for (Entry e : mdbMap.entrySet()) { - Object o = e.getValue(); - if (o == null) - ret.put(e.getKey(), null); - else - //noinspection UseOfObsoleteDateTimeApi - ret.put(e.getKey(), klass.cast(((Date) o).toInstant())); - } - } else { - for (Entry e : mdbMap.entrySet()) { - Object o = e.getValue(); - if (o == null) - ret.put(e.getKey(), null); - else - ret.put(e.getKey(), klass.cast(o)); + Map ret = new HashMap<>(mdbMap.size()); + for (Entry e : mdbMap.entrySet()) { + K childKey = keyParser.apply(e.getKey()); + Object o = e.getValue(); + if (o == null) { + ret.put(childKey, null); + } else if (klass == Instant.class) { + //noinspection UseOfObsoleteDateTimeApi + ret.put(childKey, valueParser.apply(childKey, klass.cast(((Date) o).toInstant()))); + } else if (MongoData.class.isAssignableFrom(klass)) { + ret.put(childKey, valueParser.apply(childKey, klass.cast(new MongoData((Document) o)))); + } else { + ret.put(childKey, valueParser.apply(childKey, klass.cast(o))); } } return ret; @@ -209,7 +292,7 @@ public class MongoData { public void putFloat(String key, float f) { assert !containsKey(key) : "key already exists"; - doc.put(key, f); + doc.put(key, (double) f); } public void putDouble(String key, double d) { @@ -237,12 +320,9 @@ public class MongoData { doc.put(key, Date.from(date)); } - public void putArray(String key, byte [] array) { + public void putByteArray(String key, byte [] array) { assert !containsKey(key) : "key already exists"; - List mdbArray = new ArrayList<>(array.length); - for (byte b : array) - mdbArray.add((int) b); - doc.put(key, mdbArray); + doc.put(key, new Binary(array)); } public void putArray(String key, short [] array) { @@ -282,7 +362,7 @@ public class MongoData { doc.put(key, mdbArray); } - public void putArray(String key, List array) { + public void putArray(String key, Collection array) { assert !containsKey(key) : "key already exists"; List mdbArray = new ArrayList<>(array.size()); for (Object o : array) { @@ -291,7 +371,7 @@ public class MongoData { doc.put(key, mdbArray); } - public void putArray(String key, List array, Function converter) { + public void putArray(String key, Collection array, Function converter) { assert !containsKey(key) : "key already exists"; List mdbArray = new ArrayList<>(array.size()); for (T o : array) { @@ -309,7 +389,7 @@ public class MongoData { assert !containsKey(key) : "key already exists"; if (data != null) { MongoData dataDoc = new MongoData(); - data.save(dataDoc); + data.saveMongo(dataDoc); doc.put(key, dataDoc.doc); } } @@ -330,6 +410,14 @@ public class MongoData { putDocument(key, doc); } + public void putMap(String key, Map data, Function keyExtractor, Function valueExtractor) { + MongoData doc = new MongoData(); + for (Entry e : data.entrySet()) { + doc.doc.put(keyExtractor.apply(e.getKey()), translatePut(valueExtractor.apply(e.getValue()))); + } + putDocument(key, doc); + } + public void putMap(String key, Map data, Function keyExtractor, BiFunction valueExtractor) { MongoData doc = new MongoData(); for (Entry e : data.entrySet()) { @@ -344,13 +432,95 @@ public class MongoData { return Date.from((Instant) input); } else if (input instanceof String || input instanceof Boolean || input instanceof Long || input instanceof Date || input instanceof Document) { return input; + } else if (input instanceof MongoData) { + return ((MongoData) input).toDocument(); } else if (input instanceof Float || input instanceof Double) { return ((Number) input).doubleValue(); } else if (input instanceof Number) { return ((Number) input).intValue(); - } else - assert false : "bad object type: " + input; + } else if (input instanceof MongoPersistable) { + return store((MongoPersistable) input).toDocument(); + } + assert false : "bad object type: " + input; return null; } + /* Map functions */ + + @Override + public int size() { + return doc.size(); + } + + @Override + public boolean isEmpty() { + return doc.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return doc.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return doc.containsValue(value); + } + + @Override + public Object get(Object key) { + return doc.get(key); + } + + @Nullable + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException("must use another put method"); + } + + @Override + public Object remove(Object key) { + return doc.remove(key); + } + + @Override + public void putAll(@NotNull Map m) { + throw new UnsupportedOperationException("must use another put method"); + } + + @Override + public void clear() { + doc.clear(); + } + + @NotNull + @Override + public Set keySet() { + return new HashSet<>(doc.keySet()); + } + + @NotNull + @Override + public Collection values() { + return new ArrayList<>(doc.values()); + } + + @NotNull + @Override + public Set> entrySet() { + return doc.entrySet().stream().map(e -> Map.entry(e.getKey(), e.getValue())).collect(Collectors.toSet()); + } + + public static MongoData store(MongoPersistable obj) { + MongoData data = new MongoData(); + obj.saveMongo(data); + return data; + } + + public static T create(MongoData data, Supplier generator) { + T ret = generator.get(); + ret.readMongo(data); + return ret; + } + } diff --git a/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java index 12d2a3d..492bd9f 100644 --- a/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java +++ b/src/main/java/com/projectswg/common/data/encodables/mongo/MongoPersistable.java @@ -29,7 +29,7 @@ package com.projectswg.common.data.encodables.mongo; public interface MongoPersistable { - void read(MongoData data); - void save(MongoData data); + void readMongo(MongoData data); + void saveMongo(MongoData data); } diff --git a/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java b/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java index af3d0e3..15a9013 100644 --- a/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java +++ b/src/main/java/com/projectswg/common/data/encodables/oob/StringId.java @@ -97,13 +97,13 @@ public class StringId implements OutOfBandData, Persistable, MongoPersistable { } @Override - public void read(MongoData data) { + public void readMongo(MongoData data) { file = data.getString("file"); key = data.getString("key"); } @Override - public void save(MongoData data) { + public void saveMongo(MongoData data) { data.putString("file", file); data.putString("key", key); } diff --git a/src/main/java/com/projectswg/common/data/encodables/oob/waypoint/WaypointPackage.java b/src/main/java/com/projectswg/common/data/encodables/oob/waypoint/WaypointPackage.java index 3eb54df..a9d1222 100644 --- a/src/main/java/com/projectswg/common/data/encodables/oob/waypoint/WaypointPackage.java +++ b/src/main/java/com/projectswg/common/data/encodables/oob/waypoint/WaypointPackage.java @@ -26,6 +26,8 @@ ***********************************************************************************/ package com.projectswg.common.data.encodables.oob.waypoint; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.data.encodables.oob.OutOfBandData; import com.projectswg.common.data.encodables.oob.OutOfBandPackage; import com.projectswg.common.data.encodables.oob.OutOfBandPackage.Type; @@ -34,7 +36,7 @@ import com.projectswg.common.data.location.Terrain; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; -public class WaypointPackage implements OutOfBandData { +public class WaypointPackage implements OutOfBandData, MongoPersistable { private Point3D position; @@ -146,7 +148,29 @@ public class WaypointPackage implements OutOfBandData { public int getLength() { return 42 + name.length() * 2; } - + + @Override + public void saveMongo(MongoData data) { + data.putLong("objectId", objectId); + data.putLong("cellId", cellId); + data.putDocument("position", position); + data.putString("terrain", terrain.name()); + data.putString("name", name); + data.putInteger("color", color.getValue()); + data.putBoolean("active", active); + } + + @Override + public void readMongo(MongoData data) { + objectId = data.getLong("objectId", 0); + cellId = data.getLong("cellId", 0); + data.getDocument("position", position); + terrain = Terrain.valueOf(data.getString("terrain", "GONE")); + name = data.getString("name", "New Waypoint"); + color = WaypointColor.valueOf(data.getString("color", "BLUE")); + active = data.getBoolean("active", true); + } + @Override public void save(NetBufferStream stream) { stream.addByte(0); diff --git a/src/main/java/com/projectswg/common/data/encodables/tangible/SkillMod.java b/src/main/java/com/projectswg/common/data/encodables/tangible/SkillMod.java index f682eb5..2688f6d 100644 --- a/src/main/java/com/projectswg/common/data/encodables/tangible/SkillMod.java +++ b/src/main/java/com/projectswg/common/data/encodables/tangible/SkillMod.java @@ -26,12 +26,14 @@ ***********************************************************************************/ package com.projectswg.common.data.encodables.tangible; +import com.projectswg.common.data.encodables.mongo.MongoData; +import com.projectswg.common.data.encodables.mongo.MongoPersistable; import com.projectswg.common.encoding.Encodable; import com.projectswg.common.network.NetBuffer; import com.projectswg.common.network.NetBufferStream; import com.projectswg.common.persistable.Persistable; -public class SkillMod implements Encodable, Persistable { +public class SkillMod implements Encodable, Persistable, MongoPersistable { private int base, modifier; @@ -65,6 +67,18 @@ public class SkillMod implements Encodable, Persistable { return 8; } + @Override + public void readMongo(MongoData data) { + base = data.getInteger("base", 0); + modifier = data.getInteger("modifier", 0); + } + + @Override + public void saveMongo(MongoData data) { + data.putInteger("base", base); + data.putInteger("modifier", modifier); + } + @Override public void save(NetBufferStream stream) { stream.addInt(base); @@ -76,7 +90,7 @@ public class SkillMod implements Encodable, Persistable { base = stream.getInt(); modifier = stream.getInt(); } - + public void adjustBase(int adjustment) { base += adjustment; } diff --git a/src/main/java/com/projectswg/common/data/location/Location.java b/src/main/java/com/projectswg/common/data/location/Location.java index cc70a9c..2eb68e1 100644 --- a/src/main/java/com/projectswg/common/data/location/Location.java +++ b/src/main/java/com/projectswg/common/data/location/Location.java @@ -246,14 +246,14 @@ public class Location implements Encodable, Persistable, MongoPersistable { } @Override - public void read(MongoData data) { + public void readMongo(MongoData data) { data.getDocument("orientation", orientation); data.getDocument("point", point); terrain = data.containsKey("terrain") ? Terrain.valueOf(data.getString("terrain")) : null; } @Override - public void save(MongoData data) { + public void saveMongo(MongoData data) { data.putDocument("orientation", orientation); data.putDocument("point", point); if (terrain != null) diff --git a/src/main/java/com/projectswg/common/data/location/Point3D.java b/src/main/java/com/projectswg/common/data/location/Point3D.java index a383612..b897b30 100644 --- a/src/main/java/com/projectswg/common/data/location/Point3D.java +++ b/src/main/java/com/projectswg/common/data/location/Point3D.java @@ -206,14 +206,14 @@ public class Point3D implements Encodable, Persistable, MongoPersistable { } @Override - public void read(MongoData data) { + public void readMongo(MongoData data) { x = data.getDouble("x", 0); y = data.getDouble("y", 0); z = data.getDouble("z", 0); } @Override - public void save(MongoData data) { + public void saveMongo(MongoData data) { data.putDouble("x", x); data.putDouble("y", y); data.putDouble("z", z); diff --git a/src/main/java/com/projectswg/common/data/location/Quaternion.java b/src/main/java/com/projectswg/common/data/location/Quaternion.java index 1fb37d2..e649a59 100644 --- a/src/main/java/com/projectswg/common/data/location/Quaternion.java +++ b/src/main/java/com/projectswg/common/data/location/Quaternion.java @@ -240,7 +240,7 @@ public class Quaternion implements Encodable, Persistable, MongoPersistable { } @Override - public void read(MongoData data) { + public void readMongo(MongoData data) { x = data.getDouble("x", 0); y = data.getDouble("y", 0); z = data.getDouble("z", 0); @@ -248,7 +248,7 @@ public class Quaternion implements Encodable, Persistable, MongoPersistable { } @Override - public void save(MongoData data) { + public void saveMongo(MongoData data) { data.putDouble("x", x); data.putDouble("y", y); data.putDouble("z", z); diff --git a/src/main/java/com/projectswg/common/network/hcap/HcapInputStream.java b/src/main/java/com/projectswg/common/network/hcap/HcapInputStream.java new file mode 100644 index 0000000..7407194 --- /dev/null +++ b/src/main/java/com/projectswg/common/network/hcap/HcapInputStream.java @@ -0,0 +1,132 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// 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 com.projectswg.common.network.hcap; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class HcapInputStream implements AutoCloseable { + + private final DataInputStream is; + private final Map systemInformation; + private final byte version; + + public HcapInputStream(InputStream is) throws IOException { + this.is = new DataInputStream(is); + this.systemInformation = new ConcurrentHashMap<>(); + + this.version = this.is.readByte(); + readSystemInformation(); + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * This method simply performs is.close(). + * + * @exception IOException if an I/O error occurs. + * @see DataInputStream#close + */ + @Override + public void close() throws IOException { + is.close(); + } + + @NotNull + public Map getSystemInformation() { + return Collections.unmodifiableMap(systemInformation); + } + + @Nullable + public PacketRecord readPacket() throws IOException { + if (is.available() >= 11) { + boolean server = is.readBoolean(); + Instant time = Instant.ofEpochMilli(is.readLong()); + int dataLength = version < 4 ? is.readUnsignedShort() : is.readInt(); + byte [] data = new byte[dataLength]; + int ret = is.read(data, 0, dataLength); + int n = ret; + while (n < dataLength && ret >= 0) { + ret = is.read(data, n, dataLength - n); + n += ret; + } + if (n != dataLength) + return null; + return new PacketRecord(server, time, data); + } + return null; + } + + private void readSystemInformation() throws IOException { + int count = is.readByte(); + for (int i = 0; i < count; i++) { + Map.Entry entry = parseEntry(version, is.readUTF()); + systemInformation.put(entry.getKey(), entry.getValue()); + } + } + + private static Map.Entry parseEntry(byte version, String str) { + String [] keyValue = str.split("=", 2); + assert keyValue.length == 2; + String key = keyValue[0].toLowerCase(Locale.US); + String value = keyValue[1]; + + if (version == 2) { + switch (key) { + case "time.current_time": + return Map.entry(key, Instant.ofEpochMilli(Long.parseLong(value))); + case "time.time_zone": + return Map.entry(key, ZoneId.of(value.split(":")[0])); + default: + return Map.entry(key, value); + } + } else if (version >= 3) { + switch (key) { + case "time.current_time": + return Map.entry(key, Instant.parse(value)); + case "time.time_zone": + return Map.entry(key, ZoneId.of(value)); + default: + return Map.entry(key, value); + } + } else { + return Map.entry(key, value); + } + } + +} diff --git a/src/main/java/com/projectswg/common/network/hcap/HcapOutputStream.java b/src/main/java/com/projectswg/common/network/hcap/HcapOutputStream.java new file mode 100644 index 0000000..2e09baf --- /dev/null +++ b/src/main/java/com/projectswg/common/network/hcap/HcapOutputStream.java @@ -0,0 +1,88 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// 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 com.projectswg.common.network.hcap; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +public class HcapOutputStream implements AutoCloseable { + + private final DataOutputStream os; + + public HcapOutputStream(OutputStream os) throws IOException { + this.os = new DataOutputStream(os); + + this.os.writeByte(3); + writeSystemHeader(); + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * This method simply performs is.close(). + * + * @exception IOException if an I/O error occurs. + * @see DataInputStream#close + */ + @Override + public void close() throws IOException { + os.close(); + } + + public void record(PacketRecord record) throws IOException { + this.os.writeBoolean(record.isServer()); + this.os.writeLong(record.getTime().toEpochMilli()); + this.os.writeShort(record.getData().length); + this.os.write(record.getData()); + } + + private void writeSystemHeader() throws IOException { + OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); + Map systemStrings = new TreeMap<>(); + systemStrings.put("os.arch", os.getArch()); + systemStrings.put("os.details", os.getName()+":"+os.getVersion()); + systemStrings.put("os.processor_count", Integer.toString(os.getAvailableProcessors())); + systemStrings.put("java.version", System.getProperty("java.version")); + systemStrings.put("java.vendor", System.getProperty("java.vendor")); + systemStrings.put("time.time_zone", ZoneId.systemDefault().getId()); + systemStrings.put("time.current_time", Instant.now().toString()); + this.os.writeByte(systemStrings.size()); // Count of strings + for (Entry e : systemStrings.entrySet()) + this.os.writeUTF(e.getKey() + "=" + e.getValue()); + } + +} diff --git a/src/main/java/com/projectswg/common/network/hcap/PacketRecord.java b/src/main/java/com/projectswg/common/network/hcap/PacketRecord.java new file mode 100644 index 0000000..889c3f1 --- /dev/null +++ b/src/main/java/com/projectswg/common/network/hcap/PacketRecord.java @@ -0,0 +1,85 @@ +/*********************************************************************************** + * Copyright (c) 2019 /// 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 com.projectswg.common.network.hcap; + +import com.projectswg.common.network.NetBuffer; +import com.projectswg.common.network.packets.PacketType; +import com.projectswg.common.network.packets.SWGPacket; +import com.projectswg.common.network.packets.swg.zone.object_controller.ObjectController; +import com.projectswg.common.utilities.ByteUtilities; + +import java.nio.BufferUnderflowException; +import java.time.Instant; + +public class PacketRecord { + + private final boolean server; + private final Instant time; + private final byte[] data; + + public PacketRecord(boolean server, Instant time, byte[] data) { + this.server = server; + this.time = time; + this.data = data; + } + + public boolean isServer() { + return server; + } + + public Instant getTime() { + return time; + } + + public byte[] getData() { + return data; + } + + public PacketType parseType() { + NetBuffer data = NetBuffer.wrap(this.data); + data.position(2); + return PacketType.fromCrc(data.getInt()); + } + + public SWGPacket parse() { + NetBuffer data = NetBuffer.wrap(this.data); + data.position(2); + PacketType type = PacketType.fromCrc(data.getInt()); + data.position(0); + SWGPacket packet; + if (type == PacketType.OBJECT_CONTROLLER) { + return ObjectController.decodeController(data); + } else { + packet = PacketType.getForCrc(type.getCrc()); + if (packet != null) + packet.decode(data); + return packet; + } + } + +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 1d1b59c..0151bc2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,6 +2,7 @@ module com.projectswg.common { requires transitive me.joshlarson.jlcommon; requires static java.sql; requires static java.desktop; + requires transitive java.management; requires org.mongodb.bson; requires org.jetbrains.annotations; requires org.bouncycastle.provider;