public void ToData(BinaryWriter writer) { foreach (var entry in Entries) { // Allocate file space for a fake entry with all zeros entry.ChunkOffset = writer.BaseStream.Position; writer.BaseStream.Position += CoreEntry.DataHeaderSize; if (entry.ContainedObject is byte[] asBytes) { // Unsupported (raw data) object type writer.Write(asBytes); } else { RTTI.SerializeType(writer, entry.ContainedObject); } // Now rewrite it with the updated fields entry.ChunkSize = (uint)(writer.BaseStream.Position - entry.ChunkOffset - CoreEntry.DataHeaderSize); long oldPosition = writer.BaseStream.Position; writer.BaseStream.Position = entry.ChunkOffset; RTTI.SerializeType(writer, entry); writer.BaseStream.Position = oldPosition; } }
public static void Save(string filePath, List <object> objects) { using (var writer = new BinaryWriter(File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None))) { foreach (var topLevelObject in objects) { // Allocate file space for a fake entry with all zeros var entry = new Entry(); RTTI.SerializeType(writer, entry); if (topLevelObject is byte[] asBytes) { // Handle unsupported (raw data) object types writer.Write(asBytes); } else { RTTI.SerializeType(writer, topLevelObject); } entry.TypeId = RTTI.GetIdByType(topLevelObject.GetType()); entry.ChunkSize = (uint)(writer.BaseStream.Position - entry.ChunkOffset - 12); // Now rewrite it with the updated fields long oldPosition = writer.BaseStream.Position; writer.BaseStream.Position = entry.ChunkOffset; RTTI.SerializeType(writer, entry); writer.BaseStream.Position = oldPosition; } } }
public object DeserializeType(Type type) { // Special handling for int32/uint32 if (!type.IsEnum) { if (type == typeof(int)) { return(ReadVariableLengthInt()); } else if (type == typeof(uint)) { return((uint)ReadVariableLengthInt()); } } // Enums and trivial types if (RTTI.DeserializeTrivialType(Reader, type, out object trivialValue)) { return(trivialValue); } // Classes and structs if (DeserializeObjectType(type, out object objectValue)) { return(objectValue); } throw new NotImplementedException($"Unhandled object type '{type.FullName}'"); }
public void AddObject(object obj) { Entries.Add(new CoreEntry() { TypeId = RTTI.GetIdByType(obj.GetType()), ContainedObject = obj, }); }
public void Serialize(BinaryWriter writer) { writer.Write((uint)Count); foreach (var element in this) { RTTI.SerializeType(writer, element); } }
/// <summary> /// Deserialize a set of core objects from a BinaryReader. /// </summary> /// <param name="reader">Source stream</param> /// <param name="ignoreUnknownChunks">If set to true and an unknown RTTI type is encountered, read the object as /// an array of bytes. Otherwise, raise an exception.</param> public static CoreBinary FromData(BinaryReader reader, bool ignoreUnknownChunks = true) { var core = new CoreBinary(); while (reader.StreamRemainder() > 0) { #if COREFILE_DEBUG System.Diagnostics.Debugger.Log(0, "Info", $"Beginning chunk parse at {reader.BaseStream.Position:X}\n"); #endif var entry = RTTI.DeserializeType <CoreEntry>(reader); var topLevelObjectType = RTTI.GetTypeById(entry.TypeId); long expectedStreamPos = reader.BaseStream.Position + entry.ChunkSize; if (entry.ChunkSize > reader.StreamRemainder()) { throw new InvalidDataException($"Invalid chunk size {entry.ChunkSize} was supplied in Core file at offset {entry.ChunkOffset:X}"); } if (topLevelObjectType != null) { entry.ContainedObject = RTTI.DeserializeType(reader, topLevelObjectType); } else { if (!ignoreUnknownChunks) { throw new InvalidDataException($"Unknown type ID {entry.TypeId:X16} found in Core file at offset {entry.ChunkOffset:X}"); } // Invalid or unknown chunk ID hit - create an array of bytes "object" and try to continue with the rest of the file entry.ContainedObject = reader.ReadBytes(entry.ChunkSize); } #if COREFILE_DEBUG if (reader.BaseStream.Position < expectedStreamPos) { System.Diagnostics.Debugger.Log(0, "Warn", $"Short read of a chunk while deserializing object. {reader.BaseStream.Position} < {expectedStreamPos}. TypeId = {entry.TypeId:X16}\n"); } #endif // Check for overflows and underflows if (reader.BaseStream.Position > expectedStreamPos) { throw new Exception("Read past the end of a chunk while deserializing object"); } else if (reader.BaseStream.Position < expectedStreamPos) { throw new Exception("Short read of a chunk while deserializing object"); } reader.BaseStream.Position = expectedStreamPos; core._entries.Add(entry); } return(core); }
public static List <object> Load(string filePath, bool ignoreUnknownChunks = false) { var coreFileObjects = new List <object>(); using (var reader = new BinaryReader(File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))) { while (reader.BaseStream.Position != reader.BaseStream.Length) { //Console.WriteLine($"Beginning chunk parse at {reader.BaseStream.Position:X}"); var entry = RTTI.DeserializeType <Entry>(reader); long currentFilePos = reader.BaseStream.Position; long expectedFilePos = currentFilePos + entry.ChunkSize; if ((currentFilePos + entry.ChunkSize) > reader.StreamRemainder()) { throw new InvalidDataException($"Invalid chunk size {entry.ChunkSize} was supplied in Core file at offset {currentFilePos:X}"); } // TODO: This needs to be part of Entry Type topLevelObjectType = RTTI.GetTypeById(entry.TypeId); object topLevelObject = null; if (topLevelObjectType != null) { topLevelObject = RTTI.DeserializeType(reader, topLevelObjectType); } else { if (!ignoreUnknownChunks) { throw new InvalidDataException($"Unknown type ID {entry.TypeId:X16} found in Core file at offset {currentFilePos:X}"); } // Invalid or unknown chunk ID hit - create an array of bytes "object" and try to continue with the rest of the file topLevelObject = reader.ReadBytes(entry.ChunkSize); } if (reader.BaseStream.Position > expectedFilePos) { throw new Exception("Read past the end of a chunk while deserializing object"); } else if (reader.BaseStream.Position < expectedFilePos) { throw new Exception("Short read of a chunk while deserializing object"); } reader.BaseStream.Position = expectedFilePos; coreFileObjects.Add(topLevelObject); } } return(coreFileObjects); }
public void Deserialize(BinaryReader reader) { uint itemCount = reader.ReadUInt32(); if (itemCount > reader.StreamRemainder()) { throw new Exception("HashMap item count is out of bounds"); } for (uint i = 0; i < itemCount; i++) { uint entryHash = reader.ReadUInt32(); var newObj = RTTI.DeserializeType <T>(reader); // Hash is derived from the key member in T Add(entryHash, newObj); } }
public void Deserialize(BinaryReader reader) { uint itemCount = reader.ReadUInt32(); if (itemCount > reader.StreamRemainder()) { throw new Exception("HashSet item count is out of bounds"); } for (uint i = 0; i < itemCount; i++) { uint entryHash = reader.ReadUInt32(); var newObj = RTTI.DeserializeType <T>(reader); // TODO: is entryHash actually a hash? Add(entryHash, newObj); } }
public bool DeserializeObjectType(Type type, out object objectInstance, object existingObjectInstance = null) { if (!type.IsClass && !type.IsValueType) { objectInstance = null; return(false); } objectInstance = existingObjectInstance ?? RTTI.CreateObjectInstance(type); if (objectInstance is RTTI.ISaveSerializable asSerializable) { // Custom deserialization function implemented. Let the interface do the work. asSerializable.DeserializeStateObject(this); } else { DeserializeObjectClassMembers(type, objectInstance); } return(true); }
public void Deserialize(BinaryReader reader) { uint itemCount = reader.ReadUInt32(); if (itemCount > reader.StreamRemainder()) { throw new Exception("Array item count is out of bounds"); } if (itemCount == 0) { return; } if (typeof(T) == typeof(int)) { var bytes = reader.ReadBytesStrict(itemCount * sizeof(int)); var ints = new int[itemCount]; Buffer.BlockCopy(bytes, 0, ints, 0, bytes.Length); (this as List <int>).AddRange(ints); } else if (typeof(T) == typeof(byte)) { // Avoid wasting time on large arrays (this as List <byte>).AddRange(reader.ReadBytesStrict(itemCount)); } else { for (uint i = 0; i < itemCount; i++) { var newObj = RTTI.DeserializeType <T>(reader); Add(newObj); } } }
public SaveGameSystem(string savePath, FileMode mode = FileMode.Open) { if (mode == FileMode.Open) { var fileHandle = File.Open(savePath, mode, FileAccess.Read, FileShare.Read); using (var reader = new BinaryReader(fileHandle, Encoding.UTF8, true)) { // Offset 0x0 string gameVersionString = Encoding.ASCII.GetString(reader.ReadBytesStrict(32)); uint gameVersion = reader.ReadUInt32(); byte saveVersion = reader.ReadByte(); byte saveFlags = reader.ReadByte(); // { 0x80 = unknown, 0x1 = NG+, 0x2 = DLC entitlements } ushort worldIdHash = reader.ReadUInt16(); // CRC32-C xor'd - "World" bool isCoopGameMode = reader.ReadBooleanStrict(); _ = reader.ReadBytesStrict(3); // Offset 0x2C uint gameStateBlockLength = reader.ReadUInt32(); var gameStateBlock = reader.ReadBytesStrict(256); if (gameStateBlockLength != 84) { throw new Exception(); } // Offset 0x130 int unknown1 = reader.ReadInt32(); // Sign extended int saveType = reader.ReadInt32(); // Sign extended { 1 = manual, 2 = quick, 4 = auto, 8 = NG+ start point } var gameModuleGUID = new GGUUID().FromData(reader); // Field from `class GameModule` var uniqueSaveGUID = new GGUUID().FromData(reader); // CoCreateGuid() on save var gameLoadGUID = new GGUUID().FromData(reader); // CoCreateGuid() on game start var systemTypeGUID = new GGUUID().FromData(reader); // Possibly GUID for Win32System or physics double playTimeInSeconds = reader.ReadDouble(); _ = reader.ReadBytesStrict(108); // Offset 0x1EC var dataBlockMD5 = reader.ReadBytesStrict(16); uint dataBlockLength = reader.ReadUInt32(); // Parse actual save data State = new SaveState(reader, saveVersion, (uint)reader.BaseStream.Position, dataBlockLength); var unknownData1 = State.Reader.ReadBytesStrict(24); var unknownObject1 = State.ReadObjectHandle(); var unknownString1 = State.ReadIndexedString();// Likely entity RTTI name for the player's current mount. Instanced by AIManager. var unknownData2 = State.Reader.ReadBytesStrict(24); var unknownObject2 = State.ReadObjectHandle(); GlobalGameModule = RTTI.CreateObjectInstance <GameModule>(); GlobalStreamingStrategyManagerGame = RTTI.CreateObjectInstance <StreamingStrategyManagerGame>(); GlobalSceneManagerGame = RTTI.CreateObjectInstance <SceneManagerGame>(); // GameModule info { byte unknownByte = State.Reader.ReadByte(); if (unknownByte != 0) { var unknownData = State.Reader.ReadBytesStrict(24); } } GlobalStreamingStrategyManagerGame.ReadSave(State); GlobalSceneManagerGame.ReadSave(State); GlobalFactDatabase = State.DeserializeType <FactDatabase>(); GlobalGameModule.ReadSaveSystem(this); } } else if (mode == FileMode.Create || mode == FileMode.CreateNew) { throw new NotImplementedException("Writing archives is not supported at the moment"); } else { throw new NotImplementedException("Archive file mode must be Open, Create, or CreateNew"); } }
private void ReadHeaderData() { Reader.BaseStream.Position = SaveDataOffset + SaveDataLength - 0x8; uint typeDataChunkOffset = Reader.ReadUInt32(); uint rawDataChunkOffset = Reader.ReadUInt32(); // String/GUID tables Reader.BaseStream.Position = SaveDataOffset + rawDataChunkOffset; { // UTF-8 strings StringPool = StringTableContainer.FromData(this); // UTF-16 strings WideStringPool = WideStringTableContainer.FromData(this); // GUIDs if (SaveVersion >= 26) { GUIDPool = GUIDTableContainer.FromData(this); } } // Serialized type information and object instances Reader.BaseStream.Position = SaveDataOffset + typeDataChunkOffset; { // Create basic objects that are immediately registered with the engine int objectInstanceTypeCount = ReadVariableLengthOffset(); LocalSaveObjects = new Dictionary <int, object>(); for (int i = 0; i < objectInstanceTypeCount; i++) { Type objectType = RTTI.GetTypeByName(ReadIndexedString()); int instanceCount = ReadVariableLengthOffset(); for (int j = 0; j < instanceCount; j++) { int objectId = ReadVariableLengthOffset(); LocalSaveObjects.Add(objectId, RTTI.CreateObjectInstance(objectType)); } } // Handles to game data objects int gameDataObjectCount = ReadVariableLengthOffset(); GameDataObjects = new Dictionary <int, BaseGGUUID>(); for (int i = 0; i < gameDataObjectCount; i++) { int objectId = ReadVariableLengthOffset(); var guid = new BaseGGUUID().FromData(Reader); GameDataObjects.Add(objectId, guid); } // RTTI/class member layouts RTTIContainer = RTTIMapContainer.FromData(this); // File prefetch int prefetchFileCount = ReadVariableLengthOffset(); PrefetchFilePaths = new List <string>(prefetchFileCount); for (int i = 0; i < prefetchFileCount; i++) { PrefetchFilePaths.Add(ReadIndexedString()); } } Reader.BaseStream.Position = SaveDataOffset; }