Compare commits

...

9 Commits

Author SHA1 Message Date
ParantezTech b522776988 [CPU] optimize strcasecmp for hot path 2026-07-13 12:12:30 +03:00
Digote 4bd42795c7 [logging] Migrate HLE diagnostics to SharpEmuLog (#80)
Signed-off-by: Digote <45742711+Digote@users.noreply.github.com>
2026-07-13 12:11:27 +03:00
Mike Saito c03ca32a02 fix(vfs): harden getdirentries and APR filepath resolution (#77)
* fix(vfs): defer host cursor commit in getdirentries

Follow-up VFS hardening task applying the same guest-writes-first discipline established in the time subsystem to directory enumeration.

Deferred Commit in KernelGetdirentriesCore:

- Reordered guest output so the 512-byte dirent buffer is written first via TryWriteCompat, basep is updated second (when non-null) via TryWriteUInt64Compat, and directory.NextIndex is advanced only after both guest writes succeed.

- Removed the early basep write at method entry that could mutate guest memory before buffer validation and advance the host cursor before a successful dirent delivery, causing permanent entry loss on MEMORY_FAULT at bufferAddress.

- EOF handling: when currentIndex >= Entries.Length, write basep with the final offset and return 0 without mutating NextIndex, matching FreeBSD getdirentries(2) semantics and preventing infinite retry loops.

KernelGetdents path: basePointerAddress is passed as 0, so the transaction collapses to buffer write then host cursor advance with no basep side effect.

Out of scope: coalesced {id, size} writes in sceKernelAprResolveFilepathsToIdsAndFileSizes; NetCtl connected-state stubs.

Files: KernelMemoryCompatExports.cs

* fix(vfs): resolve-first bulk commit in APR filepath resolution

Refactored sceKernelAprResolveFilepathsToIdsAndFileSizes to stop writing ids and sizes into guest memory one element at a time.

- Removed the uint.MaxValue placeholder write at the start of each loop iteration.

- Path resolution and file size lookup now fill host-side buffers first; on EFAULT or NOT_FOUND the guest ids/sizes arrays are left untouched.

- ids and sizes are packed into contiguous byte buffers and written with one TryWriteCompat call per output array instead of separate TryWriteUInt32Compat / TryWriteUInt64Compat per index.

- AmprFileRegistry.Register is called only after guest writes succeed.

- AmprFileRegistry.ComputeFileId is internal so ids can be computed without registering paths during the resolve loop.

Files: KernelMemoryCompatExports.cs, AmprFileRegistry.cs
2026-07-13 12:10:20 +03:00
kuba 6e2878f2ff Add commit hash to video window title (#93) 2026-07-13 11:21:41 +03:00
ParantezTech 2e09b015bc Merge branch 'main' of https://github.com/par274/sharpemu 2026-07-13 01:10:08 +03:00
ParantezTech 7b9efd1539 [readme] update screenshot for dreaming sarah 2026-07-13 01:09:45 +03:00
Kaotic fbf2c2d00a [GUI] Add detachable console window (#91)
Add a Split button to detach the emulator console into a resizable window.

The detached console keeps search, auto-scroll, copy, and clear controls. The main console hides while detached and restores when the split window closes if it was previously open.
2026-07-13 00:16:21 +03:00
kuba 8c1507777c [agc] Reset transparent Chowdren effect-layer fills (#83)
Treat the exact untextured transparent-black premultiplied fill used by Chowdren as an overwrite. This prevents Dreaming Sarah fog and vignette render targets from accumulating across frames; SHARPEMU_DISABLE_TRANSPARENT_FILL_CLEAR=1 restores prior behavior.
2026-07-12 19:26:48 +03:00
Berk 8ebc43e758 [github] Fix placeholder formatting in game compatibility template (#84) 2026-07-12 18:46:03 +03:00
18 changed files with 329 additions and 41 deletions
@@ -88,7 +88,7 @@ body:
attributes:
label: Last Milestone / First Error
description: Paste the **last useful milestone line** or the **first `WARNING`, `ERROR`, or `CRITICAL` line connected to the stop** from the log. `DEBUG`/`TRACE` lines do not count unless a real error follows them.
placeholder: e.g. CpuEngine: native-only OR [ERROR] Native backend FAILED: ...
placeholder: "e.g. CpuEngine: native-only OR [ERROR] Native backend FAILED: ..."
validations:
required: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 101 KiB

+4 -1
View File
@@ -216,7 +216,10 @@
}
},
"sharpemu.hle": {
"type": "Project"
"type": "Project",
"dependencies": {
"SharpEmu.Logging": "[1.0.0, )"
}
},
"sharpemu.libs": {
"type": "Project",
@@ -705,6 +705,7 @@ public sealed partial class DirectExecutionBackend
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
"qvMUCyyaCSI" or // sceKernelAprSubmitCommandBufferAndGetId
"Q2V+iqvjgC0" or // vsnprintf
"AV6ipCNa4Rw" or // strcasecmp
"q1cHNfGycLI" or // scePadRead
"xk0AcarP3V4" or // scePadOpen
"yH17Q6NWtVg" or // sceUserServiceGetEvent
@@ -869,6 +870,7 @@ public sealed partial class DirectExecutionBackend
"WkkeywLJcgU" or // wcslen
"Ovb2dSJOAuE" or // strcmp
"aesyjrHVWy4" or // strncmp
"AV6ipCNa4Rw" or // strcasecmp
"pNtJdE3x49E" or // wcscmp
"fV2xHER+bKE" or // wcscoll
"E8wCoUEbfzk" or // wcsncmp
+1 -1
View File
@@ -140,7 +140,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
LastMilestoneLog = null;
KernelModuleRegistry.Reset();
var image = LoadImage(normalizedEbootPath);
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version);
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version, BuildInfo.CommitSha);
SaveDataExports.ConfigureApplicationInfo(image.TitleId);
LogAppBundleInfo(normalizedEbootPath, image);
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
+4 -1
View File
@@ -65,7 +65,10 @@
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
},
"sharpemu.hle": {
"type": "Project"
"type": "Project",
"dependencies": {
"SharpEmu.Logging": "[1.0.0, )"
}
},
"sharpemu.libs": {
"type": "Project",
+150
View File
@@ -0,0 +1,150 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Threading;
using System.Collections.Specialized;
namespace SharpEmu.GUI;
public sealed class ConsoleWindow : Window
{
private readonly AvaloniaList<LogLine> _sourceLines;
private readonly AvaloniaList<LogLine> _visibleLines = new();
private readonly ListBox _list;
private readonly TextBox _searchBox;
private readonly CheckBox _autoScrollCheck;
public ConsoleWindow(
AvaloniaList<LogLine> lines,
Action clear,
bool autoScroll)
{
_sourceLines = lines;
Title = "SharpEmu Console";
Width = 980;
Height = 620;
MinWidth = 520;
MinHeight = 320;
Background = new SolidColorBrush(Color.Parse("#0D1017"));
Icon = new WindowIcon(AssetLoader.Open(new Uri("avares://SharpEmu.GUI/Assets/SharpEmu.ico")));
_searchBox = new TextBox { Watermark = "Search...", Width = 320, Margin = new Thickness(0, 0, 12, 0) };
_autoScrollCheck = new CheckBox
{
Content = "Auto-scroll",
IsChecked = autoScroll,
FontSize = 12,
Margin = new Thickness(0, 0, 12, 0),
VerticalAlignment = VerticalAlignment.Center,
};
var copyButton = new Button
{
Classes = { "ghost" },
Content = "Copy",
Padding = new Thickness(10, 4),
Margin = new Thickness(0, 0, 8, 0),
};
var clearButton = new Button { Classes = { "ghost" }, Content = "Clear", Padding = new Thickness(10, 4) };
copyButton.Click += async (_, _) => await CopyAsync();
clearButton.Click += (_, _) => clear();
_searchBox.TextChanged += (_, _) => RefreshVisibleLines();
_list = new ListBox
{
Classes = { "console" },
ItemsSource = _visibleLines,
BorderThickness = new Thickness(1),
BorderBrush = new SolidColorBrush(Color.Parse("#232B3A")),
ItemTemplate = new FuncDataTemplate<LogLine>((_, _) =>
{
var text = new TextBlock { TextWrapping = TextWrapping.NoWrap };
text.Bind(TextBlock.TextProperty, new Binding(nameof(LogLine.Text)));
text.Bind(TextBlock.ForegroundProperty, new Binding(nameof(LogLine.Brush)));
return text;
}),
};
Content = new Grid
{
Margin = new Thickness(12),
RowDefinitions = new RowDefinitions("Auto,*"),
Children =
{
new Grid
{
Margin = new Thickness(0, 0, 0, 8),
ColumnDefinitions = new ColumnDefinitions("*,Auto,Auto,Auto,Auto"),
Children =
{
new TextBlock
{
Classes = { "sectionTitle" },
Text = "CONSOLE",
VerticalAlignment = VerticalAlignment.Center,
},
_searchBox.WithGridColumn(1),
_autoScrollCheck.WithGridColumn(2),
copyButton.WithGridColumn(3),
clearButton.WithGridColumn(4),
},
},
_list.WithGridRow(1),
},
};
lines.CollectionChanged += OnLinesChanged;
Closed += (_, _) => lines.CollectionChanged -= OnLinesChanged;
RefreshVisibleLines();
}
private void OnLinesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
RefreshVisibleLines();
if (_autoScrollCheck.IsChecked == true)
{
Dispatcher.UIThread.Post(() => (_list.Scroll as ScrollViewer)?.ScrollToEnd());
}
}
private void RefreshVisibleLines()
{
var query = _searchBox.Text ?? string.Empty;
_visibleLines.Clear();
_visibleLines.AddRange(string.IsNullOrWhiteSpace(query)
? _sourceLines
: _sourceLines.Where(line => line.Text.Contains(query, StringComparison.OrdinalIgnoreCase)));
}
private async Task CopyAsync()
{
if (_visibleLines.Count == 0 || Clipboard is null)
{
return;
}
await Clipboard.SetTextAsync(string.Join(Environment.NewLine, _visibleLines.Select(line => line.Text)));
}
}
file static class GridExtensions
{
public static T WithGridColumn<T>(this T control, int column) where T : Control
{
Grid.SetColumn(control, column);
return control;
}
public static T WithGridRow<T>(this T control, int row) where T : Control
{
Grid.SetRow(control, row);
return control;
}
}
+7 -5
View File
@@ -160,15 +160,17 @@ SPDX-License-Identifier: GPL-2.0-or-later
<Border Grid.Row="2" x:Name="ConsolePanel" Classes="card" Padding="0" Height="240"
Margin="0,12,0,0" IsVisible="False">
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto, Auto" Margin="16,12,16,8">
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto" Margin="16,12,16,8">
<TextBlock Classes="sectionTitle" Text="CONSOLE" VerticalAlignment="Center" />
<TextBox Grid.Column="1" FontSize="12" Margin="0,0,12,0" x:Name="ConsoleSearchBox"
Watermark="Search..." MaxWidth="5500" />
<TextBox Grid.Column="1" FontSize="12" Margin="0,0,12,0" x:Name="ConsoleSearchBox"
Watermark="Search..." Width="320" />
<CheckBox Grid.Column="2" x:Name="AutoScrollCheck" Content="Auto-scroll" IsChecked="True"
FontSize="12" Margin="0,0,12,0" />
<Button Grid.Column="3" x:Name="CopyLogButton" Classes="ghost" Content="Copy" FontSize="12"
<Button Grid.Column="3" x:Name="DetachConsoleButton" Classes="ghost" Content="Split" FontSize="12"
Padding="10,4" Margin="0,0,8,0" />
<Button Grid.Column="4" x:Name="ClearLogButton" Classes="ghost" Content="Clear" FontSize="12"
<Button Grid.Column="4" x:Name="CopyLogButton" Classes="ghost" Content="Copy" FontSize="12"
Padding="10,4" Margin="0,0,8,0" />
<Button Grid.Column="5" x:Name="ClearLogButton" Classes="ghost" Content="Clear" FontSize="12"
Padding="10,4" />
</Grid>
<ListBox Grid.Row="1" x:Name="ConsoleList" Classes="console" BorderThickness="0,1,0,0"
+28 -1
View File
@@ -42,6 +42,7 @@ public partial class MainWindow : Window
private GuiSettings _settings = new();
private EmulatorProcess? _emulator;
private ConsoleWindow? _consoleWindow;
private StreamWriter? _fileLog;
private readonly SndPreviewPlayer _sndPreview = new();
private string? _emulatorExePath;
@@ -98,8 +99,9 @@ public partial class MainWindow : Window
StopButton.Click += (_, _) => StopEmulator();
ClearLogButton.Click += (_, _) => _consoleLines.Clear();
CopyLogButton.Click += async (_, _) => await CopyConsoleAsync();
DetachConsoleButton.Click += (_, _) => ShowConsoleWindow();
OptionsToggle.IsCheckedChanged += (_, _) => OptionsPanel.IsVisible = OptionsToggle.IsChecked == true;
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true;
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null;
SelectLogFilePathButton.Click += async (_, _) => await SelectFilePathAsync();
TitleMusicToggle.IsCheckedChanged += (_, _) => OnTitleMusicToggled();
DiscordToggle.IsCheckedChanged += (_, _) =>
@@ -329,6 +331,7 @@ public partial class MainWindow : Window
_gamepadTimer.Stop();
_sndPreview.Stop();
_discord?.Dispose();
_consoleWindow?.Close();
_emulator?.Dispose();
DropFileLog();
}
@@ -1470,4 +1473,28 @@ public partial class MainWindow : Window
var text = string.Join(Environment.NewLine, _consoleLines.Select(line => line.Text));
await Clipboard.SetTextAsync(text);
}
private void ShowConsoleWindow()
{
if (_consoleWindow is { } window)
{
window.Activate();
return;
}
ConsoleSearchBox.Text = string.Empty;
ConsoleToggle.IsChecked = false;
ConsolePanel.IsVisible = false;
_consoleWindow = new ConsoleWindow(
_consoleLines,
() => { _consoleLines.Clear(); _allConsoleLines.Clear(); },
AutoScrollCheck.IsChecked == true);
_consoleWindow.Closed += (_, _) =>
{
_consoleWindow = null;
ConsoleToggle.IsChecked = true;
ConsolePanel.IsVisible = true;
};
_consoleWindow.Show(this);
}
}
+6 -4
View File
@@ -3,11 +3,13 @@
using System.Buffers.Binary;
using System.Linq;
using SharpEmu.Logging;
namespace SharpEmu.HLE;
public sealed class Aerolib : ISymbolCatalog
{
private static readonly SharpEmuLogger Log = SharpEmuLog.For("Aerolib");
private static readonly Lazy<Aerolib> _instance = new(() => new Aerolib());
private static readonly Aerolib EmptyCatalog = new Aerolib(empty: true);
@@ -108,14 +110,14 @@ public sealed class Aerolib : ISymbolCatalog
if (resourceName == null)
{
Console.Error.WriteLine("[AEROLIB] Embedded resource 'aerolib.bin' not found");
Log.Error("Embedded resource 'aerolib.bin' not found");
return;
}
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
Console.Error.WriteLine("[AEROLIB] Failed to open embedded resource stream");
Log.Error("Failed to open embedded resource stream");
return;
}
@@ -145,11 +147,11 @@ public sealed class Aerolib : ISymbolCatalog
_byExportName[name] = symbol;
}
Console.Error.WriteLine($"[AEROLIB] Loaded {_byNid.Count} NID entries from binary resource");
Log.Info($"Loaded {_byNid.Count} NID entries from binary resource");
}
catch (Exception ex)
{
Console.Error.WriteLine($"[AEROLIB] Failed to load embedded aerolib.bin: {ex.Message}");
Log.Error($"Failed to load embedded aerolib.bin: {ex.Message}", ex);
}
}
+6 -3
View File
@@ -3,11 +3,14 @@
using System.Collections.Concurrent;
using System.Reflection;
using SharpEmu.Logging;
namespace SharpEmu.HLE;
public sealed class ModuleManager : IModuleManager
{
private static readonly SharpEmuLogger Log = SharpEmuLog.For("HLE");
private readonly ConcurrentDictionary<string, Delegate> _dispatchTable = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ExportedFunction> _exportTable = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ExportedFunction> _exportNameTable = new(StringComparer.Ordinal);
@@ -47,7 +50,7 @@ public sealed class ModuleManager : IModuleManager
var handler = CreateHandler(type, method, instances);
if (!_dispatchTable.TryAdd(exportInfo.Value.Nid, handler))
{
Console.Error.WriteLine($"[HLE] Duplicate NID '{exportInfo.Value.Nid}' ({exportInfo.Value.ExportName}) — already registered, skipping.");
Log.Warning($"Duplicate NID '{exportInfo.Value.Nid}' ({exportInfo.Value.ExportName}) — already registered, skipping.");
continue;
}
@@ -106,7 +109,7 @@ public sealed class ModuleManager : IModuleManager
if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export))
{
Console.Error.WriteLine($"[HLE] NID '{nid}' not found in dispatch table.");
Log.Warning($"NID '{nid}' not found in dispatch table.");
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
return false;
@@ -114,7 +117,7 @@ public sealed class ModuleManager : IModuleManager
if ((export.Target & context.TargetGeneration) == 0)
{
Console.Error.WriteLine($"[HLE] NID '{nid}' ({export.Name}) found but not implemented for generation {context.TargetGeneration} (targets: {export.Target}).");
Log.Warning($"NID '{nid}' ({export.Name}) found but not implemented for generation {context.TargetGeneration} (targets: {export.Target}).");
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED);
result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED;
return false;
+4
View File
@@ -12,6 +12,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SharpEmu.Logging\SharpEmu.Logging.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Aerolib\aerolib.bin" />
</ItemGroup>
+5 -1
View File
@@ -1,6 +1,10 @@
{
"version": 2,
"dependencies": {
"net10.0": {}
"net10.0": {
"sharpemu.logging": {
"type": "Project"
}
}
}
}
+51 -1
View File
@@ -3296,10 +3296,60 @@ public static class AgcExports
globalMemoryBindings,
vertexInputs,
renderTargets,
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()));
ApplyTransparentPremultipliedFillClear(
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()),
textures,
vertexInputs,
pixelEvaluation.InitialScalarRegisters));
return true;
}
private static readonly bool _transparentFillClearEnabled = !string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_TRANSPARENT_FILL_CLEAR"),
"1",
StringComparison.Ordinal);
/// <summary>
/// Chowdren resets its effect layers with an untextured transparent-black
/// fill using premultiplied blending. With One/OneMinusSrcAlpha that draw
/// is otherwise a no-op, causing fog and vignette layers to accumulate.
/// Treat precisely that draw shape as an overwrite.
/// </summary>
private static VulkanGuestRenderState ApplyTransparentPremultipliedFillClear(
VulkanGuestRenderState renderState,
IReadOnlyList<TranslatedImageBinding> textures,
IReadOnlyList<Gen5VertexInputBinding> vertexInputs,
IReadOnlyList<uint> pixelUserData)
{
if (!_transparentFillClearEnabled ||
textures.Count != 0 ||
vertexInputs.Count != 0 ||
pixelUserData.Count < 4 ||
renderState.Blend is not
{
Enable: true,
ColorSrcFactor: 1,
ColorDstFactor: 5,
ColorFunc: 0,
})
{
return renderState;
}
for (var index = 0; index < 4; index++)
{
if ((pixelUserData[index] & 0x7FFF_FFFFu) != 0)
{
return renderState;
}
}
return renderState with
{
Blend = renderState.Blend with { Enable = false },
};
}
private static VulkanGuestIndexBuffer? CreateVulkanIndexBuffer(
CpuContext ctx,
SubmittedDcbState state,
+1 -1
View File
@@ -21,7 +21,7 @@ internal static class AmprFileRegistry
return _hostPathsById.TryGetValue(id, out hostPath!);
}
private static uint ComputeFileId(string guestPath)
internal static uint ComputeFileId(string guestPath)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(guestPath);
@@ -1689,15 +1689,15 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var entryCount = (int)count;
Span<uint> localIds = count <= 256 ? stackalloc uint[entryCount] : new uint[entryCount];
Span<ulong> localSizes = count <= 128 ? stackalloc ulong[entryCount] : new ulong[entryCount];
var resolvedGuestPaths = new string[entryCount];
var resolvedHostPaths = new string[entryCount];
for (ulong i = 0; i < count; i++)
{
if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), uint.MaxValue))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var index = (int)i;
if (!TryResolveAprFilepath(ctx, pathListAddress, i, out var guestPath))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
@@ -1712,23 +1712,47 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var fileId = AmprFileRegistry.Register(guestPath, hostPath);
var fileId = AmprFileRegistry.ComputeFileId(guestPath);
LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8} size={fileSize}");
if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
localIds[index] = fileId;
localSizes[index] = fileSize;
resolvedGuestPaths[index] = guestPath;
resolvedHostPaths[index] = hostPath;
}
Span<byte> sizePayload = count <= 64 ? stackalloc byte[entryCount * sizeof(ulong)] : new byte[entryCount * sizeof(ulong)];
for (ulong i = 0; i < count; i++)
{
BinaryPrimitives.WriteUInt64LittleEndian(sizePayload[(int)(i * sizeof(ulong))..], localSizes[(int)i]);
}
if (!TryWriteCompat(ctx, sizesAddress, sizePayload))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (idsAddress != 0)
{
Span<byte> idPayload = count <= 128 ? stackalloc byte[entryCount * sizeof(uint)] : new byte[entryCount * sizeof(uint)];
for (ulong i = 0; i < count; i++)
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
BinaryPrimitives.WriteUInt32LittleEndian(idPayload[(int)(i * sizeof(uint))..], localIds[(int)i]);
}
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
if (!TryWriteCompat(ctx, idsAddress, idPayload))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
for (var i = 0; i < entryCount; i++)
{
AmprFileRegistry.Register(resolvedGuestPaths[i], resolvedHostPaths[i]);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -6350,19 +6374,19 @@ public static class KernelMemoryCompatExports
}
var currentIndex = directory.NextIndex;
if (basePointerAddress != 0 && !TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (currentIndex >= directory.Entries.Length)
{
if (basePointerAddress != 0 &&
!TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var entryName = directory.Entries[currentIndex];
directory.NextIndex = currentIndex + 1;
var entryBytes = Encoding.UTF8.GetBytes(entryName);
var nameLength = Math.Min(entryBytes.Length, 255);
@@ -6381,6 +6405,13 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (basePointerAddress != 0 &&
!TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
directory.NextIndex = currentIndex + 1;
ctx[CpuRegister.Rax] = 512;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -64,7 +64,7 @@ public static class VideoOutExports
private static long _vblankSignalCount;
private static long _flipSubmitCount;
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version, string? emulatorCommitSha)
{
var parts = new List<string>();
if (!string.IsNullOrWhiteSpace(title))
@@ -79,9 +79,10 @@ public static class VideoOutExports
var application = parts.Count == 0 ? "VideoOut" : string.Join(' ', parts);
var versionSuffix = string.IsNullOrWhiteSpace(version) ? string.Empty : $" v{version.Trim()}";
var commitSuffix = string.IsNullOrWhiteSpace(emulatorCommitSha) ? string.Empty : $" \u00b7 {emulatorCommitSha.Trim()}";
lock (_stateGate)
{
_windowTitle = $"SharpEmu - {application}{versionSuffix}";
_windowTitle = $"SharpEmu{commitSuffix} - {application}{versionSuffix}";
}
}
+6
View File
@@ -98,6 +98,12 @@
"contentHash": "Iy22JopynbOJ32vA0lBhFEzGi65GQJBuJHYBYRBpydrDpNoTiHnjIXfA65Gu+8qsOr/ZEoIF8r9aHCgAXuO6DA=="
},
"sharpemu.hle": {
"type": "Project",
"dependencies": {
"SharpEmu.Logging": "[1.0.0, )"
}
},
"sharpemu.logging": {
"type": "Project"
}
}