mirror of
https://github.com/LNH-team/pico-loader.git
synced 2026-06-02 17:26:48 +02:00
Initial work on patch list
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using CsvHelper.Configuration;
|
||||
using CsvHelper;
|
||||
using System.Globalization;
|
||||
using PicoLoaderConverter.Common;
|
||||
|
||||
namespace PicoLoaderConverter.ApList;
|
||||
|
||||
@@ -112,7 +113,7 @@ sealed class ApListFactory
|
||||
private ApListEntry ConvertFromCvsApListEntry(CsvApListEntry csvApListEntry)
|
||||
{
|
||||
return new ApListEntry(
|
||||
GameCodeToUint(csvApListEntry.GameCode),
|
||||
GameCodeHelper.GameCodeToUint(csvApListEntry.GameCode),
|
||||
(byte)csvApListEntry.GameVersion,
|
||||
ParseDSProtectVersion(csvApListEntry.DSProtectVersion),
|
||||
(byte)csvApListEntry.DSProtectFunctionMask,
|
||||
@@ -138,16 +139,6 @@ sealed class ApListFactory
|
||||
};
|
||||
}
|
||||
|
||||
private uint GameCodeToUint(string gameCode)
|
||||
{
|
||||
if (gameCode.Length != 4)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Game code '{gameCode}' is not valid. It must consist of exactly 4 characters.", nameof(gameCode));
|
||||
}
|
||||
return (uint)gameCode[0] | ((uint)gameCode[1] << 8) | ((uint)gameCode[2] << 16) | ((uint)gameCode[3] << 24);
|
||||
}
|
||||
|
||||
private DSProtectVersion ParseDSProtectVersion(string dsProtectVersion)
|
||||
{
|
||||
return dsProtectVersion switch
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace PicoLoaderConverter.Common;
|
||||
|
||||
static class GameCodeHelper
|
||||
{
|
||||
public static uint GameCodeToUint(string gameCode)
|
||||
{
|
||||
if (gameCode.Length != 4)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Game code '{gameCode}' is not valid. It must consist of exactly 4 characters.", nameof(gameCode));
|
||||
}
|
||||
return gameCode[0] | (uint)gameCode[1] << 8 | (uint)gameCode[2] << 16 | (uint)gameCode[3] << 24;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PicoLoaderConverter.Json;
|
||||
|
||||
sealed class JsonHexBytesConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(byte[]);
|
||||
}
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
string? text = reader.Value as string;
|
||||
var bytesString = text?.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? [];
|
||||
return bytesString.Select(byteString =>
|
||||
{
|
||||
if (byteString.StartsWith("0x"))
|
||||
{
|
||||
byteString = byteString[2..];
|
||||
}
|
||||
return Convert.ToByte(byteString, 16);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PicoLoaderConverter.Json;
|
||||
|
||||
sealed class JsonHexNumberConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(uint);
|
||||
}
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
string? text = reader.Value as string;
|
||||
if (text?.StartsWith("0x") is true)
|
||||
{
|
||||
text = text[2..];
|
||||
}
|
||||
return Convert.ToUInt32(text, 16);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace PicoLoaderConverter.PatchList;
|
||||
|
||||
sealed class PatchList
|
||||
{
|
||||
public IReadOnlyList<PatchListEntry> Entries { get; }
|
||||
|
||||
public PatchList(IEnumerable<PatchListEntry> entries)
|
||||
{
|
||||
Entries = entries.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PicoLoaderConverter.PatchList;
|
||||
|
||||
sealed class PatchListEntry
|
||||
{
|
||||
public string GameCode { get; init; } = string.Empty;
|
||||
public byte GameVersion { get; init; }
|
||||
public string GameName { get; init; } = string.Empty;
|
||||
public PatchListEntryPatch[] Patches { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Newtonsoft.Json;
|
||||
using PicoLoaderConverter.Json;
|
||||
|
||||
namespace PicoLoaderConverter.PatchList;
|
||||
|
||||
sealed class PatchListEntryPatch
|
||||
{
|
||||
public PatchType Type { get; init; }
|
||||
public string Description { get; init; } = string.Empty;
|
||||
|
||||
// replace
|
||||
[JsonConverter(typeof(JsonHexNumberConverter))]
|
||||
public uint Address { get; init; }
|
||||
|
||||
[JsonConverter(typeof(JsonHexBytesConverter))]
|
||||
public byte[] Data { get; init; } = [];
|
||||
|
||||
// metafortress
|
||||
[JsonProperty(ItemConverterType = typeof(JsonHexNumberConverter))]
|
||||
public uint[] Addresses { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Newtonsoft.Json;
|
||||
using PicoLoaderConverter.Common;
|
||||
|
||||
namespace PicoLoaderConverter.PatchList;
|
||||
|
||||
sealed class PatchListFactory
|
||||
{
|
||||
public PatchList FromJson(string json)
|
||||
{
|
||||
var result = JsonConvert.DeserializeObject<PatchListEntry[]>(json) ?? [];
|
||||
return new PatchList(result);
|
||||
}
|
||||
|
||||
public byte[] ToBinary(PatchList patchList)
|
||||
{
|
||||
var memoryStream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(memoryStream);
|
||||
var sortedEntries = patchList.Entries
|
||||
.OrderBy(entry => GameCodeHelper.GameCodeToUint(entry.GameCode))
|
||||
.ThenBy(entry => entry.GameVersion)
|
||||
.ToArray();
|
||||
writer.Write(sortedEntries.Length);
|
||||
foreach (var entry in sortedEntries)
|
||||
{
|
||||
writer.Write(GameCodeHelper.GameCodeToUint(entry.GameCode));
|
||||
writer.Write(0);
|
||||
}
|
||||
for (int i = 0; i < sortedEntries.Length; i++)
|
||||
{
|
||||
var entry = sortedEntries[i];
|
||||
long entryStart = writer.BaseStream.Position;
|
||||
writer.BaseStream.Position = 4 + i * 8 + 4;
|
||||
writer.Write((uint)((entryStart << 8) | entry.GameVersion));
|
||||
writer.BaseStream.Position = entryStart;
|
||||
writer.Write((ushort)0);
|
||||
writer.Write((ushort)entry.Patches.Length);
|
||||
foreach (var patch in entry.Patches)
|
||||
{
|
||||
WritePatch(writer, patch);
|
||||
}
|
||||
long entryEnd = writer.BaseStream.Position;
|
||||
writer.BaseStream.Position = entryStart;
|
||||
writer.Write((ushort)(entryEnd - entryStart));
|
||||
writer.BaseStream.Position = entryEnd;
|
||||
|
||||
}
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
private void WritePatch(BinaryWriter writer, PatchListEntryPatch patch)
|
||||
{
|
||||
writer.Write((byte)patch.Type);
|
||||
writer.Write((byte)0);
|
||||
switch (patch.Type)
|
||||
{
|
||||
case PatchType.Replace:
|
||||
{
|
||||
writer.Write((ushort)patch.Data.Length);
|
||||
writer.Write(patch.Address);
|
||||
writer.Write(patch.Data);
|
||||
while ((writer.BaseStream.Position % 4) != 0)
|
||||
{
|
||||
writer.Write((byte)0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PatchType.Metafortress:
|
||||
{
|
||||
writer.Write((ushort)patch.Addresses.Length);
|
||||
foreach (uint address in patch.Addresses)
|
||||
{
|
||||
writer.Write(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace PicoLoaderConverter.PatchList;
|
||||
|
||||
enum PatchType
|
||||
{
|
||||
Replace,
|
||||
Metafortress
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -12,7 +12,8 @@ static class Program
|
||||
/// </summary>
|
||||
private static readonly Type[] sVerbs = [
|
||||
typeof(ApListConverterVerb),
|
||||
typeof(SaveListConverterVerb)
|
||||
typeof(SaveListConverterVerb),
|
||||
typeof(PatchListConverterVerb)
|
||||
];
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using CsvHelper;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PicoLoaderConverter.Common;
|
||||
|
||||
namespace PicoLoaderConverter.SaveList;
|
||||
|
||||
@@ -97,7 +98,7 @@ sealed class SaveListFactory
|
||||
}
|
||||
|
||||
return new SaveListEntry(
|
||||
GameCodeToUint(csvSaveListEntry.GameCode),
|
||||
GameCodeHelper.GameCodeToUint(csvSaveListEntry.GameCode),
|
||||
ParseSaveType(csvSaveListEntry.SaveType),
|
||||
(byte)(csvSaveListEntry.SaveSize == 0 ? 0 : BitOperations.Log2((uint)csvSaveListEntry.SaveSize)));
|
||||
}
|
||||
@@ -113,16 +114,6 @@ sealed class SaveListFactory
|
||||
};
|
||||
}
|
||||
|
||||
private uint GameCodeToUint(string gameCode)
|
||||
{
|
||||
if (gameCode.Length != 4)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Game code '{gameCode}' is not valid. It must consist of exactly 4 characters.", nameof(gameCode));
|
||||
}
|
||||
return gameCode[0] | (uint)gameCode[1] << 8 | (uint)gameCode[2] << 16 | (uint)gameCode[3] << 24;
|
||||
}
|
||||
|
||||
private CardSaveType ParseSaveType(string saveType)
|
||||
{
|
||||
return saveType.ToLowerInvariant() switch
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using CommandLine;
|
||||
using PicoLoaderConverter.PatchList;
|
||||
|
||||
namespace PicoLoaderConverter.Verbs;
|
||||
|
||||
[Verb("patchlist", HelpText = "Convert patch list from json to bin.")]
|
||||
sealed class PatchListConverterVerb : IConverterVerb
|
||||
{
|
||||
[Option('i', Required = true, HelpText = "Input .json file.")]
|
||||
public required string InputFile { get; init; }
|
||||
|
||||
[Option('o', Required = true, HelpText = "Output .bin file.")]
|
||||
public required string OutputFile { get; init; }
|
||||
|
||||
public void Run()
|
||||
{
|
||||
// Convert patch list from json to bin
|
||||
var factory = new PatchListFactory();
|
||||
var patchList = factory.FromJson(File.ReadAllText(InputFile));
|
||||
var binaryList = factory.ToBinary(patchList);
|
||||
File.WriteAllBytes(OutputFile, binaryList);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user