/// <summary> /// Deserializes the files. /// </summary> public LoadedDatabase Deserialize() { var deserializer = new DeserializerBuilder().Build(); using var dbs = new StreamReader(Path.Combine(_inputDirectory, "info.yml")); var loadedDatabase = deserializer.Deserialize <LoadedDatabase>(dbs); var isX86 = _database.Options.Type == DatabaseType.X86Database; foreach (var loadedDatabaseClass in loadedDatabase.Classes) { var vltClass = new VltClass(loadedDatabaseClass.Name); foreach (var loadedDatabaseClassField in loadedDatabaseClass.Fields) { var field = new VltClassField( isX86 ? VLT32Hasher.Hash(loadedDatabaseClassField.Name) : VLT64Hasher.Hash(loadedDatabaseClassField.Name), loadedDatabaseClassField.Name, loadedDatabaseClassField.TypeName, loadedDatabaseClassField.Flags, loadedDatabaseClassField.Alignment, loadedDatabaseClassField.Size, loadedDatabaseClassField.MaxCount, loadedDatabaseClassField.Offset); // Handle static value if (loadedDatabaseClassField.StaticValue != null) { field.StaticValue = ConvertSerializedValueToDataValue(_database.Options.GameId, _inputDirectory, vltClass, field, null, loadedDatabaseClassField.StaticValue); } vltClass.Fields.Add(field.Key, field); } _database.AddClass(vltClass); } foreach (var loadedDatabaseType in loadedDatabase.Types) { _database.Types.Add(new DatabaseTypeInfo { Name = loadedDatabaseType.Name, Size = loadedDatabaseType.Size }); } var collectionParentDictionary = new Dictionary <string, string>(); var collectionDictionary = new Dictionary <string, VltCollection>(); var vaultsToSaveDictionary = new Dictionary <string, List <Vault> >(); var collectionsToBeAdded = new List <VltCollection>(); foreach (var file in loadedDatabase.Files) { file.LoadedVaults = new List <Vault>(); var baseDirectory = Path.Combine(_inputDirectory, file.Group, file.Name); vaultsToSaveDictionary[file.Name] = new List <Vault>(); foreach (var vault in file.Vaults) { var vaultDirectory = Path.Combine(baseDirectory, vault).Trim(); var newVault = new Vault(vault) { Database = _database, IsPrimaryVault = vault == "db" }; if (Directory.Exists(vaultDirectory)) { HashSet <string> trackedCollections = new HashSet <string>(); foreach (var dataFile in Directory.GetFiles(vaultDirectory, "*.yml")) { var className = Path.GetFileNameWithoutExtension(dataFile); var vltClass = _database.FindClass(className); if (vltClass == null) { throw new InvalidDataException($"Unknown class: {className} ({dataFile})"); } //#if DEBUG // Debug.WriteLine("Processing class '{0}' in vault '{1}' (file: {2})", className, vault, dataFile); //#else // Console.WriteLine("Processing class '{0}' in vault '{1}' (file: {2})", className, vault, dataFile); //#endif using var vr = new StreamReader(dataFile); var collections = deserializer.Deserialize <List <LoadedCollection> >(vr); foreach (var loadedCollection in collections) { // BUG 16.02.2020: we have to do this to get around a YamlDotNet bug if (loadedCollection.Name == null) { loadedCollection.Name = "null"; } foreach (var k in loadedCollection.Data.Keys.ToList() .Where(k => loadedCollection.Data[k] == null)) { loadedCollection.Data[k] = "null"; } } var newCollections = new List <VltCollection>(); void AddCollectionsToList(ICollection <VltCollection> collectionList, IEnumerable <LoadedCollection> collectionsToAdd) { if (collectionList == null) { throw new Exception("collectionList should not be null!"); } collectionsToAdd ??= new List <LoadedCollection>(); foreach (var loadedCollection in collectionsToAdd) { var newVltCollection = new VltCollection(newVault, vltClass, loadedCollection.Name); foreach (var(key, value) in loadedCollection.Data) { if (!vltClass.TryGetField(key, out var field)) { throw new SerializedDatabaseLoaderException( $"Cannot find field: {vltClass.Name}/{key}"); } newVltCollection.SetRawValue(key, ConvertSerializedValueToDataValue(_database.Options.GameId, vaultDirectory, vltClass, field, newVltCollection, value)); } collectionParentDictionary[newVltCollection.ShortPath] = loadedCollection.ParentName; collectionList.Add(newVltCollection); collectionDictionary[newVltCollection.ShortPath] = newVltCollection; } } AddCollectionsToList(newCollections, collections); foreach (var newCollection in newCollections) { if (!trackedCollections.Add(newCollection.ShortPath)) { throw new SerializedDatabaseLoaderException( $"Duplicate collection found! Multiple collections at '{newCollection.ShortPath}' have been defined in your YML files."); } collectionsToBeAdded.Add(newCollection); } } } else { Console.WriteLine("WARN: vault {0} has no folder; looked for {1}", vault, vaultDirectory); } vaultsToSaveDictionary[file.Name].Add(newVault); _database.Vaults.Add(newVault); file.LoadedVaults.Add(newVault); } } // dependency resolution var resolved = new List <VaultDependencyNode>(); var unresolved = new List <VaultDependencyNode>(); foreach (var vault in _database.Vaults) { var vaultCollections = collectionsToBeAdded.Where(c => c.Vault.Name == vault.Name).ToList(); VaultDependencyNode node = new VaultDependencyNode(vault); foreach (var vaultCollection in vaultCollections) { string parentKey = collectionParentDictionary[vaultCollection.ShortPath]; if (!string.IsNullOrEmpty(parentKey)) { var parentCollection = collectionDictionary[$"{vaultCollection.Class.Name}/{parentKey}"]; if (parentCollection.Vault.Name != vault.Name) { node.AddEdge(new VaultDependencyNode(parentCollection.Vault)); } } } ResolveDependencies(node, resolved, unresolved); Debug.WriteLine("Vault {0}: {1} collections", vault.Name, vaultCollections.Count); } resolved = resolved.Distinct(VaultDependencyNode.VaultComparer).ToList(); unresolved = unresolved.Distinct(VaultDependencyNode.VaultComparer).ToList(); if (unresolved.Count != 0) { throw new SerializedDatabaseLoaderException("Cannot continue loading - unresolved vault dependencies"); } foreach (var node in resolved) { var vault = node.Vault; var vaultCollections = collectionsToBeAdded.Where(c => c.Vault.Name == vault.Name).ToList(); Debug.WriteLine("Loading collections for vault {0} ({1})", vault.Name, vaultCollections.Count); foreach (var collection in vaultCollections) { string parentKey = collectionParentDictionary[collection.ShortPath]; if (string.IsNullOrEmpty(parentKey)) { // Add collection directly _database.RowManager.AddCollection(collection); } else { var parentCollection = collectionDictionary[$"{collection.Class.Name}/{parentKey}"]; parentCollection.AddChild(collection); } } } _loadedDatabase = loadedDatabase; return(loadedDatabase); }
/// <summary> /// Serializes the database. /// </summary> /// <param name="files">The files that were loaded.</param> public void Serialize(IEnumerable <LoadedDatabaseFile> files) { var loadedDatabase = new LoadedDatabase { Classes = new List <LoadedDatabaseClass>(), Files = new List <LoadedDatabaseFile>(), Types = new List <LoadedTypeInfo>() }; loadedDatabase.Files.AddRange(files); foreach (var databaseType in _database.Types) { loadedDatabase.Types.Add(new LoadedTypeInfo { Name = databaseType.Name, Size = databaseType.Size }); } foreach (var databaseClass in _database.Classes) { var loadedDatabaseClass = new LoadedDatabaseClass { Name = databaseClass.Name, Fields = new List <LoadedDatabaseClassField>() }; loadedDatabaseClass.Fields.AddRange(databaseClass.Fields.Values.Select(field => new LoadedDatabaseClassField { Name = field.Name, TypeName = field.TypeName, Alignment = field.Alignment, Flags = field.Flags, MaxCount = field.MaxCount, Size = field.Size, Offset = field.Offset, StaticValue = ConvertDataValueToSerializedValue(_outputDirectory, null, field, field.StaticValue) })); loadedDatabase.Classes.Add(loadedDatabaseClass); } var serializerBuilder = new SerializerBuilder(); var serializer = serializerBuilder.Build(); using var sw = new StreamWriter(Path.Combine(_outputDirectory, "info.yml")); serializer.Serialize(sw, loadedDatabase); foreach (var loadedDatabaseFile in loadedDatabase.Files) { var baseDirectory = Path.Combine(_outputDirectory, loadedDatabaseFile.Group, loadedDatabaseFile.Name); Directory.CreateDirectory(baseDirectory); foreach (var vault in loadedDatabaseFile.LoadedVaults) { var vaultDirectory = Path.Combine(baseDirectory, vault.Name).Trim(); Directory.CreateDirectory(vaultDirectory); // Problem: Gameplay data is separated into numerous vaults, so we can't easily construct a proper hierarchy // Solution: Store the name of the parent node instead of having an array of children. foreach (var collectionGroup in _database.RowManager.GetCollectionsInVault(vault) .GroupBy(v => v.Class.Name)) { var loadedCollections = new List <LoadedCollection>(); AddLoadedCollections(vaultDirectory, loadedCollections, collectionGroup); using var vw = new StreamWriter(Path.Combine(vaultDirectory, collectionGroup.Key + ".yml")); serializer.Serialize(vw, loadedCollections); } } } }