Gran Turismo 7 Fixes, AvPlayer, Razor CPU stubs, Net stubs (#927)
* [AvPlayer] improved video playback sync and stats * [AvPlayer] enabled threaded video decoding * [Fiber] fixed current fiber address query * [Font] added bitmap glyph rendering * [GPU] added unsigned scaled texture sampling * [Network] added socket receive and resolver support * [NpManager] added offline signup status * [NpSessionSignaling] added signed-out request stubs * [SigninDialog] added dialog stubs * [TextToSpeech] added initialization stubs * [SystemService] added music player disable stub * [VideoOut] added VRR stubs * [RazorCpu] added marker stubs * [Fiber] updated current fiber address test * [Pad] Added scePadGetFeatureReport stub * [Pad] For now, extra controller definitions should remain as stubs * [Runtime] Module loader correct order: libc > libSceNpCppWebApi > libScePfs > logiWheel * [SystemService] Added sceSystemServiceReenableMusicPlayer stub * [VideoOut] Add sceVideoOutVrrUnpegFromFixedRate stub * [Cpu] Reuse exited guest thread stack and TLS regions * [Kernel] Isolate temp0 storage per game process
This commit is contained in:
parent
50198a694b
commit
d0dd3fe102
|
|
@ -661,18 +661,30 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||
finally
|
||||
{
|
||||
GuestThreadExecution.RestoreGuestThread(previousGuestThreadHandle);
|
||||
_workAvailable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestStop()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_stopping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_stopping = true;
|
||||
_workAvailable.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stopping = true;
|
||||
_workAvailable.Set();
|
||||
RequestStop();
|
||||
if (!ReferenceEquals(Thread.CurrentThread, _thread))
|
||||
{
|
||||
_thread.Join(500);
|
||||
}
|
||||
_workAvailable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,6 +729,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||
private int _readyGuestThreadCount;
|
||||
|
||||
private readonly Dictionary<ulong, GuestThreadState> _guestThreads = new Dictionary<ulong, GuestThreadState>();
|
||||
private readonly Queue<(IVirtualMemory Memory, ulong StackBase, ulong StackSize, ulong TlsBase)> _reusableGuestThreadRegions = new();
|
||||
|
||||
private readonly Dictionary<ulong, ExternalGuestThreadState> _externalGuestThreads = new Dictionary<ulong, ExternalGuestThreadState>();
|
||||
|
||||
|
|
@ -5342,6 +5355,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||
_readyGuestThreads.Clear();
|
||||
Interlocked.Exchange(ref _readyGuestThreadCount, 0);
|
||||
_guestThreads.Clear();
|
||||
_reusableGuestThreadRegions.Clear();
|
||||
_externalGuestThreads.Clear();
|
||||
_pendingGuestExceptions.Clear();
|
||||
Volatile.Write(ref _pendingGuestExceptionCount, 0);
|
||||
|
|
@ -5380,13 +5394,50 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||
stackSize = GuestThreadStackSize;
|
||||
}
|
||||
|
||||
if (!TryMapGuestThreadRegion(virtualMemory, GuestThreadStackBaseAddress, stackSize, ProgramHeaderFlags.Read | ProgramHeaderFlags.Write, out var stackBase, out error))
|
||||
ulong stackBase = 0;
|
||||
ulong tlsBase = 0;
|
||||
lock (_guestThreadGate)
|
||||
{
|
||||
return false;
|
||||
var count = _reusableGuestThreadRegions.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var region = _reusableGuestThreadRegions.Dequeue();
|
||||
if (stackBase == 0 && ReferenceEquals(region.Memory, virtualMemory) && region.StackSize >= stackSize)
|
||||
{
|
||||
stackBase = region.StackBase;
|
||||
stackSize = region.StackSize;
|
||||
tlsBase = region.TlsBase;
|
||||
}
|
||||
else
|
||||
{
|
||||
_reusableGuestThreadRegions.Enqueue(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!TryMapGuestThreadTlsRegion(virtualMemory, out var tlsBase, out error))
|
||||
if (stackBase == 0)
|
||||
{
|
||||
return false;
|
||||
if (!TryMapGuestThreadRegion(virtualMemory, GuestThreadStackBaseAddress, stackSize, ProgramHeaderFlags.Read | ProgramHeaderFlags.Write, out stackBase, out error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!TryMapGuestThreadTlsRegion(virtualMemory, out tlsBase, out error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> zero = stackalloc byte[4096];
|
||||
zero.Clear();
|
||||
var start = tlsBase - GuestThreadTlsPrefixSize;
|
||||
for (ulong offset = 0; offset < GuestThreadTlsPrefixSize + GuestThreadTlsSize; offset += (ulong)zero.Length)
|
||||
{
|
||||
if (!virtualMemory.TryWrite(start + offset, zero[..(int)Math.Min((ulong)zero.Length, GuestThreadTlsPrefixSize + GuestThreadTlsSize - offset)]))
|
||||
{
|
||||
error = "failed to reset reused guest TLS";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var trackedMemory = new TrackedCpuMemory(virtualMemory);
|
||||
|
|
@ -5851,6 +5902,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||
{
|
||||
pendingAfterExecutorRelease = pending;
|
||||
}
|
||||
if (thread.State == GuestThreadRunState.Exited &&
|
||||
TryGetVirtualMemory(thread.Context, out var memory))
|
||||
{
|
||||
_reusableGuestThreadRegions.Enqueue((memory, thread.StackBase, thread.StackSize, thread.Context.FsBase));
|
||||
thread.ExecutionRunner?.RequestStop();
|
||||
}
|
||||
}
|
||||
if (pendingAfterExecutorRelease is { } pendingException &&
|
||||
!TryRaiseGuestException(
|
||||
|
|
|
|||
|
|
@ -667,14 +667,15 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
|||
|
||||
var moduleDirectories = new[]
|
||||
{
|
||||
// A linked PRX can be beside eboot.bin. Load only the app-root modules
|
||||
// named by the main image at boot. Map the other app-root modules so
|
||||
// sceKernelLoadStartModule can start them later.
|
||||
(Path: ebootDirectory, StartAtBoot: true, LinkedOnly: true, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
(Path: ebootDirectory, StartAtBoot: false, LinkedOnly: false, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
(Path: Path.Combine(ebootDirectory, "sce_module"), StartAtBoot: true, LinkedOnly: false, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
(Path: Path.Combine(ebootDirectory, "sce_modules"), StartAtBoot: true, LinkedOnly: false, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
(Path: Path.Combine(ebootDirectory, "Media", "Modules"), StartAtBoot: true, LinkedOnly: false, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
// A linked PRX can be beside eboot.bin. Load only the app-root modules
|
||||
// named by the main image at boot, after the normal module directories
|
||||
// they may depend on. Map the other app-root modules so
|
||||
// sceKernelLoadStartModule can start them later.
|
||||
(Path: ebootDirectory, StartAtBoot: true, LinkedOnly: true, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
(Path: ebootDirectory, StartAtBoot: false, LinkedOnly: false, SearchOption: SearchOption.TopDirectoryOnly),
|
||||
// Unity native plugins are loaded later through sceKernelLoadStartModule. Map
|
||||
// them up front so the HLE loader can return a real module handle and dlsym
|
||||
// can resolve their exports, but defer DT_INIT until the guest requests them.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
|
@ -111,7 +112,7 @@ public static class AppContentExports
|
|||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(ResolveTemp0Root());
|
||||
Directory.CreateDirectory(KernelMemoryCompatExports.ResolveTemp0Root());
|
||||
var mountPointBytes = Encoding.ASCII.GetBytes($"{Temp0MountPoint}\0");
|
||||
if (!ctx.Memory.TryWrite(mountPointAddress, mountPointBytes))
|
||||
{
|
||||
|
|
@ -163,7 +164,7 @@ public static class AppContentExports
|
|||
{
|
||||
try
|
||||
{
|
||||
var storageRoot = ResolveTemp0Root();
|
||||
var storageRoot = KernelMemoryCompatExports.ResolveTemp0Root();
|
||||
Directory.CreateDirectory(storageRoot);
|
||||
var volumeRoot = Path.GetPathRoot(Path.GetFullPath(storageRoot));
|
||||
if (!string.IsNullOrWhiteSpace(volumeRoot))
|
||||
|
|
@ -240,28 +241,4 @@ public static class AppContentExports
|
|||
Console.Error.WriteLine($"[LOADER][TRACE] app_content.{message}");
|
||||
}
|
||||
|
||||
private static string ResolveTemp0Root()
|
||||
{
|
||||
const string temp0VariableName = "SHARPEMU_TEMP0_DIR";
|
||||
var configuredRoot = Environment.GetEnvironmentVariable(temp0VariableName);
|
||||
if (!string.IsNullOrWhiteSpace(configuredRoot))
|
||||
{
|
||||
return Path.GetFullPath(configuredRoot);
|
||||
}
|
||||
|
||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||
var appName = string.IsNullOrWhiteSpace(app0Root)
|
||||
? "default"
|
||||
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
|
||||
if (string.IsNullOrWhiteSpace(appName))
|
||||
{
|
||||
appName = "default";
|
||||
}
|
||||
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
|
||||
var root = Path.Combine(AppContext.BaseDirectory, "user", "temp", appName, "temp0");
|
||||
Environment.SetEnvironmentVariable(temp0VariableName, root);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,6 +351,8 @@ public static class AvPlayerExports
|
|||
: VideoFrameReadResult.Pending;
|
||||
}
|
||||
|
||||
public int QueuedFrameCount => _frames.Reader.Count;
|
||||
|
||||
private void DecodeFrames()
|
||||
{
|
||||
try
|
||||
|
|
@ -456,6 +458,12 @@ public static class AvPlayerExports
|
|||
public ulong LastVideoTimestamp { get; set; }
|
||||
public ulong EventDataBuffer { get; set; }
|
||||
public long NextFrameIndex { get; set; }
|
||||
public long VideoCalls { get; set; }
|
||||
public long VideoDelivered { get; set; }
|
||||
public long VideoPending { get; set; }
|
||||
public long VideoSyncWait { get; set; }
|
||||
public long VideoWriteFailures { get; set; }
|
||||
public long VideoStatsSecond { get; set; } = -1;
|
||||
public ulong AudioBufferBase { get; set; }
|
||||
public int NextAudioBuffer { get; set; }
|
||||
public long NextAudioFrameIndex { get; set; }
|
||||
|
|
@ -498,6 +506,12 @@ public static class AvPlayerExports
|
|||
DisposePlaybackResources();
|
||||
PlaybackClock.Reset();
|
||||
NextFrameIndex = 0;
|
||||
VideoCalls = 0;
|
||||
VideoDelivered = 0;
|
||||
VideoPending = 0;
|
||||
VideoSyncWait = 0;
|
||||
VideoWriteFailures = 0;
|
||||
VideoStatsSecond = -1;
|
||||
LastGuestBuffer = 0;
|
||||
LastVideoTimestamp = 0;
|
||||
SeekVideoFramePending = false;
|
||||
|
|
@ -793,6 +807,7 @@ public static class AvPlayerExports
|
|||
player.PlaybackClock.Stop();
|
||||
player.FallbackPlayback?.Pause();
|
||||
Console.Error.WriteLine($"[AVPLAYER][INFO] pause handle=0x{player.Handle:X16}");
|
||||
TraceVideoStats(player);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1619,6 +1634,14 @@ public static class AvPlayerExports
|
|||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
player.VideoCalls++;
|
||||
var statsSecond = player.PlaybackClock.ElapsedMilliseconds / 1000;
|
||||
if (statsSecond > player.VideoStatsSecond)
|
||||
{
|
||||
player.VideoStatsSecond = statsSecond;
|
||||
TraceVideoStats(player);
|
||||
}
|
||||
|
||||
if (player.Paused)
|
||||
{
|
||||
if (player.SeekVideoFramePending &&
|
||||
|
|
@ -1658,47 +1681,26 @@ public static class AvPlayerExports
|
|||
}
|
||||
|
||||
var fps = Math.Max(1.0, player.FramesPerSecond);
|
||||
var expectedFrame = player.NextFrameIndex;
|
||||
if (player.AvSyncMode == AvSyncModeDefault)
|
||||
{
|
||||
var expectedFrame = CalculateExpectedDefaultSyncVideoFrame(
|
||||
expectedFrame = CalculateExpectedDefaultSyncVideoFrame(
|
||||
player.HasAudio,
|
||||
player.NextAudioFrameIndex,
|
||||
player.PlaybackClock.Elapsed.TotalSeconds,
|
||||
fps);
|
||||
if (player.NextFrameIndex > expectedFrame)
|
||||
{
|
||||
player.VideoSyncWait++;
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
var behind = expectedFrame - player.NextFrameIndex;
|
||||
if (behind > MaxCatchUpFrames)
|
||||
{
|
||||
// Limit this call's work without changing the shared playback clock.
|
||||
expectedFrame = player.NextFrameIndex + MaxCatchUpFrames;
|
||||
TraceOnce(
|
||||
"catch_up_capped",
|
||||
$"catch_up capped behind={behind} max={MaxCatchUpFrames} " +
|
||||
$"fps={fps:F3} {player.Width}x{player.Height}");
|
||||
}
|
||||
|
||||
while (player.NextFrameIndex < expectedFrame)
|
||||
{
|
||||
var skippedFrame = ReadFrame(player);
|
||||
if (skippedFrame == VideoFrameReadResult.Pending)
|
||||
{
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
if (skippedFrame == VideoFrameReadResult.End)
|
||||
{
|
||||
return FinishStream(ctx, player);
|
||||
}
|
||||
player.NextFrameIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
var frameResult = ReadFrame(player);
|
||||
if (frameResult == VideoFrameReadResult.Pending)
|
||||
{
|
||||
player.VideoPending++;
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
if (frameResult == VideoFrameReadResult.End)
|
||||
|
|
@ -1707,9 +1709,23 @@ public static class AvPlayerExports
|
|||
}
|
||||
|
||||
var frameIndex = player.NextFrameIndex;
|
||||
// Only discard old frames when a replacement is already decoded.
|
||||
for (var skipped = 0;
|
||||
skipped < MaxCatchUpFrames &&
|
||||
player.NextFrameIndex < expectedFrame &&
|
||||
player.VideoDecoder!.QueuedFrameCount > 1;
|
||||
skipped++)
|
||||
{
|
||||
player.NextFrameIndex++;
|
||||
if (ReadFrame(player) != VideoFrameReadResult.Ready)
|
||||
{
|
||||
break;
|
||||
}
|
||||
frameIndex = player.NextFrameIndex;
|
||||
}
|
||||
var timestamp = player.StartTimeMilliseconds +
|
||||
checked((ulong)Math.Round(frameIndex * 1000.0 / fps));
|
||||
player.NextFrameIndex++;
|
||||
player.NextFrameIndex = frameIndex + 1;
|
||||
if (!WriteVideoFrame(
|
||||
ctx,
|
||||
player,
|
||||
|
|
@ -1718,15 +1734,28 @@ public static class AvPlayerExports
|
|||
frameIndex,
|
||||
extended))
|
||||
{
|
||||
player.VideoWriteFailures++;
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
player.LastVideoTimestamp = timestamp;
|
||||
player.VideoDelivered++;
|
||||
|
||||
Trace($"video_frame handle=0x{player.Handle:X16} ex={extended} ts={timestamp} data=0x{player.LastGuestBuffer:X16}");
|
||||
return SetReturn(ctx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TraceVideoStats(PlayerState player)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[AVPLAYER][INFO] video_stats handle=0x{player.Handle:X16} " +
|
||||
$"clock_ms={player.PlaybackClock.ElapsedMilliseconds} audio_blocks={player.NextAudioFrameIndex} " +
|
||||
$"calls={player.VideoCalls} delivered={player.VideoDelivered} " +
|
||||
$"pending={player.VideoPending} sync_wait={player.VideoSyncWait} " +
|
||||
$"write_fail={player.VideoWriteFailures} next_frame={player.NextFrameIndex} " +
|
||||
$"queued={player.VideoDecoder?.QueuedFrameCount ?? 0} last_ts={player.LastVideoTimestamp}");
|
||||
}
|
||||
|
||||
private static int FinishStream(CpuContext ctx, PlayerState player)
|
||||
{
|
||||
TraceOnce(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Diagnostics;
|
||||
|
||||
public static class RazorCpuExports
|
||||
{
|
||||
[SysAbiExport(Nid = "zw+celG7zSI", ExportName = "sceRazorCpuPushMarker",
|
||||
Target = Generation.Gen5, LibraryName = "libSceRazorCpu")]
|
||||
public static int PushMarker(CpuContext ctx) => ctx.SetReturn(0);
|
||||
|
||||
[SysAbiExport(Nid = "YpkGsMXP3ew", ExportName = "sceRazorCpuPopMarker",
|
||||
Target = Generation.Gen5, LibraryName = "libSceRazorCpu")]
|
||||
public static int PopMarker(CpuContext ctx) => ctx.SetReturn(0);
|
||||
}
|
||||
|
|
@ -306,13 +306,7 @@ public static class FiberExports
|
|||
return SetReturn(ctx, FiberErrorNull);
|
||||
}
|
||||
|
||||
var fiberAddress = ResolveCurrentFiberAddress(ctx);
|
||||
if (fiberAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, FiberErrorPermission);
|
||||
}
|
||||
|
||||
return TryWriteUInt64(ctx, outAddress, fiberAddress)
|
||||
return TryWriteUInt64(ctx, outAddress, ResolveCurrentFiberAddress(ctx))
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, FiberErrorInvalid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Libs.Font;
|
||||
|
|
@ -16,6 +18,8 @@ public static class FontExports
|
|||
private static readonly Stack<ulong> FreeGlyphs = new();
|
||||
private static ulong _librarySelectionAddress;
|
||||
private static ulong _rendererSelectionAddress;
|
||||
private static readonly ConcurrentDictionary<ulong, float> FontHeights = new();
|
||||
private static readonly Lazy<byte[]?> BitmapFont = new(LoadBitmapFont);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "whrS4oksXc4",
|
||||
|
|
@ -98,7 +102,7 @@ public static class FontExports
|
|||
ExportName = "sceFontSetScalePixel",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceFont")]
|
||||
public static int SetScalePixel(CpuContext ctx) => SetSuccess(ctx);
|
||||
public static int SetScalePixel(CpuContext ctx) => SetFontHeight(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "TMtqoFQjjbA",
|
||||
|
|
@ -119,7 +123,7 @@ public static class FontExports
|
|||
ExportName = "sceFontSetupRenderScalePixel",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceFont")]
|
||||
public static int SetupRenderScalePixel(CpuContext ctx) => SetSuccess(ctx);
|
||||
public static int SetupRenderScalePixel(CpuContext ctx) => SetFontHeight(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "lz9y9UFO2UU",
|
||||
|
|
@ -288,19 +292,9 @@ public static class FontExports
|
|||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
var values = new[] { 8.0f, 16.0f, 0.0f, 12.0f, 8.0f, 0.0f, 0.0f, 16.0f };
|
||||
for (var index = 0; index < values.Length; index++)
|
||||
{
|
||||
if (!TryWriteUInt32(
|
||||
ctx,
|
||||
metricsAddress + (ulong)(index * sizeof(float)),
|
||||
BitConverter.SingleToUInt32Bits(values[index])))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
|
||||
return SetSuccess(ctx);
|
||||
return WriteGlyphMetrics(ctx, metricsAddress, GetFontHeight(ctx[CpuRegister.Rdi]))
|
||||
? SetSuccess(ctx)
|
||||
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
|
|
@ -367,6 +361,26 @@ public static class FontExports
|
|||
return SetSuccess(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "vRxf4d0ulPs",
|
||||
ExportName = "sceFontRenderSurfaceSetScissor",
|
||||
Target = Generation.Gen5, LibraryName = "libSceFont")]
|
||||
public static int RenderSurfaceSetScissor(CpuContext ctx)
|
||||
{
|
||||
var surfaceAddress = ctx[CpuRegister.Rdi];
|
||||
if (surfaceAddress == 0)
|
||||
{
|
||||
return SetSuccess(ctx);
|
||||
}
|
||||
|
||||
return TryWriteUInt32(ctx, surfaceAddress + 0x18, (uint)ctx[CpuRegister.Rsi]) &&
|
||||
TryWriteUInt32(ctx, surfaceAddress + 0x1C, (uint)ctx[CpuRegister.Rdx]) &&
|
||||
TryWriteUInt32(ctx, surfaceAddress + 0x20, (uint)ctx[CpuRegister.Rcx]) &&
|
||||
TryWriteUInt32(ctx, surfaceAddress + 0x24, (uint)ctx[CpuRegister.R8])
|
||||
? SetSuccess(ctx)
|
||||
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "C-4Qw5Srlyw",
|
||||
ExportName = "sceFontGenerateCharGlyph",
|
||||
|
|
@ -434,37 +448,162 @@ public static class FontExports
|
|||
LibraryName = "libSceFont")]
|
||||
public static int RenderCharGlyphImageHorizontal(CpuContext ctx)
|
||||
{
|
||||
var surfaceAddress = ctx[CpuRegister.Rdx];
|
||||
var metricsAddress = ctx[CpuRegister.Rcx];
|
||||
var resultAddress = ctx[CpuRegister.R8];
|
||||
|
||||
if (metricsAddress != 0)
|
||||
var height = GetFontHeight(ctx[CpuRegister.Rdi]);
|
||||
var width = Math.Clamp((height + 1) / 2, 4, 128);
|
||||
var rows = BitmapFont.Value;
|
||||
if (rows is null)
|
||||
{
|
||||
var values = new[] { 8.0f, 16.0f, 0.0f, 12.0f, 8.0f, 0.0f, 0.0f, 16.0f };
|
||||
for (var index = 0; index < values.Length; index++)
|
||||
{
|
||||
if (!TryWriteUInt32(
|
||||
ctx,
|
||||
metricsAddress + (ulong)(index * sizeof(float)),
|
||||
BitConverter.SingleToUInt32Bits(values[index])))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (metricsAddress != 0 && !WriteGlyphMetrics(ctx, metricsAddress, height))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
ctx.GetXmmRegister(0, out var xBits, out _);
|
||||
ctx.GetXmmRegister(1, out var yBits, out _);
|
||||
var x = BitConverter.UInt32BitsToSingle((uint)xBits);
|
||||
var y = BitConverter.UInt32BitsToSingle((uint)yBits);
|
||||
var left = float.IsFinite(x) ? Math.Max((int)x, 0) : 0;
|
||||
var top = float.IsFinite(y) ? Math.Max((int)(y - height * 0.75f), 0) : 0;
|
||||
|
||||
ulong buffer = 0;
|
||||
uint pitch = 0;
|
||||
uint pixelSize = 0;
|
||||
if (resultAddress != 0)
|
||||
{
|
||||
Span<byte> cleared = stackalloc byte[RenderOutputSize];
|
||||
cleared.Clear();
|
||||
if (!ctx.Memory.TryWrite(resultAddress, cleared))
|
||||
Span<byte> result = stackalloc byte[RenderOutputSize];
|
||||
result.Clear();
|
||||
if (surfaceAddress != 0 && ctx.TryReadUInt64(surfaceAddress, out buffer) &&
|
||||
ctx.TryReadUInt32(surfaceAddress + 8, out pitch) &&
|
||||
ctx.TryReadUInt32(surfaceAddress + 12, out pixelSize))
|
||||
{
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(result[8..], buffer);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[16..], pitch);
|
||||
result[20] = (byte)pixelSize;
|
||||
}
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[24..], (uint)left);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[28..], (uint)top);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[32..], (uint)width);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[36..], (uint)height);
|
||||
BinaryPrimitives.WriteSingleLittleEndian(result[44..], height * 0.75f);
|
||||
BinaryPrimitives.WriteSingleLittleEndian(result[48..], width);
|
||||
BinaryPrimitives.WriteSingleLittleEndian(result[52..], width);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[56..], (uint)width);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result[60..], (uint)height);
|
||||
if (!ctx.Memory.TryWrite(resultAddress, result))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
|
||||
if (surfaceAddress != 0 && !DrawGlyph(ctx, surfaceAddress, (uint)ctx[CpuRegister.Rsi], left, top, width, height, rows))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
return SetSuccess(ctx);
|
||||
}
|
||||
|
||||
private static int SetFontHeight(CpuContext ctx)
|
||||
{
|
||||
ctx.GetXmmRegister(1, out var heightBits, out _);
|
||||
var height = BitConverter.UInt32BitsToSingle((uint)heightBits);
|
||||
if (float.IsFinite(height) && height > 1)
|
||||
{
|
||||
FontHeights[ctx[CpuRegister.Rdi]] = Math.Clamp((int)(height + 0.5f), 8, 128);
|
||||
}
|
||||
return SetSuccess(ctx);
|
||||
}
|
||||
|
||||
private static int GetFontHeight(ulong font) =>
|
||||
FontHeights.TryGetValue(font, out var height) ? (int)height : 16;
|
||||
|
||||
private static bool WriteGlyphMetrics(CpuContext ctx, ulong address, int height)
|
||||
{
|
||||
var width = Math.Clamp((height + 1) / 2, 4, 128);
|
||||
Span<byte> metrics = stackalloc byte[32];
|
||||
float[] values = [width, height, 0, height * 0.75f, width, 0, 0, height];
|
||||
for (var index = 0; index < values.Length; index++)
|
||||
{
|
||||
BinaryPrimitives.WriteSingleLittleEndian(metrics[(index * 4)..], values[index]);
|
||||
}
|
||||
return ctx.Memory.TryWrite(address, metrics);
|
||||
}
|
||||
|
||||
internal static bool DrawGlyph(CpuContext ctx, ulong surface, uint code, int left, int top,
|
||||
int glyphWidth, int glyphHeight, byte[] font)
|
||||
{
|
||||
if (!ctx.TryReadUInt64(surface, out var buffer) ||
|
||||
!ctx.TryReadUInt32(surface + 8, out var pitch) ||
|
||||
!ctx.TryReadUInt32(surface + 12, out var pixelSize) ||
|
||||
!ctx.TryReadUInt32(surface + 16, out var width) ||
|
||||
!ctx.TryReadUInt32(surface + 20, out var height) ||
|
||||
!ctx.TryReadUInt32(surface + 24, out var sx0) ||
|
||||
!ctx.TryReadUInt32(surface + 28, out var sy0) ||
|
||||
!ctx.TryReadUInt32(surface + 32, out var sx1) ||
|
||||
!ctx.TryReadUInt32(surface + 36, out var sy1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pixelSize &= 0xff;
|
||||
if (buffer == 0 || pixelSize is < 1 or > 4 || pitch == 0 || width == 0 || height == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
code = code < 256 ? code : (uint)'?';
|
||||
var x0 = Math.Max(left, (int)Math.Min(sx0, width));
|
||||
var y0 = Math.Max(top, (int)Math.Min(sy0, height));
|
||||
var x1 = Math.Min(left + glyphWidth, (int)Math.Min(sx1, width));
|
||||
var y1 = Math.Min(top + glyphHeight, (int)Math.Min(sy1, height));
|
||||
if (x0 >= x1 || y0 >= y1 || (ulong)x1 * pixelSize > pitch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var line = new byte[(x1 - x0) * pixelSize];
|
||||
for (var yy = y0; yy < y1; yy++)
|
||||
{
|
||||
var address = buffer + (ulong)yy * pitch + (ulong)x0 * pixelSize;
|
||||
if (!ctx.Memory.TryRead(address, line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (var xx = x0; xx < x1; xx++)
|
||||
{
|
||||
var sourceY = (yy - top) * 16 / glyphHeight;
|
||||
var sourceX = (xx - left) * 8 / glyphWidth;
|
||||
if ((font[code * 16 + sourceY] & (0x80 >> sourceX)) != 0)
|
||||
{
|
||||
line.AsSpan((xx - x0) * (int)pixelSize, (int)pixelSize).Fill(0xff);
|
||||
}
|
||||
}
|
||||
if (!ctx.Memory.TryWrite(address, line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[]? LoadBitmapFont()
|
||||
{
|
||||
var library = Path.Combine(AppContext.BaseDirectory, "plugins", "avutil-59.dll");
|
||||
if (!NativeLibrary.TryLoad(library, out var handle) ||
|
||||
!NativeLibrary.TryGetExport(handle, "avpriv_vga16_font", out var data))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var font = new byte[4096];
|
||||
Marshal.Copy(data, font, 0, font.Length);
|
||||
return font;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "vzHs3C8lWJk",
|
||||
ExportName = "sceFontCloseFont",
|
||||
|
|
|
|||
|
|
@ -306,6 +306,7 @@ public static class GuestPixelFormats
|
|||
private static readonly FormatFacts[] Facts =
|
||||
[
|
||||
new(GuestPixelFormat.Bits8UNorm, 1, 0, 1, true, false),
|
||||
new(GuestPixelFormat.Bits8UScaled, 1, 0, 1, true, false),
|
||||
new(GuestPixelFormat.Bits8SNorm, 0, 0, 1, false, false),
|
||||
new(GuestPixelFormat.Bits8UInt, 1, 0, 1, true, true),
|
||||
new(GuestPixelFormat.Bits16UNorm, 2, 0, 2, true, false),
|
||||
|
|
@ -549,7 +550,13 @@ public static class GuestPixelFormats
|
|||
}
|
||||
|
||||
public static GuestPixelFormat RemapTextureFormat(GuestPixelFormat format) =>
|
||||
format == GuestPixelFormat.Bits11_11_10UInt ? GuestPixelFormat.Bits32UInt : format;
|
||||
format switch
|
||||
{
|
||||
GuestPixelFormat.Bits8UScaled => GuestPixelFormat.Bits8UNorm,
|
||||
GuestPixelFormat.Bits8_8UScaled => GuestPixelFormat.Bits8_8UNorm,
|
||||
GuestPixelFormat.Bits11_11_10UInt => GuestPixelFormat.Bits32UInt,
|
||||
_ => format,
|
||||
};
|
||||
|
||||
public static Format HostFormat(GuestPixelFormat format) =>
|
||||
(uint)format < LookupSize ? HostLookup[(int)format] : Format.Undefined;
|
||||
|
|
|
|||
|
|
@ -5177,7 +5177,7 @@ public static partial class KernelMemoryCompatExports
|
|||
return root;
|
||||
}
|
||||
|
||||
private static string ResolveTemp0Root()
|
||||
internal static string ResolveTemp0Root()
|
||||
{
|
||||
const string temp0VariableName = "SHARPEMU_TEMP0_DIR";
|
||||
var configuredRoot = Environment.GetEnvironmentVariable(temp0VariableName);
|
||||
|
|
@ -5197,7 +5197,7 @@ public static partial class KernelMemoryCompatExports
|
|||
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
|
||||
var root = Path.Combine(AppContext.BaseDirectory, "user", "temp", appName, "temp0");
|
||||
var root = Path.Combine(Path.GetTempPath(), "SharpEmu", "temp0", appName, Path.GetRandomFileName());
|
||||
Environment.SetEnvironmentVariable(temp0VariableName, root);
|
||||
return root;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,8 +154,7 @@ internal sealed unsafe class FfmpegMediaStream : Stream
|
|||
if (codecContext is null ||
|
||||
ffmpeg.avcodec_parameters_to_context(
|
||||
codecContext,
|
||||
formatContext->streams[streamIndex]->codecpar) < 0 ||
|
||||
ffmpeg.avcodec_open2(codecContext, decoder, null) < 0)
|
||||
formatContext->streams[streamIndex]->codecpar) < 0)
|
||||
{
|
||||
if (codecContext is not null)
|
||||
{
|
||||
|
|
@ -166,6 +165,18 @@ internal sealed unsafe class FfmpegMediaStream : Stream
|
|||
return false;
|
||||
}
|
||||
|
||||
if (mediaType == AVMediaType.AVMEDIA_TYPE_VIDEO)
|
||||
{
|
||||
codecContext->thread_count = 0;
|
||||
codecContext->thread_type = ffmpeg.FF_THREAD_FRAME | ffmpeg.FF_THREAD_SLICE;
|
||||
}
|
||||
if (ffmpeg.avcodec_open2(codecContext, decoder, null) < 0)
|
||||
{
|
||||
ffmpeg.avcodec_free_context(&codecContext);
|
||||
ffmpeg.avformat_close_input(&formatContext);
|
||||
return false;
|
||||
}
|
||||
|
||||
candidate._formatContext = formatContext;
|
||||
candidate._codecContext = codecContext;
|
||||
candidate._streamIndex = streamIndex;
|
||||
|
|
|
|||
|
|
@ -15,15 +15,21 @@ namespace SharpEmu.Libs.Network;
|
|||
public static class NetExports
|
||||
{
|
||||
private const int NetErrorBadFileDescriptor = unchecked((int)0x80410109);
|
||||
private const int NetErrorFault = unchecked((int)0x8041010E);
|
||||
private const int NetErrorInvalidArgument = unchecked((int)0x80410116);
|
||||
private const int NetErrorTooManyFiles = unchecked((int)0x80410118);
|
||||
private const int NetErrorWouldBlock = unchecked((int)0x80410123);
|
||||
private const int NetErrorAddressInUse = unchecked((int)0x80410130);
|
||||
private const int NetErrorResolverNoDns = unchecked((int)0x804101E1);
|
||||
private const int NetErrorNotInitialized = unchecked((int)0x804101C8);
|
||||
private const int NetErrnoBadFileDescriptor = 9;
|
||||
private const int NetErrnoFault = 14;
|
||||
private const int NetErrnoInvalidArgument = 22;
|
||||
private const int NetErrnoTooManyFiles = 24;
|
||||
private const int NetErrnoWouldBlock = 35;
|
||||
private const int NetErrnoAddressInUse = 48;
|
||||
private const int NetErrnoNotInitialized = 200;
|
||||
private const int NetErrnoResolverNoDns = 225;
|
||||
private const int MaxNameLength = 256;
|
||||
private static ReadOnlySpan<byte> OfflineMacAddress => [0x02, 0x53, 0x48, 0x41, 0x52, 0x50];
|
||||
|
||||
|
|
@ -32,7 +38,8 @@ public static class NetExports
|
|||
private static readonly ConcurrentDictionary<int, Socket> _sockets = new();
|
||||
private static int _nextPoolId;
|
||||
private static int _nextResolverId = 0x2000;
|
||||
private static int _nextSocketId = 0x4000;
|
||||
private static int _nextSocketId = 255;
|
||||
private static readonly object _socketIdGate = new();
|
||||
// The platform networking module is usable immediately after it is loaded.
|
||||
// Games and middleware (notably FMOD) can create internal sockets before an
|
||||
// explicit sceNetInit call reaches application code.
|
||||
|
|
@ -164,8 +171,12 @@ public static class NetExports
|
|||
try
|
||||
{
|
||||
var socket = new Socket(addressFamily, socketType, protocolType);
|
||||
var id = Interlocked.Increment(ref _nextSocketId);
|
||||
_sockets[id] = socket;
|
||||
var id = AddSocket(socket);
|
||||
if (id < 0)
|
||||
{
|
||||
socket.Dispose();
|
||||
return SetNetError(ctx, NetErrorTooManyFiles, NetErrnoTooManyFiles);
|
||||
}
|
||||
TraceNet("socket.create", id, unchecked((ulong)family), unchecked((ulong)type), unchecked((ulong)protocol));
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)id);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
|
|
@ -194,6 +205,110 @@ public static class NetExports
|
|||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "9wO9XrMsNhc",
|
||||
ExportName = "sceNetRecv",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetRecv(CpuContext ctx) => ReceiveSocket(ctx, withAddress: false);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "304ooNZxWDY",
|
||||
ExportName = "sceNetRecvfrom",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetRecvfrom(CpuContext ctx) => ReceiveSocket(ctx, withAddress: true);
|
||||
|
||||
private static int ReceiveSocket(CpuContext ctx, bool withAddress)
|
||||
{
|
||||
var id = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var bufferAddress = ctx[CpuRegister.Rsi];
|
||||
var requested = ctx[CpuRegister.Rdx];
|
||||
var flags = unchecked((int)ctx[CpuRegister.Rcx]);
|
||||
var address = withAddress ? ctx[CpuRegister.R8] : 0;
|
||||
var addressLength = withAddress ? ctx[CpuRegister.R9] : 0;
|
||||
if (!_sockets.TryGetValue(id, out var socket))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
|
||||
if (bufferAddress == 0 || (address != 0 && addressLength == 0) ||
|
||||
(flags & ~(0x2 | 0x4 | 0x80 | 0x20000)) != 0)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
if (requested == 0)
|
||||
{
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
var count = (int)Math.Min(requested, 1UL << 20);
|
||||
var socketFlags = (flags & 0x2) != 0 ? SocketFlags.Peek : SocketFlags.None;
|
||||
|
||||
try
|
||||
{
|
||||
if ((flags & 0x80) != 0 && !socket.Poll(0, SelectMode.SelectRead))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorWouldBlock, NetErrnoWouldBlock);
|
||||
}
|
||||
|
||||
var payload = new byte[count];
|
||||
IPEndPoint? endpoint = null;
|
||||
int received;
|
||||
if (address == 0)
|
||||
{
|
||||
received = socket.Receive(payload, socketFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
|
||||
received = socket.ReceiveFrom(payload, socketFlags, ref remote);
|
||||
endpoint = remote as IPEndPoint;
|
||||
}
|
||||
if (!ctx.Memory.TryWrite(bufferAddress, payload.AsSpan(0, received)))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorFault, NetErrnoFault);
|
||||
}
|
||||
|
||||
if (address != 0 && endpoint is not null)
|
||||
{
|
||||
Span<byte> lengthBytes = stackalloc byte[sizeof(uint)];
|
||||
if (!ctx.Memory.TryRead(addressLength, lengthBytes))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorFault, NetErrnoFault);
|
||||
}
|
||||
|
||||
Span<byte> guestAddress = stackalloc byte[16];
|
||||
guestAddress[0] = 16;
|
||||
guestAddress[1] = 2;
|
||||
BinaryPrimitives.WriteUInt16BigEndian(guestAddress[2..], unchecked((ushort)endpoint.Port));
|
||||
endpoint.Address.MapToIPv4().GetAddressBytes().CopyTo(guestAddress[4..]);
|
||||
var copyLength = (int)Math.Min(BinaryPrimitives.ReadUInt32LittleEndian(lengthBytes), 16u);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(lengthBytes, 16);
|
||||
if (!ctx.Memory.TryWrite(address, guestAddress[..copyLength]) ||
|
||||
!ctx.Memory.TryWrite(addressLength, lengthBytes))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorFault, NetErrnoFault);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.SetReturn(received);
|
||||
}
|
||||
catch (SocketException exception) when (exception.SocketErrorCode is SocketError.WouldBlock or SocketError.IOPending or SocketError.TimedOut)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorWouldBlock, NetErrnoWouldBlock);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "2mKX2Spso7I",
|
||||
ExportName = "sceNetSetsockopt",
|
||||
|
|
@ -563,8 +678,12 @@ public static class NetExports
|
|||
try
|
||||
{
|
||||
var accepted = socket.Accept();
|
||||
var acceptedId = Interlocked.Increment(ref _nextSocketId);
|
||||
_sockets[acceptedId] = accepted;
|
||||
var acceptedId = AddSocket(accepted);
|
||||
if (acceptedId < 0)
|
||||
{
|
||||
accepted.Dispose();
|
||||
return SetNetError(ctx, NetErrorTooManyFiles, NetErrnoTooManyFiles);
|
||||
}
|
||||
TraceNet("socket.accept", acceptedId, unchecked((ulong)id), 0, 0);
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)acceptedId);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
|
|
@ -771,6 +890,58 @@ public static class NetExports
|
|||
return ctx.SetReturn(result);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Nd91WaWmG2w",
|
||||
ExportName = "sceNetResolverStartNtoa",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetResolverStartNtoa(CpuContext ctx)
|
||||
{
|
||||
var id = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
if (!_resolvers.TryGetValue(id, out var resolver))
|
||||
{
|
||||
return SetNetError(ctx, NetErrorBadFileDescriptor, NetErrnoBadFileDescriptor);
|
||||
}
|
||||
|
||||
var destination = ctx[CpuRegister.Rdx];
|
||||
var flags = unchecked((int)ctx[CpuRegister.R9]);
|
||||
if (destination == 0 || ctx[CpuRegister.Rsi] == 0 ||
|
||||
!TryReadUtf8Z(ctx, ctx[CpuRegister.Rsi], MaxNameLength, out var hostname) ||
|
||||
(flags & ~0x10000) != 0)
|
||||
{
|
||||
return SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
if ((flags & 0x10000) == 0 && IPAddress.TryParse(hostname, out var parsed) &&
|
||||
parsed.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
return ctx.Memory.TryWrite(destination, parsed.GetAddressBytes())
|
||||
? ctx.SetReturn(0)
|
||||
: SetNetError(ctx, NetErrorInvalidArgument, NetErrnoInvalidArgument);
|
||||
}
|
||||
|
||||
_resolvers.TryUpdate(id, resolver with { LastError = NetErrorResolverNoDns }, resolver);
|
||||
return SetNetError(ctx, NetErrorResolverNoDns, NetErrnoResolverNoDns);
|
||||
}
|
||||
|
||||
private static int AddSocket(Socket socket)
|
||||
{
|
||||
lock (_socketIdGate)
|
||||
{
|
||||
// Guest fd_set has 1024 bits; IDs outside it corrupt the guest stack.
|
||||
for (var attempt = 0; attempt < 768; attempt++)
|
||||
{
|
||||
_nextSocketId = _nextSocketId == 1023 ? 256 : _nextSocketId + 1;
|
||||
if (_sockets.TryAdd(_nextSocketId, socket))
|
||||
{
|
||||
return _nextSocketId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static bool TryTranslateSocketParameters(
|
||||
int family,
|
||||
int type,
|
||||
|
|
@ -900,6 +1071,13 @@ public static class NetExports
|
|||
return 1;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "9vA2aW+CHuA",
|
||||
ExportName = "sceNetInetNtop",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNet")]
|
||||
public static int NetInetNtop(CpuContext ctx) => PosixInetNtop(ctx);
|
||||
|
||||
private static void TraceNet(string operation, int id, ulong arg0, ulong arg1, ulong arg2)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NET"), "1", StringComparison.Ordinal))
|
||||
|
|
|
|||
|
|
@ -261,6 +261,26 @@ public static class NpManagerExports
|
|||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Oad3rvY-NJQ",
|
||||
ExportName = "sceNpHasSignedUp",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNpManager")]
|
||||
public static int NpHasSignedUp(CpuContext ctx)
|
||||
{
|
||||
var resultAddress = ctx[CpuRegister.Rsi];
|
||||
if (resultAddress == 0 || unchecked((int)ctx[CpuRegister.Rdi]) == -1)
|
||||
{
|
||||
return SetReturn(ctx, NpErrorInvalidArgument);
|
||||
}
|
||||
|
||||
Span<byte> signedUp = stackalloc byte[1];
|
||||
signedUp[0] = 0; // No online account is configured in the offline profile.
|
||||
return ctx.Memory.TryWrite(resultAddress, signedUp)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Ec63y59l9tw",
|
||||
ExportName = "sceNpSetNpTitleId",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace SharpEmu.Libs.Np;
|
|||
|
||||
public static class NpSessionSignalingExports
|
||||
{
|
||||
private const int NpErrorSignedOut = unchecked((int)0x80550006);
|
||||
private static int _nextContextId;
|
||||
|
||||
[SysAbiExport(
|
||||
|
|
@ -31,4 +32,18 @@ public static class NpSessionSignalingExports
|
|||
ctx[CpuRegister.Rax] = unchecked((ulong)contextId);
|
||||
return contextId;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "r8mVMwlafF8",
|
||||
ExportName = "sceNpSessionSignalingRequestPrepare",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceNpSessionSignaling")]
|
||||
public static int NpSessionSignalingRequestPrepare(CpuContext ctx) => ctx.SetReturn(NpErrorSignedOut);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Z9Q9LzQDXf0",
|
||||
ExportName = "sceNpSessionSignalingDestroyContext",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceNpSessionSignaling")]
|
||||
public static int NpSessionSignalingDestroyContext(CpuContext ctx) => ctx.SetReturn(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,46 +217,21 @@ public static class PadExports
|
|||
: ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "PZSoY8j0Pko",
|
||||
ExportName = "scePadGetFeatureReport",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libScePad")]
|
||||
public static int PadGetFeatureReport(CpuContext ctx) =>
|
||||
ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
|
||||
[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 (!IsPrimaryPadHandle(handle))
|
||||
{
|
||||
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);
|
||||
}
|
||||
public static int PadGetExtControllerInformation(CpuContext ctx) =>
|
||||
ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "AcslpN1jHR8",
|
||||
|
|
|
|||
|
|
@ -76,10 +76,44 @@ public static class GameServiceStubs
|
|||
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSigninDialog")]
|
||||
public static int SigninDialogInitialize(CpuContext ctx) => Ok(ctx);
|
||||
|
||||
[SysAbiExport(Nid = "JlpJVoRWv7U", ExportName = "sceSigninDialogOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSigninDialog")]
|
||||
public static int SigninDialogOpen(CpuContext ctx) => Ok(ctx);
|
||||
|
||||
[SysAbiExport(Nid = "Bw31liTFT3A", ExportName = "sceSigninDialogUpdateStatus",
|
||||
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSigninDialog")]
|
||||
public static int SigninDialogUpdateStatus(CpuContext ctx) => ctx.SetReturn(3);
|
||||
|
||||
[SysAbiExport(Nid = "nqG7rqnYw1U", ExportName = "sceSigninDialogGetResult",
|
||||
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSigninDialog")]
|
||||
public static int SigninDialogGetResult(CpuContext ctx)
|
||||
{
|
||||
var address = ctx[CpuRegister.Rdi];
|
||||
if (address == 0)
|
||||
{
|
||||
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
Span<byte> result = stackalloc byte[16];
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(result, 1); // User canceled.
|
||||
return ctx.Memory.TryWrite(address, result)
|
||||
? Ok(ctx)
|
||||
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
[SysAbiExport(Nid = "LXlmS6PvJdU", ExportName = "sceSigninDialogTerminate",
|
||||
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceSigninDialog")]
|
||||
public static int SigninDialogTerminate(CpuContext ctx) => Ok(ctx);
|
||||
|
||||
[SysAbiExport(Nid = "UOjiprYwVNw", ExportName = "sceTextToSpeech2Initialize",
|
||||
Target = Generation.Gen5, LibraryName = "libSceTextToSpeech2")]
|
||||
public static int TextToSpeech2Initialize(CpuContext ctx) => Ok(ctx);
|
||||
|
||||
[SysAbiExport(Nid = "X0HZNbSiqyg", ExportName = "sceTextToSpeech2Open",
|
||||
Target = Generation.Gen5, LibraryName = "libSceTextToSpeech2")]
|
||||
public static int TextToSpeech2Open(CpuContext ctx) =>
|
||||
ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
[SysAbiExport(Nid = "kvYEw2lBndk", ExportName = "sceGameLiveStreamingInitialize",
|
||||
Target = Generation.Gen5, LibraryName = "libSceGameLiveStreaming")]
|
||||
public static int GameLiveStreamingInitialize(CpuContext ctx) => Ok(ctx);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ public static class SystemServiceExports
|
|||
Volatile.Write(ref _systemLanguage, systemLanguage);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "x1UB9bwDSOw",
|
||||
ExportName = "sceSystemServiceDisableMusicPlayer",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceSystemService")]
|
||||
public static int SystemServiceDisableMusicPlayer(CpuContext ctx) => ctx.SetReturn(0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "9kPCz7Or+1Y",
|
||||
ExportName = "sceSystemServiceReenableMusicPlayer",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceSystemService")]
|
||||
public static int SystemServiceReenableMusicPlayer(CpuContext ctx) => ctx.SetReturn(0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "3RQ5aQfnstU",
|
||||
ExportName = "sceSystemServiceGetNoticeScreenSkipFlag",
|
||||
|
|
|
|||
|
|
@ -862,6 +862,28 @@ public static partial class VideoOutExports
|
|||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
#pragma warning disable SHEM006
|
||||
[SysAbiExport(
|
||||
Nid = "kP2L8t3j-aM",
|
||||
ExportName = "VideoOutVrrStatus_kP2L8t3j_aM",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceVideoOutVrrStatus")]
|
||||
public static int VideoOutVrrStatus(CpuContext ctx) => ctx.SetReturn(0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "5tRaBjtdTzY",
|
||||
ExportName = "sceVideoOutVrrPegToFixedRate",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutVrrPegToFixedRate(CpuContext ctx) => ctx.SetReturn(0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "T4ucGB8CsnM",
|
||||
ExportName = "sceVideoOutVrrUnpegFromFixedRate",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceVideoOut")]
|
||||
public static int VideoOutVrrUnpegFromFixedRate(CpuContext ctx) => ctx.SetReturn(0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Xru92wHJRmg",
|
||||
ExportName = "sceVideoOutAddVblankEvent",
|
||||
|
|
|
|||
|
|
@ -5114,6 +5114,25 @@ public static partial class Gen5SpirvTranslator
|
|||
SpirvImageResource resource,
|
||||
uint texel)
|
||||
{
|
||||
if (resource.ConversionFormat is GuestImageFormat.Format8Uscaled or GuestImageFormat.Format8x2Uscaled)
|
||||
{
|
||||
var scaled = _module.AddInstruction(SpirvOp.FMul, resource.VectorType, texel,
|
||||
_module.AddInstruction(SpirvOp.CompositeConstruct, resource.VectorType,
|
||||
Float(255), Float(255), Float(1), Float(1)));
|
||||
var channels = new uint[4];
|
||||
for (var component = 0; component < channels.Length; component++)
|
||||
{
|
||||
var selector = (resource.ShaderSwizzle >> (component * 3)) & 7u;
|
||||
channels[component] = selector switch
|
||||
{
|
||||
1u => Float(1),
|
||||
>= 4u => _module.AddInstruction(SpirvOp.CompositeExtract, _floatType, scaled, selector - 4u),
|
||||
_ => Float(0),
|
||||
};
|
||||
}
|
||||
return _module.AddInstruction(SpirvOp.CompositeConstruct, resource.VectorType, channels);
|
||||
}
|
||||
|
||||
if (!TryGetPackedImageConversion(
|
||||
resource,
|
||||
out var componentCount,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ namespace SharpEmu.ShaderCompiler.Resources;
|
|||
public static class GuestImageFormat
|
||||
{
|
||||
public const uint Invalid = 0;
|
||||
public const uint Format8Uscaled = 3;
|
||||
public const uint Format8x2Uscaled = 16;
|
||||
public const uint Format16Unorm = 7;
|
||||
public const uint Format32Uint = 20;
|
||||
public const uint Format32Sint = 21;
|
||||
|
|
@ -32,7 +34,7 @@ public static class GuestImageFormat
|
|||
// is unsupported for sampling.
|
||||
private static readonly (uint Format, ImageNumericClass Class)[] SampledFormats =
|
||||
[
|
||||
(1, ImageNumericClass.Float), (5, ImageNumericClass.Uint), (7, ImageNumericClass.Float), (8, ImageNumericClass.Float),
|
||||
(1, ImageNumericClass.Float), (Format8Uscaled, ImageNumericClass.Float), (5, ImageNumericClass.Uint), (7, ImageNumericClass.Float), (8, ImageNumericClass.Float),
|
||||
(11, ImageNumericClass.Uint), (12, ImageNumericClass.Sint), (13, ImageNumericClass.Float), (14, ImageNumericClass.Float),
|
||||
(15, ImageNumericClass.Float), (16, ImageNumericClass.Float), (18, ImageNumericClass.Uint), (19, ImageNumericClass.Sint), (20, ImageNumericClass.Uint),
|
||||
(21, ImageNumericClass.Sint), (22, ImageNumericClass.Float), (23, ImageNumericClass.Float), (24, ImageNumericClass.Float),
|
||||
|
|
@ -67,7 +69,13 @@ public static class GuestImageFormat
|
|||
}
|
||||
|
||||
// The 11_11_10 integer format is read as one 32-bit integer.
|
||||
public static uint Remap(uint format) => format == Format11x2x10Uint ? Format32Uint : format;
|
||||
public static uint Remap(uint format) => format switch
|
||||
{
|
||||
Format8Uscaled => 1,
|
||||
Format8x2Uscaled => 14,
|
||||
Format11x2x10Uint => Format32Uint,
|
||||
_ => format,
|
||||
};
|
||||
|
||||
public static uint ImageTypeOf(ReadOnlySpan<uint> descriptor) => (descriptor[3] >> 28) & 0xF;
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public sealed class FiberExportsTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSelf_OutsideFiberContext_ReturnsPermissionError()
|
||||
public void GetSelf_OutsideFiberContext_ReturnsNullFiberAddress()
|
||||
{
|
||||
var memory = new FakeCpuMemory(Base, RegionSize);
|
||||
var context = new CpuContext(memory, Generation.Gen5);
|
||||
|
|
@ -79,7 +79,8 @@ public sealed class FiberExportsTests
|
|||
|
||||
var result = FiberExports.FiberGetSelf(context);
|
||||
|
||||
Assert.Equal(ErrorPermission, result);
|
||||
Assert.Equal(0, result);
|
||||
Assert.Equal(0UL, ReadUInt64(memory, Base + 0x100));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -69,4 +69,30 @@ public sealed class FontExportsTests
|
|||
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
|
||||
FontExports.GetVerticalLayout(_ctx));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawGlyph_WritesOnlySetPixelsInsideScissor()
|
||||
{
|
||||
const ulong surface = Base + 0x200;
|
||||
const ulong pixels = Base + 0x300;
|
||||
Span<byte> descriptor = stackalloc byte[0x28];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(descriptor, pixels);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[8..], 8);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[12..], 1);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[16..], 8);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[20..], 1);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[24..], 2);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[28..], 0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[32..], 6);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(descriptor[36..], 1);
|
||||
Assert.True(_ctx.Memory.TryWrite(surface, descriptor));
|
||||
|
||||
var font = new byte[4096];
|
||||
font['A' * 16] = 0xff;
|
||||
Assert.True(FontExports.DrawGlyph(_ctx, surface, 'A', 0, 0, 8, 1, font));
|
||||
|
||||
Span<byte> result = stackalloc byte[8];
|
||||
Assert.True(_ctx.Memory.TryRead(pixels, result));
|
||||
Assert.Equal(new byte[] { 0, 0, 255, 255, 255, 255, 0, 0 }, result.ToArray());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using SharpEmu.Libs.Gpu.Images;
|
||||
using SharpEmu.ShaderCompiler;
|
||||
using SharpEmu.ShaderCompiler.Resources;
|
||||
using SharpEmu.Libs.Gpu.Scheduling;
|
||||
using SharpEmu.Libs.Tests.Gpu.Scheduling;
|
||||
using SharpEmu.Libs.Tests.Gpu.Vulkan;
|
||||
|
|
@ -17,6 +18,25 @@ public sealed class ImageRequestBuildersTests : IClassFixture<HeadlessVulkanFixt
|
|||
{
|
||||
private const ulong Base = 0x1_0000_0000;
|
||||
|
||||
[Fact]
|
||||
public void EightBitUnsignedScaledTextureUsesUnormBackingWithShaderConversion()
|
||||
{
|
||||
Assert.Equal(ImageNumericClass.Float, GuestImageFormat.SampledNumericClass(GuestImageFormat.Format8Uscaled));
|
||||
Assert.Equal(1u, GuestImageFormat.Remap(GuestImageFormat.Format8Uscaled));
|
||||
|
||||
var request = ImageRequestBuilders.Texture(
|
||||
RegisterWords.Texture(Base, GuestPixelFormat.Bits8UScaled, 32, 32),
|
||||
new ShaderImageShape(false, false, false, false, TextureNumericClass.Float));
|
||||
Assert.Equal(Format.R8Unorm, request.Request.Description.PixelFormat);
|
||||
Assert.True(request.ExactFormat);
|
||||
|
||||
var twoChannel = ImageRequestBuilders.Texture(
|
||||
RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8UScaled, 32, 32),
|
||||
new ShaderImageShape(false, false, false, false, TextureNumericClass.Float));
|
||||
Assert.Equal(Format.R8G8Unorm, twoChannel.Request.Description.PixelFormat);
|
||||
Assert.True(twoChannel.ExactFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorageWithoutMipOperandUsesTheAvailableMipRange()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,57 @@ public sealed class NetExportsTests
|
|||
{
|
||||
private readonly CpuContext _ctx = new(new FakeCpuMemory(0x1_0000_0000, 0x1000), Generation.Gen5);
|
||||
|
||||
[Fact]
|
||||
public void SocketDescriptorsFitGuestFdSet()
|
||||
{
|
||||
_ctx[CpuRegister.Rsi] = 2; // AF_INET
|
||||
_ctx[CpuRegister.Rdx] = 2; // SOCK_DGRAM
|
||||
_ctx[CpuRegister.Rcx] = 17; // UDP
|
||||
|
||||
Assert.Equal(0, NetExports.NetSocket(_ctx));
|
||||
var first = _ctx[CpuRegister.Rax];
|
||||
Assert.Equal(0, NetExports.NetSocket(_ctx));
|
||||
var second = _ctx[CpuRegister.Rax];
|
||||
|
||||
try
|
||||
{
|
||||
Assert.InRange(first, 256UL, 1023UL);
|
||||
Assert.InRange(second, 256UL, 1023UL);
|
||||
Assert.NotEqual(first, second);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ctx[CpuRegister.Rdi] = first;
|
||||
NetExports.NetSocketClose(_ctx);
|
||||
_ctx[CpuRegister.Rdi] = second;
|
||||
NetExports.NetSocketClose(_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonblockingReceiveWithoutDataReturnsWouldBlock()
|
||||
{
|
||||
_ctx[CpuRegister.Rsi] = 2;
|
||||
_ctx[CpuRegister.Rdx] = 2;
|
||||
_ctx[CpuRegister.Rcx] = 17;
|
||||
Assert.Equal(0, NetExports.NetSocket(_ctx));
|
||||
var socket = _ctx[CpuRegister.Rax];
|
||||
|
||||
try
|
||||
{
|
||||
_ctx[CpuRegister.Rdi] = socket;
|
||||
_ctx[CpuRegister.Rsi] = 0x1_0000_0000;
|
||||
_ctx[CpuRegister.Rdx] = 1;
|
||||
_ctx[CpuRegister.Rcx] = 0x80;
|
||||
Assert.Equal(unchecked((int)0x80410123), NetExports.NetRecv(_ctx));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ctx[CpuRegister.Rdi] = socket;
|
||||
NetExports.NetSocketClose(_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Htonl_SwapsAllFourBytes()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -64,6 +64,24 @@ public sealed class PadExportsTests
|
|||
Assert.Equal(cookie, BitConverter.ToUInt64(guard));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetExtControllerInformation_DoesNotOverwriteCallerCookie()
|
||||
{
|
||||
const ulong informationAddress = Base + 0x100;
|
||||
const ulong cookieAddress = informationAddress + 0x30;
|
||||
const ulong cookie = 0xC0DEC0DECAFEBA00UL;
|
||||
|
||||
Assert.True(_memory.TryWrite(cookieAddress, BitConverter.GetBytes(cookie)));
|
||||
_ctx[CpuRegister.Rdi] = 1;
|
||||
_ctx[CpuRegister.Rsi] = informationAddress;
|
||||
|
||||
Assert.Equal(0, PadExports.PadGetExtControllerInformation(_ctx));
|
||||
|
||||
Span<byte> guard = stackalloc byte[8];
|
||||
Assert.True(_memory.TryRead(cookieAddress, guard));
|
||||
Assert.Equal(cookie, BitConverter.ToUInt64(guard));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2)]
|
||||
[InlineData(-1)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue