├── 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