Initial work on a modding interface.

This commit is contained in:
wincent
2022-01-02 22:22:15 +01:00
parent 3803b6bee4
commit 49041f678f
34 changed files with 1812 additions and 52 deletions

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RakDotNet.IO" Version="1.0.0" />
<ProjectReference Include="..\RakDotNet.IO\RakDotNet.IO.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,9 +3,11 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using InfectedRose.Database.Fdb;
using RakDotNet.IO;
using Microsoft.Data.Sqlite;
namespace InfectedRose.Database
{
@@ -157,6 +159,19 @@ namespace InfectedRose.Database
set => Source.TableHeader.Tables[index] = (value.Info, value.Data);
}
public static AccessDatabase Open(string file)
{
using var stream = File.OpenRead(file);
using var reader = new ByteReader(stream);
var source = new DatabaseFile();
source.Deserialize(reader);
return new AccessDatabase(source);
}
public static async Task<AccessDatabase> OpenAsync(string file)
{
using var stream = File.OpenRead(file);
@@ -170,7 +185,7 @@ namespace InfectedRose.Database
return new AccessDatabase(source);
}
public async Task SaveAsync(string file)
public void Save(string file)
{
using var stream = File.Create(file);
@@ -178,12 +193,136 @@ namespace InfectedRose.Database
Source.Serialize(reader);
}
public static async Task OpenEmptyAsync()
public async Task SaveAsync(string file)
{
var source = new DatabaseFile();
using var stream = File.Create(file);
source.TableHeader = new FdbTableHeader(0);
using var reader = new BitWriter(stream);
Source.Serialize(reader);
}
public static void OpenEmpty()
{
var source = new DatabaseFile { TableHeader = new FdbTableHeader(0) };
}
public void SaveSqlite(SqliteConnection connection)
{
// For each table, create a sql table
foreach (var table in this)
{
var tableName = table.Name;
var createTable = $"CREATE TABLE {tableName} (";
foreach (var column in table.TableInfo)
{
var columnName = column.Name;
string columnType;
switch (column.Type)
{
case DataType.Integer:
columnType = "INT32";
break;
case DataType.Float:
columnType = "REAL";
break;
case DataType.Text:
columnType = "TEXT_4";
break;
case DataType.Boolean:
columnType = "INT_BOOL";
break;
case DataType.Bigint:
columnType = "INTEGER";
break;
case DataType.Varchar:
columnType = "TEXT_4";
break;
default:
columnType = "TEXT_4";
break;
}
createTable += $"\"{columnName}\" {columnType} NULL,";
}
createTable = createTable.TrimEnd(',');
createTable += ");";
var command = new SqliteCommand(createTable, connection);
command.ExecuteNonQuery();
var count = table.Count;
if (count <= 0) continue;
// Create one query to insert all the rows
var insertQuery = new StringBuilder(count * table.TableInfo.Count * 8);
insertQuery.Append($"INSERT INTO {tableName} (");
foreach (var column in table.TableInfo)
{
insertQuery.Append($"\"{column.Name}\",");
}
insertQuery.Length--;
insertQuery.Append(") VALUES ");
foreach (var row in table.Data.RowHeader.RowInfos)
{
var linked = row;
while (linked != null)
{
insertQuery.Append('(');
foreach (var (type, value) in linked.DataHeader.Data.Fields)
{
if (type == DataType.Nothing)
{
insertQuery.Append("null,");
}
else if (type == DataType.Text || type == DataType.Varchar)
{
insertQuery.Append($"'{(value.ToString().Replace("'", "''"))}',");
}
else
{
insertQuery.Append($"{value},");
}
}
insertQuery.Length--;
linked = linked.Linked;
insertQuery.Append("),");
}
}
insertQuery.Length--;
insertQuery.Append(';');
var query = insertQuery.ToString();
command = new SqliteCommand(query, connection);
try
{
command.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine(query);
Console.WriteLine(e);
throw;
}
}
}
internal void RegisterSql(string sql)

View File

@@ -7,6 +7,11 @@ namespace InfectedRose.Database.Fdb
{
public long Value { get; set; }
public override string ToString()
{
return Value.ToString();
}
public void Deserialize(BitReader reader)
{
using var s = new DatabaseScope(reader, true);

View File

@@ -15,7 +15,7 @@ namespace InfectedRose.Database.Fdb
public override string ToString()
{
return "FdbString";
return Value;
}
public void Serialize(BitWriter writer)

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
@@ -10,12 +10,8 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Concepts\Tables" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="3.1.2" />
<PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.1" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.3" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>

View File

@@ -0,0 +1,109 @@
namespace InfectedRose.Interface
{
public enum ComponentId
{
ControllablePhysicsComponent = 1,
RenderComponent = 2,
SimplePhysicsComponent = 3,
CharacterComponent = 4,
ScriptComponent = 5,
BouncerComponent = 6,
DestructibleComponent = 7,
GhostComponent = 8,
SkillComponent = 9,
SpawnerComponent = 10,
ItemComponent = 11,
RebuildComponent = 12,
RebuildStartComponent = 13,
RebuildActivatorComponent = 14,
IconOnlyComponent = 15,
VendorComponent = 16,
InventoryComponent = 17,
ProjectilePhysicsComponent = 18,
ShootingGalleryComponent = 19,
RigidBodyPhantomPhysicsComponent = 20,
DropEffectComponent = 21,
ChestComponent = 22,
CollectibleComponent = 23,
BlueprintComponent = 24,
MovingPlatformComponent = 25,
PetComponent = 26,
PlatformBoundaryComponent = 27,
ModuleComponent = 28,
ArcadeComponent = 29,
VehiclePhysicsComponent = 30,
MovementAIComponent = 31,
ExhibitComponent = 32,
OverheadIconComponent = 33,
PetControlComponent = 34,
MinifigComponent = 35,
PropertyComponent = 36,
PetCreatorComponent = 37,
ModelBuilderComponent = 38,
ScriptedActivityComponent = 39,
PhantomPhysicsComponent = 40,
SpringpadComponent = 41,
B3BehaviorsComponent = 42,
PropertyEntranceComponent = 43,
FXComponent = 44,
PropertyManagementComponent = 45,
SecondVehiclePhysicsComponent = 46,
PhysicsSystemComponent = 47,
QuickBuildComponent = 48,
SwitchComponent = 49,
MinigameComponent = 50,
ChanglingComponent = 51,
ChoiceBuildComponent = 52,
PackageComponent = 53,
SoundRepeaterComponent = 54,
SoundAmbient2DComponent = 55,
SoundAmbient3DComponent = 56,
PreconditionComponent = 57,
PlayerFlagsComponent = 58,
CustomBuildAssemblyComponent = 59,
BaseCombatAIComponent = 60,
ModuleAssemblyComponent = 61,
ShowcaseModelHandlerComponent = 62,
RacingModuleComponent = 63,
GenericActivatorComponent = 64,
PropertyVendorComponent = 65,
HFLightDirectionGadgetComponent = 66,
RocketLaunchComponent = 67,
RocketLandingComponent = 68,
TriggerComponent = 69,
DroppedLootComponent = 70,
RacingControlComponent = 71,
FactionTriggerComponent = 72,
MissionNPCComponent = 73,
RacingStatsComponent = 74,
LUPExhibitComponent = 75,
BBBComponent = 76,
SoundTriggerComponent = 77,
ProximityMonitorComponent = 78,
RacingSoundTriggerComponent = 79,
ChatComponent = 80,
FriendsListComponent = 81,
GuildComponent = 82,
LocalSystemComponent = 83,
MissionComponent = 84,
MutableModelBehaviorsComponent = 85,
PathfindingControlComponent = 86,
PetTamingControlComponent = 87,
PropertyEditorComponent = 88,
SkinnedRenderComponent = 89,
SlashCommandComponent = 90,
StatusEffectComponent = 91,
TeamsComponent = 92,
TextEffectComponent = 93,
TradeComponent = 94,
UserControlComponent = 95,
IgnoreListComponent = 96,
LUPLaunchpadComponent = 97,
BuffComponent = 98,
InteractionManagerComponent = 98,
DonationVendorComponent = 100,
CombatMediatorComponent = 101,
Component107 = 107,
Possesable = 108
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp6.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Core\InfectedRose.Core.csproj" />
<ProjectReference Include="..\InfectedRose.Database\InfectedRose.Database.csproj" />
<ProjectReference Include="..\InfectedRose.Geometry\InfectedRose.Geometry.csproj" />
<ProjectReference Include="..\InfectedRose.Luz\InfectedRose.Luz.csproj" />
<ProjectReference Include="..\InfectedRose.Lvl\InfectedRose.Lvl.csproj" />
<ProjectReference Include="..\InfectedRose.Nif\InfectedRose.Nif.csproj" />
<ProjectReference Include="..\InfectedRose.Terrain\InfectedRose.Terrain.csproj" />
<ProjectReference Include="..\InfectedRose.Triggers\InfectedRose.Triggers.csproj" />
<ProjectReference Include="..\InfectedRose.Utilities\InfectedRose.Utilities.csproj" />
<ProjectReference Include="..\RakDotNet.IO\RakDotNet.IO.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.0-preview1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
{
public class Lookup
{
[JsonPropertyName("ids")]
public Dictionary<string, int> Ids { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
{
public class Manifest
{
[JsonPropertyName("name")]
public string Name { get; set; } = "mod-name";
[JsonPropertyName("files")]
public string[] Files { get; set; } = {"mod.json"};
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
{
public class Mod
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("action")]
public string Action { get; set; } = "add";
[JsonPropertyName("components")]
public string[] Components { get; set; }
[JsonPropertyName("table")]
public string Table { get; set; }
[JsonPropertyName("values")]
public Dictionary<string, object> Values { get; set; } = new Dictionary<string, object>();
public T GetValue<T>(string id)
{
return ((JsonElement) Values[id]).Deserialize<T>()!;
}
public int GetComponentType()
{
return (int) Enum.Parse(typeof(ComponentId), Type);
}
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using InfectedRose.Database;
using InfectedRose.Database.Fdb;
namespace InfectedRose.Interface
{
public class IdCallback
{
public string Id { get; set; }
public Action<int> Callback { get; set; }
}
public static class ModContext
{
public static Mods Configuration { get; set; }
public static string Root { get; set; }
public static AccessDatabase Database { get; set; }
public static Dictionary<string, int> Ids { get; set; } = new Dictionary<string, int>();
public static Dictionary<string, Mod> Mods { get; set; } = new Dictionary<string, Mod>();
public static List<string> ServerSql { get; set; } = new List<string>();
public static List<IdCallback> IdCallbacks { get; set; } = new List<IdCallback>();
public static void AwaitId(string id, Action<int> callback)
{
if (Ids.TryGetValue(id, out var value))
{
callback(value);
return;
}
IdCallbacks.Add(new IdCallback { Id = id, Callback = callback });
}
public static void RegisterId(string id, int value)
{
foreach (var idCallback in IdCallbacks.Where(callback => callback.Id == id).ToArray())
{
idCallback.Callback(value);
IdCallbacks.Remove(idCallback);
}
Ids[id] = value;
}
public static Mod GetMod(string id)
{
return Mods[id];
}
public static string ParseValue(string value)
{
if (value.StartsWith("INCLUDE:"))
{
value = value.Substring(8);
return File.ReadAllText(value);
}
if (!value.StartsWith("ASSET:"))
{
return value;
}
value = value.Substring(6);
var root = "../../res/";
if (value.StartsWith("PHYSICS:"))
{
root = "../../res/physics/";
value = value.Substring(8);
}
root = Root + root;
// Get the relative path from root to asset
var relative = Path.GetRelativePath(root, Path.Combine(value));
return relative.Replace("/", "\\\\");
}
public static void ApplyValues(Mod mod, Row row, Table table)
{
foreach (var (key, objValue) in mod.Values)
{
var value = (JsonElement) objValue;
var field = row[key];
var info = table.TableInfo.First(column => column.Name == key);
switch (info.Type)
{
case DataType.Integer:
{
var str = value.ToString();
if (str.Contains(':'))
{
AwaitId(str, id =>
{
field.Value = id;
});
}
else
{
field.Value = value.GetInt32();
}
break;
}
case DataType.Float:
field.Value = value.GetSingle();
break;
case DataType.Varchar:
case DataType.Text:
field.Value = ParseValue(value.GetString()!);
break;
case DataType.Boolean:
field.Value = value.GetBoolean();
break;
case DataType.Bigint:
field.Value = value.GetInt64();
break;
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace InfectedRose.Interface
{
public abstract class ModType
{
public abstract void Apply(Mod mod);
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace InfectedRose.Interface
{
[AttributeUsage(AttributeTargets.Class)]
public class ModTypeAttribute : Attribute
{
public string Type { get; set; }
public ModTypeAttribute(string type)
{
Type = type;
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
{
public class ModPriority
{
[JsonPropertyName("directory")]
public string Directory { get; set; }
[JsonPropertyName("priority")]
public int Priority { get; set; }
}
public class Mods
{
[JsonPropertyName("version")]
public string Version { get; set; } = "";
[JsonPropertyName("database")]
public string Database { get; set; } = "cdclient.fdb";
[JsonPropertyName("sqlite")]
public string Sqlite { get; set; } = "CDServer.sqlite";
[JsonPropertyName("priorities")]
public List<ModPriority> Priorities { get; set; } = new List<ModPriority>();
}
}

View File

@@ -0,0 +1,33 @@
namespace InfectedRose.Interface
{
[ModType("object")]
public class ObjectMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
{
return;
}
var table = ModContext.Database["Objects"];
var obj = table.Create();
ModContext.ApplyValues(mod, obj, table);
ModContext.RegisterId(mod.Id, obj.Key);
foreach (var component in mod.Components)
{
var row = ModContext.Database["ComponentsRegistry"].Create(obj.Key);
ModContext.AwaitId(component, id =>
{
row["component_id"].Value = id;
row["component_type"].Value = ModContext.GetMod(component).GetComponentType();
});
}
}
}
}

View File

@@ -0,0 +1,432 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CommandLine;
using InfectedRose.Database;
using InfectedRose.Database.Fdb;
using Microsoft.Data.Sqlite;
using RakDotNet.IO;
namespace InfectedRose.Interface
{
public static class Program
{
public const string Version = "0.1";
public static Options CommandLineOptions { get; set; }
public static Dictionary<string, ModType> ModTypes { get; set; } = new Dictionary<string, ModType>();
public class Options
{
[Option('i', "input", Required = false, HelpText = "Path to mods.json.")]
public string Input { get; set; } = "mods.json";
[Option('c', "copy", Required = false, HelpText = "Generate mods to copy this object LOT.")]
public int CopyObject { get; set; } = 0;
[Option('d', "id", Required = false, HelpText = "The id of the mod objects generated by copy.")]
public string CopyId { get; set; } = "my-object";
[Option('o', "output", Required = false, HelpText = "The file to output the generated mods to (when generating mods).")]
public string Output { get; set; } = "output.json";
}
public static T ReadJson<T>(string file)
{
using var stream = File.OpenRead(file);
return JsonSerializer.Deserialize<T>(stream) ?? throw new FileNotFoundException($"Could not find {file}!");
}
public static void WriteJson<T>(string file, T json)
{
using var stream = File.Create(file);
var options = new JsonSerializerOptions();
options.WriteIndented = true;
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
JsonSerializer.Serialize(stream, json, options);
stream.Close();
}
public static T ReadOrCreateJson<T>(string file) where T : new()
{
if (!File.Exists(file))
{
return new T();
}
using var stream = File.OpenRead(file);
return JsonSerializer.Deserialize<T>(stream) ?? new T();
}
public static string GetComponentTable(string component)
{
if (component.Contains("PhysicsComponent"))
{
return "PhysicsComponent";
}
return component;
}
public static void ApplyRow(Mod mod)
{
if (mod.Action != "add")
{
return;
}
var tableName = GetComponentTable(mod.Type);
if (!string.IsNullOrWhiteSpace(mod.Table))
{
tableName = mod.Table;
}
var table = ModContext.Database[tableName];
if (table != null)
{
var row = table.Create();
ModContext.RegisterId(mod.Id, row.Key);
ModContext.ApplyValues(mod, row, table);
}
else
{
// For entries without tables, like the MissionNPCComponent
ModContext.RegisterId(mod.Id, 0);
}
}
public static void ApplyMod(Mod mod)
{
Console.Write("\t\u21B3 ");
if (ModTypes.TryGetValue(mod.Type, out var type))
{
Console.ForegroundColor = ConsoleColor.Cyan;
type.Apply(mod);
}
else
{
Console.ForegroundColor = ConsoleColor.Gray;
ApplyRow(mod);
}
Console.WriteLine($"[{mod.Type}] \"{mod.Id}\"");
Console.ResetColor();
}
public static void ApplyModFile(Manifest manifest, string file)
{
Mod[] mods = ReadJson<Mod[]>(file);
var directory = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(Path.GetDirectoryName(file)!);
foreach (var mod in mods)
{
ModContext.Mods[mod.Id] = mod;
ApplyMod(mod);
}
Directory.SetCurrentDirectory(directory);
WriteJson(file, mods);
}
public static void ApplyManifest(string file)
{
Manifest manifest = ReadJson<Manifest>(file);
Console.Write("Applying ");
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\"{manifest.Name}\"");
Console.ResetColor();
var directory = Path.GetDirectoryName(file)!;
foreach (var modFile in manifest.Files)
{
ApplyModFile(manifest, Path.Combine(directory, modFile));
}
}
public static void SaveDatabase()
{
// Save the database
foreach (var table in ModContext.Database)
{
table.Recalculate();
}
ModContext.Database.Save(Path.Combine(CommandLineOptions.Input, "../../res/cdclient.fdb"));
// Export the database as SQLite
if (File.Exists(ModContext.Configuration.Sqlite))
{
File.Delete(ModContext.Configuration.Sqlite);
}
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ModContext.Configuration.Sqlite };
using var connection = new SqliteConnection(connectionStringBuilder.ConnectionString);
connection.Open();
ModContext.Database.SaveSqlite(connection);
// Run sql commands specified by mods on the server
foreach (var command in ModContext.ServerSql)
{
using var commandInstance = new SqliteCommand(command, connection);
commandInstance.ExecuteNonQuery();
}
connection.Close();
}
public static async Task ConsoleRotateAnimation(Task other)
{
var index = 0;
var array = new[] {"|", "/", "-", "\\", "|", "/", "-", "\\"};
while (!other.IsCompleted)
{
await Task.Delay(100);
Console.Write(array[index++ % array.Length] + "\b");
}
Console.WriteLine();
}
public static Mod[] CopyObject(int lot, string id)
{
var objectsTable = ModContext.Database["Objects"];
var componentsRegistryTable = ModContext.Database["ComponentsRegistry"];
if (!objectsTable.Seek(lot, out var objectRow))
{
throw new Exception($"Failed to find object {lot} to copy!");
}
var componentRegistryRows = componentsRegistryTable.SeekMultiple(lot).ToArray();
var mods = new List<Mod>();
var objectMod = new Mod();
mods.Add(objectMod);
objectMod.Id = id;
objectMod.Type = "object";
var components = new List<string>();
foreach (var registryRow in componentRegistryRows)
{
var componentId = (int) registryRow["component_id"].Value;
var componentType = (ComponentId) registryRow["component_type"].Value;
var componentMod = new Mod();
mods.Add(componentMod);
componentMod.Id = $"{id}:{componentType}";
componentMod.Type = $"{componentType}";
components.Add(componentMod.Id);
if (!ModContext.Database[GetComponentTable(componentMod.Type)].Seek(componentId, out var componentRow))
{
continue;
}
foreach (var field in componentRow.ToArray()[1..])
{
if (field.Type == DataType.Nothing) continue;
componentMod.Values[field.Name] = field.Value;
}
}
objectMod.Components = components.ToArray();
foreach (var field in objectRow.ToArray()[1..])
{
if (field.Type == DataType.Nothing) continue;
objectMod.Values[field.Name] = field.Value;
}
return mods.ToArray();
}
public static void Main(string[] arguments)
{
// Parse command line arguments
CommandLineOptions = Parser.Default.ParseArguments<Options>(arguments).Value;
// Collect all implemented mod types
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
var attribute = type.GetCustomAttribute<ModTypeAttribute>();
if (attribute == null)
{
continue;
}
ModTypes[attribute.Type] = Activator.CreateInstance(type) as ModType ??
throw new InvalidOperationException($"Invalid mod type class {type}!");
}
// Store the root directory
ModContext.Root = Directory.GetCurrentDirectory();
ModContext.Configuration = ReadOrCreateJson<Mods>(CommandLineOptions.Input);
// Check version
if (string.IsNullOrWhiteSpace(ModContext.Configuration.Version))
{
ModContext.Configuration.Version = Version;
}
else if (ModContext.Configuration.Version != Version)
{
throw new Exception($"Version {Version} incompatible with mods.json version {ModContext.Configuration.Version}");
}
// Create the mods.json file or update it
WriteJson(CommandLineOptions.Input, ModContext.Configuration);
// Open database
if (!File.Exists(ModContext.Configuration.Database))
{
Console.WriteLine("Please specify a valid path to the base fdb database in mods.json.");
return;
}
var origin = Console.GetCursorPosition();
Console.Write("Starting \b");
var openDatabaseTask = Task.Run(() =>
{
var database = AccessDatabase.Open(ModContext.Configuration.Database);
ModContext.Database = database;
});
var rotation = Task.Run(async () =>
{
await ConsoleRotateAnimation(openDatabaseTask);
});
Task.WaitAll(openDatabaseTask, rotation);
Console.SetCursorPosition(origin.Left, origin.Top);
if (CommandLineOptions.CopyObject != 0)
{
Console.Write($"Generating copy of {CommandLineOptions.CopyObject} as ");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"[object] \"{CommandLineOptions.CopyId}\"");
Console.ResetColor();
Console.WriteLine($" to {CommandLineOptions.Output}");
var mods = CopyObject(CommandLineOptions.CopyObject, CommandLineOptions.CopyId);
WriteJson(CommandLineOptions.Output, mods);
return;
}
// Sort the mods into a list of priorities
List<string> priorities;
if (ModContext.Configuration.Priorities.Count != 0)
{
ModContext.Configuration.Priorities.Sort((a, b) => a.Priority - b.Priority);
priorities = ModContext.Configuration.Priorities.Select(m => m.Directory).ToList();
}
else
{
priorities = new List<string>();
foreach (var file in Directory.GetFiles("./", "*manifest.json", SearchOption.AllDirectories))
{
priorities.Add(Path.GetDirectoryName(file)!);
}
}
// Apply the mods
foreach (var priority in priorities)
{
ApplyManifest(Path.Combine(priority, "./manifest.json"));
}
// Check for unresolved references and print errors
if (ModContext.IdCallbacks.Count > 0)
{
Console.ForegroundColor = ConsoleColor.Red;
foreach (var callback in ModContext.IdCallbacks)
{
Console.WriteLine($"Unknown reference to \"{callback.Id}\"");
}
Console.ResetColor();
throw new Exception($"{ModContext.IdCallbacks.Count} unknown references found!");
}
// Create the lookup.json for ids
var result = new Lookup();
result.Ids = ModContext.Ids;
WriteJson("lookup.json", result);
origin = Console.GetCursorPosition();
Console.Write("Saving \b");
var saveDatabaseTask = Task.Run(SaveDatabase);
rotation = Task.Run(async () =>
{
await ConsoleRotateAnimation(saveDatabaseTask);
});
Task.WaitAll(saveDatabaseTask, rotation);
Console.SetCursorPosition(origin.Left, origin.Top);
Console.WriteLine("Complete!");
}
}
}

View File

@@ -0,0 +1,16 @@
namespace InfectedRose.Interface
{
[ModType("sql")]
public class SqlMod : ModType
{
public override void Apply(Mod mod)
{
switch (mod.Action)
{
case "run-server":
ModContext.ServerSql.Add(ModContext.ParseValue(mod.GetValue<string>("sql")));
break;
}
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Color = System.Drawing.Color;
using Vector2 = System.Numerics.Vector2;

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
@@ -10,7 +10,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@@ -18,15 +18,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Triggers", "In
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Utilities", "InfectedRose.Utilities\InfectedRose.Utilities.csproj", "{66233254-3BF3-4AC8-84C8-83DF6E956753}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Builder", "InfectedRose.Builder\InfectedRose.Builder.csproj", "{5073EC93-8D3E-4D38-A643-40BF3B697B76}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RakDotNet.IO", "RakDotNet.IO\RakDotNet.IO.csproj", "{DD4DCE0F-FEAA-423A-9AF9-794089EBBA35}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Pipeline", "InfectedRose.Pipeline\InfectedRose.Pipeline.csproj", "{281871E0-DB69-4CA3-BB94-00B37DE9772C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.World", "InfectedRose.World\InfectedRose.World.csproj", "{B6809E62-6FC6-4445-ABB6-302021E7B91A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Interface", "InfectedRose.Interface\InfectedRose.Interface.csproj", "{1CD2D01F-218B-47A2-AE66-A673F48F84AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Builder.Behaviors", "InfectedRose.Builder.Behaviors\InfectedRose.Builder.Behaviors.csproj", "{8120B3E6-7A02-406A-95D2-C417CC8AAE68}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Interface", "InfectedRose.Interface\InfectedRose.Interface.csproj", "{DD400B27-6B50-4AF4-9D81-7A244CE1044F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -82,25 +76,13 @@ Global
{66233254-3BF3-4AC8-84C8-83DF6E956753}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66233254-3BF3-4AC8-84C8-83DF6E956753}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66233254-3BF3-4AC8-84C8-83DF6E956753}.Release|Any CPU.Build.0 = Release|Any CPU
{5073EC93-8D3E-4D38-A643-40BF3B697B76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5073EC93-8D3E-4D38-A643-40BF3B697B76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5073EC93-8D3E-4D38-A643-40BF3B697B76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5073EC93-8D3E-4D38-A643-40BF3B697B76}.Release|Any CPU.Build.0 = Release|Any CPU
{281871E0-DB69-4CA3-BB94-00B37DE9772C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{281871E0-DB69-4CA3-BB94-00B37DE9772C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{281871E0-DB69-4CA3-BB94-00B37DE9772C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{281871E0-DB69-4CA3-BB94-00B37DE9772C}.Release|Any CPU.Build.0 = Release|Any CPU
{B6809E62-6FC6-4445-ABB6-302021E7B91A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6809E62-6FC6-4445-ABB6-302021E7B91A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6809E62-6FC6-4445-ABB6-302021E7B91A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6809E62-6FC6-4445-ABB6-302021E7B91A}.Release|Any CPU.Build.0 = Release|Any CPU
{1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CD2D01F-218B-47A2-AE66-A673F48F84AC}.Release|Any CPU.Build.0 = Release|Any CPU
{8120B3E6-7A02-406A-95D2-C417CC8AAE68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8120B3E6-7A02-406A-95D2-C417CC8AAE68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8120B3E6-7A02-406A-95D2-C417CC8AAE68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8120B3E6-7A02-406A-95D2-C417CC8AAE68}.Release|Any CPU.Build.0 = Release|Any CPU
{DD4DCE0F-FEAA-423A-9AF9-794089EBBA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD4DCE0F-FEAA-423A-9AF9-794089EBBA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD4DCE0F-FEAA-423A-9AF9-794089EBBA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD4DCE0F-FEAA-423A-9AF9-794089EBBA35}.Release|Any CPU.Build.0 = Release|Any CPU
{DD400B27-6B50-4AF4-9D81-7A244CE1044F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD400B27-6B50-4AF4-9D81-7A244CE1044F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD400B27-6B50-4AF4-9D81-7A244CE1044F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD400B27-6B50-4AF4-9D81-7A244CE1044F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

247
RakDotNet.IO/BitReader.cs Normal file
View File

@@ -0,0 +1,247 @@
#if ENABLE_MONO
#define UNITY
#elif ENABLE_IL2CPP
#define UNITY
#endif
#define UNITY
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace RakDotNet.IO
{
public class BitReader : IDisposable
{
private readonly Stream _stream;
private readonly bool _leaveOpen;
private readonly bool _orderLocked;
private readonly object _lock;
private Endianness _endianness;
private bool _disposed;
private long _pos;
public virtual Stream BaseStream => _stream;
public virtual Endianness Endianness
{
get => _endianness;
set
{
if (_orderLocked)
throw new InvalidOperationException("Endianness is fixed");
if (value != _endianness)
{
// wait for read operations to complete so we don't mess them up
lock (_lock)
{
_endianness = value;
}
}
}
}
public virtual bool CanChangeEndianness => !_orderLocked;
public virtual long Position => _pos;
public BitReader(Stream stream, Endianness endianness = Endianness.LittleEndian, bool orderLocked = true, bool leaveOpen = false)
{
if (!stream.CanRead)
throw new ArgumentException("Stream is not readable", nameof(stream));
_stream = stream;
_leaveOpen = leaveOpen;
_orderLocked = orderLocked;
_lock = new object();
_endianness = endianness;
_disposed = false;
_pos = 0;
// set the stream position back to 0 if this is a read+write stream
if (_stream.CanWrite)
_stream.Position = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && !_leaveOpen)
_stream.Close();
_disposed = true;
}
}
public void Dispose() => Dispose(true);
public virtual void Close() => Dispose(true);
public virtual bool ReadBit()
{
lock (_lock)
{
var val = _stream.ReadByte();
// if we aren't ending on a new byte, go back 1 byte on the stream
if (((_pos + 1) & 7) != 0)
_stream.Position--;
return (val & (0x80 >> (byte)(_pos++ & 7))) != 0;
}
}
#if !UNITY
public virtual int Read(Span<byte> buf, int bits)
#else
public virtual int Read(byte[] buf, int bits)
#endif
{
// offset in bits, in case we're not starting on the 8th (i = 7) bit
var bitOffset = (byte)(_pos & 7);
// inverted bit offset (eg. 3 becomes 5)
var invertedOffset = (byte)(-bitOffset & 7);
// get num of bytes we have to read from the Stream, we add bitOffset so we have enough data to add in case bitOffset != 0
var byteCount = (int)Math.Ceiling((bits + bitOffset) / 8d);
// get size of output buffer
var bufSize = (int)Math.Ceiling(bits / 8d);
// lock the read so we don't mess up other calls
lock (_lock)
{
// alloc the read buf on stack
#if !UNITY
Span<byte> bytes = stackalloc byte[byteCount];
#else
var bytes = new byte[byteCount];
#endif
// read from the Stream to the buf on the stack
#if !UNITY
_stream.Read(bytes);
#else
for (var i = 0; i < byteCount; i++)
{
bytes[i] = (byte) _stream.ReadByte();
}
#endif
// swap endianness in case we're not using same endianness as host
if ((_endianness != Endianness.LittleEndian && BitConverter.IsLittleEndian) ||
(_endianness != Endianness.BigEndian && !BitConverter.IsLittleEndian))
{
#if !UNITY
bytes.Reverse();
#else
bytes = bytes.Reverse().ToArray();
#endif
}
// check if we don't have to do complex bit level operations
if (bitOffset == 0 && (bits & 7) == 0)
{
// copy read bytes to output buffer
#if !UNITY
bytes.CopyTo(buf);
#else
bytes.CopyTo(buf, 0);
#endif
_pos += bits;
return bufSize;
}
// loop over the bytes we read
for (var i = 0; bits > 0; i++)
{
// add bits starting from bitOffset to output buf
buf[i] |= (byte)(bytes[i] << bitOffset);
// if we're not reading from the start of a byte and we have enough bits left, add the remaining bits to the byte in the output buf
if (bitOffset != 0 && bits > invertedOffset)
buf[i] |= (byte)(bytes[i + 1] >> invertedOffset);
// we read a byte, remove 8 bits from the bit count
bits -= 8;
// add 8 bits minus X unused bits
_pos += bits < 0 ? bits & 7 : 8;
// shift bits we're not using
if (bits < 0)
buf[i] >>= -bits;
}
// roll back the position in case we haven't used the last byte fully
_stream.Position -= (byteCount - bufSize);
}
// return the buffer length
return bufSize;
}
#if !UNITY
public virtual int Read(byte[] buf, int index, int length, int bits)
{
if (bits > (length * 8))
throw new ArgumentOutOfRangeException(nameof(bits), "Bit count exceeds buffer length");
if (index > length)
throw new ArgumentOutOfRangeException(nameof(index), "Index exceeds buffer length");
return Read(new Span<byte>(buf, index, length), bits);
}
public virtual int Read(byte[] buf, int index, int count)
=> Read(new Span<byte>(buf, index, count), count * 8);
#endif
public virtual T Read<T>(int bits) where T : unmanaged
{
var bufSize = (int)Math.Ceiling(bits / 8d);
#if !UNITY
Span<byte> buf = stackalloc byte[bufSize];
#else
var buf = new byte[bufSize];
#endif
Read(buf, bits);
// we "cast" the Span to our struct T
#if !UNITY
return MemoryMarshal.Read<T>(buf);
#else
T result;
var handle = GCHandle.Alloc(buf, GCHandleType.Pinned);
try
{
result = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
return result;
#endif
}
public virtual T Read<T>() where T : unmanaged
=> Read<T>(Marshal.SizeOf<T>() * 8);
public virtual void ReadDeserializable(IDeserializable deserializable)
=> deserializable.Deserialize(this);
}
}

269
RakDotNet.IO/BitWriter.cs Normal file
View File

@@ -0,0 +1,269 @@
#if ENABLE_MONO
#define UNITY
#elif ENABLE_IL2CPP
#define UNITY
#endif
#define UNITY
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace RakDotNet.IO
{
public class BitWriter : IDisposable
{
private readonly Stream _stream;
private readonly bool _leaveOpen;
private readonly bool _orderLocked;
private readonly object _lock;
private Endianness _endianness;
private bool _disposed;
private long _pos;
public virtual Stream BaseStream => _stream;
public virtual Endianness Endianness
{
get => _endianness;
set
{
if (_orderLocked)
throw new InvalidOperationException("Endianness is fixed");
if (value != _endianness)
{
// wait for write operations to complete so we don't mess them up
lock (_lock)
{
_endianness = value;
}
}
}
}
public virtual bool CanChangeEndianness => !_orderLocked;
public virtual long Position => _pos;
public BitWriter(Stream stream, Endianness endianness = Endianness.LittleEndian, bool orderLocked = true, bool leaveOpen = false)
{
if (!stream.CanWrite)
throw new ArgumentException("Stream is not writeable", nameof(stream));
if (!stream.CanRead)
throw new ArgumentException("Stream is not readable", nameof(stream));
_stream = stream;
_leaveOpen = leaveOpen;
_orderLocked = orderLocked;
_lock = new object();
_endianness = endianness;
_disposed = false;
_pos = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (_leaveOpen)
_stream.Flush();
else
_stream.Close();
_disposed = true;
}
}
public void Dispose() => Dispose(true);
public virtual void Close() => Dispose(true);
public virtual void WriteBit(bool bit)
{
lock (_lock)
{
// offset in bits, in case we're not starting on the 8th (i = 7) bit
var bitOffset = (byte)(_pos & 7);
// read the last byte from the stream
var val = _stream.ReadByte();
// don't go back if we haven't actually read anything
if (val != -1)
_stream.Position--;
else // ReadByte returns -1 if we reached the end of the stream, we need unsigned data so set it to 0
val = 0;
if (bit)
{
// if we're setting, shift 0x80 (10000000) to the right by bitOffset
var mask = (byte)(0x80 >> bitOffset);
// we set the bit using our mask and bitwise OR
val |= mask;
}
else
{
// hacky mask
var mask = (byte)(1 << (-(bitOffset + 1) & 7));
// unset using bitwise AND and bitwise NOT on the mask
val &= ~mask;
}
// write the modified byte to the stream
_stream.WriteByte((byte)val);
// advance the bit position
_pos++;
// if we aren't ending on a new byte, go back 1 byte on the stream
if ((_pos & 7) != 0)
_stream.Position--;
}
}
#if !UNITY
public virtual int Write(ReadOnlySpan<byte> buf, int bits)
#else
public virtual int Write(byte[] buf, int bits)
#endif
{
// offset in bits, in case we're not starting on the 8th (i = 7) bit
var bitOffset = (byte)(_pos & 7);
// inverted bit offset (eg. 3 becomes 5)
var invertedOffset = (byte)(-bitOffset & 7);
// get num of bytes we have to read from the Stream, we add bitOffset so we have enough data to add in case bitOffset != 0
var byteCount = (int)Math.Ceiling((bits + bitOffset) / 8d);
// get size of output buffer
var bufSize = (int)Math.Ceiling(bits / 8d);
// lock the read so we don't mess up other calls
lock (_lock)
{
// check if we don't have to do complex bit level operations
if (bitOffset == 0 && (bits & 7) == 0)
{
foreach (var b in buf)
{
_stream.WriteByte(b);
}
_pos += bits;
return bufSize;
}
// allocate a buffer on the stack to write
#if !UNITY
Span<byte> bytes = stackalloc byte[byteCount];
#else
var bytes = new byte[byteCount];
#endif
// we might already have data in the stream
#if !UNITY
var readSize = _stream.Read(bytes);
#else
var readSize = _stream.Read(bytes, 0, bytes.Length);
#endif
// subtract the read bytes from the position so we can write them later
_stream.Position -= readSize;
for (var i = 0; bits > 0; i++)
{
// add bits starting from bitOffset from the input buffer to the write buffer
bytes[i] |= (byte)(buf[i] >> bitOffset);
// set the leaking bits on the next byte
if (invertedOffset < 8 && bits > invertedOffset)
bytes[i + 1] = (byte)(buf[i] << invertedOffset);
// add max 8 remaining bits to the position
_pos += bits < 8 ? bits & 7 : 8;
// we wrote a byte, remove 8 bits from the bit count
bits -= 8;
// if we're at the last byte, cut off the unused bits
if (bits < 8)
bytes[i] <<= (-bits & 7);
}
// swap endianness in case we're not using same endianness as host
if ((_endianness != Endianness.LittleEndian && BitConverter.IsLittleEndian) ||
(_endianness != Endianness.BigEndian && !BitConverter.IsLittleEndian))
{
#if !UNITY
bytes.Reverse();
#else
bytes = bytes.Reverse().ToArray();
#endif
}
// write the buffer
#if !UNITY
_stream.Write(bytes);
#else
foreach (var b in bytes)
{
_stream.WriteByte(b);
}
#endif
// roll back the position in case we haven't used the last byte fully
_stream.Position -= (byteCount - bufSize);
}
return bufSize;
}
#if !UNITY
public virtual int Write(Span<byte> buf, int bits)
=> Write((ReadOnlySpan<byte>)buf, bits);
public virtual int Write(byte[] buf, int index, int length, int bits)
{
if (bits > (length * 8))
throw new ArgumentOutOfRangeException(nameof(bits), "Bit count exceeds buffer length");
if (index > length)
throw new ArgumentOutOfRangeException(nameof(index), "Index exceeds buffer length");
return Write(new ReadOnlySpan<byte>(buf, index, length), bits);
}
#endif
public virtual int Write<T>(T val, int bits) where T : struct
{
var size = Marshal.SizeOf<T>();
var buf = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr<T>(val, ptr, false);
Marshal.Copy(ptr, buf, 0, size);
Marshal.FreeHGlobal(ptr);
#if !UNITY
return Write(new ReadOnlySpan<byte>(buf), bits);
#else
return Write(buf, bits);
#endif
}
public virtual int Write<T>(T val) where T : struct
=> Write<T>(val, Marshal.SizeOf<T>() * 8);
public virtual void Write(ISerializable serializable)
=> serializable.Serialize(this);
}
}

View File

@@ -0,0 +1,47 @@
using System.IO;
using RakDotNet.IO;
namespace RakDotNet.IO
{
public class ByteReader : BitReader
{
public ByteReader(Stream stream, Endianness endianness = Endianness.LittleEndian, bool orderLocked = true, bool leaveOpen = false) : base(stream, endianness, orderLocked, leaveOpen)
{
}
public override int Read(byte[] buf, int bits)
{
var bytes = bits / 8;
for (var i = 0; i < bytes; i++)
{
buf[i] = (byte) BaseStream.ReadByte();
}
return bits;
}
public override T Read<T>(int bits)
{
unsafe
{
var buffer = stackalloc byte[sizeof(T)];
for (var i = 0; i < sizeof(T); ++i)
{
buffer[i] = (byte) BaseStream.ReadByte();
}
return *(T*) buffer;
}
}
public override T Read<T>()
{
unsafe
{
return Read<T>(sizeof(T) * 8);
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace RakDotNet.IO
{
public enum Endianness
{
BigEndian,
LittleEndian
}
}

View File

@@ -0,0 +1,7 @@
namespace RakDotNet.IO
{
public interface IDeserializable
{
void Deserialize(BitReader reader);
}
}

View File

@@ -0,0 +1,7 @@
namespace RakDotNet.IO
{
public interface ISerializable
{
void Serialize(BitWriter writer);
}
}

165
RakDotNet.IO/LICENSE Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp6.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>9</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>