mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
df53ff59d9
* [Json] Implement sce::Json::Value and Json::String construct/set/destroy libSceJson previously only had the Initializer/MemAllocator setup path. The Value and String classes themselves were entirely absent, so a Prospero title that builds a JSON tree (Quake PPSA01880 does, to shape a web-API request) hit unresolved imports and faulted on the call. The imports it left unresolved right before its access violation are exactly these Value ctors/setters and String ctor/dtor. Model the Value/String payload host-side (JsonObjectHeap), keyed by the guest `this` pointer, following the handle-shadow pattern already used by Ngs2Exports. The guest object bytes are deliberately not written: these objects are usually stack-allocated with an unknown real layout, and writing a guessed layout risks smashing an adjacent stack canary (the same hazard the AudioOut2 context-param note in this tree records). Constructors and setters follow the Itanium ABI and return `this` in rax, which is correct whether the real setter returns void or Value&. Covered NIDs (complete-object C1/D1 variants, matching the observed imports): Value(default/bool/long/ulong/double/ValueType/char*/String), Value::~Value, Value::set(bool/long/ulong/double/ValueType/char*/String), Value::clear, String(char*/default/copy), String::~String. Only the payload the guest can reach through library methods is modelled; direct guest reads of the object bytes are out of scope and would need observed layout evidence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [Tests] Add SharpEmu.Libs.Tests covering the Json Value/String exports First test project for SharpEmu.Libs (xunit), the SharpEmu.Libs.Tests layout the maintainer already agreed to in issue #36. - A FakeCpuMemory (single contiguous region) drives the exports at the CpuContext level with no live guest. - Direct-call tests: ctor/setter round-trips for bool/int/uint/double (read from xmm0)/char*/String/ValueType, destructor cleanup, and the graceful-degradation paths (missing String shadow and a faulting char* pointer both fall back to the empty string instead of throwing). - Registration test: a real ModuleManager scans SharpEmu.Libs and the nine NIDs Quake left unresolved now resolve to the libSceJson exports and dispatch cleanly (returns `this` in rax). InternalsVisibleTo exposes JsonObjectHeap to the test assembly. The test project's packages.lock.json is committed for CI locked-mode restore; CI does not run tests yet, left as a maintainer decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [Json] Add Initializer::setGlobalNullAccessCallback Quake calls it during kexPSNWebAPI::Initialize and treats the not-found error as fatal for the whole Np Web API bring-up. Store the guest hook (never invoked by this HLE: shadows degrade to defaults instead of dereferencing missing members) and return success. Verified against the dump: the "setGlobalNullAccessCallback failed (0x80020002)" line is gone and kexPSNWebAPI::Initialize now logs "Np Web API Initialized"; the next blockers are sceNpAuthCreateRequest and sceUserServiceInitialize ordering, outside libSceJson. Also pins both Json test classes to one xunit collection: they share JsonObjectHeap statics and parallel class execution raced ResetForTests against a running test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
168 lines
6.3 KiB
C#
168 lines
6.3 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System;
|
|
using SharpEmu.HLE;
|
|
|
|
namespace SharpEmu.Libs.Json;
|
|
|
|
public static class JsonExports
|
|
{
|
|
[SysAbiExport(
|
|
Nid = "-hJRce8wn1U",
|
|
ExportName = "_ZN3sce4Json12MemAllocatorC2Ev",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson")]
|
|
public static int MemAllocatorConstructor(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
TraceJson("MemAllocator.ctor", thisAddress, 0);
|
|
ctx[CpuRegister.Rax] = thisAddress;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "OcAgPxcq5Vk",
|
|
ExportName = "_ZN3sce4Json12MemAllocatorD2Ev",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson")]
|
|
public static int MemAllocatorDestructor(CpuContext ctx)
|
|
{
|
|
TraceJson("MemAllocator.dtor", ctx[CpuRegister.Rdi], 0);
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "cK6bYHf-Q5E",
|
|
ExportName = "_ZN3sce4Json11InitializerC1Ev",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson")]
|
|
public static int InitializerConstructor(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
TraceJson("Initializer.ctor", thisAddress, 0);
|
|
ctx[CpuRegister.Rax] = thisAddress;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "RujUxbr3haM",
|
|
ExportName = "_ZN3sce4Json11InitializerD1Ev",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson")]
|
|
public static int InitializerDestructor(CpuContext ctx)
|
|
{
|
|
TraceJson("Initializer.dtor", ctx[CpuRegister.Rdi], 0);
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "Cxwy7wHq4J0",
|
|
ExportName = "_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson")]
|
|
public static int InitializerInitialize(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
var initParameterAddress = ctx[CpuRegister.Rsi];
|
|
if (thisAddress == 0)
|
|
{
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
TraceJson("Initializer.initialize", thisAddress, initParameterAddress);
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
// sce::Json::Initializer::setGlobalNullAccessCallback(const Value& (*)(ValueType, const Value*, void*), void*)
|
|
// Registers the guest hook invoked when a Value is accessed as the wrong type. Quake calls it
|
|
// during kexPSNWebAPI::Initialize and treats a non-zero return as a fatal init failure.
|
|
[SysAbiExport(
|
|
Nid = "+drDFyAS6u4",
|
|
ExportName = "_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson")]
|
|
public static int InitializerSetGlobalNullAccessCallback(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
if (thisAddress == 0)
|
|
{
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
JsonObjectHeap.GlobalNullAccessCallback = ctx[CpuRegister.Rsi];
|
|
JsonObjectHeap.GlobalNullAccessCallbackContext = ctx[CpuRegister.Rdx];
|
|
TraceJson("Initializer.setGlobalNullAccessCallback", thisAddress, ctx[CpuRegister.Rsi]);
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "WSOuge5IsCg",
|
|
ExportName = "_ZN3sce4Json14InitParameter2C1Ev",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson2")]
|
|
public static int InitParameter2Constructor(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
TraceJson("InitParameter2.ctor", thisAddress, ctx[CpuRegister.Rsi]);
|
|
ctx[CpuRegister.Rax] = thisAddress;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "I2QC8PYhJWY",
|
|
ExportName = "_ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson2")]
|
|
public static int InitParameter2SetAllocator(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
TraceJson("InitParameter2.setAllocator", thisAddress, ctx[CpuRegister.Rsi]);
|
|
ctx[CpuRegister.Rax] = thisAddress;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "Eu95jmqn5Rw",
|
|
ExportName = "_ZN3sce4Json14InitParameter217setFileBufferSizeEm",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson2")]
|
|
public static int InitParameter2SetFileBufferSize(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
TraceJson("InitParameter2.setFileBufferSize", thisAddress, ctx[CpuRegister.Rsi]);
|
|
ctx[CpuRegister.Rax] = thisAddress;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "IXW-z8pggfg",
|
|
ExportName = "_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceJson2")]
|
|
public static int Initializer2Constructor(CpuContext ctx)
|
|
{
|
|
var thisAddress = ctx[CpuRegister.Rdi];
|
|
TraceJson("Initializer2.ctor", thisAddress, ctx[CpuRegister.Rsi]);
|
|
ctx[CpuRegister.Rax] = thisAddress;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
private static void TraceJson(string operation, ulong thisAddress, ulong argument)
|
|
{
|
|
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_JSON"), "1", StringComparison.Ordinal))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][TRACE] json.{operation} this=0x{thisAddress:X16} arg=0x{argument:X16}");
|
|
}
|
|
}
|