[Json] Implement sce::Json::Value and String (construct / set / destroy) (#169)

* [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>
This commit is contained in:
José Luis Caravaca Carretero
2026-07-15 00:49:33 +02:00
committed by GitHub
parent 4c35831cb8
commit df53ff59d9
11 changed files with 948 additions and 0 deletions
@@ -0,0 +1,68 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
namespace SharpEmu.Libs.Tests;
// A single contiguous guest region backed by a byte[]. Enough to hand C strings and small
// structures to HLE exports under test without a live guest.
internal sealed class FakeCpuMemory : ICpuMemory
{
private readonly ulong _base;
private readonly byte[] _storage;
public FakeCpuMemory(ulong baseAddress, int size)
{
_base = baseAddress;
_storage = new byte[size];
}
public bool TryRead(ulong virtualAddress, Span<byte> destination)
{
if (!TryResolve(virtualAddress, destination.Length, out var offset))
{
return false;
}
_storage.AsSpan(offset, destination.Length).CopyTo(destination);
return true;
}
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
{
if (!TryResolve(virtualAddress, source.Length, out var offset))
{
return false;
}
source.CopyTo(_storage.AsSpan(offset, source.Length));
return true;
}
public ulong WriteCString(ulong virtualAddress, string text)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
TryWrite(virtualAddress, bytes);
TryWrite(virtualAddress + (ulong)bytes.Length, stackalloc byte[] { 0 });
return virtualAddress;
}
private bool TryResolve(ulong virtualAddress, int length, out int offset)
{
offset = 0;
if (virtualAddress < _base)
{
return false;
}
var relative = virtualAddress - _base;
if (relative + (ulong)length > (ulong)_storage.Length)
{
return false;
}
offset = (int)relative;
return true;
}
}
@@ -0,0 +1,80 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Json;
using Xunit;
namespace SharpEmu.Libs.Tests.Json;
// These NIDs came back "unresolved" in the Quake (PPSA01880) import log right before its
// access violation. This asserts they now resolve to the Json handlers and dispatch cleanly,
// which is the plumbing the direct-call tests cannot cover.
[Collection("JsonObjectHeap")]
public sealed class JsonExportRegistrationTests
{
private static readonly (string Nid, string Name)[] ExpectedExports =
{
("qBMjqyBn3OM", "_ZN3sce4Json5ValueC1Ev"),
("5yHuiWXo2gg", "_ZN3sce4Json5Value3setEb"),
("QxVVYhP-mvg", "_ZN3sce4Json5Value3setEl"),
("SIe1ZmW7e7s", "_ZN3sce4Json5Value3setEm"),
("BSmWDIkV4w4", "_ZN3sce4Json5Value3setEd"),
("IKQimvG9Wqs", "_ZN3sce4Json5Value3setENS0_9ValueTypeE"),
("6l3Bv2gysNc", "_ZN3sce4Json5Value3setERKNS0_6StringE"),
("9KUZFjI1IxA", "_ZN3sce4Json6StringC1EPKc"),
("cG1VE2HMl6c", "_ZN3sce4Json6StringD1Ev"),
("+drDFyAS6u4", "_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_"),
};
private static ModuleManager CreateRegisteredManager()
{
var manager = new ModuleManager();
manager.RegisterFromAssembly(typeof(JsonValueExports).Assembly, Generation.Gen5);
return manager;
}
[Fact]
public void QuakeUnresolvedJsonNids_ResolveToJsonExports()
{
var manager = CreateRegisteredManager();
foreach (var (nid, name) in ExpectedExports)
{
Assert.True(manager.TryGetExport(nid, out var export), $"NID {nid} did not register.");
Assert.Equal(name, export.Name);
Assert.Equal("libSceJson", export.LibraryName);
}
}
[Fact]
public void SetGlobalNullAccessCallback_StoresHookAndReturnsOk()
{
JsonObjectHeap.ResetForTests();
var manager = CreateRegisteredManager();
var ctx = new CpuContext(new FakeCpuMemory(0x1_0000_0000, 0x1000), Generation.Gen5);
ctx[CpuRegister.Rdi] = 0x1_0000_0000; // Initializer instance
ctx[CpuRegister.Rsi] = 0x8_0012_3456; // guest callback
ctx[CpuRegister.Rdx] = 0x1_0000_0800; // user context
Assert.True(manager.TryDispatch("+drDFyAS6u4", ctx, out var result));
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, result);
Assert.Equal(0UL, ctx[CpuRegister.Rax]);
Assert.Equal(0x8_0012_3456UL, JsonObjectHeap.GlobalNullAccessCallback);
Assert.Equal(0x1_0000_0800UL, JsonObjectHeap.GlobalNullAccessCallbackContext);
}
[Fact]
public void DispatchValueConstructor_RunsHandlerAndReturnsThis()
{
JsonObjectHeap.ResetForTests();
var manager = CreateRegisteredManager();
var ctx = new CpuContext(new FakeCpuMemory(0x1_0000_0000, 0x1000), Generation.Gen5);
ctx[CpuRegister.Rdi] = 0x1_0000_0000;
Assert.True(manager.TryDispatch("qBMjqyBn3OM", ctx, out var result));
Assert.Equal(OrbisGen2Result.ORBIS_GEN2_OK, result);
Assert.Equal(0x1_0000_0000UL, ctx[CpuRegister.Rax]);
Assert.Equal(JsonValueKind.Null, JsonObjectHeap.Values[0x1_0000_0000].Kind);
}
}
@@ -0,0 +1,188 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Json;
using Xunit;
namespace SharpEmu.Libs.Tests.Json;
// JsonObjectHeap is shared static state; both Json test classes join one collection so xUnit
// does not run them in parallel against it.
[Collection("JsonObjectHeap")]
public sealed class JsonValueExportsTests
{
private const ulong ThisAddress = 0x1_0000_0000;
private const ulong StringAddress = 0x1_0000_1000;
private const ulong TextAddress = 0x1_0000_2000;
private readonly FakeCpuMemory _memory = new(0x1_0000_0000, 0x10000);
private readonly CpuContext _ctx;
public JsonValueExportsTests()
{
JsonObjectHeap.ResetForTests();
_ctx = new CpuContext(_memory, Generation.Gen5);
}
[Fact]
public void ValueDefaultConstructor_RegistersNullAndReturnsThis()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
JsonValueExports.ValueDefaultConstructor(_ctx);
Assert.Equal(ThisAddress, _ctx[CpuRegister.Rax]);
Assert.Equal(JsonValueKind.Null, JsonObjectHeap.Values[ThisAddress].Kind);
}
[Theory]
[InlineData(0UL, false)]
[InlineData(1UL, true)]
[InlineData(0xFFFF_FF00UL, false)] // only the low byte is the bool; 0x00 low byte => false
public void ValueSetBoolean_StoresLowByte(ulong raw, bool expected)
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = raw;
JsonValueExports.ValueSetBoolean(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.Boolean, state.Kind);
Assert.Equal(expected, state.Boolean);
Assert.Equal(ThisAddress, _ctx[CpuRegister.Rax]);
}
[Fact]
public void ValueSetInteger_RoundTripsSignedValue()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = unchecked((ulong)-42L);
JsonValueExports.ValueSetInteger(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.Integer, state.Kind);
Assert.Equal(-42L, state.Integer);
}
[Fact]
public void ValueSetUnsigned_RoundTripsFullWidth()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = ulong.MaxValue;
JsonValueExports.ValueSetUnsigned(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.UInteger, state.Kind);
Assert.Equal(ulong.MaxValue, state.UnsignedInteger);
}
[Fact]
public void ValueSetReal_ReadsDoubleFromXmm0()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx.SetXmmRegister(0, unchecked((ulong)BitConverter.DoubleToInt64Bits(3.14159)), 0);
JsonValueExports.ValueSetReal(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.Real, state.Kind);
Assert.Equal(3.14159, state.Real, precision: 10);
}
[Fact]
public void ValueSetCString_ReadsGuestString()
{
_memory.WriteCString(TextAddress, "hello json");
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = TextAddress;
JsonValueExports.ValueSetCString(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.String, state.Kind);
Assert.Equal("hello json", state.Text);
}
[Fact]
public void ValueSetType_KeepsRawGuestEnumValue()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = 7;
JsonValueExports.ValueSetType(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.ExplicitType, state.Kind);
Assert.Equal(7u, state.ExplicitType);
}
[Fact]
public void StringConstructThenValueSetString_CopiesText()
{
_memory.WriteCString(TextAddress, "from string object");
_ctx[CpuRegister.Rdi] = StringAddress;
_ctx[CpuRegister.Rsi] = TextAddress;
JsonValueExports.StringCStringConstructor(_ctx);
Assert.Equal("from string object", JsonObjectHeap.Strings[StringAddress]);
Assert.Equal(StringAddress, _ctx[CpuRegister.Rax]);
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = StringAddress;
JsonValueExports.ValueSetString(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.String, state.Kind);
Assert.Equal("from string object", state.Text);
}
[Fact]
public void ValueSetString_MissingStringShadow_DegradesToEmpty()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = StringAddress; // never constructed
JsonValueExports.ValueSetString(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.String, state.Kind);
Assert.Equal(string.Empty, state.Text);
}
[Fact]
public void Destructors_RemoveShadowState()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
JsonValueExports.ValueDefaultConstructor(_ctx);
_ctx[CpuRegister.Rdi] = StringAddress;
JsonValueExports.StringDefaultConstructor(_ctx);
Assert.True(JsonObjectHeap.Values.ContainsKey(ThisAddress));
Assert.True(JsonObjectHeap.Strings.ContainsKey(StringAddress));
_ctx[CpuRegister.Rdi] = ThisAddress;
JsonValueExports.ValueDestructor(_ctx);
_ctx[CpuRegister.Rdi] = StringAddress;
JsonValueExports.StringDestructor(_ctx);
Assert.False(JsonObjectHeap.Values.ContainsKey(ThisAddress));
Assert.False(JsonObjectHeap.Strings.ContainsKey(StringAddress));
Assert.Equal(0UL, _ctx[CpuRegister.Rax]);
}
[Fact]
public void ValueSetCString_FaultingPointer_DegradesToEmptyString()
{
_ctx[CpuRegister.Rdi] = ThisAddress;
_ctx[CpuRegister.Rsi] = 0xDEAD_0000_0000; // outside the mapped region
JsonValueExports.ValueSetCString(_ctx);
var state = JsonObjectHeap.Values[ThisAddress];
Assert.Equal(JsonValueKind.String, state.Kind);
Assert.Equal(string.Empty, state.Text);
}
}
@@ -0,0 +1,22 @@
<!--
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\SharpEmu.Libs\SharpEmu.Libs.csproj" />
<ProjectReference Include="..\..\src\SharpEmu.HLE\SharpEmu.HLE.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
</Project>
@@ -0,0 +1,212 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[17.14.1, )",
"resolved": "17.14.1",
"contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
"dependencies": {
"Microsoft.CodeCoverage": "17.14.1",
"Microsoft.TestPlatform.TestHost": "17.14.1"
}
},
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
"resolved": "2.9.3",
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
"dependencies": {
"xunit.analyzers": "1.18.0",
"xunit.assert": "2.9.3",
"xunit.core": "[2.9.3]"
}
},
"xunit.runner.visualstudio": {
"type": "Direct",
"requested": "[3.1.1, )",
"resolved": "3.1.1",
"contentHash": "gNu2zhnuwjq5vQlU4S7yK/lfaKZDLmtcu+vTjnhfTlMAUYn+Hmgu8IIX0UCwWepYkk+Szx03DHx1bDnc9Fd+9w=="
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "17.14.1",
"contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg=="
},
"Microsoft.DotNet.PlatformAbstractions": {
"type": "Transitive",
"resolved": "3.1.6",
"contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg=="
},
"Microsoft.Extensions.DependencyModel": {
"type": "Transitive",
"resolved": "9.0.9",
"contentHash": "fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA=="
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.14.1",
"contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ=="
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "17.14.1",
"contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "17.14.1",
"Newtonsoft.Json": "13.0.3"
}
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"Silk.NET.Core": {
"type": "Transitive",
"resolved": "2.23.0",
"contentHash": "D7AT/nnwlB+4RZ84XY8QNGBZMJI5z9l4CSSETIJ1wCfRJzRt/341y3MRZ4HbnFz4r/IGaWOEZr86iE+0/65yyQ==",
"dependencies": {
"Microsoft.DotNet.PlatformAbstractions": "3.1.6",
"Microsoft.Extensions.DependencyModel": "9.0.9"
}
},
"Silk.NET.GLFW": {
"type": "Transitive",
"resolved": "2.23.0",
"contentHash": "UIs4sH57xlPUNHQ/1bt9rymPWlGy8IMDCNv86h0iM4TOA1CkIx0XM/n/tA4AReh1zQkNrvkxPEdZ3Blvy1dyXg==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Ultz.Native.GLFW": "3.4.0"
}
},
"Silk.NET.Maths": {
"type": "Transitive",
"resolved": "2.23.0",
"contentHash": "r8PdIVzME8EH0qAgbmRPO87I4GfgR2j8TofT7EMuRJDf1QluoQwnVypDoFJjQ2ZBSRsGYk5unYxxogI05Ogsmw=="
},
"Silk.NET.Windowing.Common": {
"type": "Transitive",
"resolved": "2.23.0",
"contentHash": "ThStSinmY9KQI8DGiF5XEhkLJVnBcgRTBTzL9ijg1wMZAYuckz7ykrNw04fjRm2Gryh6tCNGbvz2XaY0efeFzg==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Maths": "2.23.0"
}
},
"Silk.NET.Windowing.Glfw": {
"type": "Transitive",
"resolved": "2.23.0",
"contentHash": "aYBudKmENmvLRn9p15HbdvlQTnnXskcDfTfbYwSb/4fr263rGLwYuDw/txUEc2jihHJiWCp5+75Y7z5wTJWl7g==",
"dependencies": {
"Silk.NET.GLFW": "2.23.0",
"Silk.NET.Windowing.Common": "2.23.0"
}
},
"Ultz.Native.GLFW": {
"type": "Transitive",
"resolved": "3.4.0",
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
},
"xunit.analyzers": {
"type": "Transitive",
"resolved": "1.18.0",
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
},
"xunit.assert": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
},
"xunit.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]",
"xunit.extensibility.execution": "[2.9.3]"
}
},
"xunit.extensibility.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
"dependencies": {
"xunit.abstractions": "2.0.3"
}
},
"xunit.extensibility.execution": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]"
}
},
"sharpemu.hle": {
"type": "Project",
"dependencies": {
"SharpEmu.Logging": "[1.0.0, )"
}
},
"sharpemu.libs": {
"type": "Project",
"dependencies": {
"SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )"
}
},
"sharpemu.logging": {
"type": "Project"
},
"Silk.NET.Vulkan": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "3/irtlSWXZ3eTi8N6nelI6L34NTB8ZJHpqVMNzZx2aX7Ek9YEQ34NoQW8/Tljrtmkg8KRhHW8hKTEzZaKV8PgA==",
"dependencies": {
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "uRaf4j+SmH3DumjSSSUbFg33BnsGZUyXGj93O9NgGKZSJN3OTmNmQDxRew+/KiVLcgH6qzbto8aNGZ++j9GFWg==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Windowing": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "OPNPmt/lRyUKVYrFLQXVxyATqD3MKLc1iY1oKx1/2GppgmZxVZPwN12tekrQ4C7408kgB1L5JD1Wnirqqeb2kg==",
"dependencies": {
"Silk.NET.Windowing.Common": "2.23.0",
"Silk.NET.Windowing.Glfw": "2.23.0"
}
}
}
}
}