feat(a1): .NET 8 solution scaffold + Model + OpcodeTable loader

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-07-06 13:49:02 -04:00
parent a50b7f600a
commit a704765ea0
14 changed files with 196 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.9" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,2 @@
namespace Age.Engine.Model;
public sealed record Instruction(int Offset, int Opcode, IReadOnlyList<Operand> Args);

View File

@@ -0,0 +1,14 @@
namespace Age.Engine.Model;
public sealed class OpcodeTable
{
private readonly IReadOnlyDictionary<int, (string Label, int Argc)> _t;
public OpcodeTable(IReadOnlyDictionary<int, (string, int)> t) => _t = t;
public int Count => _t.Count;
public bool TryGet(int op, out string label, out int argc)
{
if (_t.TryGetValue(op, out var e)) { label = e.Label; argc = e.Argc; return true; }
label = ""; argc = -1; return false;
}
public string Label(int op) => _t.TryGetValue(op, out var e) ? e.Label : "";
public int Argc(int op) => _t.TryGetValue(op, out var e) ? e.Argc : -1;
}

View File

@@ -0,0 +1,2 @@
namespace Age.Engine.Model;
public readonly record struct Operand(int Type, long Value);

View File

@@ -0,0 +1,9 @@
namespace Age.Engine.Model;
public sealed class Script
{
public required ScriptHeader Header { get; init; }
public required IReadOnlyList<Instruction> Instructions { get; init; }
public required IReadOnlyDictionary<int, int> IndexByOffset { get; init; }
public required IReadOnlyDictionary<int, string> Strings { get; init; }
public string GetString(int offset) => Strings.TryGetValue(offset, out var s) ? s : "";
}

View File

@@ -0,0 +1,4 @@
namespace Age.Engine.Model;
public sealed record ScriptHeader(
int LocalInt1, int LocalFloats, int LocalStrings1,
int LocalInt2, int Unknown, int LocalStrings2);

View File

@@ -0,0 +1,19 @@
using System.Text.Json;
using Age.Engine.Model;
namespace Age.Engine.Sys4;
public static class OpcodeTableJson
{
public static OpcodeTable Load(string path)
{
using var doc = JsonDocument.Parse(File.ReadAllText(path));
var dict = new Dictionary<int, (string, int)>();
foreach (var e in doc.RootElement.GetProperty("opcodes").EnumerateArray())
{
int op = Convert.ToInt32(e.GetProperty("op").GetString(), 16);
string label = e.GetProperty("label").GetString() ?? "";
int argc = e.GetProperty("argc").GetInt32();
dict[op] = (label, argc);
}
return new OpcodeTable(dict);
}
}

View File

@@ -0,0 +1,33 @@
namespace Age.Engine.Sys4;
public static class Paths
{
public static string Repo { get; } = FindRepo();
public static string Workspace => Directory.GetParent(Repo)!.FullName;
public static string Extracted => Path.Combine(Workspace, "extracted");
public static string Data1 => Path.Combine(Extracted, "DATA1");
public static string GameDir => Path.Combine(Workspace, "姫狩りダンジョンマイスター");
public static string Build => Path.Combine(Repo, "build");
public static string OpcodesJson => Path.Combine(Build, "opcodes.json");
private static string FindRepo()
{
var d = new DirectoryInfo(AppContext.BaseDirectory);
while (d != null && d.Name != "age-reimpl") d = d.Parent;
if (d == null) throw new DirectoryNotFoundException(
"age-reimpl root not found above " + AppContext.BaseDirectory);
return d.FullName;
}
/// name(UPPER).BIN -> path; game-dir loose overrides shadow extracted/DATA1 (mirrors paths.scripts()).
public static Dictionary<string, string> Scripts()
{
var d = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (Directory.Exists(Data1))
foreach (var p in Directory.EnumerateFiles(Data1, "*.BIN"))
d[Path.GetFileName(p).ToUpperInvariant()] = p;
if (Directory.Exists(GameDir))
foreach (var p in Directory.EnumerateFiles(GameDir, "*.BIN"))
d[Path.GetFileName(p).ToUpperInvariant()] = p;
return d;
}
}