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;
            }
        }
Example #2
0
        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,
     });
 }
Example #5
0
        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);
        }
Example #7
0
        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);
            }
        }
Example #9
0
        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);
        }
Example #11
0
        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;
        }