mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-03 00:19:46 +08:00
4c8c67a3dd
* Add ASTRO BOT compatibility stubs * Fix ASTRO BOT compatibility stubs
332 lines
12 KiB
C#
332 lines
12 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.HLE;
|
|
using System.Buffers.Binary;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace SharpEmu.Libs.UserService;
|
|
|
|
public static class UserServiceExports
|
|
{
|
|
private const int OrbisUserServiceErrorInvalidArgument = unchecked((int)0x80960005);
|
|
private const int OrbisUserServiceErrorNoEvent = unchecked((int)0x80960007);
|
|
private const int OrbisUserServiceErrorInvalidParameter = unchecked((int)0x80960009);
|
|
private const int OrbisUserServiceErrorBufferTooShort = unchecked((int)0x8096000A);
|
|
// Retail user ids encode their local user slot in the 0x10000000 range;
|
|
// the first signed-in user maps to slot 0. Small emulator-local ids do
|
|
// not pass Unity's user-id-to-slot conversion and become slot -1.
|
|
private const int PrimaryUserId = 0x10000000;
|
|
private const int InvalidUserId = -1;
|
|
private const string PrimaryUserName = "SharpEmu";
|
|
private static int _loginEventDelivered;
|
|
|
|
[SysAbiExport(
|
|
Nid = "j3YMu1MVNNo",
|
|
ExportName = "sceUserServiceInitialize",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceInitialize(CpuContext ctx)
|
|
{
|
|
Trace("initialize");
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "CdWp0oHWGr0",
|
|
ExportName = "sceUserServiceGetInitialUser",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetInitialUser(CpuContext ctx)
|
|
{
|
|
var userIdAddress = ctx[CpuRegister.Rdi];
|
|
if (userIdAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
return TryWriteInt32(ctx, userIdAddress, PrimaryUserId)
|
|
? SetReturnWithTrace(ctx, 0, $"get_initial_user user={PrimaryUserId} out=0x{userIdAddress:X16}")
|
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "fPhymKNvK-A",
|
|
ExportName = "sceUserServiceGetLoginUserIdList",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetLoginUserIdList(CpuContext ctx)
|
|
{
|
|
var userIdListAddress = ctx[CpuRegister.Rdi];
|
|
if (userIdListAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
Span<byte> userIds = stackalloc byte[sizeof(int) * 4];
|
|
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x00..], PrimaryUserId);
|
|
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x04..], InvalidUserId);
|
|
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x08..], InvalidUserId);
|
|
BinaryPrimitives.WriteInt32LittleEndian(userIds[0x0C..], InvalidUserId);
|
|
return ctx.Memory.TryWrite(userIdListAddress, userIds)
|
|
? SetReturnWithTrace(
|
|
ctx,
|
|
0,
|
|
$"get_login_user_id_list users=[{PrimaryUserId},{InvalidUserId},{InvalidUserId},{InvalidUserId}] " +
|
|
$"out=0x{userIdListAddress:X16}")
|
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "yH17Q6NWtVg",
|
|
ExportName = "sceUserServiceGetEvent",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetEvent(CpuContext ctx)
|
|
{
|
|
var eventAddress = ctx[CpuRegister.Rdi];
|
|
if (eventAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
if (Interlocked.Exchange(ref _loginEventDelivered, 1) != 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorNoEvent);
|
|
}
|
|
|
|
Span<byte> payload = stackalloc byte[sizeof(int) * 2];
|
|
BinaryPrimitives.WriteInt32LittleEndian(payload[0..], 0);
|
|
BinaryPrimitives.WriteInt32LittleEndian(payload[sizeof(int)..], PrimaryUserId);
|
|
return ctx.Memory.TryWrite(eventAddress, payload)
|
|
? SetReturnWithTrace(
|
|
ctx,
|
|
0,
|
|
$"get_event type=login user={PrimaryUserId} out=0x{eventAddress:X16}")
|
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "1xxcMiGu2fo",
|
|
ExportName = "sceUserServiceGetUserName",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetUserName(CpuContext ctx)
|
|
{
|
|
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
|
var nameAddress = ctx[CpuRegister.Rsi];
|
|
var capacity = ctx[CpuRegister.Rdx];
|
|
if (userId != PrimaryUserId && userId != 1)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidParameter);
|
|
}
|
|
|
|
if (nameAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
var nameBytes = Encoding.UTF8.GetBytes(PrimaryUserName);
|
|
if (capacity <= (ulong)nameBytes.Length)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorBufferTooShort);
|
|
}
|
|
|
|
Span<byte> output = stackalloc byte[nameBytes.Length + 1];
|
|
nameBytes.CopyTo(output);
|
|
return ctx.Memory.TryWrite(nameAddress, output)
|
|
? SetReturnWithTrace(
|
|
ctx,
|
|
0,
|
|
$"get_user_name user={userId} name='{PrimaryUserName}' out=0x{nameAddress:X16}")
|
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
|
|
// Title-captured alias NID for the same username query.
|
|
#pragma warning disable SHEM004
|
|
[SysAbiExport(
|
|
Nid = "znaWI0gpuo8",
|
|
ExportName = "sceUserServiceGetUserName",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetUserNameAlt(CpuContext ctx) => UserServiceGetUserName(ctx);
|
|
#pragma warning restore SHEM004
|
|
|
|
// Name not yet in ps5_names.txt and the NID was captured from titles; revisit when the symbol is catalogued.
|
|
#pragma warning disable SHEM006
|
|
[SysAbiExport(
|
|
Nid = "D-CzAxQL0XI",
|
|
ExportName = "sceUserServiceGetPlatformPrivacySetting",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetPlatformPrivacySetting(CpuContext ctx)
|
|
{
|
|
var parameterId = unchecked((int)ctx[CpuRegister.Rdi]);
|
|
var valueAddress = ctx[CpuRegister.Rsi];
|
|
if (parameterId != 1000)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidParameter);
|
|
}
|
|
|
|
if (valueAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
return TryWriteInt32(ctx, valueAddress, 0)
|
|
? SetReturn(ctx, 0)
|
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
#pragma warning restore SHEM006
|
|
|
|
[SysAbiExport(
|
|
Nid = "woNpu+45RLk",
|
|
ExportName = "sceUserServiceGetAgeLevel",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAgeLevel(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 18, "get_age_level");
|
|
|
|
[SysAbiExport(
|
|
Nid = "-sD02mFDBh4",
|
|
ExportName = "sceUserServiceGetGamePresets",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetGamePresets(CpuContext ctx)
|
|
{
|
|
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
|
var presetsAddress = ctx[CpuRegister.Rsi];
|
|
if (userId != PrimaryUserId)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidParameter);
|
|
}
|
|
|
|
if (presetsAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
Span<byte> presets = stackalloc byte[0x28];
|
|
presets.Clear();
|
|
BinaryPrimitives.WriteUInt64LittleEndian(presets, (ulong)presets.Length);
|
|
if (!ctx.Memory.TryWrite(presetsAddress, presets))
|
|
{
|
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
|
|
return SetReturnWithTrace(
|
|
ctx,
|
|
0,
|
|
$"get_game_presets user={userId} out=0x{presetsAddress:X16}");
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "rnEhHqG-4xo",
|
|
ExportName = "sceUserServiceGetAccessibilityChatTranscription",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAccessibilityChatTranscription(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 0, "get_accessibility_chat_transcription");
|
|
|
|
[SysAbiExport(
|
|
Nid = "ZKJtxdgvzwg",
|
|
ExportName = "sceUserServiceGetAccessibilityPressAndHoldDelay",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAccessibilityPressAndHoldDelay(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 0, "get_accessibility_press_and_hold_delay");
|
|
|
|
[SysAbiExport(
|
|
Nid = "-3Y5GO+-i78",
|
|
ExportName = "sceUserServiceGetAccessibilityTriggerEffect",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAccessibilityTriggerEffect(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 0, "get_accessibility_trigger_effect");
|
|
|
|
[SysAbiExport(
|
|
Nid = "qWYHOFwqCxY",
|
|
ExportName = "sceUserServiceGetAccessibilityVibration",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAccessibilityVibration(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 1, "get_accessibility_vibration");
|
|
|
|
[SysAbiExport(
|
|
Nid = "hD-H81EN9Vg",
|
|
ExportName = "sceUserServiceGetAccessibilityZoomEnabled",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAccessibilityZoomEnabled(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 0, "get_accessibility_zoom_enabled");
|
|
|
|
[SysAbiExport(
|
|
Nid = "O6IW1-Dwm-w",
|
|
ExportName = "sceUserServiceGetAccessibilityZoomFollowFocus",
|
|
Target = Generation.Gen5,
|
|
LibraryName = "libSceUserService")]
|
|
public static int UserServiceGetAccessibilityZoomFollowFocus(CpuContext ctx) =>
|
|
WriteUserSettingInt32(ctx, 0, "get_accessibility_zoom_follow_focus");
|
|
|
|
private static bool TryWriteInt32(CpuContext ctx, ulong address, int value)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[sizeof(int)];
|
|
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
|
|
return ctx.Memory.TryWrite(address, bytes);
|
|
}
|
|
|
|
private static int WriteUserSettingInt32(CpuContext ctx, int value, string operation)
|
|
{
|
|
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
|
var valueAddress = ctx[CpuRegister.Rsi];
|
|
if (userId != PrimaryUserId)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidParameter);
|
|
}
|
|
|
|
if (valueAddress == 0)
|
|
{
|
|
return SetReturn(ctx, OrbisUserServiceErrorInvalidArgument);
|
|
}
|
|
|
|
if (!TryWriteInt32(ctx, valueAddress, value))
|
|
{
|
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
}
|
|
|
|
return SetReturnWithTrace(
|
|
ctx,
|
|
0,
|
|
$"{operation} user={userId} value={value} out=0x{valueAddress:X16}");
|
|
}
|
|
|
|
private static int SetReturn(CpuContext ctx, int result)
|
|
{
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
|
return result;
|
|
}
|
|
|
|
private static int SetReturnWithTrace(CpuContext ctx, int result, string message)
|
|
{
|
|
Trace(message);
|
|
return SetReturn(ctx, result);
|
|
}
|
|
|
|
private static void Trace(string message)
|
|
{
|
|
if (string.Equals(
|
|
Environment.GetEnvironmentVariable("SHARPEMU_LOG_USER_SERVICE"),
|
|
"1",
|
|
StringComparison.Ordinal))
|
|
{
|
|
var returnRip = GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame)
|
|
? frame.ReturnRip
|
|
: 0;
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][TRACE] user_service.{message} ret=0x{returnRip:X16}");
|
|
}
|
|
}
|
|
}
|