From 0f0ec9a020b4d66a073f1b7e5530468b4b8a8c5e Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Tue, 23 Jun 2026 19:28:53 +0300 Subject: [PATCH] [appContent] correct way to get metadata from game --- .../AppContent/AppContentExports.cs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/SharpEmu.Libs/AppContent/AppContentExports.cs b/src/SharpEmu.Libs/AppContent/AppContentExports.cs index c0b841b..4755955 100644 --- a/src/SharpEmu.Libs/AppContent/AppContentExports.cs +++ b/src/SharpEmu.Libs/AppContent/AppContentExports.cs @@ -4,6 +4,7 @@ using SharpEmu.HLE; using System.Buffers.Binary; using System.Text; +using System.Text.Json; namespace SharpEmu.Libs.AppContent; @@ -11,6 +12,8 @@ public static class AppContentExports { private const ulong BootParamAttrOffset = 4; private const string Temp0MountPoint = "/temp0"; + private const uint AppParamSkuFlag = 0; + private const int AppParamSkuFlagFull = 3; [SysAbiExport( Nid = "R9lA82OraNs", @@ -59,6 +62,41 @@ public static class AppContentExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "99b82IKXpH4", + ExportName = "sceAppContentAppParamGetInt", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceAppContent")] + public static int AppContentAppParamGetInt(CpuContext ctx) + { + var paramId = (uint)ctx[CpuRegister.Rdi]; + var valueAddress = ctx[CpuRegister.Rsi]; + if (valueAddress == 0) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + } + + int value; + if (paramId == AppParamSkuFlag) + { + value = AppParamSkuFlagFull; + } + else if (!TryReadUserDefinedParam(paramId, out value)) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + } + + Span valueBytes = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(valueBytes, value); + if (!ctx.Memory.TryWrite(valueAddress, valueBytes)) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + TraceAppContent($"app_param_get_int id={paramId} value={value}"); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); + } + [SysAbiExport( Nid = "buYbeLOGWmA", ExportName = "sceAppContentTemporaryDataMount2", @@ -83,6 +121,69 @@ public static class AppContentExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + private static bool TryReadUserDefinedParam(uint paramId, out int value) + { + value = 0; + if (paramId is < 1 or > 4) + { + return false; + } + + var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR"); + if (string.IsNullOrWhiteSpace(app0Root)) + { + return true; + } + + var paramJsonPath = Path.Combine(app0Root, "sce_sys", "param.json"); + if (!File.Exists(paramJsonPath)) + { + return true; + } + + try + { + using var stream = File.OpenRead(paramJsonPath); + using var document = JsonDocument.Parse(stream); + var propertyName = $"userDefinedParam{paramId}"; + if (document.RootElement.TryGetProperty(propertyName, out var element) && + element.TryGetInt32(out var parsedValue)) + { + value = parsedValue; + } + + return true; + } + catch (IOException) + { + return true; + } + catch (UnauthorizedAccessException) + { + return true; + } + catch (JsonException) + { + return true; + } + } + + private static int SetReturn(CpuContext ctx, OrbisGen2Result result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)(int)result); + return (int)result; + } + + private static void TraceAppContent(string message) + { + if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_APP_CONTENT"), "1", StringComparison.Ordinal)) + { + return; + } + + Console.Error.WriteLine($"[LOADER][TRACE] app_content.{message}"); + } + private static string ResolveTemp0Root() { const string temp0VariableName = "SHARPEMU_TEMP0_DIR";