├── Attribulator.Plugins.ModScript ├── Commands │ ├── UpdateCollectionModScriptCommand.cs │ ├── GameModScriptCommand.cs │ ├── CopyOverwriteModScriptCommand.cs │ ├── AddOverwriteModScriptCommand.cs │ ├── GenericModScriptCommand.cs │ ├── VersionModScriptCommand.cs │ ├── DeleteNodeModScriptCommand.cs │ ├── RenameNodeModScriptCommand.cs │ ├── ChangeVaultModScriptCommand.cs │ ├── DeleteFieldModScriptCommand.cs │ ├── ResizeFieldModScriptCommand.cs │ ├── AddFieldModScriptCommand.cs │ ├── CopyNodeModScriptCommand.cs │ ├── MoveNodeModScriptCommand.cs │ ├── AppendArrayModScriptCommand.cs │ ├── AddNodeModScriptCommand.cs │ ├── ResizeCollectionModScriptCommand.cs │ ├── CopyFieldsModScriptCommand.cs │ └── UpdateFieldModScriptCommand.cs ├── Attribulator.Plugins.ModScript.csproj ├── ModScriptPluginFactory.cs ├── AvailableCommandsCommand.cs ├── ModScriptPlugin.cs ├── ModScriptService.cs ├── ApplyScriptToBinCommand.cs └── ApplyScriptCommand.cs ├── Attribulator.Plugins.YAMLSupport ├── YamlSupportPlugin.cs ├── YamlSupportPluginFactory.cs └── Attribulator.Plugins.YAMLSupport.csproj ├── Attribulator.API ├── Plugin │ ├── IPlugin.cs │ ├── IPluginFactory.cs │ └── BaseCommand.cs ├── Serialization │ ├── SerializedTypeInfo.cs │ ├── SerializedDatabaseClass.cs │ ├── SerializedDatabaseClassField.cs │ ├── SerializedDatabaseFile.cs │ ├── SerializedCollection.cs │ ├── SerializedDatabaseInfo.cs │ └── IDatabaseStorageFormat.cs ├── Attribulator.API.csproj ├── Services │ ├── IPluginService.cs │ ├── ICommandService.cs │ ├── IProfileService.cs │ ├── IStorageFormatService.cs │ └── IDatabaseHelper.cs ├── Exceptions │ ├── CommandException.cs │ ├── CommandServiceException.cs │ └── ValueConversionException.cs ├── Data │ └── LoadedFile.cs ├── IProfile.cs └── Utils │ └── ValueConversionUtils.cs ├── Attribulator.CLI ├── Services │ ├── PluginServiceImpl.cs │ ├── ProfileServiceImpl.cs │ ├── CommandServiceImpl.cs │ └── StorageFormatServiceImpl.cs ├── Attribulator.CLI.csproj ├── Commands │ ├── PluginListCommand.cs │ ├── FormatListCommand.cs │ ├── ProfileListCommand.cs │ ├── UnpackCommand.cs │ ├── GenerateHashListCommand.cs │ └── PackCommand.cs ├── Build │ └── BuildCache.cs └── Program.cs ├── Attribulator.ModScript.API ├── Attribulator.ModScript.API.csproj ├── IModScriptCommand.cs ├── CommandParseException.cs ├── CommandExecutionException.cs ├── IModScriptService.cs ├── DatabaseHelper.cs ├── BaseModScriptCommand.cs └── Utils │ ├── ValueCloningUtils.cs │ └── PropertyUtils.cs ├── Attribulator.Plugins.SpeedProfiles ├── World │ ├── VaultSlotExport.cs │ └── GameplayVault.cs ├── SpeedProfilesPlugin.cs ├── SpeedProfilesPluginFactory.cs ├── Attribulator.Plugins.SpeedProfiles.csproj ├── CarbonProfile.cs ├── MostWantedProfile.cs ├── ProStreetProfile.cs ├── UndercoverProfile.cs ├── PlayStation2 │ └── CarbonProfilePs2.cs └── WorldProfile.cs ├── Attribulator.Plugins.BPSupport ├── BurnoutParadisePluginFactory.cs ├── Types │ ├── RwVector2.cs │ └── RwVector3.cs ├── Attribulator.Plugins.BPSupport.csproj ├── BurnoutParadisePlugin.cs ├── BurnoutParadiseProfile.cs └── BurnoutVaultPack.cs ├── .gitattributes ├── .github └── workflows │ └── dotnetcore.yml ├── Attribulator.sln └── .gitignore /Attribulator.Plugins.ModScript/Commands/UpdateCollectionModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Attribulator.Plugins.ModScript.Commands 2 | { 3 | // update_collection class node field [property] value 4 | public class UpdateCollectionModScriptCommand : UpdateFieldModScriptCommand 5 | { 6 | } 7 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.YAMLSupport/YamlSupportPlugin.cs: -------------------------------------------------------------------------------- 1 | using Attribulator.API.Plugin; 2 | 3 | namespace Attribulator.Plugins.YAMLSupport 4 | { 5 | public class YamlSupportPlugin : IPlugin 6 | { 7 | public string GetName() 8 | { 9 | return "YAML Support"; 10 | } 11 | 12 | public void Init() 13 | { 14 | // 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Attribulator.API/Plugin/IPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace Attribulator.API.Plugin 2 | { 3 | /// 4 | /// Exposes an interface for a plugin. 5 | /// 6 | public interface IPlugin 7 | { 8 | /// 9 | /// Gets the name of the plugin. 10 | /// 11 | /// The name of the plugin. 12 | string GetName(); 13 | 14 | /// 15 | /// Initializes the plugin. 16 | /// 17 | void Init(); 18 | } 19 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/GameModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | 4 | namespace Attribulator.Plugins.ModScript.Commands 5 | { 6 | public class GameModScriptCommand : BaseModScriptCommand 7 | { 8 | public string Game { get; private set; } 9 | 10 | public override void Parse(List parts) 11 | { 12 | Game = parts[1]; 13 | } 14 | 15 | public override void Execute(DatabaseHelper databaseHelper) 16 | { 17 | // 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/SerializedTypeInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Attribulator.API.Serialization 2 | { 3 | /// 4 | /// Represents the serialized version of . 5 | /// 6 | public class SerializedTypeInfo 7 | { 8 | /// 9 | /// Gets or sets the name of the type. 10 | /// 11 | public string Name { get; set; } 12 | 13 | /// 14 | /// Gets or sets the size of the type. 15 | /// 16 | public uint Size { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/SerializedDatabaseClass.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Attribulator.API.Serialization 4 | { 5 | /// 6 | /// Represents the serialized version of . 7 | /// 8 | public class SerializedDatabaseClass 9 | { 10 | /// 11 | /// Gets or sets the name of the class. 12 | /// 13 | public string Name { get; set; } 14 | 15 | /// 16 | /// Gets or sets the list of fields. 17 | /// 18 | public List Fields { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/CopyOverwriteModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using Attribulator.ModScript.API; 2 | 3 | namespace Attribulator.Plugins.ModScript.Commands 4 | { 5 | // copy_overwrite class sourceNode parentNode nodeName 6 | public class CopyOverwriteModScriptCommand : CopyNodeModScriptCommand 7 | { 8 | public override void Execute(DatabaseHelper databaseHelper) 9 | { 10 | var collection = GetCollection(databaseHelper, ClassName, DestinationCollectionName, false); 11 | if (collection != null) databaseHelper.RemoveCollection(collection).ForEach(RemoveCollectionFromCache); 12 | 13 | base.Execute(databaseHelper); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Services/PluginServiceImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Attribulator.API.Plugin; 4 | using Attribulator.API.Services; 5 | 6 | namespace Attribulator.CLI.Services 7 | { 8 | public class PluginServiceImpl : IPluginService 9 | { 10 | private readonly List _plugins = new List(); 11 | 12 | public void RegisterPlugin(IPlugin plugin) 13 | { 14 | if (plugin == null) throw new ArgumentNullException(nameof(plugin)); 15 | 16 | _plugins.Add(plugin); 17 | } 18 | 19 | public IEnumerable GetPlugins() 20 | { 21 | return _plugins; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Attribulator.API/Attribulator.API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | API for Attribulator extensions 7 | x86 8 | Attribulator API 9 | netcoreapp3.1 10 | 2.0.0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/AddOverwriteModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using Attribulator.ModScript.API; 2 | 3 | namespace Attribulator.Plugins.ModScript.Commands 4 | { 5 | // add_overwrite class parentNode nodeName 6 | public class AddOverwriteModScriptCommand : AddNodeModScriptCommand 7 | { 8 | public override void Execute(DatabaseHelper databaseHelper) 9 | { 10 | var existingCollection = GetCollection(databaseHelper, ClassName, CollectionName, false); 11 | if (existingCollection != null) 12 | databaseHelper.RemoveCollection(existingCollection).ForEach(RemoveCollectionFromCache); 13 | 14 | base.Execute(databaseHelper); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/SerializedDatabaseClassField.cs: -------------------------------------------------------------------------------- 1 | using VaultLib.Core.Data; 2 | 3 | namespace Attribulator.API.Serialization 4 | { 5 | /// 6 | /// Represents the serialized version of . 7 | /// 8 | public class SerializedDatabaseClassField 9 | { 10 | public string Name { get; set; } 11 | public string TypeName { get; set; } 12 | public int Alignment { get; set; } 13 | public DefinitionFlags Flags { get; set; } 14 | public ushort Offset { get; set; } 15 | public ushort Size { get; set; } 16 | public ushort MaxCount { get; set; } 17 | public object StaticValue { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/GenericModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | 4 | namespace Attribulator.Plugins.ModScript.Commands 5 | { 6 | public class GenericModScriptCommand : BaseModScriptCommand 7 | { 8 | public GenericModScriptCommand(string line) 9 | { 10 | Command = line; 11 | } 12 | 13 | public string Command { get; } 14 | 15 | public override void Parse(List parts) 16 | { 17 | // 18 | } 19 | 20 | public override void Execute(DatabaseHelper databaseHelper) 21 | { 22 | throw new CommandExecutionException("Cannot execute GenericModScriptCommand."); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Attribulator.API/Services/IPluginService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.API.Plugin; 3 | 4 | namespace Attribulator.API.Services 5 | { 6 | /// 7 | /// Exposes an interface for registering and retrieving plugins. 8 | /// 9 | public interface IPluginService 10 | { 11 | /// 12 | /// Registers a new plugin. 13 | /// 14 | /// The plugin to register. 15 | void RegisterPlugin(IPlugin plugin); 16 | 17 | /// 18 | /// Gets the registered plugins. 19 | /// 20 | /// The registered plugins. 21 | IEnumerable GetPlugins(); 22 | } 23 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/Attribulator.ModScript.API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | ModScript API for Attribulator 7 | x86 8 | Attribulator - ModScript API 9 | netcoreapp3.1 10 | 2.0.0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/VersionModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | 4 | namespace Attribulator.Plugins.ModScript.Commands 5 | { 6 | public class VersionModScriptCommand : BaseModScriptCommand 7 | { 8 | public string Version { get; private set; } 9 | 10 | public override void Parse(List parts) 11 | { 12 | Version = parts[1]; 13 | 14 | if (Version != "4.6") 15 | throw new CommandParseException( 16 | "This tool is only compatible with ModScript files for NFS-VltEd 4.6."); 17 | } 18 | 19 | public override void Execute(DatabaseHelper databaseHelper) 20 | { 21 | // 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.YAMLSupport/YamlSupportPluginFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Attribulator.API.Plugin; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Attribulator.Plugins.YAMLSupport 6 | { 7 | public class YamlSupportPluginFactory : IPluginFactory 8 | { 9 | public void Configure(IServiceCollection services) 10 | { 11 | services.AddTransient(); 12 | services.AddTransient(); 13 | } 14 | 15 | public IPlugin CreatePlugin(IServiceProvider serviceProvider) 16 | { 17 | return serviceProvider.GetService(); 18 | } 19 | 20 | public string GetId() 21 | { 22 | return "Attribulator.Plugins.YAMLSupport"; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/World/VaultSlotExport.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using VaultLib.Core; 3 | using VaultLib.Core.Exports; 4 | using VaultLib.Core.Hashing; 5 | 6 | namespace Attribulator.Plugins.SpeedProfiles.World 7 | { 8 | public class VaultSlotExport : BaseExport 9 | { 10 | public override void Read(Vault vault, BinaryReader br) 11 | { 12 | br.ReadUInt32(); 13 | } 14 | 15 | public override void Write(Vault vault, BinaryWriter bw) 16 | { 17 | bw.Write(0); 18 | } 19 | 20 | public override ulong GetExportID() 21 | { 22 | return VLT32Hasher.Hash("VaultData"); 23 | } 24 | 25 | public override string GetTypeId() 26 | { 27 | return "VaultDataType"; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/BurnoutParadisePluginFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Attribulator.API.Plugin; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Attribulator.Plugins.BPSupport 6 | { 7 | public class BurnoutParadisePluginFactory : IPluginFactory 8 | { 9 | public void Configure(IServiceCollection services) 10 | { 11 | services.AddTransient(); 12 | services.AddTransient(); 13 | } 14 | 15 | public IPlugin CreatePlugin(IServiceProvider serviceProvider) 16 | { 17 | return serviceProvider.GetRequiredService(); 18 | } 19 | 20 | public string GetId() 21 | { 22 | return "Attribulator.Plugins.BPSupport"; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/SerializedDatabaseFile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.API.Data; 3 | 4 | namespace Attribulator.API.Serialization 5 | { 6 | /// 7 | /// Represents the serialized version of . 8 | /// 9 | public class SerializedDatabaseFile 10 | { 11 | /// 12 | /// Gets or sets the name of the file. 13 | /// 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// Gets or sets the group ID of the file. 18 | /// 19 | public string Group { get; set; } 20 | 21 | /// 22 | /// Gets or sets the list of vault names. 23 | /// 24 | public List Vaults { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/SerializedCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Attribulator.API.Serialization 4 | { 5 | /// 6 | /// Represents the serialized version of . 7 | /// 8 | public class SerializedCollection 9 | { 10 | /// 11 | /// Gets or sets the name of the parent collection. 12 | /// 13 | public string ParentName { get; set; } 14 | 15 | /// 16 | /// Gets or sets the name of the collection. 17 | /// 18 | public string Name { get; set; } 19 | 20 | /// 21 | /// Gets or sets the collection data map. 22 | /// 23 | public Dictionary Data { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.YAMLSupport/Attribulator.Plugins.YAMLSupport.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | Plugin providing support for the YAML storage format 7 | x86 8 | Attribulator - YAML Support 9 | netcoreapp3.1 10 | 2.0.0 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/Types/RwVector2.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using VaultLib.Core; 3 | using VaultLib.Core.Data; 4 | using VaultLib.Core.Types; 5 | 6 | namespace Attribulator.Plugins.BPSupport.Types 7 | { 8 | public class RwVector2 : VLTBaseType 9 | { 10 | public RwVector2(VltClass @class, VltClassField field, VltCollection collection) : base(@class, field, 11 | collection) 12 | { 13 | } 14 | 15 | public RwVector2(VltClass @class, VltClassField field) : base(@class, field) 16 | { 17 | } 18 | 19 | public float X { get; set; } 20 | public float Y { get; set; } 21 | 22 | public override void Read(Vault vault, BinaryReader br) 23 | { 24 | X = br.ReadSingle(); 25 | Y = br.ReadSingle(); 26 | } 27 | 28 | public override void Write(Vault vault, BinaryWriter bw) 29 | { 30 | bw.Write(X); 31 | bw.Write(Y); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.API/Services/ICommandService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Attribulator.API.Plugin; 4 | 5 | namespace Attribulator.API.Services 6 | { 7 | /// 8 | /// Exposes an interface for registering and retrieving commands. 9 | /// 10 | public interface ICommandService 11 | { 12 | /// 13 | /// Registers a new command type. 14 | /// 15 | /// The command type. 16 | void RegisterCommand() where TCommand : BaseCommand; 17 | 18 | /// 19 | /// Registers a new command type. 20 | /// 21 | void RegisterCommand(Type type); 22 | 23 | /// 24 | /// Gets the registered command types. 25 | /// 26 | /// An that produces the command types. 27 | IEnumerable GetCommandTypes(); 28 | } 29 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/DeleteNodeModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | 4 | namespace Attribulator.Plugins.ModScript.Commands 5 | { 6 | public class DeleteNodeModScriptCommand : BaseModScriptCommand 7 | { 8 | public string ClassName { get; set; } 9 | public string CollectionName { get; set; } 10 | 11 | public override void Parse(List parts) 12 | { 13 | if (parts.Count != 3) throw new CommandParseException($"Expected 3 tokens, got {parts.Count}"); 14 | 15 | ClassName = CleanHashString(parts[1]); 16 | CollectionName = CleanHashString(parts[2]); 17 | } 18 | 19 | public override void Execute(DatabaseHelper databaseHelper) 20 | { 21 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 22 | 23 | databaseHelper.RemoveCollection(collection).ForEach(RemoveCollectionFromCache); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/SerializedDatabaseInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Attribulator.API.Serialization 4 | { 5 | /// 6 | /// Simple database info container 7 | /// 8 | public class SerializedDatabaseInfo 9 | { 10 | /// 11 | /// Gets or sets the list of serialized classes. 12 | /// 13 | public List Classes { get; set; } 14 | 15 | /// 16 | /// Gets or sets the list of serialized type records. 17 | /// 18 | public List Types { get; set; } 19 | 20 | /// 21 | /// Gets or sets the list of serialized file records. 22 | /// 23 | public List Files { get; set; } 24 | 25 | /// 26 | /// Gets or sets the name of the primary vault. 27 | /// 28 | public string PrimaryVaultName { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Attribulator.Plugins.ModScript.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | Plugin providing ModScript support for Attribulator 7 | x86 8 | Attribulator - ModScript Support 9 | netcoreapp3.1 10 | 2.0.0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Attribulator.API/Exceptions/CommandException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Attribulator.API.Exceptions 5 | { 6 | [Serializable] 7 | public class CommandException : Exception 8 | { 9 | // 10 | // For guidelines regarding the creation of new exception types, see 11 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 12 | // and 13 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 14 | // 15 | 16 | public CommandException() 17 | { 18 | } 19 | 20 | public CommandException(string message) : base(message) 21 | { 22 | } 23 | 24 | public CommandException(string message, Exception inner) : base(message, inner) 25 | { 26 | } 27 | 28 | protected CommandException( 29 | SerializationInfo info, 30 | StreamingContext context) : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/IModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Attribulator.ModScript.API 4 | { 5 | /// 6 | /// Exposes a basic interface for a ModScript command. 7 | /// 8 | public interface IModScriptCommand 9 | { 10 | /// 11 | /// Gets or sets the command string. 12 | /// 13 | public string Line { get; set; } 14 | 15 | /// 16 | /// Gets or sets the command line number. 17 | /// 18 | public long LineNumber { get; set; } 19 | 20 | /// 21 | /// Parses the given command tokens. 22 | /// 23 | /// The tokens to be parsed. 24 | void Parse(List parts); 25 | 26 | /// 27 | /// Executes the command. 28 | /// 29 | /// An instance of the class. 30 | void Execute(DatabaseHelper databaseHelper); 31 | } 32 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/CommandParseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Attribulator.ModScript.API 5 | { 6 | [Serializable] 7 | public class CommandParseException : Exception 8 | { 9 | // 10 | // For guidelines regarding the creation of new exception types, see 11 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 12 | // and 13 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 14 | // 15 | 16 | public CommandParseException() 17 | { 18 | } 19 | 20 | public CommandParseException(string message) : base(message) 21 | { 22 | } 23 | 24 | public CommandParseException(string message, Exception inner) : base(message, inner) 25 | { 26 | } 27 | 28 | protected CommandParseException( 29 | SerializationInfo info, 30 | StreamingContext context) : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.API/Exceptions/CommandServiceException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Attribulator.API.Exceptions 5 | { 6 | [Serializable] 7 | public class CommandServiceException : Exception 8 | { 9 | // 10 | // For guidelines regarding the creation of new exception types, see 11 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 12 | // and 13 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 14 | // 15 | 16 | public CommandServiceException() 17 | { 18 | } 19 | 20 | public CommandServiceException(string message) : base(message) 21 | { 22 | } 23 | 24 | public CommandServiceException(string message, Exception inner) : base(message, inner) 25 | { 26 | } 27 | 28 | protected CommandServiceException( 29 | SerializationInfo info, 30 | StreamingContext context) : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.API/Exceptions/ValueConversionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Attribulator.API.Exceptions 5 | { 6 | [Serializable] 7 | public class ValueConversionException : Exception 8 | { 9 | // 10 | // For guidelines regarding the creation of new exception types, see 11 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 12 | // and 13 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 14 | // 15 | 16 | public ValueConversionException() 17 | { 18 | } 19 | 20 | public ValueConversionException(string message) : base(message) 21 | { 22 | } 23 | 24 | public ValueConversionException(string message, Exception inner) : base(message, inner) 25 | { 26 | } 27 | 28 | protected ValueConversionException( 29 | SerializationInfo info, 30 | StreamingContext context) : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/ModScriptPluginFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Attribulator.API.Plugin; 3 | using Attribulator.ModScript.API; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Attribulator.Plugins.ModScript 7 | { 8 | /// 9 | /// Plugin factory for the ModScript plugin. 10 | /// 11 | public class ModScriptPluginFactory : IPluginFactory 12 | { 13 | public void Configure(IServiceCollection services) 14 | { 15 | services.AddSingleton(); 16 | services.AddTransient(); 17 | services.AddTransient(); 18 | services.AddTransient(); 19 | services.AddSingleton(); 20 | } 21 | 22 | public IPlugin CreatePlugin(IServiceProvider serviceProvider) 23 | { 24 | return serviceProvider.GetService(); 25 | } 26 | 27 | public string GetId() 28 | { 29 | return "Attribulator.Plugins.ModScript"; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/CommandExecutionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Attribulator.ModScript.API 5 | { 6 | [Serializable] 7 | public class CommandExecutionException : Exception 8 | { 9 | // 10 | // For guidelines regarding the creation of new exception types, see 11 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 12 | // and 13 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 14 | // 15 | 16 | public CommandExecutionException() 17 | { 18 | } 19 | 20 | public CommandExecutionException(string message) : base(message) 21 | { 22 | } 23 | 24 | public CommandExecutionException(string message, Exception inner) : base(message, inner) 25 | { 26 | } 27 | 28 | protected CommandExecutionException( 29 | SerializationInfo info, 30 | StreamingContext context) : base(info, context) 31 | { 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/Types/RwVector3.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using VaultLib.Core; 3 | using VaultLib.Core.Data; 4 | using VaultLib.Core.Types; 5 | 6 | namespace Attribulator.Plugins.BPSupport.Types 7 | { 8 | public class RwVector3 : VLTBaseType 9 | { 10 | public RwVector3(VltClass @class, VltClassField field, VltCollection collection) : base(@class, field, 11 | collection) 12 | { 13 | } 14 | 15 | public RwVector3(VltClass @class, VltClassField field) : base(@class, field) 16 | { 17 | } 18 | 19 | public float X { get; set; } 20 | public float Y { get; set; } 21 | public float Z { get; set; } 22 | 23 | public override void Read(Vault vault, BinaryReader br) 24 | { 25 | X = br.ReadSingle(); 26 | Y = br.ReadSingle(); 27 | Z = br.ReadSingle(); 28 | br.ReadUInt32(); 29 | } 30 | 31 | public override void Write(Vault vault, BinaryWriter bw) 32 | { 33 | bw.Write(X); 34 | bw.Write(Y); 35 | bw.Write(Z); 36 | bw.Write(0); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/Attribulator.Plugins.BPSupport.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | Plugin providing support for Burnout Paradise. 7 | x86 8 | Attribulator - Burnout Paradise Support 9 | netcoreapp3.1 10 | 2.0.0 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/SpeedProfilesPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using Attribulator.API.Plugin; 4 | using VaultLib.Core.Hashing; 5 | using CarbonModule = VaultLib.Support.Carbon; 6 | using MostWantedModule = VaultLib.Support.MostWanted; 7 | using ProStreetModule = VaultLib.Support.ProStreet; 8 | using UndercoverModule = VaultLib.Support.Undercover; 9 | using WorldModule = VaultLib.Support.World; 10 | 11 | namespace Attribulator.Plugins.SpeedProfiles 12 | { 13 | public class SpeedProfilesPlugin : IPlugin 14 | { 15 | public string GetName() 16 | { 17 | return "Speed Profiles"; 18 | } 19 | 20 | public void Init() 21 | { 22 | HashManager.LoadDictionary(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), 23 | "Resources", "hashes.txt")); 24 | 25 | new CarbonModule.ModuleDef().Load(); 26 | new MostWantedModule.ModuleDef().Load(); 27 | new ProStreetModule.ModuleDef().Load(); 28 | new UndercoverModule.ModuleDef().Load(); 29 | new WorldModule.ModuleDef().Load(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/SpeedProfilesPluginFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Attribulator.API.Plugin; 3 | using Attribulator.Plugins.SpeedProfiles.PlayStation2; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Attribulator.Plugins.SpeedProfiles 7 | { 8 | public class SpeedProfilesPluginFactory : IPluginFactory 9 | { 10 | public void Configure(IServiceCollection services) 11 | { 12 | // PC profiles 13 | services.AddTransient(); 14 | services.AddTransient(); 15 | services.AddTransient(); 16 | services.AddTransient(); 17 | services.AddTransient(); 18 | 19 | // Console profiles 20 | services.AddTransient(); 21 | 22 | services.AddTransient(); 23 | } 24 | 25 | public IPlugin CreatePlugin(IServiceProvider serviceProvider) 26 | { 27 | return serviceProvider.GetService(); 28 | } 29 | 30 | public string GetId() 31 | { 32 | return "Attribulator.Plugins.SpeedProfiles"; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Attribulator.API/Data/LoadedFile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using VaultLib.Core; 3 | 4 | namespace Attribulator.API.Data 5 | { 6 | /// 7 | /// Represents a file loaded into a database. 8 | /// 9 | public class LoadedFile 10 | { 11 | /// 12 | /// Gets the name of the file. 13 | /// 14 | public string Name { get; } 15 | 16 | /// 17 | /// Gets the group ID of the file. 18 | /// 19 | public string Group { get; } 20 | 21 | /// 22 | /// Gets the vaults loaded from the file. 23 | /// 24 | public IEnumerable Vaults { get; } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The name of the file. 30 | /// 31 | /// The list of vaults in the file. 32 | public LoadedFile(string name, string group, IEnumerable vaults) 33 | { 34 | this.Name = name; 35 | this.Group = group; 36 | this.Vaults = vaults; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/RenameNodeModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | 4 | namespace Attribulator.Plugins.ModScript.Commands 5 | { 6 | // rename_node class node name 7 | public class RenameNodeModScriptCommand : BaseModScriptCommand 8 | { 9 | public string ClassName { get; set; } 10 | public string CollectionName { get; set; } 11 | public string NewName { get; set; } 12 | 13 | public override void Parse(List parts) 14 | { 15 | if (parts.Count != 4) throw new CommandParseException($"Expected 4 tokens, got {parts.Count}"); 16 | 17 | ClassName = parts[1]; 18 | CollectionName = parts[2]; 19 | NewName = parts[3]; 20 | } 21 | 22 | public override void Execute(DatabaseHelper databaseHelper) 23 | { 24 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 25 | 26 | if (GetCollection(databaseHelper, ClassName, NewName, false) != null) 27 | throw new CommandExecutionException( 28 | $"rename_node failed because there is already a collection called '{NewName}'"); 29 | 30 | RemoveCollectionFromCache(collection); 31 | databaseHelper.RenameCollection(collection, NewName); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Attribulator.CLI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | Allows the user to edit VLT databases through text (.yml) files 7 | Exe 8 | x86 9 | Attribulator CLI 10 | netcoreapp3.1 11 | 2.0.0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Attribulator.API/Services/IProfileService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Attribulator.API.Services 5 | { 6 | /// 7 | /// Exposes an interface for registering and retrieving profiles. 8 | /// 9 | public interface IProfileService 10 | { 11 | /// 12 | /// Registers a new profile type. 13 | /// 14 | /// The profile type. 15 | void RegisterProfile() where TProfile : IProfile; 16 | 17 | /// 18 | /// Registers a new profile type. 19 | /// 20 | void RegisterProfile(Type profileType); 21 | 22 | /// 23 | /// Gets the registered profiles. 24 | /// 25 | /// An that produces the profiles. 26 | IEnumerable GetProfiles(); 27 | 28 | /// 29 | /// Gets the profile mapped to the given ID. 30 | /// 31 | /// The profile ID. 32 | /// The object. 33 | /// Thrown when a profile cannot be found. 34 | IProfile GetProfile(string profileId); 35 | } 36 | } -------------------------------------------------------------------------------- /Attribulator.API/Plugin/IPluginFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Attribulator.API.Plugin 6 | { 7 | /// 8 | /// Exposes an interface for a plugin factory. 9 | /// 10 | public interface IPluginFactory 11 | { 12 | /// 13 | /// Configures the dependency injection container. 14 | /// 15 | /// 16 | void Configure(IServiceCollection services); 17 | 18 | /// 19 | /// Builds the plugin object. 20 | /// 21 | /// The DI service provider. 22 | /// The plugin object. 23 | IPlugin CreatePlugin(IServiceProvider serviceProvider); 24 | 25 | /// 26 | /// Gets the unique ID of the plugin. 27 | /// 28 | /// The unique ID of the plugin. 29 | string GetId(); 30 | 31 | /// 32 | /// Gets the list of IDs of required plugins. 33 | /// 34 | /// The list of required plugin IDs. 35 | IEnumerable GetRequiredPlugins() 36 | { 37 | return Array.Empty(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/IModScriptService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Attribulator.ModScript.API 4 | { 5 | /// 6 | /// Exposes an interface for working with ModScript files. 7 | /// 8 | public interface IModScriptService 9 | { 10 | /// 11 | /// Parses the given command strings and produces objects. 12 | /// 13 | /// An instance of that produces command strings. 14 | /// A stream of objects. 15 | IEnumerable ParseCommands(IEnumerable commands); 16 | 17 | /// 18 | /// Registers a new command type under the given name. 19 | /// 20 | /// The name of the command. 21 | /// The type of the command. 22 | void RegisterCommand(string name) where TCommand : IModScriptCommand, new(); 23 | 24 | /// 25 | /// Returns the names of the available ModScript commands. 26 | /// 27 | /// The names of the available commands. 28 | IEnumerable GetAvailableCommandNames(); 29 | } 30 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/AvailableCommandsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Attribulator.API.Plugin; 5 | using Attribulator.ModScript.API; 6 | using CommandLine; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Attribulator.Plugins.ModScript 11 | { 12 | [Verb("script-commands", HelpText = "List the available ModScript commands.")] 13 | public class AvailableCommandsCommand : BaseCommand 14 | { 15 | private ILogger _logger; 16 | 17 | public override void SetServiceProvider(IServiceProvider serviceProvider) 18 | { 19 | base.SetServiceProvider(serviceProvider); 20 | 21 | _logger = serviceProvider.GetRequiredService>(); 22 | } 23 | 24 | public override Task Execute() 25 | { 26 | var modScriptService = ServiceProvider.GetRequiredService(); 27 | var commandNames = modScriptService.GetAvailableCommandNames().ToList(); 28 | 29 | _logger.LogInformation("ModScript Commands ({NumCommands}):", commandNames.Count); 30 | foreach (var commandName in commandNames) 31 | _logger.LogInformation("{Name}", commandName); 32 | 33 | return Task.FromResult(0); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/ChangeVaultModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | 4 | namespace Attribulator.Plugins.ModScript.Commands 5 | { 6 | // change_vault class node vaultName 7 | public class ChangeVaultModScriptCommand : BaseModScriptCommand 8 | { 9 | public string ClassName { get; set; } 10 | public string CollectionName { get; set; } 11 | public string VaultName { get; set; } 12 | 13 | public override void Parse(List parts) 14 | { 15 | if (parts.Count != 4) 16 | throw new CommandParseException($"Expected 4 tokens, got {parts.Count} ({string.Join(' ', parts)})"); 17 | 18 | ClassName = CleanHashString(parts[1]); 19 | CollectionName = CleanHashString(parts[2]); 20 | VaultName = CleanHashString(parts[3]); 21 | } 22 | 23 | public override void Execute(DatabaseHelper databaseHelper) 24 | { 25 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 26 | var vault = databaseHelper.Database.Vaults.Find(v => v.Name == VaultName); 27 | 28 | if (vault == null) 29 | { 30 | throw new CommandExecutionException($"Cannot find vault: {VaultName}"); 31 | } 32 | 33 | collection.SetVault(vault); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Services/ProfileServiceImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Attribulator.API; 4 | using Attribulator.API.Services; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Attribulator.CLI.Services 8 | { 9 | public class ProfileServiceImpl : IProfileService 10 | { 11 | private readonly List _profiles = new List(); 12 | private readonly IServiceProvider _serviceProvider; 13 | 14 | public ProfileServiceImpl(IServiceProvider serviceProvider) 15 | { 16 | _serviceProvider = serviceProvider; 17 | } 18 | 19 | public void RegisterProfile() where TProfile : IProfile 20 | { 21 | RegisterProfile(typeof(TProfile)); 22 | } 23 | 24 | public void RegisterProfile(Type profileType) 25 | { 26 | _profiles.Add((IProfile) _serviceProvider.GetRequiredService(profileType)); 27 | } 28 | 29 | public IEnumerable GetProfiles() 30 | { 31 | return _profiles; 32 | } 33 | 34 | public IProfile GetProfile(string profileId) 35 | { 36 | foreach (var profile in _profiles) 37 | if (profile.GetProfileId() == profileId) 38 | return profile; 39 | 40 | throw new KeyNotFoundException($"Cannot find profile: {profileId}"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Commands/PluginListCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Attribulator.API.Plugin; 5 | using Attribulator.API.Services; 6 | using CommandLine; 7 | using JetBrains.Annotations; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Attribulator.CLI.Commands 12 | { 13 | [Verb("plugins", HelpText = "List the loaded plugins.")] 14 | [UsedImplicitly] 15 | public class PluginListCommand : BaseCommand 16 | { 17 | private ILogger _logger; 18 | 19 | public override void SetServiceProvider(IServiceProvider serviceProvider) 20 | { 21 | base.SetServiceProvider(serviceProvider); 22 | 23 | _logger = serviceProvider.GetRequiredService>(); 24 | } 25 | 26 | public override Task Execute() 27 | { 28 | var pluginService = ServiceProvider.GetRequiredService(); 29 | var plugins = pluginService.GetPlugins().ToList(); 30 | 31 | _logger.LogInformation("Plugins ({NumPlugins}):", plugins.Count); 32 | foreach (var plugin in plugins) 33 | _logger.LogInformation("{Name} - version {Version}", plugin.GetName(), 34 | plugin.GetType().Assembly.GetName().Version); 35 | 36 | return Task.FromResult(0); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/DeleteFieldModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | using VaultLib.Core.Hashing; 4 | 5 | namespace Attribulator.Plugins.ModScript.Commands 6 | { 7 | // delete_field class node field 8 | public class DeleteFieldModScriptCommand : BaseModScriptCommand 9 | { 10 | public string ClassName { get; set; } 11 | public string CollectionName { get; set; } 12 | public string FieldName { get; set; } 13 | 14 | public override void Parse(List parts) 15 | { 16 | if (parts.Count != 4) throw new CommandParseException($"Expected 4 tokens, got {parts.Count}"); 17 | 18 | ClassName = CleanHashString(parts[1]); 19 | CollectionName = CleanHashString(parts[2]); 20 | FieldName = CleanHashString(parts[3]); 21 | } 22 | 23 | public override void Execute(DatabaseHelper databaseHelper) 24 | { 25 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 26 | if (collection.HasEntry(FieldName)) 27 | { 28 | collection.RemoveValue(FieldName); 29 | } 30 | else 31 | { 32 | var hashed = $"0x{VLT32Hasher.Hash(FieldName):X8}"; 33 | 34 | if (collection.HasEntry(hashed)) 35 | collection.RemoveValue(hashed); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Services/CommandServiceImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using Attribulator.API.Exceptions; 5 | using Attribulator.API.Plugin; 6 | using Attribulator.API.Services; 7 | using CommandLine; 8 | 9 | namespace Attribulator.CLI.Services 10 | { 11 | public class CommandServiceImpl : ICommandService 12 | { 13 | private readonly ISet _commandTypes = new HashSet(); 14 | 15 | public void RegisterCommand() where TCommand : BaseCommand 16 | { 17 | RegisterCommand(typeof(TCommand)); 18 | } 19 | 20 | public void RegisterCommand(Type type) 21 | { 22 | if (!typeof(BaseCommand).IsAssignableFrom(type)) 23 | { 24 | throw new CommandServiceException($"Command type [{type}] does not inherit from ICommand."); 25 | } 26 | 27 | if (type.GetCustomAttribute(typeof(VerbAttribute)) == null) 28 | { 29 | throw new CommandServiceException($"Command type [{type}] is not annotated with [Verb]."); 30 | } 31 | 32 | if (!_commandTypes.Add(type)) 33 | { 34 | throw new CommandServiceException($"Command type [{type}] is already registered."); 35 | } 36 | } 37 | 38 | public IEnumerable GetCommandTypes() 39 | { 40 | return _commandTypes; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/BurnoutParadisePlugin.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using Attribulator.API.Plugin; 4 | using Attribulator.Plugins.BPSupport.Types; 5 | using VaultLib.Core; 6 | using VaultLib.Core.Exports; 7 | using VaultLib.Core.Exports.Implementations; 8 | using VaultLib.Core.Hashing; 9 | using VaultLib.ModernBase.Exports; 10 | using VaultLib.ModernBase.Structures; 11 | 12 | namespace Attribulator.Plugins.BPSupport 13 | { 14 | public class BurnoutParadisePlugin : IPlugin 15 | { 16 | public string GetName() 17 | { 18 | return "Burnout Paradise Support"; 19 | } 20 | 21 | public void Init() 22 | { 23 | HashManager.LoadDictionary(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), 24 | "Resources", "hashes.txt")); 25 | ExportFactory.SetClassLoadCreator("BURNOUT_PARADISE"); 26 | ExportFactory.SetCollectionLoadCreator("BURNOUT_PARADISE"); 27 | ExportFactory.SetDatabaseLoadCreator("BURNOUT_PARADISE"); 28 | ExportFactory.SetExportEntryCreator("BURNOUT_PARADISE"); 29 | ExportFactory.SetPointerCreator("BURNOUT_PARADISE"); 30 | 31 | TypeRegistry.Register("Attrib::Types::RwVector2", "BURNOUT_PARADISE"); 32 | TypeRegistry.Register("Attrib::Types::RwVector3", "BURNOUT_PARADISE"); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/Attribulator.Plugins.SpeedProfiles.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heyitsleo/NFSTools 5 | NFSTools 6 | Plugin providing profiles for the Speed engine games 7 | x86 8 | Attribulator - Speed Profiles 9 | netcoreapp3.1 10 | 2.0.0 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Attribulator.CLI/Commands/FormatListCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Attribulator.API.Plugin; 5 | using Attribulator.API.Services; 6 | using CommandLine; 7 | using JetBrains.Annotations; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Attribulator.CLI.Commands 12 | { 13 | [Verb("formats", HelpText = "List the available storage formats.")] 14 | [UsedImplicitly] 15 | public class FormatListCommand : BaseCommand 16 | { 17 | private ILogger _logger; 18 | 19 | public override void SetServiceProvider(IServiceProvider serviceProvider) 20 | { 21 | base.SetServiceProvider(serviceProvider); 22 | 23 | _logger = serviceProvider.GetRequiredService>(); 24 | } 25 | 26 | public override Task Execute() 27 | { 28 | var storageFormatService = ServiceProvider.GetRequiredService(); 29 | var storageFormats = storageFormatService.GetStorageFormats().ToList(); 30 | 31 | _logger.LogInformation("Storage Formats ({NumFormats}):", storageFormats.Count); 32 | foreach (var storageFormat in storageFormats) 33 | _logger.LogInformation("{Name} - ID: {Id}", storageFormat.GetFormatName(), 34 | storageFormat.GetFormatId()); 35 | 36 | return Task.FromResult(0); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Commands/ProfileListCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Attribulator.API.Plugin; 5 | using Attribulator.API.Services; 6 | using CommandLine; 7 | using JetBrains.Annotations; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Attribulator.CLI.Commands 12 | { 13 | [Verb("profiles", HelpText = "List the loaded profiles.")] 14 | [UsedImplicitly] 15 | public class ProfileListCommand : BaseCommand 16 | { 17 | private ILogger _logger; 18 | 19 | public override void SetServiceProvider(IServiceProvider serviceProvider) 20 | { 21 | base.SetServiceProvider(serviceProvider); 22 | 23 | _logger = serviceProvider.GetRequiredService>(); 24 | } 25 | 26 | public override Task Execute() 27 | { 28 | var profileService = ServiceProvider.GetRequiredService(); 29 | var profiles = profileService.GetProfiles().ToList(); 30 | 31 | _logger.LogInformation("Profiles ({NumProfiles}):", profiles.Count); 32 | foreach (var profile in profiles) 33 | _logger.LogInformation("{Name} - ID: {Id} (Game: {GameId}); DB Type: {DbType}", profile.GetName(), 34 | profile.GetProfileId(), profile.GetGameId(), profile.GetDatabaseType()); 35 | 36 | return Task.FromResult(0); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Attribulator.API/Plugin/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Attribulator.API.Exceptions; 4 | using Attribulator.API.Services; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Attribulator.API.Plugin 8 | { 9 | /// 10 | /// Base class for commands 11 | /// 12 | public abstract class BaseCommand 13 | { 14 | /// 15 | /// Gets or sets the instance. 16 | /// 17 | protected IServiceProvider ServiceProvider { get; private set; } 18 | 19 | /// 20 | /// Sets the instance. 21 | /// 22 | /// The new instance. 23 | public virtual void SetServiceProvider(IServiceProvider serviceProvider) 24 | { 25 | ServiceProvider = serviceProvider; 26 | } 27 | 28 | /// 29 | /// Executes the command. 30 | /// 31 | /// The return code of the command (0 for success) 32 | public abstract Task Execute(); 33 | 34 | protected IProfile FindProfile(string gameId) 35 | { 36 | if (ServiceProvider == null) throw new CommandException("ServiceProvider is not set!"); 37 | 38 | return ServiceProvider.GetRequiredService().GetProfile(gameId); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Attribulator.API/Services/IStorageFormatService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Attribulator.API.Serialization; 4 | 5 | namespace Attribulator.API.Services 6 | { 7 | /// 8 | /// Exposes an interface for registering and retrieving storage formats. 9 | /// 10 | public interface IStorageFormatService 11 | { 12 | /// 13 | /// Registers a new storage format type. 14 | /// 15 | /// The storage format type. 16 | void RegisterStorageFormat() where TStorageFormat : IDatabaseStorageFormat; 17 | 18 | /// 19 | /// Registers a new storage format type. 20 | /// 21 | void RegisterStorageFormat(Type storageFormatType); 22 | 23 | /// 24 | /// Gets the registered storage formats. 25 | /// 26 | /// An that produces the storage formats. 27 | IEnumerable GetStorageFormats(); 28 | 29 | /// 30 | /// Gets the storage format mapped to the given format ID. 31 | /// 32 | /// The format ID. 33 | /// The object. 34 | /// Thrown when the format cannot be found. 35 | IDatabaseStorageFormat GetStorageFormat(string formatId); 36 | } 37 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Services/StorageFormatServiceImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Attribulator.API.Serialization; 4 | using Attribulator.API.Services; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Attribulator.CLI.Services 8 | { 9 | public class StorageFormatServiceImpl : IStorageFormatService 10 | { 11 | private readonly IServiceProvider _serviceProvider; 12 | private readonly List _storageFormats = new List(); 13 | 14 | public StorageFormatServiceImpl(IServiceProvider serviceProvider) 15 | { 16 | _serviceProvider = serviceProvider; 17 | } 18 | 19 | public void RegisterStorageFormat() where TStorageFormat : IDatabaseStorageFormat 20 | { 21 | RegisterStorageFormat(typeof(TStorageFormat)); 22 | } 23 | 24 | public void RegisterStorageFormat(Type storageFormatType) 25 | { 26 | _storageFormats.Add((IDatabaseStorageFormat) _serviceProvider.GetRequiredService(storageFormatType)); 27 | } 28 | 29 | public IEnumerable GetStorageFormats() 30 | { 31 | return _storageFormats; 32 | } 33 | 34 | public IDatabaseStorageFormat GetStorageFormat(string formatId) 35 | { 36 | foreach (var storageFormat in _storageFormats) 37 | if (storageFormat.GetFormatId() == formatId) 38 | return storageFormat; 39 | 40 | throw new KeyNotFoundException($"Cannot find format: {formatId}"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Attribulator.API/Services/IDatabaseHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using VaultLib.Core; 3 | using VaultLib.Core.Data; 4 | using VaultLib.Core.DB; 5 | 6 | namespace Attribulator.API.Services 7 | { 8 | /// 9 | /// Exposes an interface for easily retrieving data from a . 10 | /// 11 | public interface IDatabaseHelper 12 | { 13 | /// 14 | /// Gets the collections in the database. 15 | /// 16 | /// The collections in the database. 17 | IEnumerable GetCollections(); 18 | 19 | /// 20 | /// Gets the collections under the given class name. 21 | /// 22 | /// The class name to filter by. 23 | /// The collections under the given class name. 24 | IEnumerable GetCollections(string className); 25 | 26 | /// 27 | /// Gets the collections under the given class. 28 | /// 29 | /// The class to filter by. 30 | /// The collections under the given class. 31 | IEnumerable GetCollections(VltClass @class); 32 | 33 | /// 34 | /// Gets the collections under the given vault. 35 | /// 36 | /// The vault to filter by. 37 | /// The collections under the given vault. 38 | IEnumerable GetCollections(Vault vault); 39 | } 40 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Build/BuildCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Attribulator.CLI.Build 7 | { 8 | /// 9 | /// Represents a file hash cache. This is used to shorten BIN compilation times. 10 | /// 11 | [JsonObject] 12 | public class BuildCache 13 | { 14 | [JsonProperty("entries")] 15 | public ConcurrentDictionary Entries { get; set; } = 16 | new ConcurrentDictionary(); 17 | 18 | [JsonProperty("last_updated")] public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now; 19 | 20 | /// 21 | /// Retrieves the cache entry with the given name. 22 | /// 23 | /// The name to search for. 24 | /// The cache entry with the given name. 25 | public BuildCacheEntry FindEntry(string name) 26 | { 27 | return Entries.TryGetValue(name, out var value) ? value : null; 28 | } 29 | } 30 | 31 | /// 32 | /// Represents an entry in a object. 33 | /// 34 | [JsonObject] 35 | public class BuildCacheEntry 36 | { 37 | /// 38 | /// Gets or sets the entry hash. 39 | /// 40 | public string Hash { get; set; } 41 | 42 | /// 43 | /// Gets or sets the list of dependencies. 44 | /// 45 | public HashSet Dependencies { get; set; } = new HashSet(); 46 | 47 | /// 48 | /// Gets or sets the modification timestamp. 49 | /// 50 | public DateTimeOffset LastModified { get; set; } = DateTimeOffset.Now; 51 | } 52 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/BurnoutParadiseProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Attribulator.API; 5 | using Attribulator.API.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.BPSupport 10 | { 11 | public class BurnoutParadiseProfile : IProfile 12 | { 13 | public IEnumerable LoadFiles(Database database, string directory) 14 | { 15 | var filesToLoad = Directory.GetFiles(directory, "*.bin", SearchOption.TopDirectoryOnly) 16 | .Where(f => !Path.GetFileNameWithoutExtension(f).Equals("schema")) 17 | .ToList(); 18 | filesToLoad.Insert(0, Path.Combine(directory, "schema.bin")); 19 | 20 | return (from file in filesToLoad 21 | let vaultPack = new BurnoutVaultPack(Path.GetFileNameWithoutExtension(file)) 22 | let br = new BinaryReader(File.OpenRead(file)) 23 | let vaults = vaultPack.Load(br, database, new PackLoadingOptions()) 24 | select new LoadedFile(Path.GetFileNameWithoutExtension(file), "main", vaults)).ToList(); 25 | } 26 | 27 | public void SaveFiles(Database database, string directory, IEnumerable files) 28 | { 29 | foreach (var file in files) 30 | { 31 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 32 | IVaultPack vaultPack = new BurnoutVaultPack(file.Name); 33 | using var fs = new FileStream(Path.Combine(directory, file.Group, file.Name + ".bin"), 34 | FileMode.Create, FileAccess.ReadWrite); 35 | using var bw = new BinaryWriter(fs); 36 | vaultPack.Save(bw, file.Vaults.ToList(), new PackSavingOptions()); 37 | } 38 | } 39 | 40 | public string GetName() 41 | { 42 | return "Burnout Paradise"; 43 | } 44 | 45 | public string GetGameId() 46 | { 47 | return "BURNOUT_PARADISE"; 48 | } 49 | 50 | public string GetProfileId() 51 | { 52 | return "BURNOUT_PARADISE"; 53 | } 54 | 55 | public DatabaseType GetDatabaseType() 56 | { 57 | return DatabaseType.X64Database; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/ResizeFieldModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | using VaultLib.Core; 4 | using VaultLib.Core.Types; 5 | 6 | namespace Attribulator.Plugins.ModScript.Commands 7 | { 8 | public class ResizeFieldModScriptCommand : BaseModScriptCommand 9 | { 10 | public string ClassName { get; set; } 11 | public string CollectionName { get; set; } 12 | public string FieldName { get; set; } 13 | public ushort NewCapacity { get; set; } 14 | 15 | public override void Parse(List parts) 16 | { 17 | if (parts.Count != 5) throw new CommandParseException($"Expected 5 tokens but got {parts.Count}"); 18 | 19 | ClassName = CleanHashString(parts[1]); 20 | CollectionName = CleanHashString(parts[2]); 21 | FieldName = CleanHashString(parts[3]); 22 | 23 | if (!ushort.TryParse(parts[4], out var newCapacity)) 24 | throw new CommandParseException($"Failed to parse '{parts[4]}' as a number"); 25 | 26 | NewCapacity = newCapacity; 27 | } 28 | 29 | public override void Execute(DatabaseHelper databaseHelper) 30 | { 31 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 32 | var field = GetField(collection.Class, FieldName); 33 | 34 | if (!field.IsArray) 35 | throw new CommandExecutionException($"Field {ClassName}[{FieldName}] is not an array!"); 36 | 37 | if (field.MaxCount < NewCapacity) 38 | throw new CommandExecutionException( 39 | $"Cannot resize field {ClassName}[{FieldName}] beyond maximum count (requested {NewCapacity} but limit is {field.MaxCount})"); 40 | 41 | var array = collection.GetRawValue(FieldName); 42 | 43 | if (NewCapacity < array.Items.Count) 44 | while (NewCapacity < array.Items.Count) 45 | array.Items.RemoveAt(array.Items.Count - 1); 46 | else if (NewCapacity > array.Items.Count) 47 | while (NewCapacity > array.Items.Count) 48 | array.Items.Add(TypeRegistry.ConstructInstance(array.ItemType, collection.Class, field, 49 | collection)); 50 | 51 | if (!field.IsInLayout) array.Capacity = NewCapacity; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Attribulator.API/IProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.API.Data; 3 | using VaultLib.Core.DB; 4 | 5 | namespace Attribulator.API 6 | { 7 | /// 8 | /// Exposes an interface for a VLT data profile. 9 | /// 10 | public interface IProfile 11 | { 12 | /// 13 | /// Loads VLT files from the given directory into the given object. 14 | /// 15 | /// The instance of to load data into. 16 | /// The directory to load VLT files from. 17 | /// An enumerator of objects. 18 | IEnumerable LoadFiles(Database database, string directory); 19 | 20 | /// 21 | /// Saves the given files to the given directory. 22 | /// 23 | /// The instance. 24 | /// The directory to save files to. 25 | /// The list of objects to save. 26 | void SaveFiles(Database database, string directory, IEnumerable files); 27 | 28 | /// 29 | /// Gets the name of the profile. 30 | /// 31 | /// The name of the profile. 32 | /// 33 | /// "Need for Speed: Most Wanted" 34 | /// 35 | string GetName(); 36 | 37 | /// 38 | /// Gets the game ID of the profile. 39 | /// 40 | /// The game ID of the profile 41 | /// 42 | /// MOST_WANTED 43 | /// 44 | string GetGameId(); 45 | 46 | /// 47 | /// Gets the unique ID of the profile. 48 | /// 49 | /// The unique ID of the profile 50 | /// 51 | /// MOST_WANTED 52 | /// 53 | string GetProfileId(); 54 | 55 | /// 56 | /// Gets the database type of the profile. 57 | /// 58 | /// The database type. 59 | DatabaseType GetDatabaseType(); 60 | } 61 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/ModScriptPlugin.cs: -------------------------------------------------------------------------------- 1 | using Attribulator.API.Plugin; 2 | using Attribulator.ModScript.API; 3 | using Attribulator.Plugins.ModScript.Commands; 4 | 5 | namespace Attribulator.Plugins.ModScript 6 | { 7 | /// 8 | /// Base class for the ModScript plugin. 9 | /// 10 | public class ModScriptPlugin : IPlugin 11 | { 12 | private readonly IModScriptService _modScriptService; 13 | 14 | public ModScriptPlugin(IModScriptService modScriptService) 15 | { 16 | _modScriptService = modScriptService; 17 | } 18 | 19 | public string GetName() 20 | { 21 | return "ModScript Support"; 22 | } 23 | 24 | public void Init() 25 | { 26 | _modScriptService.RegisterCommand("append_array"); 27 | _modScriptService.RegisterCommand("version"); 28 | _modScriptService.RegisterCommand("game"); 29 | _modScriptService.RegisterCommand("resize_field"); 30 | _modScriptService.RegisterCommand("update_field"); 31 | _modScriptService.RegisterCommand("copy_node"); 32 | _modScriptService.RegisterCommand("add_node"); 33 | _modScriptService.RegisterCommand("change_vault"); 34 | _modScriptService.RegisterCommand("copy_fields"); 35 | _modScriptService.RegisterCommand("delete_node"); 36 | _modScriptService.RegisterCommand("add_field"); 37 | _modScriptService.RegisterCommand("delete_field"); 38 | _modScriptService.RegisterCommand("rename_node"); 39 | _modScriptService.RegisterCommand("move_node"); 40 | _modScriptService.RegisterCommand("add_overwrite"); 41 | _modScriptService.RegisterCommand("copy_overwrite"); 42 | _modScriptService.RegisterCommand("resize_collection"); 43 | _modScriptService.RegisterCommand("update_collection"); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/AddFieldModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | using VaultLib.Core; 4 | using VaultLib.Core.Types; 5 | 6 | namespace Attribulator.Plugins.ModScript.Commands 7 | { 8 | // add_field class node field 9 | public class AddFieldModScriptCommand : BaseModScriptCommand 10 | { 11 | public string ClassName { get; set; } 12 | public string CollectionName { get; set; } 13 | public string FieldName { get; set; } 14 | public ushort ArrayCapacity { get; set; } 15 | 16 | public override void Parse(List parts) 17 | { 18 | if (parts.Count != 4 && parts.Count != 5) 19 | throw new CommandParseException($"Expected 4 or 5 tokens, got {parts.Count}"); 20 | 21 | ClassName = CleanHashString(parts[1]); 22 | CollectionName = CleanHashString(parts[2]); 23 | FieldName = CleanHashString(parts[3]); 24 | 25 | if (parts.Count == 5) ArrayCapacity = ushort.Parse(parts[4]); 26 | } 27 | 28 | public override void Execute(DatabaseHelper databaseHelper) 29 | { 30 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 31 | var field = collection.Class[FieldName]; 32 | 33 | if (field.IsInLayout) 34 | throw new CommandExecutionException($"add_field failed because field '{field.Name}' is a base field"); 35 | 36 | if (collection.HasEntry(field.Name)) 37 | return; 38 | 39 | var vltBaseType = 40 | TypeRegistry.CreateInstance(databaseHelper.Database.Options.GameId, collection.Class, field, 41 | collection); 42 | 43 | if (vltBaseType is VLTArrayType array) 44 | { 45 | if (ArrayCapacity > field.MaxCount) 46 | throw new CommandExecutionException( 47 | $"Cannot add field {ClassName}[{FieldName}] with capacity beyond maximum (requested {ArrayCapacity} but limit is {field.MaxCount})"); 48 | 49 | array.Capacity = ArrayCapacity; 50 | array.ItemAlignment = field.Alignment; 51 | array.FieldSize = field.Size; 52 | array.Items = new List(); 53 | 54 | for (var i = 0; i < ArrayCapacity; i++) 55 | array.Items.Add(TypeRegistry.ConstructInstance(array.ItemType, collection.Class, field, 56 | collection)); 57 | } 58 | 59 | collection.SetRawValue(field.Name, vltBaseType); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/CopyNodeModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.ModScript.API; 3 | using VaultLib.Core.Data; 4 | 5 | namespace Attribulator.Plugins.ModScript.Commands 6 | { 7 | // copy_node class sourceNode parentNode nodeName 8 | public class CopyNodeModScriptCommand : BaseModScriptCommand 9 | { 10 | public string ClassName { get; set; } 11 | public string SourceCollectionName { get; set; } 12 | public string ParentCollectionName { get; set; } 13 | public string DestinationCollectionName { get; set; } 14 | 15 | public override void Parse(List parts) 16 | { 17 | if (parts.Count != 4 && parts.Count != 5) 18 | throw new CommandParseException($"4 or 5 tokens expected, got {parts.Count}"); 19 | 20 | ClassName = parts[1]; 21 | SourceCollectionName = parts[2]; 22 | ParentCollectionName = parts.Count == 5 ? parts[3] : ""; 23 | DestinationCollectionName = parts[^1]; 24 | } 25 | 26 | public override void Execute(DatabaseHelper databaseHelper) 27 | { 28 | var collection = GetCollection(databaseHelper, ClassName, SourceCollectionName); 29 | 30 | if (collection == null) 31 | throw new CommandExecutionException( 32 | $"copy_node failed because there is no collection called '{SourceCollectionName}'"); 33 | 34 | if (databaseHelper.FindCollectionByName(ClassName, DestinationCollectionName) != null) 35 | throw new CommandExecutionException( 36 | $"copy_node failed because there is already a collection called '{DestinationCollectionName}'"); 37 | 38 | VltCollection parentCollection = null; 39 | 40 | if (!string.IsNullOrWhiteSpace(ParentCollectionName)) 41 | { 42 | parentCollection = databaseHelper.FindCollectionByName(ClassName, ParentCollectionName); 43 | 44 | if (parentCollection == null) 45 | throw new CommandExecutionException( 46 | $"copy_node failed because the parent collection called '{ParentCollectionName}' does not exist"); 47 | } 48 | 49 | var newCollection = new VltCollection(collection.Vault, collection.Class, DestinationCollectionName); 50 | databaseHelper.CopyCollection(databaseHelper.Database, collection, newCollection); 51 | 52 | if (newCollection.Class.HasField("CollectionName")) 53 | newCollection.SetDataValue("CollectionName", DestinationCollectionName); 54 | 55 | databaseHelper.AddCollection(newCollection, parentCollection); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Commands/UnpackCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Attribulator.API.Plugin; 5 | using Attribulator.API.Services; 6 | using CommandLine; 7 | using JetBrains.Annotations; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using VaultLib.Core.DB; 11 | 12 | namespace Attribulator.CLI.Commands 13 | { 14 | [Verb("unpack", HelpText = "Unpack binary VLT files.")] 15 | public class UnpackCommand : BaseCommand 16 | { 17 | private ILogger _logger; 18 | 19 | [Option('i', HelpText = "Directory to read BIN files from", Required = true)] 20 | [UsedImplicitly] 21 | public string InputDirectory { get; set; } 22 | 23 | [Option('o', HelpText = "Directory to write unpacked files to", Required = true)] 24 | [UsedImplicitly] 25 | public string OutputDirectory { get; set; } 26 | 27 | [Option('p', HelpText = "The profile to use", Required = true)] 28 | [UsedImplicitly] 29 | public string ProfileName { get; set; } 30 | 31 | [Option('f', HelpText = "The format to use", Required = true)] 32 | [UsedImplicitly] 33 | public string StorageFormatName { get; set; } 34 | 35 | public override void SetServiceProvider(IServiceProvider serviceProvider) 36 | { 37 | base.SetServiceProvider(serviceProvider); 38 | 39 | _logger = ServiceProvider.GetRequiredService>(); 40 | } 41 | 42 | public override Task Execute() 43 | { 44 | if (!Directory.Exists(InputDirectory)) 45 | return Task.FromException( 46 | new DirectoryNotFoundException($"Cannot find input directory: {InputDirectory}")); 47 | 48 | if (!Directory.Exists(OutputDirectory)) Directory.CreateDirectory(OutputDirectory); 49 | 50 | var profile = ServiceProvider.GetRequiredService().GetProfile(ProfileName); 51 | var storageFormat = ServiceProvider.GetRequiredService() 52 | .GetStorageFormat(StorageFormatName); 53 | var database = new Database(new DatabaseOptions(profile.GetGameId(), profile.GetDatabaseType())); 54 | _logger.LogInformation("Loading database from disk..."); 55 | var files = profile.LoadFiles(database, InputDirectory); 56 | database.CompleteLoad(); 57 | _logger.LogInformation("Unpacking database to disk..."); 58 | 59 | storageFormat.Serialize(database, OutputDirectory, files); 60 | 61 | _logger.LogInformation("Done!"); 62 | return Task.FromResult(0); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/MoveNodeModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Attribulator.ModScript.API; 4 | using VaultLib.Core.Data; 5 | 6 | namespace Attribulator.Plugins.ModScript.Commands 7 | { 8 | // move_node class node [parent] 9 | public class MoveNodeModScriptCommand : BaseModScriptCommand 10 | { 11 | public string ClassName { get; set; } 12 | public string CollectionName { get; set; } 13 | public string ParentName { get; set; } 14 | 15 | public override void Parse(List parts) 16 | { 17 | if (parts.Count < 3 || parts.Count > 4) 18 | throw new CommandParseException("Expected command to be in format: move_node class node [parent]"); 19 | 20 | ClassName = CleanHashString(parts[1]); 21 | CollectionName = CleanHashString(parts[2]); 22 | ParentName = parts.Count == 4 ? parts[3] : null; 23 | 24 | if (ParentName == CollectionName) 25 | throw new CommandParseException("Parent name cannot be the same as collection name."); 26 | } 27 | 28 | public override void Execute(DatabaseHelper databaseHelper) 29 | { 30 | var collectionToMove = GetCollection(databaseHelper, ClassName, CollectionName); 31 | VltCollection newParentCollection = null; 32 | 33 | if (ParentName != null) 34 | { 35 | newParentCollection = GetCollection(databaseHelper, ClassName, ParentName); 36 | 37 | if (IsChild(databaseHelper, collectionToMove, newParentCollection)) 38 | throw new CommandExecutionException( 39 | $"Requested parent collection {ParentName} is a child of {CollectionName}."); 40 | } 41 | 42 | // Did the parent change? 43 | if (ReferenceEquals(newParentCollection, collectionToMove.Parent)) return; 44 | 45 | // Disassociated from parent? Add to DB 46 | if (newParentCollection == null) 47 | { 48 | collectionToMove.Parent.RemoveChild(collectionToMove); 49 | databaseHelper.AddCollection(collectionToMove); 50 | } 51 | else 52 | { 53 | // Handle new parent 54 | newParentCollection.AddChild(collectionToMove); 55 | databaseHelper.Database.RowManager.RemoveCollection(collectionToMove); 56 | } 57 | } 58 | 59 | private bool IsChild(DatabaseHelper databaseHelper, VltCollection root, VltCollection test) 60 | { 61 | var flattenedChildren = 62 | databaseHelper.Database.RowManager.EnumerateFlattenedCollections(root.Children); 63 | 64 | return flattenedChildren.Any(child => ReferenceEquals(child, test)); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/ModScriptService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Attribulator.ModScript.API; 5 | 6 | namespace Attribulator.Plugins.ModScript 7 | { 8 | public class ModScriptService : IModScriptService 9 | { 10 | private readonly Dictionary> _commandMappings = 11 | new Dictionary>(); 12 | 13 | public IEnumerable ParseCommands(IEnumerable commands) 14 | { 15 | var lineNumber = 0L; 16 | foreach (var command in commands.Select(s => s.Trim())) 17 | { 18 | lineNumber++; 19 | if (string.IsNullOrEmpty(command)) continue; 20 | if (command.StartsWith("#", StringComparison.Ordinal)) continue; 21 | 22 | var parts = command.Split('"') 23 | .Select((element, index) => index % 2 == 0 // If even index 24 | ? element.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries) // Split the item 25 | : new[] {element}) // Keep the entire item 26 | .SelectMany(element => element).ToList(); 27 | 28 | for (var index = 0; index < parts.Count; index++) 29 | { 30 | var part = parts[index]; 31 | if (part.StartsWith("0x", StringComparison.Ordinal)) 32 | parts[index] = $"0x{part.Substring(2).ToUpper()}"; 33 | } 34 | 35 | // Find command 36 | if (_commandMappings.TryGetValue(parts[0], out var creator)) 37 | { 38 | var newCommand = creator(command); 39 | newCommand.LineNumber = lineNumber; 40 | 41 | try 42 | { 43 | newCommand.Parse(parts); 44 | } 45 | catch (Exception exception) 46 | { 47 | throw new CommandParseException($"Failed to parse command at line {lineNumber}: {command}", 48 | exception); 49 | } 50 | 51 | yield return newCommand; 52 | } 53 | else 54 | { 55 | throw new CommandParseException($"Unknown command: {parts[0]} (line {lineNumber} [{command}])"); 56 | } 57 | } 58 | } 59 | 60 | public void RegisterCommand(string name) where TCommand : IModScriptCommand, new() 61 | { 62 | _commandMappings.Add(name, line => new TCommand {Line = line}); 63 | } 64 | 65 | public IEnumerable GetAvailableCommandNames() 66 | { 67 | return _commandMappings.Keys; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.BPSupport/BurnoutVaultPack.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.IO; 4 | using CoreLibraries.IO; 5 | using VaultLib.Core; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.BPSupport 10 | { 11 | public class BurnoutVaultPack : IVaultPack 12 | { 13 | private readonly string _vaultName; 14 | 15 | public BurnoutVaultPack(string vaultName) 16 | { 17 | _vaultName = vaultName; 18 | } 19 | 20 | public IList Load(BinaryReader br, Database database, PackLoadingOptions loadingOptions = null) 21 | { 22 | var vltOffset = br.ReadUInt32(); 23 | var vltSize = br.ReadUInt32(); 24 | var binOffset = br.ReadUInt32(); 25 | var binSize = br.ReadUInt32(); 26 | 27 | if (vltOffset > br.BaseStream.Length) 28 | throw new InvalidDataException(); 29 | 30 | if (binOffset > br.BaseStream.Length) 31 | throw new InvalidDataException(); 32 | 33 | br.BaseStream.Position = vltOffset; 34 | var vltData = new byte[vltSize]; 35 | 36 | if (br.Read(vltData) != vltData.Length) throw new InvalidDataException(); 37 | 38 | br.BaseStream.Position = binOffset; 39 | var binData = new byte[binSize]; 40 | 41 | if (br.Read(binData) != binData.Length) throw new InvalidDataException(); 42 | 43 | var vault = new Vault(_vaultName) 44 | { 45 | BinStream = new MemoryStream(binData), 46 | VltStream = new MemoryStream(vltData) 47 | }; 48 | 49 | using (var loadingWrapper = new VaultLoadingWrapper(vault, loadingOptions?.ByteOrder ?? ByteOrder.Little)) 50 | { 51 | database.LoadVault(vault, loadingWrapper); 52 | } 53 | 54 | return new ReadOnlyCollection(new List(new[] {vault})); 55 | } 56 | 57 | public void Save(BinaryWriter bw, IList vaults, PackSavingOptions savingOptions) 58 | { 59 | bw.Write(0x10); 60 | var vault = vaults[0]; 61 | var vw = new VaultWriter(vault, new VaultSaveOptions {HashMode = VaultHashMode.Hash64}); 62 | var streamInfo = vw.BuildVault(); 63 | bw.Write((uint) streamInfo.VltStream.Length); 64 | bw.Write(0); 65 | bw.Write((uint) streamInfo.BinStream.Length); 66 | 67 | streamInfo.VltStream.CopyTo(bw.BaseStream); 68 | bw.AlignWriter(0x10); 69 | var binOffset = bw.BaseStream.Position; 70 | streamInfo.BinStream.CopyTo(bw.BaseStream); 71 | var endOffset = bw.BaseStream.Position; 72 | 73 | bw.BaseStream.Position = 8; 74 | bw.Write((uint) binOffset); 75 | bw.BaseStream.Position = endOffset; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: Build Application 2 | 3 | on: 4 | push: 5 | tags: ['v*'] 6 | jobs: 7 | build-windows: 8 | runs-on: windows-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Setup .NET Core 12 | uses: actions/setup-dotnet@v1 13 | with: 14 | dotnet-version: 3.1.102 15 | - name: Build and publish with dotnet 16 | run: dotnet publish -c Release 17 | - name: Install plugins 18 | run: | 19 | xcopy /s /E /I /Y /q "Attribulator.Plugins.ModScript\\bin\\x86\\Release\\netcoreapp3.1\\publish" "Attribulator.CLI\\bin\\x86\\Release\\netcoreapp3.1\\publish\\plugins\\Attribulator.Plugins.ModScript" 20 | xcopy /s /E /I /Y /q "Attribulator.Plugins.YAMLSupport\\bin\\x86\\Release\\netcoreapp3.1\\publish" "Attribulator.CLI\\bin\\x86\\Release\\netcoreapp3.1\\publish\\plugins\\Attribulator.Plugins.YAMLSupport" 21 | xcopy /s /E /I /Y /q "Attribulator.Plugins.SpeedProfiles\\bin\\x86\\Release\\netcoreapp3.1\\publish" "Attribulator.CLI\\bin\\x86\\Release\\netcoreapp3.1\\publish\\plugins\\Attribulator.Plugins.SpeedProfiles" 22 | - name: Add NativeLibrary.dll 23 | run: curl -o Attribulator.CLI/bin/x86/Release/netcoreapp3.1/publish/NativeLibrary.dll https://s.heyitsleo.io/NFSTools/NativeLibrary.dll 24 | - name: Upload artifact 25 | uses: actions/upload-artifact@v1 26 | with: 27 | name: release_windows 28 | path: Attribulator.CLI/bin/x86/Release/netcoreapp3.1/publish 29 | upload-release: 30 | needs: [build-windows] 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/download-artifact@v1 34 | name: Download Windows artifact 35 | with: 36 | name: release_windows 37 | path: release_windows 38 | - name: Make zip archives 39 | run: | 40 | zip -r release_windows.zip release_windows 41 | - name: Create Release 42 | id: create_release 43 | uses: actions/create-release@latest 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 46 | with: 47 | tag_name: ${{ github.ref }} 48 | release_name: ${{ github.ref }} 49 | body: _Generated by GitHub Actions_ 50 | draft: true 51 | prerelease: false 52 | - name: Upload Windows Asset 53 | id: upload-windows-asset 54 | uses: actions/upload-release-asset@v1 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | with: 58 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 59 | asset_path: ./release_windows.zip 60 | asset_name: release_windows.zip 61 | asset_content_type: application/zip 62 | -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/CarbonProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Attribulator.API; 5 | using Attribulator.API.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.SpeedProfiles 10 | { 11 | /// 12 | /// Basic profile for PC 32-bit NFS Carbon 13 | /// 14 | public class CarbonProfile : IProfile 15 | { 16 | public IEnumerable LoadFiles(Database database, string directory) 17 | { 18 | return (from file in GetFilesToLoad() 19 | let path = Path.Combine(directory, file) 20 | let standardVaultPack = new StandardVaultPack() 21 | let br = new BinaryReader(File.OpenRead(path)) 22 | let vaults = standardVaultPack.Load(br, database, new PackLoadingOptions()) 23 | select new LoadedFile(Path.GetFileNameWithoutExtension(file), "main", vaults)).ToList(); 24 | } 25 | 26 | public void SaveFiles(Database database, string directory, IEnumerable files) 27 | { 28 | foreach (var file in files) 29 | { 30 | IVaultPack vaultPack = new StandardVaultPack(); 31 | 32 | //var standardVaultPack = new StandardVaultPack(); 33 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 34 | var outPath = Path.Combine(directory, file.Group, file.Name + ".bin"); 35 | using var bw = new BinaryWriter(File.Open(outPath, FileMode.Create, FileAccess.ReadWrite)); 36 | vaultPack.Save(bw, file.Vaults.ToList(), new PackSavingOptions()); 37 | bw.Close(); 38 | 39 | if (file.Name == "gameplay") 40 | { 41 | using var outStream = new FileStream(Path.ChangeExtension(outPath, "lzc"), FileMode.Create, 42 | FileAccess.Write); 43 | using var inStream = new FileStream(outPath, FileMode.Open, FileAccess.Read); 44 | using var outWriter = new BinaryWriter(outStream); 45 | outWriter.Write(0x57574152); // RAWW 46 | outWriter.Write((byte) 0x01); 47 | outWriter.Write((byte) 0x10); 48 | outWriter.Write((ushort) 0); 49 | outWriter.Write((int) inStream.Length); 50 | outWriter.Write((int) (inStream.Length + 16)); 51 | inStream.CopyTo(outStream); 52 | } 53 | } 54 | } 55 | 56 | public string GetName() 57 | { 58 | return "Need for Speed Carbon"; 59 | } 60 | 61 | public string GetGameId() 62 | { 63 | return "CARBON"; 64 | } 65 | 66 | public string GetProfileId() 67 | { 68 | return "CARBON"; 69 | } 70 | 71 | public DatabaseType GetDatabaseType() 72 | { 73 | return DatabaseType.X86Database; 74 | } 75 | 76 | private static IEnumerable GetFilesToLoad() 77 | { 78 | return new[] {"attributes.bin", "fe_attrib.bin", "gameplay.bin"}; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/MostWantedProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Attribulator.API; 5 | using Attribulator.API.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.SpeedProfiles 10 | { 11 | /// 12 | /// Basic profile for PC 32-bit NFS Most Wanted 13 | /// 14 | public class MostWantedProfile : IProfile 15 | { 16 | public IEnumerable LoadFiles(Database database, string directory) 17 | { 18 | return (from file in GetFilesToLoad() 19 | let path = Path.Combine(directory, file) 20 | let standardVaultPack = new StandardVaultPack() 21 | let br = new BinaryReader(File.OpenRead(path)) 22 | let vaults = standardVaultPack.Load(br, database, new PackLoadingOptions()) 23 | select new LoadedFile(Path.GetFileNameWithoutExtension(file), "main", vaults)).ToList(); 24 | } 25 | 26 | public void SaveFiles(Database database, string directory, IEnumerable files) 27 | { 28 | foreach (var file in files) 29 | { 30 | IVaultPack vaultPack = new StandardVaultPack(); 31 | 32 | //var standardVaultPack = new StandardVaultPack(); 33 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 34 | var outPath = Path.Combine(directory, file.Group, file.Name + ".bin"); 35 | using var bw = new BinaryWriter(File.Open(outPath, FileMode.Create, FileAccess.ReadWrite)); 36 | vaultPack.Save(bw, file.Vaults.ToList(), new PackSavingOptions()); 37 | bw.Close(); 38 | 39 | if (file.Name == "gameplay") 40 | { 41 | using var outStream = new FileStream(Path.ChangeExtension(outPath, "lzc"), FileMode.Create, 42 | FileAccess.Write); 43 | using var inStream = new FileStream(outPath, FileMode.Open, FileAccess.Read); 44 | using var outWriter = new BinaryWriter(outStream); 45 | outWriter.Write(0x57574152); // RAWW 46 | outWriter.Write((byte) 0x01); 47 | outWriter.Write((byte) 0x10); 48 | outWriter.Write((ushort) 0); 49 | outWriter.Write((int) inStream.Length); 50 | outWriter.Write((int) (inStream.Length + 16)); 51 | inStream.CopyTo(outStream); 52 | } 53 | } 54 | } 55 | 56 | public string GetName() 57 | { 58 | return "Need for Speed Most Wanted"; 59 | } 60 | 61 | public string GetGameId() 62 | { 63 | return "MOST_WANTED"; 64 | } 65 | 66 | public string GetProfileId() 67 | { 68 | return "MOST_WANTED"; 69 | } 70 | 71 | public DatabaseType GetDatabaseType() 72 | { 73 | return DatabaseType.X86Database; 74 | } 75 | 76 | private static IEnumerable GetFilesToLoad() 77 | { 78 | return new[] {"attributes.bin", "fe_attrib.bin", "gameplay.bin"}; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/ProStreetProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Attribulator.API; 5 | using Attribulator.API.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.SpeedProfiles 10 | { 11 | /// 12 | /// Basic profile for PC 32-bit NFS ProStreet 13 | /// 14 | public class ProStreetProfile : IProfile 15 | { 16 | public IEnumerable LoadFiles(Database database, string directory) 17 | { 18 | return (from file in GetFilesToLoad() 19 | let path = Path.Combine(directory, file) 20 | let standardVaultPack = new StandardVaultPack() 21 | let br = new BinaryReader(File.OpenRead(path)) 22 | let vaults = standardVaultPack.Load(br, database, new PackLoadingOptions()) 23 | select new LoadedFile(Path.GetFileNameWithoutExtension(file), "main", vaults)).ToList(); 24 | } 25 | 26 | public void SaveFiles(Database database, string directory, IEnumerable files) 27 | { 28 | foreach (var file in files) 29 | { 30 | IVaultPack vaultPack = new StandardVaultPack(); 31 | 32 | //var standardVaultPack = new StandardVaultPack(); 33 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 34 | var outPath = Path.Combine(directory, file.Group, file.Name + ".bin"); 35 | using var bw = new BinaryWriter(File.Open(outPath, FileMode.Create, FileAccess.ReadWrite)); 36 | vaultPack.Save(bw, file.Vaults.ToList(), new PackSavingOptions()); 37 | bw.Close(); 38 | 39 | if (file.Name == "gameplay") 40 | { 41 | using var outStream = new FileStream(Path.ChangeExtension(outPath, "lzc"), FileMode.Create, 42 | FileAccess.Write); 43 | using var inStream = new FileStream(outPath, FileMode.Open, FileAccess.Read); 44 | using var outWriter = new BinaryWriter(outStream); 45 | outWriter.Write(0x57574152); // RAWW 46 | outWriter.Write((byte) 0x01); 47 | outWriter.Write((byte) 0x10); 48 | outWriter.Write((ushort) 0); 49 | outWriter.Write((int) inStream.Length); 50 | outWriter.Write((int) (inStream.Length + 16)); 51 | inStream.CopyTo(outStream); 52 | } 53 | } 54 | } 55 | 56 | public string GetName() 57 | { 58 | return "Need for Speed ProStreet"; 59 | } 60 | 61 | public string GetGameId() 62 | { 63 | return "PROSTREET"; 64 | } 65 | 66 | public string GetProfileId() 67 | { 68 | return "PROSTREET"; 69 | } 70 | 71 | public DatabaseType GetDatabaseType() 72 | { 73 | return DatabaseType.X86Database; 74 | } 75 | 76 | private static IEnumerable GetFilesToLoad() 77 | { 78 | return new[] {"attributes.bin", "cars_vault.bin", "fe_attrib.bin", "gameplay.bin"}; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/UndercoverProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Attribulator.API; 5 | using Attribulator.API.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.SpeedProfiles 10 | { 11 | /// 12 | /// Basic profile for PC 32-bit NFS Undercover 13 | /// 14 | public class UndercoverProfile : IProfile 15 | { 16 | public IEnumerable LoadFiles(Database database, string directory) 17 | { 18 | return (from file in GetFilesToLoad() 19 | let path = Path.Combine(directory, file) 20 | let standardVaultPack = new StandardVaultPack() 21 | let br = new BinaryReader(File.OpenRead(path)) 22 | let vaults = standardVaultPack.Load(br, database, new PackLoadingOptions()) 23 | select new LoadedFile(Path.GetFileNameWithoutExtension(file), "main", vaults)).ToList(); 24 | } 25 | 26 | public void SaveFiles(Database database, string directory, IEnumerable files) 27 | { 28 | foreach (var file in files) 29 | { 30 | IVaultPack vaultPack = new StandardVaultPack(); 31 | 32 | //var standardVaultPack = new StandardVaultPack(); 33 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 34 | var outPath = Path.Combine(directory, file.Group, file.Name + ".bin"); 35 | using var bw = new BinaryWriter(File.Open(outPath, FileMode.Create, FileAccess.ReadWrite)); 36 | vaultPack.Save(bw, file.Vaults.ToList(), new PackSavingOptions()); 37 | bw.Close(); 38 | 39 | if (file.Name == "gameplay") 40 | { 41 | using var outStream = new FileStream(Path.ChangeExtension(outPath, "lzc"), FileMode.Create, 42 | FileAccess.Write); 43 | using var inStream = new FileStream(outPath, FileMode.Open, FileAccess.Read); 44 | using var outWriter = new BinaryWriter(outStream); 45 | outWriter.Write(0x57574152); // RAWW 46 | outWriter.Write((byte) 0x01); 47 | outWriter.Write((byte) 0x10); 48 | outWriter.Write((ushort) 0); 49 | outWriter.Write((int) inStream.Length); 50 | outWriter.Write((int) (inStream.Length + 16)); 51 | inStream.CopyTo(outStream); 52 | } 53 | } 54 | } 55 | 56 | public string GetName() 57 | { 58 | return "Need for Speed Undercover"; 59 | } 60 | 61 | public string GetGameId() 62 | { 63 | return "UNDERCOVER"; 64 | } 65 | 66 | public string GetProfileId() 67 | { 68 | return "UNDERCOVER"; 69 | } 70 | 71 | public DatabaseType GetDatabaseType() 72 | { 73 | return DatabaseType.X86Database; 74 | } 75 | 76 | private static IEnumerable GetFilesToLoad() 77 | { 78 | return new[] {"attributes.bin", "cars_vault.bin", "fe_attrib.bin", "gameplay.bin"}; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/PlayStation2/CarbonProfilePs2.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Attribulator.API; 5 | using Attribulator.API.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.SpeedProfiles.PlayStation2 10 | { 11 | /// 12 | /// Basic profile for PS2 32-bit NFS Carbon 13 | /// 14 | public class CarbonProfilePs2 : IProfile 15 | { 16 | public IEnumerable LoadFiles(Database database, string directory) 17 | { 18 | return (from file in GetFilesToLoad() 19 | let path = Path.Combine(directory, file) 20 | let standardVaultPack = new StandardVaultPack() 21 | let br = new BinaryReader(File.OpenRead(path)) 22 | let vaults = standardVaultPack.Load(br, database, new PackLoadingOptions()) 23 | select new LoadedFile(Path.GetFileNameWithoutExtension(file), "main", vaults)).ToList(); 24 | } 25 | 26 | public void SaveFiles(Database database, string directory, IEnumerable files) 27 | { 28 | foreach (var file in files) 29 | { 30 | IVaultPack vaultPack = new StandardVaultPack(); 31 | 32 | //var standardVaultPack = new StandardVaultPack(); 33 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 34 | var outPath = Path.Combine(directory, file.Group, file.Name + ".bin"); 35 | using var bw = new BinaryWriter(File.Open(outPath, FileMode.Create, FileAccess.ReadWrite)); 36 | vaultPack.Save(bw, file.Vaults.ToList(), new PackSavingOptions()); 37 | bw.Close(); 38 | 39 | if (file.Name == "gameplay") 40 | { 41 | using var outStream = new FileStream(Path.ChangeExtension(outPath, "lzc"), FileMode.Create, 42 | FileAccess.Write); 43 | using var inStream = new FileStream(outPath, FileMode.Open, FileAccess.Read); 44 | using var outWriter = new BinaryWriter(outStream); 45 | outWriter.Write(0x57574152); // RAWW 46 | outWriter.Write((byte) 0x01); 47 | outWriter.Write((byte) 0x10); 48 | outWriter.Write((ushort) 0); 49 | outWriter.Write((int) inStream.Length); 50 | outWriter.Write((int) (inStream.Length + 16)); 51 | inStream.CopyTo(outStream); 52 | } 53 | } 54 | } 55 | 56 | public string GetName() 57 | { 58 | return "Need for Speed Carbon (PlayStation 2)"; 59 | } 60 | 61 | public string GetGameId() 62 | { 63 | return "CARBON"; 64 | } 65 | 66 | public string GetProfileId() 67 | { 68 | return "CARBON_PS2"; 69 | } 70 | 71 | public DatabaseType GetDatabaseType() 72 | { 73 | return DatabaseType.X86Database; 74 | } 75 | 76 | private static IEnumerable GetFilesToLoad() 77 | { 78 | return new[] {"attributes.bin", "fe_attrib.bin", "gameplay.bin"}; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/WorldProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using Attribulator.API; 6 | using Attribulator.API.Data; 7 | using Attribulator.Plugins.SpeedProfiles.World; 8 | using VaultLib.Core.DB; 9 | using VaultLib.Core.Pack; 10 | 11 | namespace Attribulator.Plugins.SpeedProfiles 12 | { 13 | public class WorldProfile : IProfile 14 | { 15 | public IEnumerable LoadFiles(Database database, string directory) 16 | { 17 | var files = new List(); 18 | foreach (var file in GetFilesToLoad(directory)) 19 | { 20 | //var standardVaultPack = new StandardVaultPack(); 21 | using var br = new BinaryReader(File.OpenRead(file)); 22 | 23 | IVaultPack vaultPack = new StandardVaultPack(); 24 | var group = "main"; 25 | 26 | if (file.Contains("gc.vaults")) 27 | { 28 | vaultPack = new GameplayVault(null); 29 | group = "gameplay"; 30 | } 31 | 32 | var vaults = vaultPack.Load(br, database, new PackLoadingOptions()); 33 | 34 | files.Add(new LoadedFile(Path.GetFileNameWithoutExtension(file), group, vaults)); 35 | } 36 | 37 | return files; 38 | } 39 | 40 | public void SaveFiles(Database database, string directory, IEnumerable files) 41 | { 42 | foreach (var file in files) 43 | { 44 | var vaultsToSave = file.Vaults.ToList(); 45 | 46 | IVaultPack vaultPack = new StandardVaultPack(); 47 | 48 | if (file.Group == "gameplay") 49 | vaultPack = new GameplayVault(file.Name); 50 | 51 | //var standardVaultPack = new StandardVaultPack(); 52 | Directory.CreateDirectory(Path.Combine(directory, file.Group)); 53 | var outPath = Path.Combine(directory, file.Group, file.Name + ".bin"); 54 | Debug.WriteLine("Saving file '{0}' to '{1}' ({2} vaults)", file.Name, outPath, vaultsToSave.Count); 55 | using var bw = new BinaryWriter(File.Open(outPath, FileMode.Create, FileAccess.ReadWrite)); 56 | vaultPack.Save(bw, vaultsToSave, new PackSavingOptions()); 57 | bw.Close(); 58 | } 59 | } 60 | 61 | public string GetName() 62 | { 63 | return "Need for Speed World"; 64 | } 65 | 66 | public string GetGameId() 67 | { 68 | return "WORLD"; 69 | } 70 | 71 | public string GetProfileId() 72 | { 73 | return "WORLD"; 74 | } 75 | 76 | public DatabaseType GetDatabaseType() 77 | { 78 | return DatabaseType.X86Database; 79 | } 80 | 81 | private static IEnumerable GetFilesToLoad(string directory) 82 | { 83 | yield return Path.Combine(directory, "attributes.bin"); 84 | yield return Path.Combine(directory, "commerce.bin"); 85 | yield return Path.Combine(directory, "fe_attrib.bin"); 86 | 87 | foreach (var file in Directory.GetFiles(Path.Combine(directory, "gc.vaults"), "*.bin", 88 | SearchOption.TopDirectoryOnly)) 89 | yield return file; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Attribulator.API/Serialization/IDatabaseStorageFormat.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Attribulator.API.Data; 4 | using VaultLib.Core.DB; 5 | 6 | namespace Attribulator.API.Serialization 7 | { 8 | /// 9 | /// Exposes an interface for reading and writing serialized databases. 10 | /// 11 | public interface IDatabaseStorageFormat 12 | { 13 | /// 14 | /// Deserializes and returns the INFORMATION about the database stored in the given directory. 15 | /// 16 | /// The path to the directory to read data from. 17 | /// A new instance of the object containing information about the database. 18 | SerializedDatabaseInfo LoadInfo(string sourceDirectory); 19 | 20 | /// 21 | /// Deserializes data in the given directory and loads it into the given database. 22 | /// 23 | /// The path to the directory to read data from. 24 | /// The instance to load data into. 25 | /// The names of the database files to load. 26 | /// 27 | /// An enumerable object of instances. 28 | /// 29 | Task> DeserializeAsync(string sourceDirectory, Database destinationDatabase, 30 | IEnumerable fileNames = null); 31 | 32 | /// 33 | /// Serializes data in the given database to files in the given directory. 34 | /// 35 | /// The instance to load data from. 36 | /// The path to the directory to write data to. 37 | /// The loaded files 38 | void Serialize(Database sourceDatabase, string destinationDirectory, IEnumerable loadedFiles); 39 | 40 | /// 41 | /// Gets the identifier of the storage format. 42 | /// 43 | /// The format identifier. 44 | string GetFormatId(); 45 | 46 | /// 47 | /// Gets the name of the storage format. 48 | /// 49 | /// The format name. 50 | string GetFormatName(); 51 | 52 | /// 53 | /// Returns a value indicating whether the storage format can read from the given directory. 54 | /// 55 | /// The directory to test. 56 | /// true if the storage format can read from the directory; otherwise, false. 57 | bool CanDeserializeFrom(string sourceDirectory); 58 | 59 | /// 60 | /// Computes a hash of the data stored for the given file. 61 | /// 62 | /// The base directory to load data from. 63 | /// The file information object. 64 | /// A hash string. 65 | ValueTask ComputeHashAsync(string sourceDirectory, SerializedDatabaseFile file); 66 | } 67 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Commands/GenerateHashListCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Attribulator.API.Exceptions; 7 | using Attribulator.API.Plugin; 8 | using Attribulator.API.Services; 9 | using CommandLine; 10 | using JetBrains.Annotations; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Logging; 13 | using VaultLib.Core.DB; 14 | 15 | namespace Attribulator.CLI.Commands 16 | { 17 | [Verb("generate-hashlist", HelpText = "Generate a hash-source list file from an unpacked database.")] 18 | public class GenerateHashListCommand : BaseCommand 19 | { 20 | private ILogger _logger; 21 | 22 | [Option('i', "input", HelpText = "Directory to read unpacked files from", Required = true)] 23 | [UsedImplicitly] 24 | public string InputDirectory { get; set; } 25 | 26 | [Option('o', "output", HelpText = "Path to generated file", Required = true)] 27 | [UsedImplicitly] 28 | public string OutputPath { get; set; } 29 | 30 | [Option('p', "profile", HelpText = "The profile to use", Required = true)] 31 | [UsedImplicitly] 32 | public string ProfileName { get; set; } 33 | 34 | public override void SetServiceProvider(IServiceProvider serviceProvider) 35 | { 36 | base.SetServiceProvider(serviceProvider); 37 | 38 | _logger = ServiceProvider.GetRequiredService>(); 39 | } 40 | 41 | public override async Task Execute() 42 | { 43 | if (!Directory.Exists(InputDirectory)) 44 | throw new DirectoryNotFoundException($"Cannot find input directory: {InputDirectory}"); 45 | 46 | var profile = ServiceProvider.GetRequiredService().GetProfile(ProfileName); 47 | var storageFormatService = ServiceProvider.GetRequiredService(); 48 | var storageFormat = storageFormatService.GetStorageFormats() 49 | .FirstOrDefault(testStorageFormat => testStorageFormat.CanDeserializeFrom(InputDirectory)); 50 | 51 | if (storageFormat == null) 52 | throw new CommandException( 53 | $"Cannot find storage format that is compatible with directory [{InputDirectory}]."); 54 | 55 | var database = new Database(new DatabaseOptions(profile.GetGameId(), profile.GetDatabaseType())); 56 | _logger.LogInformation("Loading database from disk..."); 57 | await storageFormat.DeserializeAsync(InputDirectory, database); 58 | _logger.LogInformation("Loaded database"); 59 | 60 | var strList = new HashSet(); 61 | 62 | foreach (var vltClass in database.Classes) 63 | { 64 | if (!vltClass.Name.StartsWith("0x")) strList.Add(vltClass.Name); 65 | 66 | foreach (var vltClassField in vltClass.Fields.Values.Where(vltClassField => 67 | !vltClassField.Name.StartsWith("0x"))) 68 | strList.Add(vltClassField.Name); 69 | } 70 | 71 | foreach (var vltCollection in database.RowManager.EnumerateFlattenedCollections()) 72 | if (!vltCollection.Name.StartsWith("0x")) 73 | strList.Add(vltCollection.Name); 74 | 75 | await File.WriteAllLinesAsync(OutputPath, strList); 76 | _logger.LogInformation("Exported {NumEntries} entries to {OutPath}", strList.Count, OutputPath); 77 | return 0; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/AppendArrayModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Attribulator.API.Utils; 3 | using Attribulator.ModScript.API; 4 | using VaultLib.Core; 5 | using VaultLib.Core.Types; 6 | using VaultLib.Core.Types.Abstractions; 7 | using VaultLib.Core.Types.EA.Reflection; 8 | using VaultLib.Core.Utils; 9 | 10 | namespace Attribulator.Plugins.ModScript.Commands 11 | { 12 | // append_array class node field [value] 13 | public class AppendArrayModScriptCommand : BaseModScriptCommand 14 | { 15 | private bool _hasValue; 16 | public string ClassName { get; set; } 17 | public string CollectionName { get; set; } 18 | public string FieldName { get; set; } 19 | public string Value { get; set; } 20 | 21 | public override void Parse(List parts) 22 | { 23 | if (parts.Count < 4) throw new CommandParseException("Expected at least 4 tokens"); 24 | 25 | ClassName = parts[1]; 26 | CollectionName = CleanHashString(parts[2]); 27 | FieldName = CleanHashString(parts[3]); 28 | 29 | if (parts.Count > 4) 30 | { 31 | Value = parts[4]; 32 | _hasValue = true; 33 | } 34 | } 35 | 36 | public override void Execute(DatabaseHelper databaseHelper) 37 | { 38 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 39 | var field = GetField(collection.Class, FieldName); 40 | 41 | if (!field.IsArray) 42 | throw new CommandExecutionException($"Field {ClassName}[{FieldName}] is not an array!"); 43 | 44 | if (!collection.HasEntry(FieldName)) 45 | throw new CommandExecutionException( 46 | $"Collection {collection.ShortPath} does not have an entry for {FieldName}."); 47 | 48 | var array = collection.GetRawValue(FieldName); 49 | 50 | if (array.Items.Count == array.Capacity && field.IsInLayout) 51 | throw new CommandExecutionException("Cannot append to a full array when it is a layout field"); 52 | 53 | if (array.Items.Count + 1 > field.MaxCount) 54 | throw new CommandExecutionException( 55 | "Appending to this array would cause it to exceed the maximum number of allowed elements."); 56 | 57 | var itemToEdit = TypeRegistry.ConstructInstance(array.ItemType, collection.Class, field, collection); 58 | 59 | if (_hasValue) 60 | switch (itemToEdit) 61 | { 62 | case PrimitiveTypeBase primitiveTypeBase: 63 | ValueConversionUtils.DoPrimitiveConversion(primitiveTypeBase, Value); 64 | break; 65 | case IStringValue stringValue: 66 | stringValue.SetString(Value); 67 | break; 68 | case BaseRefSpec refSpec: 69 | // NOTE: This is a compatibility feature for certain types, such as GCollectionKey, which are technically a RefSpec. 70 | refSpec.CollectionKey = Value; 71 | break; 72 | default: 73 | throw new CommandExecutionException( 74 | $"Object stored in {collection.Class.Name}[{field.Name}] is not a simple type and cannot be used in a value-append command"); 75 | } 76 | 77 | array.Items.Add(itemToEdit); 78 | 79 | if (!field.IsInLayout) array.Capacity++; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/DatabaseHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using System.Linq; 4 | using Attribulator.ModScript.API.Utils; 5 | using VaultLib.Core; 6 | using VaultLib.Core.Data; 7 | using VaultLib.Core.DB; 8 | 9 | namespace Attribulator.ModScript.API 10 | { 11 | public class DatabaseHelper 12 | { 13 | public DatabaseHelper(Database database) 14 | { 15 | Database = database; 16 | Collections = database.RowManager.GetFlattenedCollections().ToDictionary(c => c.ShortPath, c => c); 17 | } 18 | 19 | public Dictionary Collections { get; } 20 | public Database Database { get; } 21 | public List Vaults => Database.Vaults; 22 | 23 | public VltCollection FindCollectionByName(string className, string collectionName) 24 | { 25 | var key = $"{className}/{collectionName}"; 26 | return Collections.TryGetValue(key, out var collection) ? collection : null; 27 | } 28 | 29 | public IEnumerable GetCollectionsInVault(Vault vault) 30 | { 31 | return Collections.Values.Where(c => ReferenceEquals(c.Vault, vault)); 32 | } 33 | 34 | public VltCollection AddCollection(Vault addToVault, string className, string collectionName, 35 | VltCollection parentCollection) 36 | { 37 | if (FindCollectionByName(className, collectionName) != null) 38 | throw new DuplicateNameException( 39 | $"A collection in the class '{className}' with the name '{collectionName}' already exists."); 40 | 41 | var collection = new VltCollection(addToVault, Database.FindClass(className), collectionName); 42 | return AddCollection(collection, parentCollection); 43 | } 44 | 45 | public VltCollection AddCollection(VltCollection collection, VltCollection parentCollection = null) 46 | { 47 | if (parentCollection != null) 48 | parentCollection.AddChild(collection); 49 | else 50 | Database.RowManager.Rows.Add(collection); 51 | 52 | Collections[collection.ShortPath] = collection; 53 | 54 | return collection; 55 | } 56 | 57 | public void RenameCollection(VltCollection collection, string newName) 58 | { 59 | Collections.Remove(collection.ShortPath); 60 | collection.SetName(newName); 61 | if (collection.Class.HasField("CollectionName")) collection.SetDataValue("CollectionName", newName); 62 | Collections.Add(collection.ShortPath, collection); 63 | } 64 | 65 | public List RemoveCollection(VltCollection collection) 66 | { 67 | var removed = new List {collection}; 68 | 69 | // Disassociate children 70 | var hasParent = collection.Parent != null; 71 | collection.Parent?.RemoveChild(collection); 72 | Collections.Remove(collection.ShortPath); 73 | 74 | foreach (var collectionChild in collection.Children.ToList()) 75 | removed.AddRange(RemoveCollection(collectionChild)); 76 | 77 | if (!hasParent) Database.RowManager.RemoveCollection(collection); 78 | 79 | return removed; 80 | } 81 | 82 | public void CopyCollection(Database database, VltCollection from, VltCollection to) 83 | { 84 | foreach (var dataPair in from.GetData()) 85 | { 86 | var field = from.Class[dataPair.Key]; 87 | to.SetRawValue(dataPair.Key, 88 | ValueCloningUtils.CloneValue(database, dataPair.Value, to.Class, field, to)); 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/AddNodeModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Attribulator.ModScript.API; 4 | using VaultLib.Core; 5 | using VaultLib.Core.Data; 6 | using VaultLib.Core.Types; 7 | 8 | namespace Attribulator.Plugins.ModScript.Commands 9 | { 10 | // add_node class parentNode nodeName 11 | public class AddNodeModScriptCommand : BaseModScriptCommand 12 | { 13 | public string ClassName { get; set; } 14 | public string ParentCollectionName { get; set; } 15 | public string CollectionName { get; set; } 16 | 17 | public override void Parse(List parts) 18 | { 19 | if (parts.Count != 3 && parts.Count != 4) 20 | throw new CommandParseException($"3 or 4 tokens expected, got {parts.Count}"); 21 | 22 | ClassName = CleanHashString(parts[1]); 23 | ParentCollectionName = parts.Count == 4 ? CleanHashString(parts[2]) : ""; 24 | CollectionName = CleanHashString(parts[^1]); 25 | } 26 | 27 | public override void Execute(DatabaseHelper databaseHelper) 28 | { 29 | VltCollection parentCollection = null; 30 | if (!string.IsNullOrEmpty(ParentCollectionName)) 31 | if ((parentCollection = GetCollection(databaseHelper, ClassName, ParentCollectionName, false)) == null) 32 | throw new CommandExecutionException( 33 | $"add_node failed because parent collection does not exist: {ClassName}/{ParentCollectionName}"); 34 | 35 | if (GetCollection(databaseHelper, ClassName, CollectionName, false) != null) 36 | throw new CommandExecutionException( 37 | $"add_node failed because collection already exists: {ClassName}/{CollectionName}"); 38 | 39 | Vault addToVault; 40 | 41 | if (parentCollection != null) 42 | addToVault = parentCollection.Vault; 43 | else 44 | addToVault = databaseHelper.Vaults.FirstOrDefault(vault => 45 | databaseHelper.GetCollectionsInVault(vault) 46 | .Any(collection => collection.Class.Name == ClassName)); 47 | 48 | if (addToVault == null) 49 | throw new CommandExecutionException("failed to determine vault to insert new collection into"); 50 | 51 | var newNode = databaseHelper.AddCollection(addToVault, ClassName, CollectionName, parentCollection); 52 | var vltClass = newNode.Class; 53 | 54 | if (parentCollection != null) 55 | databaseHelper.CopyCollection(databaseHelper.Database, parentCollection, newNode); 56 | else 57 | foreach (var baseField in vltClass.BaseFields) 58 | { 59 | var vltBaseType = TypeRegistry.CreateInstance(databaseHelper.Database.Options.GameId, vltClass, 60 | baseField, 61 | newNode); 62 | 63 | if (vltBaseType is VLTArrayType array) 64 | { 65 | array.Capacity = baseField.MaxCount; 66 | array.ItemAlignment = baseField.Alignment; 67 | array.FieldSize = baseField.Size; 68 | var itemType = array.ItemType; 69 | 70 | for (var i = 0; i < array.Capacity; i++) 71 | array.Items.Add(TypeRegistry.ConstructInstance(itemType, vltClass, baseField, newNode)); 72 | } 73 | 74 | newNode.SetRawValue(baseField.Name, 75 | vltBaseType); 76 | } 77 | 78 | if (vltClass.HasField("CollectionName")) newNode.SetDataValue("CollectionName", CollectionName); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/ResizeCollectionModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Attribulator.ModScript.API; 5 | using Attribulator.ModScript.API.Utils; 6 | using VaultLib.Core.Types; 7 | 8 | namespace Attribulator.Plugins.ModScript.Commands 9 | { 10 | // resize_collection class node field [property path] size 11 | public class ResizeCollectionModScriptCommand : BaseModScriptCommand 12 | { 13 | public string ClassName { get; set; } 14 | public string CollectionName { get; set; } 15 | public string FieldName { get; set; } 16 | public int ArrayIndex { get; set; } 17 | public List PropertyPath { get; set; } 18 | public ushort Size { get; set; } 19 | 20 | public override void Parse(List parts) 21 | { 22 | if (parts.Count < 6) throw new CommandParseException("Expected at least 6 tokens"); 23 | 24 | ClassName = parts[1]; 25 | CollectionName = CleanHashString(parts[2]); 26 | FieldName = parts[3]; 27 | PropertyPath = new List(); 28 | 29 | var split = FieldName.Split(new[] {'[', ']'}, StringSplitOptions.RemoveEmptyEntries); 30 | 31 | switch (split.Length) 32 | { 33 | case 2: 34 | if (split[1] == "^") 35 | ArrayIndex = -1; 36 | else 37 | ArrayIndex = int.Parse(split[1]); 38 | FieldName = split[0]; 39 | break; 40 | case 1: 41 | FieldName = split[0]; 42 | break; 43 | default: 44 | throw new CommandParseException("Badly malformed update_field command..."); 45 | } 46 | 47 | FieldName = CleanHashString(FieldName); 48 | PropertyPath = parts.Skip(4).Take(parts.Count - 5).ToList(); 49 | Size = ushort.Parse(parts[^1]); 50 | } 51 | 52 | public override void Execute(DatabaseHelper databaseHelper) 53 | { 54 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 55 | var field = GetField(collection.Class, FieldName); 56 | var data = collection.GetRawValue(field.Name); 57 | var itemToEdit = data; 58 | 59 | if (data is VLTArrayType array) 60 | { 61 | if (ArrayIndex == -1) 62 | ArrayIndex = array.Items.Count - 1; 63 | if (ArrayIndex >= 0 && ArrayIndex < array.Items.Count) 64 | itemToEdit = array.Items[ArrayIndex]; 65 | else 66 | throw new CommandExecutionException( 67 | $"resize_collection command is out of bounds. Checked: 0 <= {ArrayIndex} < {array.Items.Count}"); 68 | } 69 | 70 | var parsedProperties = PropertyUtils.ParsePath(PropertyPath).ToList(); 71 | var retrievedProperty = 72 | (PropertyUtils.ReflectedProperty) PropertyUtils.GetProperty(itemToEdit, parsedProperties); 73 | var retrievedValue = retrievedProperty.GetValue(); 74 | 75 | if (!(retrievedValue is Array retrievedArray)) 76 | throw new CommandExecutionException("Value is not an array."); 77 | 78 | var elementType = retrievedProperty.GetPropertyType().GetElementType(); 79 | 80 | if (elementType == null) throw new CommandExecutionException("GetElementType() returned null"); 81 | 82 | var newArray = Array.CreateInstance(elementType, Size); 83 | 84 | for (var i = 0; i < retrievedArray.Length && i < Size; i++) 85 | newArray.SetValue(retrievedArray.GetValue(i), i); 86 | 87 | retrievedProperty.SetValue(newArray); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.SpeedProfiles/World/GameplayVault.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using CoreLibraries.IO; 5 | using VaultLib.Core; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Pack; 8 | 9 | namespace Attribulator.Plugins.SpeedProfiles.World 10 | { 11 | public class GameplayVault : IVaultPack 12 | { 13 | private readonly string _name; 14 | 15 | public GameplayVault(string name) 16 | { 17 | _name = name; 18 | } 19 | 20 | public void Save(BinaryWriter bw, IList vaults, PackSavingOptions savingOptions = null) 21 | { 22 | if (vaults.Count != 1) throw new InvalidDataException("Can only save exactly 1 vault"); 23 | 24 | var nameChars = new char[0x2C]; 25 | _name.CopyTo(0, nameChars, 0, _name.Length); 26 | 27 | var vaultWriter = new VaultWriter(vaults[0], new VaultSaveOptions()); 28 | vaultWriter.ExportManager.AddExport(new VaultSlotExport()); 29 | var vaultStreamInfo = vaultWriter.BuildVault(); 30 | 31 | bw.Write(nameChars); 32 | 33 | var binOffsetPos = bw.BaseStream.Position; 34 | bw.Write(0); 35 | bw.Write((uint) vaultStreamInfo.BinStream.Length); 36 | var vltOffsetPos = bw.BaseStream.Position; 37 | bw.Write(0); 38 | bw.Write((uint) vaultStreamInfo.VltStream.Length); 39 | var fileSizePos = bw.BaseStream.Position; 40 | bw.Write(0); 41 | 42 | bw.AlignWriter(0x40); 43 | var binOffset = bw.BaseStream.Position; 44 | vaultStreamInfo.BinStream.CopyTo(bw.BaseStream); 45 | 46 | bw.AlignWriter(0x40); 47 | var vltOffset = bw.BaseStream.Position; 48 | vaultStreamInfo.VltStream.CopyTo(bw.BaseStream); 49 | //bw.AlignWriter(0x80); 50 | 51 | bw.BaseStream.Position = binOffsetPos; 52 | bw.Write((uint) binOffset); 53 | 54 | bw.BaseStream.Position = vltOffsetPos; 55 | bw.Write((uint) vltOffset); 56 | 57 | bw.BaseStream.Position = fileSizePos; 58 | bw.Write((uint) bw.BaseStream.Length); 59 | 60 | bw.BaseStream.Position = bw.BaseStream.Length; 61 | } 62 | 63 | public IList Load(BinaryReader br, Database database, PackLoadingOptions loadingOptions) 64 | { 65 | var name = new string(br.ReadChars(0x2C)).Trim('\0'); 66 | 67 | var binOffset = br.ReadInt32(); 68 | var binSize = br.ReadInt32(); 69 | var vltOffset = br.ReadInt32(); 70 | var vltSize = br.ReadInt32(); 71 | var fileSize = br.ReadInt32(); 72 | 73 | if (fileSize != br.BaseStream.Length) throw new InvalidDataException("Corrupted file"); 74 | 75 | var vault = new Vault(name); 76 | var byteOrder = loadingOptions?.ByteOrder ?? ByteOrder.Little; 77 | br.BaseStream.Seek(binOffset, SeekOrigin.Begin); 78 | var binBuffer = new byte[binSize]; 79 | if (br.Read(binBuffer, 0, binBuffer.Length) != binBuffer.Length) 80 | throw new Exception($"Failed to read {binBuffer.Length} bytes of BIN data"); 81 | br.BaseStream.Seek(vltOffset, SeekOrigin.Begin); 82 | var vltBuffer = new byte[vltSize]; 83 | if (br.Read(vltBuffer, 0, vltBuffer.Length) != vltBuffer.Length) 84 | throw new Exception($"Failed to read {vltBuffer.Length} bytes of VLT data"); 85 | vault.BinStream = new MemoryStream(binBuffer); 86 | vault.VltStream = new MemoryStream(vltBuffer); 87 | using (var loadingWrapper = new VaultLoadingWrapper(vault, byteOrder)) 88 | { 89 | database.LoadVault(vault, loadingWrapper); 90 | } 91 | 92 | return new List(new[] {vault}); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/CopyFieldsModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Attribulator.ModScript.API; 5 | using Attribulator.ModScript.API.Utils; 6 | using VaultLib.Core.Data; 7 | using VaultLib.Core.Types; 8 | 9 | namespace Attribulator.Plugins.ModScript.Commands 10 | { 11 | // copy_fields class sourceNode targetNode options 12 | public class CopyFieldsModScriptCommand : BaseModScriptCommand 13 | { 14 | [Flags] 15 | public enum CopyOptions 16 | { 17 | Base = 1, // copy+overwrite all base fields 18 | Optional = 2, // copy nonexistent optional fields 19 | OverwriteOptional = 4 // copy+overwrite all optional fields 20 | } 21 | 22 | public string ClassName { get; set; } 23 | public string SourceCollectionName { get; set; } 24 | public string DestinationCollectionName { get; set; } 25 | public CopyOptions Options { get; set; } 26 | 27 | public override void Parse(List parts) 28 | { 29 | if (parts.Count != 5) throw new CommandParseException($"Expected 5 tokens, got {parts.Count}"); 30 | 31 | ClassName = CleanHashString(parts[1]); 32 | SourceCollectionName = CleanHashString(parts[2]); 33 | DestinationCollectionName = CleanHashString(parts[3]); 34 | var copyOptionEntries = parts[4].Split('|', StringSplitOptions.RemoveEmptyEntries).ToList(); 35 | 36 | if (copyOptionEntries.Contains("base")) 37 | Options |= CopyOptions.Base; 38 | if (copyOptionEntries.Contains("optional")) 39 | Options |= CopyOptions.Optional; 40 | if (copyOptionEntries.Contains("overwrite")) 41 | Options |= CopyOptions.OverwriteOptional; 42 | } 43 | 44 | public override void Execute(DatabaseHelper databaseHelper) 45 | { 46 | var srcCollection = GetCollection(databaseHelper, ClassName, SourceCollectionName); 47 | var dstCollection = GetCollection(databaseHelper, ClassName, DestinationCollectionName); 48 | var values = new Dictionary(); 49 | 50 | if ((Options & CopyOptions.Base) != 0) 51 | foreach (var baseField in srcCollection.Class.BaseFields) 52 | values.Add(baseField, 53 | ValueCloningUtils.CloneValue(databaseHelper.Database, srcCollection.GetRawValue(baseField.Name), 54 | srcCollection.Class, 55 | baseField, dstCollection)); 56 | 57 | if ((Options & CopyOptions.Optional) != 0) 58 | foreach (var (key, value) in srcCollection.GetData()) 59 | { 60 | var field = srcCollection.Class[key]; 61 | 62 | if (!field.IsInLayout) 63 | values.Add(field, 64 | ValueCloningUtils.CloneValue(databaseHelper.Database, value, srcCollection.Class, field, 65 | dstCollection)); 66 | } 67 | 68 | // base will always overwrite 69 | // optional by itself will copy anything that doesn't exist 70 | // optional + overwrite will copy nonexistent fields and overwrite the other ones(optional only) 71 | if ((Options & CopyOptions.Base) != 0) 72 | foreach (var (key, value) in values) 73 | if (key.IsInLayout) 74 | dstCollection.SetRawValue(key.Name, value); 75 | 76 | if ((Options & CopyOptions.Optional) != 0) 77 | foreach (var (field, value) in values) 78 | if (!field.IsInLayout && (!dstCollection.HasEntry(field.Name) || 79 | (Options & CopyOptions.OverwriteOptional) != 0)) 80 | dstCollection.SetRawValue(field.Name, value); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/BaseModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using VaultLib.Core.Data; 5 | using VaultLib.Core.Hashing; 6 | 7 | namespace Attribulator.ModScript.API 8 | { 9 | /// 10 | /// Base class for ModScript commands. 11 | /// 12 | public abstract class BaseModScriptCommand : IModScriptCommand 13 | { 14 | private static readonly Dictionary<(string, string), VltClassField> 15 | FieldCache = new Dictionary<(string, string), VltClassField>(); 16 | 17 | private static readonly Dictionary<(string, string), VltCollection> CollectionCache = 18 | new Dictionary<(string, string), VltCollection>(); 19 | 20 | public string Line { get; set; } 21 | public long LineNumber { get; set; } 22 | 23 | /// 24 | public abstract void Parse(List parts); 25 | 26 | /// 27 | public abstract void Execute(DatabaseHelper databaseHelper); 28 | 29 | /// 30 | /// Finds the collection with the given name in the given class. 31 | /// 32 | /// An instance of the class. 33 | /// The class name. 34 | /// The collection name. 35 | /// Whether to throw an exception if the collection is not found. 36 | /// An instance of the class. 37 | /// if the collection cannot be found 38 | protected static VltCollection GetCollection(DatabaseHelper database, string className, string collectionName, 39 | bool throwOnMissing = true) 40 | { 41 | if (CollectionCache.TryGetValue((className, collectionName), out var collection)) return collection; 42 | 43 | collection = database.FindCollectionByName(className, collectionName); 44 | 45 | if (collection != null) return CollectionCache[(className, collectionName)] = collection; 46 | 47 | if (throwOnMissing) 48 | throw new CommandExecutionException($"Cannot find collection: {className}/{collectionName}"); 49 | return null; 50 | } 51 | 52 | /// 53 | /// Finds the field with the given name in the given class. 54 | /// 55 | /// The object to search in. 56 | /// The field name. 57 | /// An instance of the class. 58 | /// if the field cannot be found 59 | protected static VltClassField GetField(VltClass vltClass, string fieldName) 60 | { 61 | if (vltClass == null) throw new CommandExecutionException("GetField() was given a null VltClass!"); 62 | 63 | if (FieldCache.TryGetValue((vltClass.Name, fieldName), out var field)) return field; 64 | 65 | return FieldCache[(vltClass.Name, fieldName)] = vltClass.FindField(fieldName); 66 | } 67 | 68 | /// 69 | /// Converts the given hash-string to its source string if possible. 70 | /// 71 | /// The string to convert. 72 | /// The original string. 73 | protected string CleanHashString(string hashString) 74 | { 75 | if (hashString.StartsWith("0x", StringComparison.Ordinal)) 76 | hashString = 77 | HashManager.ResolveVLT(uint.Parse(hashString.Substring(2), NumberStyles.AllowHexSpecifier)); 78 | 79 | return hashString; 80 | } 81 | 82 | protected static void RemoveCollectionFromCache(VltCollection vltCollection) 83 | { 84 | CollectionCache.Remove((vltCollection.Class.Name, vltCollection.Name)); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /Attribulator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Attribulator.CLI", "Attribulator.CLI\Attribulator.CLI.csproj", "{D6E79A48-C05E-4881-8536-97260CAEF4D4}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attribulator.Plugins.ModScript", "Attribulator.Plugins.ModScript\Attribulator.Plugins.ModScript.csproj", "{37C10CE0-6137-4DF7-81FE-384F38BCE63F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attribulator.API", "Attribulator.API\Attribulator.API.csproj", "{4498502A-E599-4E79-BB21-003D1F1858BB}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attribulator.Plugins.SpeedProfiles", "Attribulator.Plugins.SpeedProfiles\Attribulator.Plugins.SpeedProfiles.csproj", "{25144B8B-69D6-4AE9-865D-C82035F4D05D}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attribulator.Plugins.YAMLSupport", "Attribulator.Plugins.YAMLSupport\Attribulator.Plugins.YAMLSupport.csproj", "{7BA487BA-D629-4732-A5D2-F1C32BDB5882}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attribulator.ModScript.API", "Attribulator.ModScript.API\Attribulator.ModScript.API.csproj", "{2C4AFABD-4592-4453-A7B5-092639D55707}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Attribulator.Plugins.BPSupport", "Attribulator.Plugins.BPSupport\Attribulator.Plugins.BPSupport.csproj", "{A1CACD2C-9D85-4372-89E5-A22C2D1ACCFC}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|x86 = Debug|x86 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {D6E79A48-C05E-4881-8536-97260CAEF4D4}.Debug|x86.ActiveCfg = Debug|x86 27 | {D6E79A48-C05E-4881-8536-97260CAEF4D4}.Debug|x86.Build.0 = Debug|x86 28 | {D6E79A48-C05E-4881-8536-97260CAEF4D4}.Release|x86.ActiveCfg = Release|x86 29 | {D6E79A48-C05E-4881-8536-97260CAEF4D4}.Release|x86.Build.0 = Release|x86 30 | {4498502A-E599-4E79-BB21-003D1F1858BB}.Debug|x86.ActiveCfg = Debug|x86 31 | {4498502A-E599-4E79-BB21-003D1F1858BB}.Debug|x86.Build.0 = Debug|x86 32 | {4498502A-E599-4E79-BB21-003D1F1858BB}.Release|x86.ActiveCfg = Release|x86 33 | {4498502A-E599-4E79-BB21-003D1F1858BB}.Release|x86.Build.0 = Release|x86 34 | {37C10CE0-6137-4DF7-81FE-384F38BCE63F}.Debug|x86.ActiveCfg = Debug|x86 35 | {37C10CE0-6137-4DF7-81FE-384F38BCE63F}.Debug|x86.Build.0 = Debug|x86 36 | {37C10CE0-6137-4DF7-81FE-384F38BCE63F}.Release|x86.ActiveCfg = Release|x86 37 | {37C10CE0-6137-4DF7-81FE-384F38BCE63F}.Release|x86.Build.0 = Release|x86 38 | {25144B8B-69D6-4AE9-865D-C82035F4D05D}.Debug|x86.ActiveCfg = Debug|x86 39 | {25144B8B-69D6-4AE9-865D-C82035F4D05D}.Debug|x86.Build.0 = Debug|x86 40 | {25144B8B-69D6-4AE9-865D-C82035F4D05D}.Release|x86.ActiveCfg = Release|x86 41 | {25144B8B-69D6-4AE9-865D-C82035F4D05D}.Release|x86.Build.0 = Release|x86 42 | {7BA487BA-D629-4732-A5D2-F1C32BDB5882}.Debug|x86.ActiveCfg = Debug|x86 43 | {7BA487BA-D629-4732-A5D2-F1C32BDB5882}.Debug|x86.Build.0 = Debug|x86 44 | {7BA487BA-D629-4732-A5D2-F1C32BDB5882}.Release|x86.ActiveCfg = Release|x86 45 | {7BA487BA-D629-4732-A5D2-F1C32BDB5882}.Release|x86.Build.0 = Release|x86 46 | {2C4AFABD-4592-4453-A7B5-092639D55707}.Debug|x86.ActiveCfg = Debug|x86 47 | {2C4AFABD-4592-4453-A7B5-092639D55707}.Debug|x86.Build.0 = Debug|x86 48 | {2C4AFABD-4592-4453-A7B5-092639D55707}.Release|x86.ActiveCfg = Release|x86 49 | {2C4AFABD-4592-4453-A7B5-092639D55707}.Release|x86.Build.0 = Release|x86 50 | {A1CACD2C-9D85-4372-89E5-A22C2D1ACCFC}.Debug|x86.ActiveCfg = Debug|x86 51 | {A1CACD2C-9D85-4372-89E5-A22C2D1ACCFC}.Debug|x86.Build.0 = Debug|x86 52 | {A1CACD2C-9D85-4372-89E5-A22C2D1ACCFC}.Release|x86.ActiveCfg = Release|x86 53 | {A1CACD2C-9D85-4372-89E5-A22C2D1ACCFC}.Release|x86.Build.0 = Release|x86 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {2FA33738-5C31-4F3F-AB6F-1C3473088D2E} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/ApplyScriptToBinCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Attribulator.API.Plugin; 6 | using Attribulator.API.Services; 7 | using Attribulator.ModScript.API; 8 | using CommandLine; 9 | using JetBrains.Annotations; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | using VaultLib.Core.DB; 13 | 14 | namespace Attribulator.Plugins.ModScript 15 | { 16 | [Verb("apply-script-bin", HelpText = "Apply a ModScript to a compiled database.")] 17 | [UsedImplicitly] 18 | public class ApplyScriptToBinCommand : BaseCommand 19 | { 20 | private ILogger _logger; 21 | private IModScriptService _modScriptService; 22 | 23 | [Option('i', HelpText = "Directory to read compiled files from", Required = true)] 24 | [UsedImplicitly] 25 | public string InputDirectory { get; set; } 26 | 27 | [Option('o', HelpText = "Directory to write new files to", Required = true)] 28 | [UsedImplicitly] 29 | public string OutputDirectory { get; set; } 30 | 31 | [Option('p', HelpText = "The ID of the profile to use", Required = true)] 32 | [UsedImplicitly] 33 | public string ProfileName { get; set; } 34 | 35 | [Option('s', HelpText = "The path to the .nfsms file", Required = true)] 36 | [UsedImplicitly] 37 | public string ModScriptPath { get; set; } 38 | 39 | public override void SetServiceProvider(IServiceProvider serviceProvider) 40 | { 41 | base.SetServiceProvider(serviceProvider); 42 | 43 | _logger = ServiceProvider.GetRequiredService>(); 44 | _modScriptService = ServiceProvider.GetRequiredService(); 45 | } 46 | 47 | public override Task Execute() 48 | { 49 | if (!Directory.Exists(InputDirectory)) 50 | return Task.FromException( 51 | new DirectoryNotFoundException($"Cannot find input directory: {InputDirectory}")); 52 | 53 | if (!File.Exists(ModScriptPath)) 54 | throw new FileNotFoundException($"Cannot find ModScript file: {ModScriptPath}"); 55 | 56 | if (!Directory.Exists(OutputDirectory)) Directory.CreateDirectory(OutputDirectory); 57 | 58 | var profile = ServiceProvider.GetRequiredService().GetProfile(ProfileName); 59 | _logger.LogInformation("Loading database from disk..."); 60 | var database = new Database(new DatabaseOptions(profile.GetGameId(), profile.GetDatabaseType())); 61 | var files = profile.LoadFiles(database, InputDirectory); 62 | database.CompleteLoad(); 63 | _logger.LogInformation("Loaded database"); 64 | 65 | var modScriptDatabase = new DatabaseHelper(database); 66 | var scriptStopwatch = Stopwatch.StartNew(); 67 | var numCommands = 0L; 68 | 69 | foreach (var command in _modScriptService.ParseCommands(File.ReadLines(ModScriptPath))) 70 | try 71 | { 72 | command.Execute(modScriptDatabase); 73 | numCommands++; 74 | } 75 | catch (Exception e) 76 | { 77 | _logger.LogError(e, "Failed to execute script command at line {LineNumber}: {Line}", 78 | command.LineNumber, command.Line); 79 | return Task.FromResult(1); 80 | } 81 | 82 | scriptStopwatch.Stop(); 83 | 84 | var commandsPerSecond = (ulong) (numCommands / (scriptStopwatch.ElapsedMilliseconds / 1000.0)); 85 | _logger.LogInformation( 86 | "Applied {NumCommands} command(s) from script in {ElapsedMilliseconds}ms ({Duration}; ~ {NumPerSec}/sec)", 87 | numCommands, scriptStopwatch.ElapsedMilliseconds, scriptStopwatch.Elapsed, commandsPerSecond); 88 | 89 | _logger.LogInformation("Saving binaries"); 90 | profile.SaveFiles(database, OutputDirectory, files); 91 | 92 | _logger.LogInformation("Done!"); 93 | 94 | return Task.FromResult(0); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /Attribulator.API/Utils/ValueConversionUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Reflection; 6 | using Attribulator.API.Exceptions; 7 | using VaultLib.Core.Hashing; 8 | using VaultLib.Core.Types; 9 | using VaultLib.Core.Types.EA.Reflection; 10 | 11 | namespace Attribulator.API.Utils 12 | { 13 | public static class ValueConversionUtils 14 | { 15 | private static readonly Dictionary TypeCache = new Dictionary(); 16 | 17 | public static VLTBaseType DoPrimitiveConversion(PrimitiveTypeBase primitiveTypeBase, string str) 18 | { 19 | var type = primitiveTypeBase.GetType(); 20 | if (TypeCache.TryGetValue(type, out var conversionType)) 21 | return DoPrimitiveConversion(primitiveTypeBase, str, conversionType); 22 | 23 | // Do primitive conversion 24 | var primitiveInfoAttribute = 25 | type.GetCustomAttribute(); 26 | 27 | if (primitiveInfoAttribute == null) 28 | { 29 | // Try to determine enum type 30 | if (type.IsGenericType && 31 | type.GetGenericTypeDefinition() == typeof(VLTEnumType<>)) 32 | primitiveInfoAttribute = new PrimitiveInfoAttribute(type.GetGenericArguments()[0]); 33 | else 34 | throw new InvalidDataException("Cannot determine primitive type"); 35 | } 36 | 37 | var primitiveType = primitiveInfoAttribute.PrimitiveType; 38 | TypeCache[type] = primitiveType; 39 | return DoPrimitiveConversion(primitiveTypeBase, str, primitiveType); 40 | } 41 | 42 | private static VLTBaseType DoPrimitiveConversion(PrimitiveTypeBase primitiveTypeBase, string str, 43 | Type conversionType) 44 | { 45 | if (conversionType.IsEnum) 46 | { 47 | if (str.StartsWith("0x", StringComparison.Ordinal) && 48 | uint.TryParse(str.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, 49 | out var val)) 50 | primitiveTypeBase.SetValue((IConvertible) Enum.Parse(conversionType, val.ToString())); 51 | else 52 | primitiveTypeBase.SetValue((IConvertible) Enum.Parse(conversionType, str)); 53 | } 54 | else 55 | { 56 | if (str.StartsWith("0x", StringComparison.Ordinal) && uint.TryParse(str.Substring(2), 57 | NumberStyles.AllowHexSpecifier, 58 | CultureInfo.InvariantCulture, out var val)) 59 | primitiveTypeBase.SetValue((IConvertible) Convert.ChangeType(val, conversionType)); 60 | else 61 | try 62 | { 63 | primitiveTypeBase.SetValue( 64 | (IConvertible) Convert.ChangeType(str, conversionType, CultureInfo.InvariantCulture)); 65 | } 66 | catch (Exception e) 67 | { 68 | throw new ValueConversionException($"Failed to parse value [{str}] as type {conversionType}", 69 | e); 70 | } 71 | } 72 | 73 | return primitiveTypeBase; 74 | } 75 | 76 | public static object DoPrimitiveConversion(object value, string str) 77 | { 78 | if (value == null) 79 | // we don't know the type, just assume we need a string 80 | return str; 81 | 82 | var type = value.GetType(); 83 | 84 | if (type == typeof(uint)) 85 | { 86 | if (str.StartsWith("0x", StringComparison.Ordinal)) 87 | return uint.Parse(str.Substring(2), NumberStyles.AllowHexSpecifier); 88 | if (!uint.TryParse(str, out _)) 89 | return VLT32Hasher.Hash(str); 90 | } 91 | else if (type == typeof(int)) 92 | { 93 | if (str.StartsWith("0x", StringComparison.Ordinal)) 94 | return int.Parse(str.Substring(2), NumberStyles.AllowHexSpecifier); 95 | if (!uint.TryParse(str, out _)) 96 | return unchecked((int) VLT32Hasher.Hash(str)); 97 | } 98 | 99 | return type.IsEnum ? Enum.Parse(type, str) : Convert.ChangeType(str, type, CultureInfo.InvariantCulture); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Attribulator.ModScript.API/Utils/ValueCloningUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using VaultLib.Core; 5 | using VaultLib.Core.Data; 6 | using VaultLib.Core.DB; 7 | using VaultLib.Core.Types; 8 | using VaultLib.Core.Types.EA.Reflection; 9 | 10 | namespace Attribulator.ModScript.API.Utils 11 | { 12 | /// 13 | /// Exposes a utility function to clone VLT objects 14 | /// 15 | public static class ValueCloningUtils 16 | { 17 | /// 18 | /// Creates a complete copy of the given object. 19 | /// 20 | /// The database to resolve types from. 21 | /// The object to clone. 22 | /// The VLT class holding the field. 23 | /// The VLT field holding the object. 24 | /// The VLT collection. 25 | /// A new instance of the object with all properties copied. 26 | public static VLTBaseType CloneValue(Database database, VLTBaseType originalValue, VltClass vltClass, 27 | VltClassField vltClassField, 28 | VltCollection vltCollection) 29 | { 30 | var newValue = originalValue is VLTArrayType 31 | ? TypeRegistry.CreateInstance(database.Options.GameId, vltClass, vltClassField, vltCollection) 32 | : TypeRegistry.ConstructInstance( 33 | TypeRegistry.ResolveType(database.Options.GameId, vltClassField.TypeName), vltClass, 34 | vltClassField, vltCollection); 35 | 36 | if (originalValue is VLTArrayType array) 37 | { 38 | var newArray = (VLTArrayType) newValue; 39 | newArray.Capacity = array.Capacity; 40 | newArray.ItemAlignment = vltClassField.Alignment; 41 | newArray.FieldSize = vltClassField.Size; 42 | newArray.Items = array.Items 43 | .Select(i => CloneValue(database, i, vltClass, vltClassField, vltCollection)).ToList(); 44 | 45 | return newArray; 46 | } 47 | 48 | switch (originalValue) 49 | { 50 | case PrimitiveTypeBase primitiveTypeBase: 51 | var convertible = primitiveTypeBase.GetValue(); 52 | if (convertible != null) ((PrimitiveTypeBase) newValue).SetValue(convertible); 53 | return newValue; 54 | default: 55 | return CloneObjectWithReflection(originalValue, newValue, vltClass, vltClassField, vltCollection); 56 | } 57 | } 58 | 59 | private static VLTBaseType CloneObjectWithReflection(VLTBaseType originalValue, VLTBaseType newValue, 60 | VltClass vltClass, VltClassField vltClassField, 61 | VltCollection vltCollection) 62 | { 63 | var properties = originalValue.GetType() 64 | .GetProperties(BindingFlags.Public | BindingFlags.Instance) 65 | .Where(p => p.SetMethod?.IsPublic ?? false) 66 | .ToArray(); 67 | 68 | foreach (var propertyInfo in properties) 69 | { 70 | var value = propertyInfo.GetValue(originalValue); 71 | 72 | switch (value) 73 | { 74 | case null: 75 | propertyInfo.SetValue(newValue, null); 76 | continue; 77 | case VLTBaseType vltBaseType: 78 | propertyInfo.SetValue(newValue, CloneObjectWithReflection( 79 | vltBaseType, 80 | TypeRegistry.ConstructInstance(propertyInfo.PropertyType, vltClass, vltClassField, 81 | vltCollection), 82 | vltClass, vltClassField, vltCollection)); 83 | break; 84 | case string str: 85 | propertyInfo.SetValue(newValue, new string(str)); 86 | break; 87 | default: 88 | if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType.IsEnum) 89 | propertyInfo.SetValue(newValue, value); 90 | else if (value is Array array) 91 | propertyInfo.SetValue(newValue, array.Clone()); 92 | break; 93 | } 94 | } 95 | 96 | return newValue; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/Commands/UpdateFieldModScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using Attribulator.API.Utils; 6 | using Attribulator.ModScript.API; 7 | using Attribulator.ModScript.API.Utils; 8 | using VaultLib.Core.Types; 9 | using VaultLib.Core.Types.Abstractions; 10 | using VaultLib.Core.Types.Attrib.Types; 11 | using VaultLib.Core.Types.EA.Reflection; 12 | using VaultLib.Core.Utils; 13 | 14 | namespace Attribulator.Plugins.ModScript.Commands 15 | { 16 | // update_field class node field [property] value 17 | public class UpdateFieldModScriptCommand : BaseModScriptCommand 18 | { 19 | public string ClassName { get; set; } 20 | public string CollectionName { get; set; } 21 | public string FieldName { get; set; } 22 | public int ArrayIndex { get; set; } 23 | public List PropertyPath { get; set; } 24 | public string Value { get; set; } 25 | 26 | public override void Parse(List parts) 27 | { 28 | if (parts.Count < 5) throw new CommandParseException("Expected at least 5 tokens"); 29 | 30 | ClassName = parts[1]; 31 | CollectionName = CleanHashString(parts[2]); 32 | FieldName = parts[3]; 33 | PropertyPath = new List(); 34 | 35 | var split = FieldName.Split(new[] {'[', ']'}, StringSplitOptions.RemoveEmptyEntries); 36 | 37 | switch (split.Length) 38 | { 39 | case 2: 40 | if (split[1] == "^") 41 | ArrayIndex = -1; 42 | else 43 | ArrayIndex = int.Parse(split[1]); 44 | FieldName = split[0]; 45 | break; 46 | case 1: 47 | FieldName = split[0]; 48 | break; 49 | default: 50 | throw new CommandParseException("Badly malformed update_field command..."); 51 | } 52 | 53 | FieldName = CleanHashString(FieldName); 54 | 55 | if (parts.Count > 5) 56 | { 57 | PropertyPath = parts.Skip(4).Take(parts.Count - 5).ToList(); 58 | Value = parts[^1]; 59 | } 60 | else 61 | { 62 | Value = parts[4]; 63 | } 64 | } 65 | 66 | public override void Execute(DatabaseHelper databaseHelper) 67 | { 68 | var collection = GetCollection(databaseHelper, ClassName, CollectionName); 69 | var field = GetField(collection.Class, FieldName); 70 | var data = collection.GetRawValue(field.Name); 71 | var itemToEdit = data; 72 | 73 | if (data is VLTArrayType array) 74 | { 75 | if (ArrayIndex == -1) 76 | ArrayIndex = array.Items.Count - 1; 77 | if (ArrayIndex >= 0 && ArrayIndex < array.Items.Count) 78 | itemToEdit = array.Items[ArrayIndex]; 79 | else 80 | throw new CommandExecutionException( 81 | $"update_field command is out of bounds. Checked: 0 <= {ArrayIndex} < {array.Items.Count}"); 82 | } 83 | 84 | if (PropertyPath.Count == 0) 85 | { 86 | switch (itemToEdit) 87 | { 88 | case PrimitiveTypeBase primitiveTypeBase: 89 | ValueConversionUtils.DoPrimitiveConversion(primitiveTypeBase, Value); 90 | break; 91 | case IStringValue stringValue: 92 | stringValue.SetString(Value); 93 | break; 94 | case BaseRefSpec refSpec: 95 | // NOTE: This is a compatibility feature for certain types, such as GCollectionKey, which are technically a RefSpec. 96 | refSpec.CollectionKey = Value; 97 | break; 98 | default: 99 | throw new CommandExecutionException( 100 | $"cannot handle update for {collection.Class.Name}[{field.Name}]"); 101 | } 102 | } 103 | else 104 | { 105 | // TODO for VaultLib: change Matrix to be multiple floats instead of 1 array 106 | if (itemToEdit is Matrix matrix && PropertyPath.Count == 1) 107 | { 108 | var matrixPath = 109 | PropertyPath[0].Split(new[] {'[', ']'}, StringSplitOptions.RemoveEmptyEntries)[1]; 110 | var indices = matrixPath.Split(',', StringSplitOptions.RemoveEmptyEntries) 111 | .Select(int.Parse) 112 | .ToArray(); 113 | if (indices.Length != 2) throw new CommandExecutionException("invalid matrix access"); 114 | 115 | matrix.Data ??= new float[16]; 116 | matrix.Data[4 * (indices[0] - 1) + (indices[1] - 1)] = 117 | float.Parse(Value, CultureInfo.InvariantCulture); 118 | } 119 | else 120 | { 121 | var parsedProperties = PropertyUtils.ParsePath(PropertyPath).ToList(); 122 | var retrievedProperty = PropertyUtils.GetProperty(itemToEdit, parsedProperties); 123 | var retrievedValue = retrievedProperty.GetValue(); 124 | 125 | var value = ValueConversionUtils.DoPrimitiveConversion(retrievedValue, Value); 126 | if (value == null) throw new Exception(); 127 | 128 | retrievedProperty.SetValue(value); 129 | } 130 | } 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /Attribulator.Plugins.ModScript/ApplyScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Attribulator.API.Exceptions; 8 | using Attribulator.API.Plugin; 9 | using Attribulator.API.Services; 10 | using Attribulator.ModScript.API; 11 | using CommandLine; 12 | using JetBrains.Annotations; 13 | using Microsoft.Extensions.DependencyInjection; 14 | using Microsoft.Extensions.Logging; 15 | using VaultLib.Core.DB; 16 | 17 | namespace Attribulator.Plugins.ModScript 18 | { 19 | [Verb("apply-script", HelpText = "Apply a ModScript to a database.")] 20 | [UsedImplicitly] 21 | public class ApplyScriptCommand : BaseCommand 22 | { 23 | private ILogger _logger; 24 | private IModScriptService _modScriptService; 25 | 26 | [Option('i', HelpText = "Directory to read unpacked files from", Required = true)] 27 | [UsedImplicitly] 28 | public string InputDirectory { get; set; } 29 | 30 | [Option('o', HelpText = "Directory to write BIN files to", Required = true)] 31 | [UsedImplicitly] 32 | public string OutputDirectory { get; set; } 33 | 34 | [Option('p', HelpText = "The ID of the profile to use", Required = true)] 35 | [UsedImplicitly] 36 | public string ProfileName { get; set; } 37 | 38 | [Option('s', HelpText = "The path(s) to the .nfsms file(s)", Required = true)] 39 | [UsedImplicitly] 40 | public IEnumerable ModScriptPaths { get; set; } 41 | 42 | [Option("no-backup", HelpText = "Disable backup generation.")] 43 | [UsedImplicitly] 44 | public bool DisableBackup { get; set; } 45 | 46 | [Option("no-bins", HelpText = "Disable binary generation.")] 47 | [UsedImplicitly] 48 | public bool DisableBinGeneration { get; set; } 49 | 50 | public override void SetServiceProvider(IServiceProvider serviceProvider) 51 | { 52 | base.SetServiceProvider(serviceProvider); 53 | 54 | _logger = ServiceProvider.GetRequiredService>(); 55 | _modScriptService = ServiceProvider.GetRequiredService(); 56 | } 57 | 58 | public override async Task Execute() 59 | { 60 | if (!Directory.Exists(InputDirectory)) 61 | throw new DirectoryNotFoundException($"Cannot find input directory: {InputDirectory}"); 62 | 63 | var scriptFiles = ModScriptPaths.ToList(); 64 | 65 | foreach (var scriptFile in scriptFiles) 66 | if (!File.Exists(scriptFile)) 67 | throw new FileNotFoundException($"Cannot find ModScript file: {scriptFile}"); 68 | 69 | if (!Directory.Exists(OutputDirectory)) Directory.CreateDirectory(OutputDirectory); 70 | 71 | var profile = ServiceProvider.GetRequiredService().GetProfile(ProfileName); 72 | var storageFormatService = ServiceProvider.GetRequiredService(); 73 | var storageFormat = storageFormatService.GetStorageFormats() 74 | .FirstOrDefault(testStorageFormat => testStorageFormat.CanDeserializeFrom(InputDirectory)); 75 | 76 | if (storageFormat == null) 77 | throw new CommandException( 78 | $"Cannot find storage format that is compatible with directory [{InputDirectory}]."); 79 | 80 | var database = new Database(new DatabaseOptions(profile.GetGameId(), profile.GetDatabaseType())); 81 | _logger.LogInformation("Loading database from disk..."); 82 | var files = (await storageFormat.DeserializeAsync(InputDirectory, database)).ToList(); 83 | _logger.LogInformation("Loaded database"); 84 | 85 | var overallStopwatch = Stopwatch.StartNew(); 86 | var modScriptDatabase = new DatabaseHelper(database); 87 | var totalCommands = 0L; 88 | var totalMilliseconds = 0.0d; 89 | 90 | foreach (var scriptFile in scriptFiles) 91 | { 92 | _logger.LogInformation("Processing script: {FileName}", scriptFile); 93 | var fileStopwatch = Stopwatch.StartNew(); 94 | var numCommands = 0L; 95 | 96 | foreach (var command in _modScriptService.ParseCommands(File.ReadLines(scriptFile))) 97 | try 98 | { 99 | command.Execute(modScriptDatabase); 100 | numCommands++; 101 | } 102 | catch (Exception e) 103 | { 104 | _logger.LogError(e, "Failed to execute script command at line {LineNumber}: {Line}", 105 | command.LineNumber, command.Line); 106 | return 1; 107 | } 108 | 109 | fileStopwatch.Stop(); 110 | 111 | var commandsPerSecond = (ulong) (numCommands / (fileStopwatch.Elapsed.TotalMilliseconds / 1000.0)); 112 | _logger.LogInformation( 113 | "Applied {NumCommands} command(s) from script [{FileName}] in {ElapsedMilliseconds}ms ({Duration}; ~ {NumPerSec}/sec)", 114 | numCommands, scriptFile, fileStopwatch.ElapsedMilliseconds, fileStopwatch.Elapsed, 115 | commandsPerSecond); 116 | 117 | totalCommands += numCommands; 118 | totalMilliseconds += fileStopwatch.Elapsed.TotalMilliseconds; 119 | } 120 | 121 | overallStopwatch.Stop(); 122 | var totalCommandsPerSecond = 123 | (ulong) (totalCommands / (totalMilliseconds / 1000.0)); 124 | 125 | _logger.LogInformation( 126 | "Completed in {OverallTimeInMilliseconds}ms ({OverallDuration}).", overallStopwatch.ElapsedMilliseconds, 127 | overallStopwatch.Elapsed); 128 | 129 | _logger.LogInformation( 130 | "Overall: Applied {NumCommands} command(s) from {NumScripts} script(s) (execution time: {ElapsedMilliseconds}ms / {Duration}; ~ {NumPerSec}/sec)", 131 | totalCommands, scriptFiles.Count, Math.Round(totalMilliseconds), 132 | TimeSpan.FromMilliseconds(totalMilliseconds), 133 | totalCommandsPerSecond); 134 | 135 | if (!DisableBackup) 136 | { 137 | _logger.LogInformation("Generating backup"); 138 | Directory.Move(InputDirectory, 139 | $"{InputDirectory.TrimEnd('/', '\\')}_{DateTimeOffset.Now.ToUnixTimeSeconds()}"); 140 | Directory.CreateDirectory(InputDirectory); 141 | } 142 | 143 | _logger.LogInformation("Saving database"); 144 | storageFormat.Serialize(database, InputDirectory, files); 145 | 146 | // TODO: should build cache be updated? 147 | 148 | if (!DisableBinGeneration) 149 | { 150 | _logger.LogInformation("Saving binaries"); 151 | profile.SaveFiles(database, OutputDirectory, files); 152 | } 153 | 154 | _logger.LogInformation("Done!"); 155 | 156 | return 0; 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb 341 | 342 | */launchSettings.json 343 | /YAMLDatabase/Properties/launchSettings.json 344 | -------------------------------------------------------------------------------- /Attribulator.ModScript.API/Utils/PropertyUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using JetBrains.Annotations; 7 | using VaultLib.Core.Types; 8 | using VaultLib.Core.Types.Abstractions; 9 | 10 | namespace Attribulator.ModScript.API.Utils 11 | { 12 | /// 13 | /// Exposes utility functions to parse property paths. 14 | /// 15 | /// CollectionKey 16 | /// ShiftPattern[1] XValues[0] 17 | /// ShiftPattern[1] XValues[0] Value 18 | /// TopArray[2] NestedArray[3] AnotherArray[4] StructValue StringVal 19 | public static class PropertyUtils 20 | { 21 | /// 22 | /// Parses the given property path and returns a stream of objects 23 | /// 24 | /// The property path to parse. 25 | /// An enumerable stream of objects representing the property path. 26 | public static IEnumerable ParsePath(string path) 27 | { 28 | return ParsePath(path.Split(' ', StringSplitOptions.RemoveEmptyEntries)); 29 | } 30 | 31 | /// 32 | /// Parses the given property path and returns a stream of objects 33 | /// 34 | /// The property path to parse. 35 | /// An enumerable stream of objects representing the property path. 36 | public static IEnumerable ParsePath(IEnumerable path) 37 | { 38 | foreach (var pathSegment in path) 39 | if (!ushort.TryParse(pathSegment, out var index)) 40 | { 41 | // Do full parse 42 | var split = pathSegment.Split(new[] {'[', ']'}, StringSplitOptions.RemoveEmptyEntries); 43 | 44 | switch (split.Length) 45 | { 46 | case 1: 47 | yield return new ParsedProperty {Name = split[0], HasIndex = false}; 48 | break; 49 | case 2: 50 | yield return new ParsedProperty 51 | {Name = split[0], Index = ushort.Parse(split[1]), HasIndex = true}; 52 | break; 53 | default: 54 | throw new InvalidDataException($"Cannot parse segment: {pathSegment}"); 55 | } 56 | } 57 | else 58 | { 59 | yield return new ParsedProperty {Index = index, Name = null, HasIndex = true}; 60 | } 61 | } 62 | 63 | /// 64 | /// Retrieves the relevant property for the given property path in the given object. 65 | /// 66 | /// The object to examine 67 | /// The property path 68 | /// The value 69 | public static RetrievedProperty GetProperty([NotNull] VLTBaseType baseObject, [NotNull] string propertyPath) 70 | { 71 | return GetProperty(baseObject, ParsePath(propertyPath)); 72 | } 73 | 74 | /// 75 | /// Retrieves the relevant property for the given property path in the given object. 76 | /// 77 | /// The object to examine 78 | /// The parsed property path 79 | /// The value 80 | public static RetrievedProperty GetProperty([NotNull] VLTBaseType baseObject, 81 | [NotNull] IEnumerable propertyPath) 82 | { 83 | object itemToExamine = baseObject; 84 | var pathList = propertyPath.ToList(); 85 | PropertyInfo propertyInfo = null; 86 | 87 | for (var index = 0; index < pathList.Count; index++) 88 | { 89 | if (itemToExamine == null) throw new CommandExecutionException("Cannot manipulate null object"); 90 | 91 | var parsedProperty = pathList[index]; 92 | if (parsedProperty.Name != null) 93 | { 94 | var propName = parsedProperty.Name; 95 | if (itemToExamine is BaseRefSpec) 96 | propName = propName switch 97 | { 98 | "Collection" => "CollectionKey", 99 | "Class" => "ClassKey", 100 | _ => propName 101 | }; 102 | 103 | propertyInfo = itemToExamine.GetType() 104 | .GetProperty(propName, BindingFlags.Public | BindingFlags.Instance); 105 | 106 | if (propertyInfo == null) 107 | throw new CommandExecutionException( 108 | $"Property not found on type {itemToExamine.GetType()}: {propName}"); 109 | 110 | if (!(propertyInfo.SetMethod?.IsPublic ?? false)) 111 | throw new CommandExecutionException( 112 | $"{itemToExamine.GetType()}[{propName}] is read-only"); 113 | 114 | if (index == pathList.Count - 1) break; 115 | itemToExamine = propertyInfo.GetValue(itemToExamine); 116 | } 117 | 118 | if (parsedProperty.HasIndex) 119 | { 120 | if (!(itemToExamine is Array array)) 121 | throw new CommandExecutionException("Not working with an array!"); 122 | 123 | if (parsedProperty.Index >= array.Length) 124 | throw new CommandExecutionException( 125 | $"Array index out of bounds (requested {parsedProperty.Index} but there are {array.Length} elements)"); 126 | 127 | itemToExamine = array.GetValue(parsedProperty.Index); 128 | 129 | if (itemToExamine == null) throw new CommandExecutionException("Cannot manipulate null object"); 130 | 131 | var elementType = itemToExamine.GetType(); 132 | 133 | if (elementType.IsPrimitive || elementType == typeof(string)) 134 | return new ArrayProperty(array, parsedProperty.Index, 135 | propertyInfo!.PropertyType.GetElementType()); 136 | } 137 | } 138 | 139 | return new ReflectedProperty(propertyInfo, itemToExamine); 140 | } 141 | 142 | public struct ParsedProperty 143 | { 144 | public string Name; 145 | public ushort Index; 146 | public bool HasIndex; 147 | } 148 | 149 | public abstract class RetrievedProperty 150 | { 151 | public abstract object GetValue(); 152 | public abstract void SetValue(object value); 153 | } 154 | 155 | public class ReflectedProperty : RetrievedProperty 156 | { 157 | private readonly PropertyInfo _propertyInfo; 158 | private readonly object _targetObject; 159 | 160 | public ReflectedProperty(PropertyInfo propertyInfo, object targetObject) 161 | { 162 | _propertyInfo = propertyInfo; 163 | _targetObject = targetObject; 164 | } 165 | 166 | public override object GetValue() 167 | { 168 | return _propertyInfo.GetValue(_targetObject); 169 | } 170 | 171 | public override void SetValue(object value) 172 | { 173 | _propertyInfo.SetValue(_targetObject, value); 174 | } 175 | 176 | public Type GetPropertyType() 177 | { 178 | return _propertyInfo.PropertyType; 179 | } 180 | } 181 | 182 | public class ArrayProperty : RetrievedProperty 183 | { 184 | private readonly Array _array; 185 | private readonly Type _elementType; 186 | private readonly int _index; 187 | 188 | public ArrayProperty(Array array, int index, Type elementType) 189 | { 190 | _array = array; 191 | _index = index; 192 | _elementType = elementType; 193 | } 194 | 195 | public override object GetValue() 196 | { 197 | return _array.GetValue(_index); 198 | } 199 | 200 | public override void SetValue(object value) 201 | { 202 | _array.SetValue(value, _index); 203 | } 204 | 205 | public Type GetElementType() 206 | { 207 | return _elementType; 208 | } 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Commands/PackCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Attribulator.API.Data; 8 | using Attribulator.API.Exceptions; 9 | using Attribulator.API.Plugin; 10 | using Attribulator.API.Services; 11 | using Attribulator.CLI.Build; 12 | using CommandLine; 13 | using Dasync.Collections; 14 | using JetBrains.Annotations; 15 | using Microsoft.Extensions.DependencyInjection; 16 | using Microsoft.Extensions.Logging; 17 | using Newtonsoft.Json; 18 | using VaultLib.Core.DB; 19 | 20 | namespace Attribulator.CLI.Commands 21 | { 22 | [Verb("pack", HelpText = "Pack a database to BIN files.")] 23 | public class PackCommand : BaseCommand 24 | { 25 | private ILogger _logger; 26 | 27 | [Option('i', "input", HelpText = "Directory to read unpacked files from", Required = true)] 28 | [UsedImplicitly] 29 | public string InputDirectory { get; set; } 30 | 31 | [Option('o', "output", HelpText = "Directory to write BIN files to", Required = true)] 32 | [UsedImplicitly] 33 | public string OutputDirectory { get; set; } 34 | 35 | [Option('p', "profile", HelpText = "The profile to use", Required = true)] 36 | [UsedImplicitly] 37 | public string ProfileName { get; set; } 38 | 39 | [Option('c', "cache", HelpText = "Enable the build cache")] 40 | [UsedImplicitly] 41 | public bool UseCache { get; set; } 42 | 43 | [Option('b', "backup", HelpText = "Make a backup of BIN files")] 44 | [UsedImplicitly] 45 | public bool MakeBackup { get; set; } 46 | 47 | public override void SetServiceProvider(IServiceProvider serviceProvider) 48 | { 49 | base.SetServiceProvider(serviceProvider); 50 | 51 | _logger = ServiceProvider.GetRequiredService>(); 52 | } 53 | 54 | public override async Task Execute() 55 | { 56 | if (!Directory.Exists(InputDirectory)) 57 | throw new DirectoryNotFoundException($"Cannot find input directory: {InputDirectory}"); 58 | 59 | if (!Directory.Exists(OutputDirectory)) Directory.CreateDirectory(OutputDirectory); 60 | 61 | var profile = ServiceProvider.GetRequiredService().GetProfile(ProfileName); 62 | var storageFormatService = ServiceProvider.GetRequiredService(); 63 | var storageFormat = storageFormatService.GetStorageFormats() 64 | .FirstOrDefault(testStorageFormat => testStorageFormat.CanDeserializeFrom(InputDirectory)); 65 | 66 | if (storageFormat == null) 67 | throw new CommandException( 68 | $"Cannot find storage format that is compatible with directory [{InputDirectory}]."); 69 | 70 | 71 | // Parallel hash check 72 | // TODO refactor build cache system to be reusable 73 | var fileNamesToCompile = new ConcurrentBag(); 74 | var cache = new BuildCache(); 75 | var dbInternalPath = Path.Combine(InputDirectory, ".db"); 76 | var cacheFilePath = Path.Combine(dbInternalPath, ".cache.json"); 77 | 78 | var dbInfo = storageFormat.LoadInfo(InputDirectory); 79 | 80 | if (UseCache) 81 | { 82 | if (Directory.Exists(dbInternalPath) && File.Exists(cacheFilePath)) 83 | { 84 | // Load cache from file 85 | _logger.LogInformation("Loading cache from disk..."); 86 | cache = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(cacheFilePath)); 87 | _logger.LogInformation("Loaded cache from disk"); 88 | } 89 | 90 | _logger.LogInformation("Performing cache check..."); 91 | 92 | var depMap = new Dictionary>(); 93 | 94 | foreach (var (key, value) in cache.Entries) 95 | foreach (var dependency in value.Dependencies) 96 | { 97 | if (!depMap.ContainsKey(dependency)) depMap[dependency] = new List(); 98 | 99 | depMap[dependency].Add(key); 100 | } 101 | 102 | await dbInfo.Files.ParallelForEachAsync(async f => 103 | { 104 | var storageHash = await storageFormat.ComputeHashAsync(InputDirectory, f); 105 | var cacheKey = $"{f.Group}_{f.Name}"; 106 | var cacheEntry = cache.FindEntry(cacheKey) ?? new BuildCacheEntry(); 107 | 108 | if (cacheEntry.Hash != storageHash) 109 | { 110 | if (depMap.TryGetValue(cacheKey, out var depList)) 111 | foreach (var dep in depList) 112 | fileNamesToCompile.Add(dep); 113 | foreach (var dependency in cacheEntry.Dependencies) fileNamesToCompile.Add(dependency); 114 | fileNamesToCompile.Add(f.Name); 115 | cacheEntry.Hash = storageHash; 116 | cacheEntry.LastModified = DateTimeOffset.Now; 117 | cache.Entries[cacheKey] = cacheEntry; 118 | _logger.LogInformation("Detected change in file {Group}[{Name}]", f.Group, f.Name); 119 | } 120 | }, Environment.ProcessorCount); 121 | } 122 | else 123 | { 124 | // filesToCompile = new ConcurrentBag(files); 125 | fileNamesToCompile = new ConcurrentBag(dbInfo.Files.Select(f => f.Name)); 126 | } 127 | 128 | if (fileNamesToCompile.Count > 0) 129 | { 130 | var database = new Database(new DatabaseOptions(profile.GetGameId(), profile.GetDatabaseType())); 131 | _logger.LogInformation("Loading database from disk..."); 132 | var files = 133 | (await storageFormat.DeserializeAsync(InputDirectory, database, fileNamesToCompile)).ToList(); 134 | _logger.LogInformation("Loaded database"); 135 | _logger.LogInformation("Saving files..."); 136 | var filesToCompile = files.Where(loadedFile => fileNamesToCompile.Contains(loadedFile.Name)).ToList(); 137 | 138 | if (MakeBackup) 139 | { 140 | _logger.LogInformation("Generating backup"); 141 | Directory.Move(OutputDirectory, 142 | $"{OutputDirectory.TrimEnd('/', '\\')}_{DateTimeOffset.Now.ToUnixTimeSeconds()}"); 143 | Directory.CreateDirectory(OutputDirectory); 144 | } 145 | 146 | profile.SaveFiles(database, OutputDirectory, filesToCompile); 147 | 148 | if (UseCache) 149 | { 150 | _logger.LogInformation("Writing cache..."); 151 | Directory.CreateDirectory(dbInternalPath); 152 | 153 | var vaultFileMap = new Dictionary(); 154 | 155 | foreach (var file in files) 156 | foreach (var vault in file.Vaults) 157 | vaultFileMap[vault.Name] = file.Name; 158 | 159 | foreach (var f in filesToCompile) 160 | { 161 | var cacheKey = $"{f.Group}_{f.Name}"; 162 | var cacheEntry = cache.FindEntry(cacheKey); 163 | cacheEntry.Dependencies = ComputeDependencies(vaultFileMap, f, database); 164 | } 165 | 166 | cache.LastUpdated = DateTimeOffset.Now; 167 | await File.WriteAllTextAsync(cacheFilePath, JsonConvert.SerializeObject(cache)); 168 | } 169 | } 170 | else 171 | { 172 | _logger.LogInformation("Binaries are up-to-date."); 173 | } 174 | 175 | _logger.LogInformation("Done!"); 176 | return 0; 177 | } 178 | 179 | private static HashSet ComputeDependencies(IReadOnlyDictionary vaultFileMap, 180 | LoadedFile file, 181 | Database database) 182 | { 183 | var fileDependencies = new HashSet(); 184 | foreach (var vault in file.Vaults) 185 | { 186 | var collections = database.RowManager.GetCollectionsInVault(vault); 187 | 188 | foreach (var collection in collections) 189 | { 190 | if (collection.Parent == null || collection.Parent.Vault == vault) continue; 191 | 192 | var vaultFile = vaultFileMap[collection.Parent.Vault.Name]; 193 | if (vaultFile != file.Name) fileDependencies.Add(vaultFile); 194 | } 195 | } 196 | 197 | return fileDependencies; 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /Attribulator.CLI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Attribulator.API; 7 | using Attribulator.API.Plugin; 8 | using Attribulator.API.Serialization; 9 | using Attribulator.API.Services; 10 | using Attribulator.CLI.Commands; 11 | using Attribulator.CLI.Services; 12 | using CommandLine; 13 | using McMaster.NETCore.Plugins; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Serilog; 16 | 17 | namespace Attribulator.CLI 18 | { 19 | internal static class Program 20 | { 21 | public static async Task Main(string[] args) 22 | { 23 | // Setup 24 | var services = new ServiceCollection(); 25 | var loaders = GetPluginLoaders(); 26 | 27 | // Register services 28 | services.AddSingleton(); 29 | services.AddSingleton(); 30 | services.AddSingleton(); 31 | services.AddSingleton(); 32 | 33 | // Set up logging 34 | Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); 35 | services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true)); 36 | 37 | try 38 | { 39 | var plugins = ConfigurePlugins(services, loaders); 40 | await using var serviceProvider = services.BuildServiceProvider(); 41 | 42 | // Load everything from DI container 43 | LoadCommands(services, serviceProvider); 44 | LoadProfiles(services, serviceProvider); 45 | LoadStorageFormats(services, serviceProvider); 46 | LoadPlugins(plugins, serviceProvider); 47 | 48 | // Off to the races! 49 | return await RunApplication(serviceProvider, args); 50 | } 51 | catch (Exception e) 52 | { 53 | Log.Error(e, "An error occurred while initializing the application."); 54 | return 1; 55 | } 56 | } 57 | 58 | private static async Task RunApplication(IServiceProvider serviceProvider, IEnumerable args) 59 | { 60 | var commandService = serviceProvider.GetRequiredService(); 61 | var commandTypes = commandService.GetCommandTypes().ToArray(); 62 | try 63 | { 64 | return await Parser.Default.ParseArguments(args, commandTypes) 65 | .MapResult((BaseCommand cmd) => 66 | { 67 | cmd.SetServiceProvider(serviceProvider); 68 | return cmd.Execute(); 69 | }, errs => Task.FromResult(1)); 70 | } 71 | catch (Exception e) 72 | { 73 | Log.Error(e, "An error occurred while running the application."); 74 | return 1; 75 | } 76 | } 77 | 78 | private static void LoadPlugins(IEnumerable plugins, IServiceProvider serviceProvider) 79 | { 80 | var pluginService = serviceProvider.GetRequiredService(); 81 | 82 | foreach (var pluginFactory in plugins) 83 | { 84 | var plugin = pluginFactory.CreatePlugin(serviceProvider); 85 | plugin.Init(); 86 | pluginService.RegisterPlugin(plugin); 87 | } 88 | } 89 | 90 | private static void LoadCommands(ServiceCollection services, IServiceProvider serviceProvider) 91 | { 92 | var commandService = serviceProvider.GetRequiredService(); 93 | var commandTypes = (from service in services 94 | where typeof(BaseCommand).IsAssignableFrom(service.ImplementationType) 95 | select service.ImplementationType).ToList(); 96 | 97 | // First register our own commands 98 | commandService.RegisterCommand(); 99 | commandService.RegisterCommand(); 100 | commandService.RegisterCommand(); 101 | commandService.RegisterCommand(); 102 | commandService.RegisterCommand(); 103 | commandService.RegisterCommand(); 104 | 105 | // Then register plugin commands 106 | foreach (var commandType in commandTypes) commandService.RegisterCommand(commandType); 107 | } 108 | 109 | private static void LoadProfiles(ServiceCollection services, IServiceProvider serviceProvider) 110 | { 111 | var profileTypes = (from service in services 112 | where typeof(IProfile).IsAssignableFrom(service.ImplementationType) 113 | select service.ImplementationType).ToList(); 114 | var profileService = serviceProvider.GetRequiredService(); 115 | foreach (var profileType in profileTypes) profileService.RegisterProfile(profileType); 116 | } 117 | 118 | private static void LoadStorageFormats(ServiceCollection services, IServiceProvider serviceProvider) 119 | { 120 | var storageFormatTypes = (from service in services 121 | where typeof(IDatabaseStorageFormat).IsAssignableFrom(service.ImplementationType) 122 | select service.ImplementationType).ToList(); 123 | var storageFormatService = serviceProvider.GetRequiredService(); 124 | foreach (var storageFormatType in storageFormatTypes) 125 | storageFormatService.RegisterStorageFormat(storageFormatType); 126 | } 127 | 128 | private static IEnumerable GetPluginLoaders() 129 | { 130 | // create plugin loaders 131 | var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); 132 | 133 | return (from dir in Directory.GetDirectories(pluginsDir) 134 | let dirName = Path.GetFileName(dir) 135 | select Path.Combine(dir, dirName + ".dll") 136 | into pluginDll 137 | where File.Exists(pluginDll) 138 | select PluginLoader.CreateFromAssemblyFile(pluginDll, new[] 139 | { 140 | // Basic stuff 141 | typeof(IPluginFactory), typeof(IServiceCollection), 142 | 143 | // Application stuff 144 | typeof(BaseCommand), typeof(IProfile), 145 | 146 | // CommandLineParser 147 | typeof(VerbAttribute) 148 | }, conf => conf.PreferSharedTypes = true)).ToList(); 149 | } 150 | 151 | private static IEnumerable ConfigurePlugins(IServiceCollection services, 152 | IEnumerable loaders) 153 | { 154 | var idToFactoryMap = new Dictionary(); 155 | 156 | foreach (var loader in loaders) 157 | foreach (var pluginType in from pluginType in loader.LoadDefaultAssembly().GetTypes() 158 | where typeof(IPluginFactory).IsAssignableFrom(pluginType) && !pluginType.IsAbstract 159 | select pluginType) 160 | { 161 | var pluginFactory = (IPluginFactory) Activator.CreateInstance(pluginType); 162 | 163 | if (pluginFactory == null) 164 | throw new Exception("Activator.CreateInstance returned null while trying to load plugin"); 165 | 166 | idToFactoryMap.Add(pluginFactory.GetId(), pluginFactory); 167 | } 168 | 169 | var unresolved = new List(); 170 | var resolved = new List(); 171 | var dependents = 172 | new Dictionary>(); // key: plugin; value: list of plugins that require it 173 | 174 | foreach (var (id, factory) in idToFactoryMap) 175 | { 176 | var node = new PluginResolutionNode(id); 177 | 178 | foreach (var requiredPlugin in factory.GetRequiredPlugins()) 179 | { 180 | node.AddEdge(new PluginResolutionNode(requiredPlugin)); 181 | 182 | if (!dependents.ContainsKey(requiredPlugin)) 183 | dependents.Add(requiredPlugin, new List()); 184 | dependents[requiredPlugin].Add(id); 185 | } 186 | 187 | ResolveDependencies(node, resolved, unresolved); 188 | } 189 | 190 | resolved = resolved.Distinct(PluginResolutionNode.IdComparer).ToList(); 191 | unresolved = unresolved.Distinct(PluginResolutionNode.IdComparer).ToList(); 192 | 193 | if (unresolved.Count != 0) throw new Exception("unresolved.Count != 0"); 194 | 195 | var list = new List(); 196 | 197 | foreach (var node in resolved) 198 | if (idToFactoryMap.TryGetValue(node.Id, out var factory)) 199 | { 200 | factory.Configure(services); 201 | list.Add(factory); 202 | } 203 | else 204 | { 205 | throw new Exception( 206 | $"Encountered unresolved dependency while loading plugins: {node.Id} (dependents: {string.Join(", ", dependents[node.Id])})"); 207 | } 208 | 209 | return list; 210 | } 211 | 212 | private static void ResolveDependencies(PluginResolutionNode node, List resolved, 213 | List unresolved) 214 | { 215 | unresolved.Add(node); 216 | 217 | foreach (var edge in node.Edges) 218 | if (!resolved.Contains(edge)) 219 | ResolveDependencies(edge, resolved, unresolved); 220 | 221 | resolved.Add(node); 222 | unresolved.Remove(node); 223 | } 224 | 225 | private class PluginResolutionNode 226 | { 227 | public PluginResolutionNode(string id) 228 | { 229 | Id = id; 230 | Edges = new List(); 231 | } 232 | 233 | public string Id { get; } 234 | 235 | public List Edges { get; } 236 | 237 | public static IEqualityComparer IdComparer { get; } = new IdEqualityComparer(); 238 | 239 | public void AddEdge(PluginResolutionNode node) 240 | { 241 | Edges.Add(node); 242 | } 243 | 244 | private sealed class IdEqualityComparer : IEqualityComparer 245 | { 246 | public bool Equals(PluginResolutionNode x, PluginResolutionNode y) 247 | { 248 | if (ReferenceEquals(x, y)) return true; 249 | if (ReferenceEquals(x, null)) return false; 250 | if (ReferenceEquals(y, null)) return false; 251 | if (x.GetType() != y.GetType()) return false; 252 | return x.Id == y.Id; 253 | } 254 | 255 | public int GetHashCode(PluginResolutionNode obj) 256 | { 257 | return obj.Id != null ? obj.Id.GetHashCode() : 0; 258 | } 259 | } 260 | } 261 | } 262 | } --------------------------------------------------------------------------------