mirror of
https://github.com/Wincent01/InfectedRose.git
synced 2025-12-16 17:49:50 -06:00
Initial work on a modding interface.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace InfectedRose.Database.Fdb
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "FdbString";
|
||||
return Value;
|
||||
}
|
||||
|
||||
public void Serialize(BitWriter writer)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
109
InfectedRose.Interface/ComponentId.cs
Normal file
109
InfectedRose.Interface/ComponentId.cs
Normal 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
|
||||
}
|
||||
}
|
||||
27
InfectedRose.Interface/InfectedRose.Interface.csproj
Normal file
27
InfectedRose.Interface/InfectedRose.Interface.csproj
Normal 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>
|
||||
11
InfectedRose.Interface/Lookup.cs
Normal file
11
InfectedRose.Interface/Lookup.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
13
InfectedRose.Interface/Manifest.cs
Normal file
13
InfectedRose.Interface/Manifest.cs
Normal 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"};
|
||||
}
|
||||
}
|
||||
38
InfectedRose.Interface/Mod.cs
Normal file
38
InfectedRose.Interface/Mod.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
InfectedRose.Interface/ModContext.cs
Normal file
142
InfectedRose.Interface/ModContext.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
InfectedRose.Interface/ModType.cs
Normal file
7
InfectedRose.Interface/ModType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace InfectedRose.Interface
|
||||
{
|
||||
public abstract class ModType
|
||||
{
|
||||
public abstract void Apply(Mod mod);
|
||||
}
|
||||
}
|
||||
15
InfectedRose.Interface/ModTypeAttribute.cs
Normal file
15
InfectedRose.Interface/ModTypeAttribute.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
InfectedRose.Interface/Mods.cs
Normal file
29
InfectedRose.Interface/Mods.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
33
InfectedRose.Interface/ObjectMod.cs
Normal file
33
InfectedRose.Interface/ObjectMod.cs
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
432
InfectedRose.Interface/Program.cs
Normal file
432
InfectedRose.Interface/Program.cs
Normal 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
InfectedRose.Interface/SqlMod.cs
Normal file
16
InfectedRose.Interface/SqlMod.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Color = System.Drawing.Color;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp6.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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
247
RakDotNet.IO/BitReader.cs
Normal 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
269
RakDotNet.IO/BitWriter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
47
RakDotNet.IO/ByteReader.cs
Normal file
47
RakDotNet.IO/ByteReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
RakDotNet.IO/Endianness.cs
Normal file
8
RakDotNet.IO/Endianness.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace RakDotNet.IO
|
||||
{
|
||||
public enum Endianness
|
||||
{
|
||||
BigEndian,
|
||||
LittleEndian
|
||||
}
|
||||
}
|
||||
7
RakDotNet.IO/IDeserializable.cs
Normal file
7
RakDotNet.IO/IDeserializable.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RakDotNet.IO
|
||||
{
|
||||
public interface IDeserializable
|
||||
{
|
||||
void Deserialize(BitReader reader);
|
||||
}
|
||||
}
|
||||
7
RakDotNet.IO/ISerializable.cs
Normal file
7
RakDotNet.IO/ISerializable.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RakDotNet.IO
|
||||
{
|
||||
public interface ISerializable
|
||||
{
|
||||
void Serialize(BitWriter writer);
|
||||
}
|
||||
}
|
||||
165
RakDotNet.IO/LICENSE
Normal file
165
RakDotNet.IO/LICENSE
Normal 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.
|
||||
17
RakDotNet.IO/RakDotNet.IO.csproj
Normal file
17
RakDotNet.IO/RakDotNet.IO.csproj
Normal 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>
|
||||
Reference in New Issue
Block a user