mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-15 15:36:11 +08:00
[playgo] In some games, chunk scenarios assertion rewrite
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SharpEmu.Libs.PlayGo;
|
||||
|
||||
@@ -14,15 +15,35 @@ public static class PlayGoExports
|
||||
private const int OrbisPlayGoErrorBadHandle = unchecked((int)0x80B20009);
|
||||
private const int OrbisPlayGoErrorBadPointer = unchecked((int)0x80B2000A);
|
||||
private const int OrbisPlayGoErrorBadSize = unchecked((int)0x80B2000B);
|
||||
private const int OrbisPlayGoErrorBadChunkId = unchecked((int)0x80B2000C);
|
||||
private const int OrbisPlayGoErrorNotSupportPlayGo = unchecked((int)0x80B2000E);
|
||||
private const int OrbisPlayGoErrorBadLocus = unchecked((int)0x80B20010);
|
||||
private const ulong PlayGoInitBufAddrOffset = 0;
|
||||
private const ulong PlayGoInitBufSizeOffset = 8;
|
||||
private const uint PlayGoMinimumInitBufferSize = 0x200000;
|
||||
private const uint PlayGoHandle = 1;
|
||||
private const int PlayGoLocusNotDownloaded = 0;
|
||||
private const int PlayGoLocusLocalSlow = 2;
|
||||
private const int PlayGoLocusLocalFast = 3;
|
||||
private const int PlayGoInstallSpeedSuspended = 0;
|
||||
private const int PlayGoInstallSpeedTrickle = 1;
|
||||
private const int PlayGoInstallSpeedFull = 2;
|
||||
private const uint MaxPlayGoQueryEntries = 0x4000;
|
||||
|
||||
private static readonly Regex ChunkIdPattern = new(
|
||||
@"<chunk\s+[^>]*\bid\s*=\s*""(?<id>\d+)""",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex DefaultChunkPattern = new(
|
||||
@"default_chunk\s*=\s*""(?<id>\d+)""",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly object _stateGate = new();
|
||||
private static bool _initialized;
|
||||
private static bool _hasPlayGoData;
|
||||
private static bool _opened;
|
||||
private static PlayGoMetadata _metadata = PlayGoMetadata.Empty;
|
||||
private static int _installSpeed = PlayGoInstallSpeedTrickle;
|
||||
private static ulong _languageMask = ulong.MaxValue;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ts6GlZOKRrE",
|
||||
@@ -67,7 +88,10 @@ public static class PlayGoExports
|
||||
return OrbisPlayGoErrorAlreadyInitialized;
|
||||
}
|
||||
|
||||
_hasPlayGoData = HasPlayGoChunkData();
|
||||
_metadata = LoadPlayGoMetadata();
|
||||
_installSpeed = PlayGoInstallSpeedTrickle;
|
||||
_languageMask = ulong.MaxValue;
|
||||
_opened = false;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -100,10 +124,12 @@ public static class PlayGoExports
|
||||
return OrbisPlayGoErrorNotInitialized;
|
||||
}
|
||||
|
||||
if (!_hasPlayGoData)
|
||||
if (!_metadata.Available)
|
||||
{
|
||||
return OrbisPlayGoErrorNotSupportPlayGo;
|
||||
}
|
||||
|
||||
_opened = true;
|
||||
}
|
||||
|
||||
Span<byte> handleBytes = stackalloc byte[sizeof(uint)];
|
||||
@@ -132,7 +158,8 @@ public static class PlayGoExports
|
||||
}
|
||||
|
||||
_initialized = false;
|
||||
_hasPlayGoData = false;
|
||||
_opened = false;
|
||||
_metadata = PlayGoMetadata.Empty;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -157,20 +184,567 @@ public static class PlayGoExports
|
||||
{
|
||||
return OrbisPlayGoErrorBadHandle;
|
||||
}
|
||||
|
||||
_opened = false;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool HasPlayGoChunkData()
|
||||
[SysAbiExport(
|
||||
Nid = "73fF1MFU8hA",
|
||||
ExportName = "scePlayGoGetChunkId",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetChunkId(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var outChunkIdList = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var outEntries = ctx[CpuRegister.Rcx];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (outEntries == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (outChunkIdList != 0 && numberOfEntries == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
ushort[] chunkIds;
|
||||
lock (_stateGate)
|
||||
{
|
||||
chunkIds = _metadata.ChunkIds;
|
||||
}
|
||||
|
||||
var availableEntries = chunkIds.Length == 0 ? 1u : (uint)chunkIds.Length;
|
||||
if (outChunkIdList == 0)
|
||||
{
|
||||
return TryWriteUInt32(ctx, outEntries, availableEntries)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var entriesToWrite = Math.Min(numberOfEntries, availableEntries);
|
||||
if (entriesToWrite > MaxPlayGoQueryEntries)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < entriesToWrite; i++)
|
||||
{
|
||||
var chunkId = chunkIds.Length == 0 ? (ushort)0 : chunkIds[i];
|
||||
if (!TryWriteUInt16(ctx, outChunkIdList + (i * sizeof(ushort)), chunkId))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
return TryWriteUInt32(ctx, outEntries, entriesToWrite)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "v6EZ-YWRdMs",
|
||||
ExportName = "scePlayGoGetEta",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetEta(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var chunkIds = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var outEta = ctx[CpuRegister.Rcx];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (chunkIds == 0 || outEta == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (numberOfEntries == 0 || numberOfEntries > MaxPlayGoQueryEntries)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
return ValidateChunkIds(ctx, chunkIds, numberOfEntries) is { } chunkError && chunkError != 0
|
||||
? chunkError
|
||||
: TryWriteInt64(ctx, outEta, 0)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "rvBSfTimejE",
|
||||
ExportName = "scePlayGoGetInstallSpeed",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetInstallSpeed(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var outSpeed = ctx[CpuRegister.Rsi];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (outSpeed == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
int speed;
|
||||
lock (_stateGate)
|
||||
{
|
||||
speed = _installSpeed;
|
||||
}
|
||||
|
||||
return TryWriteInt32(ctx, outSpeed, speed)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "3OMbYZBaa50",
|
||||
ExportName = "scePlayGoGetLanguageMask",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetLanguageMask(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var outLanguageMask = ctx[CpuRegister.Rsi];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (outLanguageMask == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
ulong languageMask;
|
||||
lock (_stateGate)
|
||||
{
|
||||
languageMask = _languageMask;
|
||||
}
|
||||
|
||||
return ctx.TryWriteUInt64(outLanguageMask, languageMask)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "uWIYLFkkwqk",
|
||||
ExportName = "scePlayGoGetLocus",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetLocus(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var chunkIds = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var outLoci = ctx[CpuRegister.Rcx];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (chunkIds == 0 || outLoci == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (numberOfEntries == 0 || numberOfEntries > MaxPlayGoQueryEntries)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
var loci = new byte[numberOfEntries];
|
||||
for (uint i = 0; i < numberOfEntries; i++)
|
||||
{
|
||||
if (!TryReadUInt16(ctx, chunkIds + (i * sizeof(ushort)), out var chunkId))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!IsKnownChunkId(chunkId))
|
||||
{
|
||||
loci[i] = PlayGoLocusNotDownloaded;
|
||||
return ctx.Memory.TryWrite(outLoci, loci)
|
||||
? OrbisPlayGoErrorBadChunkId
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
loci[i] = PlayGoLocusLocalFast;
|
||||
}
|
||||
|
||||
return ctx.Memory.TryWrite(outLoci, loci)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-RJWNMK3fC8",
|
||||
ExportName = "scePlayGoGetProgress",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetProgress(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var chunkIds = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var outProgress = ctx[CpuRegister.Rcx];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (chunkIds == 0 || outProgress == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (numberOfEntries == 0 || numberOfEntries > MaxPlayGoQueryEntries)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
var chunkError = ValidateChunkIds(ctx, chunkIds, numberOfEntries);
|
||||
if (chunkError != 0)
|
||||
{
|
||||
return chunkError;
|
||||
}
|
||||
|
||||
Span<byte> progress = stackalloc byte[sizeof(ulong) * 2];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(progress, 0);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(progress[sizeof(ulong)..], 0);
|
||||
return ctx.Memory.TryWrite(outProgress, progress)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Nn7zKwnA5q0",
|
||||
ExportName = "scePlayGoGetToDoList",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoGetToDoList(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var outTodoList = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var outEntries = ctx[CpuRegister.Rcx];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (outTodoList == 0 || outEntries == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (numberOfEntries == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
return TryWriteUInt32(ctx, outEntries, 0)
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-Q1-u1a7p0g",
|
||||
ExportName = "scePlayGoPrefetch",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoPrefetch(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var chunkIds = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
var minimumLocus = unchecked((int)ctx[CpuRegister.Rcx]);
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (chunkIds == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
if (numberOfEntries == 0 || numberOfEntries > MaxPlayGoQueryEntries)
|
||||
{
|
||||
return OrbisPlayGoErrorBadSize;
|
||||
}
|
||||
|
||||
if (minimumLocus is not PlayGoLocusNotDownloaded and not PlayGoLocusLocalSlow and not PlayGoLocusLocalFast)
|
||||
{
|
||||
return OrbisPlayGoErrorBadLocus;
|
||||
}
|
||||
|
||||
return ValidateChunkIds(ctx, chunkIds, numberOfEntries) is { } chunkError && chunkError != 0
|
||||
? chunkError
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4AAcTU9R3XM",
|
||||
ExportName = "scePlayGoSetInstallSpeed",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoSetInstallSpeed(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var speed = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (speed is not PlayGoInstallSpeedSuspended and not PlayGoInstallSpeedTrickle and not PlayGoInstallSpeedFull)
|
||||
{
|
||||
return OrbisPlayGoErrorInvalidArgument;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_installSpeed = speed;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "LosLlHOpNqQ",
|
||||
ExportName = "scePlayGoSetLanguageMask",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoSetLanguageMask(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var languageMask = ctx[CpuRegister.Rsi];
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
lock (_stateGate)
|
||||
{
|
||||
_languageMask = languageMask;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "gUPGiOQ1tmQ",
|
||||
ExportName = "scePlayGoSetToDoList",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePlayGo")]
|
||||
public static int PlayGoSetToDoList(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var todoList = ctx[CpuRegister.Rsi];
|
||||
var numberOfEntries = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
|
||||
var validation = ValidateHandle(handle);
|
||||
if (validation != 0)
|
||||
{
|
||||
return validation;
|
||||
}
|
||||
|
||||
if (todoList == 0)
|
||||
{
|
||||
return OrbisPlayGoErrorBadPointer;
|
||||
}
|
||||
|
||||
return numberOfEntries == 0
|
||||
? OrbisPlayGoErrorBadSize
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static int ValidateHandle(uint handle)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return OrbisPlayGoErrorNotInitialized;
|
||||
}
|
||||
|
||||
if (handle != PlayGoHandle || !_opened)
|
||||
{
|
||||
return OrbisPlayGoErrorBadHandle;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static int ValidateChunkIds(CpuContext ctx, ulong chunkIds, uint numberOfEntries)
|
||||
{
|
||||
for (uint i = 0; i < numberOfEntries; i++)
|
||||
{
|
||||
if (!TryReadUInt16(ctx, chunkIds + (i * sizeof(ushort)), out var chunkId))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!IsKnownChunkId(chunkId))
|
||||
{
|
||||
return OrbisPlayGoErrorBadChunkId;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private static bool IsKnownChunkId(ushort chunkId)
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
return _metadata.ChunkIds.Length == 0 || Array.BinarySearch(_metadata.ChunkIds, chunkId) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static PlayGoMetadata LoadPlayGoMetadata()
|
||||
{
|
||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||
if (string.IsNullOrWhiteSpace(app0Root))
|
||||
{
|
||||
return PlayGoMetadata.Empty;
|
||||
}
|
||||
|
||||
var playGoDat = Path.Combine(app0Root, "sce_sys", "playgo-chunk.dat");
|
||||
var scenarioJson = Path.Combine(app0Root, "sce_sys", "playgo-scenario.json");
|
||||
var chunkDefsXml = Path.Combine(app0Root, "playgo-chunkdefs.xml");
|
||||
|
||||
var hasMetadata = File.Exists(playGoDat) || File.Exists(scenarioJson) || File.Exists(chunkDefsXml);
|
||||
if (!hasMetadata)
|
||||
{
|
||||
return PlayGoMetadata.Empty;
|
||||
}
|
||||
|
||||
var chunkIds = LoadChunkIds(chunkDefsXml);
|
||||
return new PlayGoMetadata(true, chunkIds);
|
||||
}
|
||||
|
||||
private static ushort[] LoadChunkIds(string chunkDefsXml)
|
||||
{
|
||||
if (!File.Exists(chunkDefsXml))
|
||||
{
|
||||
return Array.Empty<ushort>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var xml = File.ReadAllText(chunkDefsXml);
|
||||
var chunkIds = new HashSet<ushort>();
|
||||
AddChunkIds(xml, DefaultChunkPattern, chunkIds);
|
||||
AddChunkIds(xml, ChunkIdPattern, chunkIds);
|
||||
|
||||
var sorted = chunkIds.ToArray();
|
||||
Array.Sort(sorted);
|
||||
return sorted;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return Array.Empty<ushort>();
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Array.Empty<ushort>();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddChunkIds(string xml, Regex pattern, HashSet<ushort> chunkIds)
|
||||
{
|
||||
foreach (Match match in pattern.Matches(xml))
|
||||
{
|
||||
if (ushort.TryParse(match.Groups["id"].Value, out var chunkId))
|
||||
{
|
||||
chunkIds.Add(chunkId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryReadUInt16(CpuContext ctx, ulong address, out ushort value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
|
||||
if (!ctx.Memory.TryRead(address, buffer))
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
var hostPath = Path.Combine(app0Root, "sce_sys", "playgo-chunk.dat");
|
||||
return File.Exists(hostPath);
|
||||
value = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt16(CpuContext ctx, ulong address, ushort value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryWriteInt32(CpuContext ctx, ulong address, int value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static bool TryWriteInt64(CpuContext ctx, ulong address, long value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
|
||||
return ctx.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private sealed record PlayGoMetadata(bool Available, ushort[] ChunkIds)
|
||||
{
|
||||
public static readonly PlayGoMetadata Empty = new(false, Array.Empty<ushort>());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user