mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
Add 21 missing SysAbi exports and GUI Environment tab for SHARPEMU_* toggles (#189)
Fills NID gaps hit by PS5 titles during boot, controller setup, and rendering, and surfaces the common runtime switches in the GUI. All exports are additive (no behavior change to existing exports) and free of NID and export-name collisions with upstream. New export libraries: - libSceBluetoothHid: Init/RegisterDevice/RegisterCallback success stubs so titles proceed past Bluetooth controller setup (opt-out via SHARPEMU_BTHID_UNAVAILABLE=1). - libSceNpCppWebApi: Common::initialize no-op success; UE5 online titles abort PS5-component startup on a negative SCE error. Additions to existing libraries: - libScePad: scePadOpenExt (shared PadOpenCore, accepts special ports 1/2 and the ScePadOpenExtParam pointer), scePadClose, scePadGetExtControllerInformation. - libSceVideoOut: sceVideoOutConfigureOutput, sceVideoOutInitializeOutputOptions. - libSceAgc: DCB builders sceAgcDcbSetIndexCount, sceAgcDcbJump, DcbSetPredication, SetPacketPredication (emit valid skippable packets; full draw processing TODO). - libSceAmpr: measure and write KernelEventQueueOnCompletion pair. - libKernel: scePthreadGet/Setschedparam, sceKernelChmod (validate and accept; POSIX permission bits have no host equivalent on Windows). - libSceNetCtl: sceNetCtlRegisterCallbackV6 (delegates to the v4 callback). - libSceMouse: sceMouseInit. - libSceUserService: sceUserServiceGetAgeLevel (adult, skips parental gates). GUI: new Options Environment tab exposing common SHARPEMU_* switches as toggles (BTHID_UNAVAILABLE, DISABLE_IMPORT_LOOP_GUARD, VK_VALIDATION, DUMP_SPIRV, LOG_DIRECT_MEMORY, LOG_NP). Persisted in gui-settings.json and applied to the emulator process environment at launch; localized with English fallback.
This commit is contained in:
@@ -48,6 +48,9 @@ public sealed class GuiSettings
|
||||
/// <summary>Publish launcher/game status to Discord Rich Presence.</summary>
|
||||
public bool DiscordRichPresence { get; set; } = true;
|
||||
|
||||
/// <summary>Names of SHARPEMU_* switches set to "1" in the emulator's environment at launch.</summary>
|
||||
public List<string> EnvironmentToggles { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Discord application ID used for Rich Presence; the default is the
|
||||
/// SharpEmu application. Override to rebrand what Discord shows as
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
"Library.Loading": "Loading library…",
|
||||
|
||||
"Options.General": "General",
|
||||
"Options.Env.Tab": "Environment",
|
||||
"Options.Section.Environment": "ENVIRONMENT VARIABLES",
|
||||
"Options.Env.Desc": "Switches passed to the emulator as environment variables at launch.",
|
||||
"Options.Env.Bthid.Desc": "Report Bluetooth HID as unavailable for titles whose wheel/FFB middleware polls forever.\nLeave off normally. Some titles freeze when init fails.",
|
||||
"Options.Env.LoopGuard.Desc": "Do not force quit titles that repeat the same call for too long.\nTry this when a game exits on its own while loading.",
|
||||
"Options.Env.VkValidation.Desc": "Enable Vulkan validation layers for GPU debugging.\nSlow. Requires the Vulkan SDK to be installed.",
|
||||
"Options.Env.DumpSpirv.Desc": "Dump AGC shaders and their SPIR-V translations to the shader-dumps folder.\nUse when reporting shader or rendering bugs.",
|
||||
"Options.Env.LogDirectMemory.Desc": "Log direct memory allocations and failures to the console.\nUse when a game aborts or exits during boot.",
|
||||
"Options.Env.LogNp.Desc": "Log NP (PlayStation Network) library calls to the console.",
|
||||
"Options.Section.Emulation": "EMULATION",
|
||||
"Options.Section.Logging": "LOGGING",
|
||||
"Options.Section.Launcher": "LAUNCHER",
|
||||
|
||||
@@ -387,6 +387,88 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="EnvTabItem" Header="Environment" FontSize="15">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,14,0,16" Spacing="16" MaxWidth="1280" HorizontalAlignment="Left">
|
||||
|
||||
<Border Classes="card">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock x:Name="EnvSectionTitle" Classes="sectionTitle" Text="ENVIRONMENT VARIABLES" />
|
||||
<TextBlock x:Name="EnvDesc"
|
||||
Text="Switches passed to the emulator as environment variables at launch."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock Text="SHARPEMU_BTHID_UNAVAILABLE" FontSize="13" FontFamily="Consolas,monospace" />
|
||||
<TextBlock x:Name="EnvBthidDesc"
|
||||
Text="Report Bluetooth HID as unavailable for titles whose wheel/FFB middleware polls forever. Leave off normally. Some titles freeze when init fails."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Column="1" x:Name="EnvBthidToggle" OnContent="On" OffContent="Off"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock Text="SHARPEMU_DISABLE_IMPORT_LOOP_GUARD" FontSize="13" FontFamily="Consolas,monospace" />
|
||||
<TextBlock x:Name="EnvLoopGuardDesc"
|
||||
Text="Do not force quit titles that repeat the same call for too long. Try this when a game exits on its own while loading."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Column="1" x:Name="EnvLoopGuardToggle" OnContent="On" OffContent="Off"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock Text="SHARPEMU_VK_VALIDATION" FontSize="13" FontFamily="Consolas,monospace" />
|
||||
<TextBlock x:Name="EnvVkValidationDesc"
|
||||
Text="Enable Vulkan validation layers for GPU debugging. Slow. Requires the Vulkan SDK to be installed."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Column="1" x:Name="EnvVkValidationToggle" OnContent="On" OffContent="Off"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock Text="SHARPEMU_DUMP_SPIRV" FontSize="13" FontFamily="Consolas,monospace" />
|
||||
<TextBlock x:Name="EnvDumpSpirvDesc"
|
||||
Text="Dump AGC shaders and their SPIR-V translations to the shader-dumps folder. Use when reporting shader or rendering bugs."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Column="1" x:Name="EnvDumpSpirvToggle" OnContent="On" OffContent="Off"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock Text="SHARPEMU_LOG_DIRECT_MEMORY" FontSize="13" FontFamily="Consolas,monospace" />
|
||||
<TextBlock x:Name="EnvLogDirectMemoryDesc"
|
||||
Text="Log direct memory allocations and failures to the console. Use when a game aborts or exits during boot."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Column="1" x:Name="EnvLogDirectMemoryToggle" OnContent="On" OffContent="Off"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock Text="SHARPEMU_LOG_NP" FontSize="13" FontFamily="Consolas,monospace" />
|
||||
<TextBlock x:Name="EnvLogNpDesc"
|
||||
Text="Log NP (PlayStation Network) library calls to the console."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Column="1" x:Name="EnvLogNpToggle" OnContent="On" OffContent="Off"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</Panel>
|
||||
|
||||
@@ -125,6 +125,18 @@ public partial class MainWindow : Window
|
||||
UpdateDiscordPresence();
|
||||
};
|
||||
SelectLogFilePathButton.Click += async (_, _) => await SelectLogFilePathAsync();
|
||||
EnvBthidToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_BTHID_UNAVAILABLE", EnvBthidToggle.IsChecked == true);
|
||||
EnvLoopGuardToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_DISABLE_IMPORT_LOOP_GUARD", EnvLoopGuardToggle.IsChecked == true);
|
||||
EnvVkValidationToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_VK_VALIDATION", EnvVkValidationToggle.IsChecked == true);
|
||||
EnvDumpSpirvToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_DUMP_SPIRV", EnvDumpSpirvToggle.IsChecked == true);
|
||||
EnvLogDirectMemoryToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_LOG_DIRECT_MEMORY", EnvLogDirectMemoryToggle.IsChecked == true);
|
||||
EnvLogNpToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_LOG_NP", EnvLogNpToggle.IsChecked == true);
|
||||
LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged();
|
||||
|
||||
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
|
||||
@@ -403,6 +415,15 @@ public partial class MainWindow : Window
|
||||
LoadingStateText.Text = loc.Get("Library.Loading");
|
||||
|
||||
GeneralTabItem.Header = loc.Get("Options.General");
|
||||
EnvTabItem.Header = loc.Get("Options.Env.Tab");
|
||||
EnvSectionTitle.Text = loc.Get("Options.Section.Environment");
|
||||
EnvDesc.Text = loc.Get("Options.Env.Desc");
|
||||
EnvBthidDesc.Text = loc.Get("Options.Env.Bthid.Desc");
|
||||
EnvLoopGuardDesc.Text = loc.Get("Options.Env.LoopGuard.Desc");
|
||||
EnvVkValidationDesc.Text = loc.Get("Options.Env.VkValidation.Desc");
|
||||
EnvDumpSpirvDesc.Text = loc.Get("Options.Env.DumpSpirv.Desc");
|
||||
EnvLogDirectMemoryDesc.Text = loc.Get("Options.Env.LogDirectMemory.Desc");
|
||||
EnvLogNpDesc.Text = loc.Get("Options.Env.LogNp.Desc");
|
||||
EmulationSectionTitle.Text = loc.Get("Options.Section.Emulation");
|
||||
LoggingSectionTitle.Text = loc.Get("Options.Section.Logging");
|
||||
LauncherSectionTitle.Text = loc.Get("Options.Section.Launcher");
|
||||
@@ -584,9 +605,34 @@ public partial class MainWindow : Window
|
||||
OverrideLogFileToggle.IsChecked = _settings.OverrideLogFile;
|
||||
TitleMusicToggle.IsChecked = _settings.PlayTitleMusic;
|
||||
DiscordToggle.IsChecked = _settings.DiscordRichPresence;
|
||||
EnvBthidToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_BTHID_UNAVAILABLE");
|
||||
EnvLoopGuardToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_DISABLE_IMPORT_LOOP_GUARD");
|
||||
EnvVkValidationToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_VK_VALIDATION");
|
||||
EnvDumpSpirvToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_DUMP_SPIRV");
|
||||
EnvLogDirectMemoryToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_DIRECT_MEMORY");
|
||||
EnvLogNpToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_NP");
|
||||
UpdateLogFilePathText();
|
||||
}
|
||||
|
||||
// Environment variables set on this process at the previous launch; children
|
||||
// inherit the process environment, so stale names must be cleared explicitly.
|
||||
private readonly HashSet<string> _appliedEnvironmentVariables = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private void SetEnvironmentToggle(string name, bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
if (!_settings.EnvironmentToggles.Contains(name))
|
||||
{
|
||||
_settings.EnvironmentToggles.Add(name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings.EnvironmentToggles.Remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
private string SelectedLogLevel()
|
||||
{
|
||||
return LogLevelBox.SelectedIndex switch
|
||||
@@ -1402,6 +1448,24 @@ public partial class MainWindow : Window
|
||||
Localization.Instance.Format("Launch.Command", string.Join(' ', arguments)),
|
||||
DimLineBrush);
|
||||
|
||||
// Apply the enabled switches to this process; both emulator launch paths
|
||||
// (CreateProcessW and Process.Start) inherit it. Clear switches turned
|
||||
// off since the previous launch.
|
||||
foreach (var staleName in _appliedEnvironmentVariables)
|
||||
{
|
||||
if (!_settings.EnvironmentToggles.Contains(staleName))
|
||||
{
|
||||
Environment.SetEnvironmentVariable(staleName, null);
|
||||
}
|
||||
}
|
||||
|
||||
_appliedEnvironmentVariables.Clear();
|
||||
foreach (var name in _settings.EnvironmentToggles)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(name, "1");
|
||||
_appliedEnvironmentVariables.Add(name);
|
||||
}
|
||||
|
||||
var emulator = new EmulatorProcess();
|
||||
emulator.OutputReceived += (line, isError) => _pendingLines.Enqueue((line, isError));
|
||||
emulator.Exited += code => Dispatcher.UIThread.Post(() => OnEmulatorExited(code));
|
||||
|
||||
@@ -29,6 +29,7 @@ public static class AgcExports
|
||||
private const uint ItDispatchDirect = 0x15;
|
||||
private const uint ItDispatchIndirect = 0x16;
|
||||
private const uint ItWaitRegMem = 0x3C;
|
||||
private const uint ItIndirectBuffer = 0x3F;
|
||||
private const uint ItEventWrite = 0x46;
|
||||
private const uint ItDmaData = 0x50;
|
||||
private const uint ItSetContextReg = 0x69;
|
||||
@@ -1943,6 +1944,96 @@ public static class AgcExports
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
// Guest draw-command builders: emit valid (skippable) packets and return a live
|
||||
// command pointer so the guest's command-buffer build succeeds; full draw processing is TODO.
|
||||
[SysAbiExport(
|
||||
Nid = "8N2tmT3jmC8",
|
||||
ExportName = "sceAgcDcbSetIndexCount",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbSetIndexCount(CpuContext ctx)
|
||||
{
|
||||
var dcb = ctx[CpuRegister.Rdi];
|
||||
var indexCount = (uint)ctx[CpuRegister.Rsi];
|
||||
if (dcb == 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, dcb, 2, out var cmd) ||
|
||||
!ctx.TryWriteUInt32(cmd, Pm4(2, ItNop, RZero)) ||
|
||||
!ctx.TryWriteUInt32(cmd + 4, indexCount))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
return ReturnPointer(ctx, cmd);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "xSAR0LTcRKM",
|
||||
ExportName = "sceAgcDcbJump",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbJump(CpuContext ctx)
|
||||
{
|
||||
var dcb = ctx[CpuRegister.Rdi];
|
||||
var target = ctx[CpuRegister.Rsi];
|
||||
var sizeDwords = (uint)ctx[CpuRegister.Rdx];
|
||||
if (dcb == 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, dcb, 4, out var cmd) ||
|
||||
!ctx.TryWriteUInt32(cmd, Pm4(4, ItIndirectBuffer, RZero)) ||
|
||||
!ctx.TryWriteUInt32(cmd + 4, (uint)(target & 0xFFFF_FFFFUL)) ||
|
||||
!ctx.TryWriteUInt32(cmd + 8, (uint)((target >> 32) & 0xFFFFUL)) ||
|
||||
!ctx.TryWriteUInt32(cmd + 12, sizeDwords & 0xFFFFF))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
return ReturnPointer(ctx, cmd);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bbFueFP+J4k",
|
||||
ExportName = "sceAgcDcbSetPredication",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbSetPredication(CpuContext ctx)
|
||||
{
|
||||
var dcb = ctx[CpuRegister.Rdi];
|
||||
var address = ctx[CpuRegister.Rsi];
|
||||
if (dcb == 0)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, dcb, 3, out var cmd) ||
|
||||
!ctx.TryWriteUInt32(cmd, Pm4(3, ItNop, RZero)) ||
|
||||
!ctx.TryWriteUInt32(cmd + 4, (uint)(address & 0xFFFF_FFFFUL)) ||
|
||||
!ctx.TryWriteUInt32(cmd + 8, (uint)(address >> 32)))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
return ReturnPointer(ctx, cmd);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w6Dj1VJt5qY",
|
||||
ExportName = "sceAgcSetPacketPredication",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int SetPacketPredication(CpuContext ctx)
|
||||
{
|
||||
// Global predication toggle on a packet; a no-op is safe for rendering.
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w2rJhmD+dsE",
|
||||
ExportName = "sceAgcDriverAddEqEvent",
|
||||
|
||||
@@ -348,6 +348,18 @@ public static class AmprExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Zi3dBUjgyXI",
|
||||
ExportName = "sceAmprMeasureCommandSizeWriteKernelEventQueueOnCompletion",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int MeasureCommandSizeWriteKernelEventQueueOnCompletion(CpuContext ctx)
|
||||
{
|
||||
TraceAmpr(ctx, "measure_write_equeue_complete", 0, KernelEventQueueRecordSize, 0);
|
||||
ctx[CpuRegister.Rax] = KernelEventQueueRecordSize;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "C+IEj+BsAFM",
|
||||
ExportName = "sceAmprMeasureCommandSizeWriteAddressOnCompletion",
|
||||
@@ -440,6 +452,40 @@ public static class AmprExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "o67gODLFpls",
|
||||
ExportName = "sceAmprCommandBufferWriteKernelEventQueueOnCompletion",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAmpr")]
|
||||
public static int CommandBufferWriteKernelEventQueueOnCompletion(CpuContext ctx)
|
||||
{
|
||||
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||
var equeue = ctx[CpuRegister.Rsi];
|
||||
var ident = ctx[CpuRegister.Rdx];
|
||||
var completionToken = ctx[CpuRegister.Rcx];
|
||||
var userData = ctx[CpuRegister.R8];
|
||||
|
||||
if (commandBuffer == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!AppendKernelEventQueueRecord(
|
||||
ctx,
|
||||
commandBuffer,
|
||||
equeue,
|
||||
ident,
|
||||
completionToken,
|
||||
userData))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
TraceAmpr(ctx, "write_equeue_complete", commandBuffer, ident, completionToken);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "sJXyWHjP-F8",
|
||||
ExportName = "sceAmprCommandBufferWriteAddressOnCompletion",
|
||||
|
||||
@@ -1834,6 +1834,45 @@ public static class KernelMemoryCompatExports
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "fgIsQ10xYVA",
|
||||
ExportName = "sceKernelChmod",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelChmod(CpuContext ctx)
|
||||
{
|
||||
var pathAddress = ctx[CpuRegister.Rdi];
|
||||
var mode = unchecked((uint)ctx[CpuRegister.Rsi]);
|
||||
if (pathAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var hostPath = ResolveGuestPath(guestPath);
|
||||
if (IsReadOnlyGuestMutationPath(guestPath))
|
||||
{
|
||||
LogOpenTrace($"chmod readonly path='{guestPath}' host='{hostPath}' mode=0x{mode:X}");
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
if (!File.Exists(hostPath) && !Directory.Exists(hostPath))
|
||||
{
|
||||
AddNegativeStatCacheForGuestPath(guestPath);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// POSIX permission bits have no host equivalent on Windows; accept the call
|
||||
// so guests that chmod their freshly created files/directories can proceed.
|
||||
LogOpenTrace($"chmod path='{guestPath}' host='{hostPath}' mode=0x{mode:X}");
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "NNtFaKJbPt0",
|
||||
ExportName = "_close",
|
||||
|
||||
@@ -382,6 +382,47 @@ public static class KernelPthreadExtendedCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "oIRFTjoILbg",
|
||||
ExportName = "scePthreadSetschedparam",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSetschedparam(CpuContext ctx) => PosixPthreadSetschedparam(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "P41kTWUS3EI",
|
||||
ExportName = "scePthreadGetschedparam",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadGetschedparam(CpuContext ctx)
|
||||
{
|
||||
var thread = ctx[CpuRegister.Rdi];
|
||||
var outPolicyAddress = ctx[CpuRegister.Rsi];
|
||||
var outSchedParamAddress = ctx[CpuRegister.Rdx];
|
||||
if (thread == 0 || outPolicyAddress == 0 || outSchedParamAddress == 0)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
int policy;
|
||||
int priority;
|
||||
lock (_stateGate)
|
||||
{
|
||||
var state = GetOrCreateThreadStateLocked(thread);
|
||||
policy = state.Attributes.SchedPolicy;
|
||||
priority = state.Priority;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteInt32(outPolicyAddress, policy) ||
|
||||
!ctx.TryWriteInt32(outSchedParamAddress, priority))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "nsYoNRywwNg",
|
||||
ExportName = "scePthreadAttrInit",
|
||||
|
||||
@@ -7,6 +7,17 @@ namespace SharpEmu.Libs.Mouse;
|
||||
|
||||
public static class MouseExports
|
||||
{
|
||||
[SysAbiExport(
|
||||
Nid = "Qs0wWulgl7U",
|
||||
ExportName = "sceMouseInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceMouse")]
|
||||
public static int MouseInit(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
// Returns 0 read entries: no mouse is connected. This NID was previously misbound
|
||||
// as an sceNgs2VoiceGetState alias.
|
||||
[SysAbiExport(
|
||||
|
||||
@@ -146,6 +146,13 @@ public static class NetCtlExports
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_OK, typeof(long));
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "1NE9OWdBIww",
|
||||
ExportName = "sceNetCtlRegisterCallbackV6",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNetCtl")]
|
||||
public static int NetCtlRegisterCallbackV6(CpuContext ctx) => NetCtlRegisterCallback(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "obuxdTiwkF8",
|
||||
ExportName = "sceNetCtlGetInfo",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Np;
|
||||
|
||||
// Stub for sce::Np::CppWebApi: titles abort PS5-component startup if
|
||||
// Common::initialize returns a negative SCE error, so no-op success is required to boot.
|
||||
public static class NpCppWebApiExports
|
||||
{
|
||||
[SysAbiExport(
|
||||
Nid = "UYPxv8MIzGo",
|
||||
ExportName = "_ZN3sce2Np9CppWebApi6Common10initializeERKNS2_10InitParamsERNS2_10LibContextE",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNpCppWebApi")]
|
||||
public static int CppWebApiCommonInitialize(CpuContext ctx)
|
||||
{
|
||||
// int Common::initialize(const InitParams&, LibContext&) — 0 on success.
|
||||
TraceCppWebApi("common_initialize", ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi]);
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
private static void TraceCppWebApi(string operation, ulong arg0, ulong arg1)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NP"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] np_cppwebapi.{operation} arg0=0x{arg0:X16} arg1=0x{arg1:X16}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Pad;
|
||||
|
||||
// Minimal libSceBluetoothHid stub: no host Bluetooth passthrough, so report
|
||||
// success and let the Pad path provide input (SHARPEMU_BTHID_UNAVAILABLE=1 fails instead).
|
||||
public static class BluetoothHidExports
|
||||
{
|
||||
private const int BluetoothHidUnavailable = unchecked((int)0x80960001);
|
||||
|
||||
private static readonly bool _reportUnavailable = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_BTHID_UNAVAILABLE"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
private static int Result(CpuContext ctx) =>
|
||||
ctx.SetReturn(_reportUnavailable ? BluetoothHidUnavailable : 0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "tul3-GzejQc",
|
||||
ExportName = "sceBluetoothHidInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceBluetoothHid")]
|
||||
public static int BluetoothHidInit(CpuContext ctx) => Result(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4FUZ+c52d2k",
|
||||
ExportName = "sceBluetoothHidRegisterDevice",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceBluetoothHid")]
|
||||
public static int BluetoothHidRegisterDevice(CpuContext ctx) => Result(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4Ypfo9RIwfM",
|
||||
ExportName = "sceBluetoothHidRegisterCallback",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceBluetoothHid")]
|
||||
public static int BluetoothHidRegisterCallback(CpuContext ctx) => Result(ctx);
|
||||
}
|
||||
@@ -48,7 +48,18 @@ public static class PadExports
|
||||
ExportName = "scePadOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadOpen(CpuContext ctx)
|
||||
public static int PadOpen(CpuContext ctx) => PadOpenCore(ctx, extended: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "WFIiSfXGUq8",
|
||||
ExportName = "scePadOpenExt",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadOpenExt(CpuContext ctx) => PadOpenCore(ctx, extended: true);
|
||||
|
||||
// scePadOpen rejects a non-null 4th arg and non-standard ports; scePadOpenExt accepts a
|
||||
// ScePadOpenExtParam* plus ports 1/2 (racing titles retry scePadOpenExt(type=2) forever if rejected).
|
||||
private static int PadOpenCore(CpuContext ctx, bool extended)
|
||||
{
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var type = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
@@ -64,7 +75,8 @@ public static class PadExports
|
||||
return ctx.SetReturn(OrbisPadErrorDeviceNoHandle);
|
||||
}
|
||||
|
||||
if (userId != PrimaryUserId || type != StandardPortType || index != 0 || parameterAddress != 0)
|
||||
var typeAccepted = extended ? type is 0 or 1 or 2 : type == StandardPortType;
|
||||
if (userId != PrimaryUserId || !typeAccepted || index != 0 || (!extended && parameterAddress != 0))
|
||||
{
|
||||
return ctx.SetReturn(OrbisPadErrorDeviceNotConnected);
|
||||
}
|
||||
@@ -83,6 +95,19 @@ public static class PadExports
|
||||
return ctx.SetReturn(PrimaryPadHandle);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "6ncge5+l5Qs",
|
||||
ExportName = "scePadClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadClose(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
return handle == PrimaryPadHandle
|
||||
? ctx.SetReturn(0)
|
||||
: ctx.SetReturn(OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "clVvL4ZDntw",
|
||||
ExportName = "scePadSetMotionSensorState",
|
||||
@@ -131,6 +156,47 @@ public static class PadExports
|
||||
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "hGbf2QTBmqc",
|
||||
ExportName = "scePadGetExtControllerInformation",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadGetExtControllerInformation(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var informationAddress = ctx[CpuRegister.Rsi];
|
||||
if (handle != PrimaryPadHandle)
|
||||
{
|
||||
return ctx.SetReturn(OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
if (informationAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
// Base ScePadControllerInformation + device-class/connection fields: report a connected
|
||||
// DualSense so the guest's open -> get-ext-info -> close probe loop resolves.
|
||||
Span<byte> information = stackalloc byte[0x40];
|
||||
information.Clear();
|
||||
BinaryPrimitives.WriteSingleLittleEndian(information[0x00..], 44.86f);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(information[0x04..], 1920);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(information[0x06..], 943);
|
||||
information[0x08] = 30;
|
||||
information[0x09] = 30;
|
||||
information[0x0A] = StandardPortType;
|
||||
information[0x0B] = 1; // connected count
|
||||
information[0x0C] = 1; // connected
|
||||
BinaryPrimitives.WriteInt32LittleEndian(information[0x10..], 0);
|
||||
information[0x1C] = 0; // deviceClass: 0 = standard controller / DualSense
|
||||
information[0x1D] = 1; // connected (ext)
|
||||
information[0x1E] = 0; // connectionType: local
|
||||
|
||||
return ctx.Memory.TryWrite(informationAddress, information)
|
||||
? ctx.SetReturn(0)
|
||||
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "YndgXqQVV7c",
|
||||
ExportName = "scePadReadState",
|
||||
|
||||
@@ -185,6 +185,31 @@ public static class UserServiceExports
|
||||
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "woNpu+45RLk",
|
||||
ExportName = "sceUserServiceGetAgeLevel",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceUserService")]
|
||||
public static int UserServiceGetAgeLevel(CpuContext ctx)
|
||||
{
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var ageLevelAddress = ctx[CpuRegister.Rsi];
|
||||
if (userId != 1000)
|
||||
{
|
||||
return ctx.SetReturn(OrbisUserServiceErrorInvalidParameter);
|
||||
}
|
||||
|
||||
if (ageLevelAddress == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisUserServiceErrorInvalidArgument);
|
||||
}
|
||||
|
||||
// Report an adult account so titles skip parental-restriction paths.
|
||||
return ctx.TryWriteInt32(ageLevelAddress, 21)
|
||||
? ctx.SetReturn(0)
|
||||
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
private static void TraceUserService(string message)
|
||||
{
|
||||
if (_traceUserService)
|
||||
|
||||
@@ -403,6 +403,34 @@ public static class VideoOutExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "+I4K03i3EL0",
|
||||
ExportName = "sceVideoOutInitializeOutputOptions",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutInitializeOutputOptions(CpuContext ctx)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w0hLuNarQxY",
|
||||
ExportName = "sceVideoOutConfigureOutput",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutConfigureOutput(CpuContext ctx)
|
||||
{
|
||||
// Accept the requested output configuration; the presenter always renders
|
||||
// at the display buffer's native size.
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
if (!TryGetPort(handle, out _))
|
||||
{
|
||||
return OrbisVideoOutErrorInvalidHandle;
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "utPrVdxio-8",
|
||||
ExportName = "sceVideoOutGetOutputStatus",
|
||||
|
||||
Reference in New Issue
Block a user