mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-11 12:08:42 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f69f81f682 |
@@ -92,11 +92,6 @@ public partial class MainWindow : Window
|
|||||||
// plain window color remains the fallback when the asset fails to load.
|
// plain window color remains the fallback when the asset fails to load.
|
||||||
private Bitmap? _defaultBackdrop;
|
private Bitmap? _defaultBackdrop;
|
||||||
|
|
||||||
// Whether the native loading/closing popup should be showing; it is a
|
|
||||||
// desktop-topmost popup, so it closes while the launcher is in the
|
|
||||||
// background or minimized and reopens from this flag on activation.
|
|
||||||
private bool _sessionLoadingActive;
|
|
||||||
|
|
||||||
// Controller navigation state.
|
// Controller navigation state.
|
||||||
private readonly DispatcherTimer _gamepadTimer;
|
private readonly DispatcherTimer _gamepadTimer;
|
||||||
private HostGamepadButtons _previousPadButtons;
|
private HostGamepadButtons _previousPadButtons;
|
||||||
@@ -155,18 +150,8 @@ public partial class MainWindow : Window
|
|||||||
};
|
};
|
||||||
_libraryBlurTimer.Tick += (_, _) => AdvanceLibraryBlur();
|
_libraryBlurTimer.Tick += (_, _) => AdvanceLibraryBlur();
|
||||||
|
|
||||||
// Native popups float above every window on the desktop; they must
|
Activated += (_, _) => UpdateSessionBarVisibility();
|
||||||
// follow the launcher into the background or a minimized state.
|
Deactivated += (_, _) => SessionBarPopup.IsOpen = false;
|
||||||
Activated += (_, _) =>
|
|
||||||
{
|
|
||||||
UpdateSessionBarVisibility();
|
|
||||||
SessionLoadingPopup.IsOpen = _sessionLoadingActive;
|
|
||||||
};
|
|
||||||
Deactivated += (_, _) =>
|
|
||||||
{
|
|
||||||
SessionBarPopup.IsOpen = false;
|
|
||||||
SessionLoadingPopup.IsOpen = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
TitleBar.PointerPressed += OnTitleBarPointerPressed;
|
TitleBar.PointerPressed += OnTitleBarPointerPressed;
|
||||||
GameList.SelectionChanged += (_, _) => UpdateSelectedGame();
|
GameList.SelectionChanged += (_, _) => UpdateSelectedGame();
|
||||||
@@ -429,15 +414,6 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isRunning || _isStopping)
|
|
||||||
{
|
|
||||||
// The game renders inside the launcher window, so the launcher
|
|
||||||
// stays active while playing. The controller belongs to the game
|
|
||||||
// then: no navigation, and Circle/B must never stop the session.
|
|
||||||
_previousPadButtons = pad.Buttons;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shoulderPressed = pad.Buttons & ~_previousPadButtons;
|
var shoulderPressed = pad.Buttons & ~_previousPadButtons;
|
||||||
if ((shoulderPressed & HostGamepadButtons.L1) != 0)
|
if ((shoulderPressed & HostGamepadButtons.L1) != 0)
|
||||||
{
|
{
|
||||||
@@ -487,6 +463,11 @@ public partial class MainWindow : Window
|
|||||||
LaunchSelected();
|
LaunchSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((pressed & HostGamepadButtons.Circle) != 0)
|
||||||
|
{
|
||||||
|
StopEmulator();
|
||||||
|
}
|
||||||
|
|
||||||
_previousPadButtons = pad.Buttons;
|
_previousPadButtons = pad.Buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1645,23 +1626,13 @@ public partial class MainWindow : Window
|
|||||||
base.OnPropertyChanged(change);
|
base.OnPropertyChanged(change);
|
||||||
if (change.Property == WindowStateProperty)
|
if (change.Property == WindowStateProperty)
|
||||||
{
|
{
|
||||||
// The XAML WindowState="Maximized" assignment raises this change
|
|
||||||
// during InitializeComponent, before named controls are wired up.
|
|
||||||
if (WindowState == WindowState.Minimized)
|
if (WindowState == WindowState.Minimized)
|
||||||
{
|
{
|
||||||
_sndPreview.Pause();
|
_sndPreview.Pause();
|
||||||
if (SessionLoadingPopup is { } popup)
|
|
||||||
{
|
|
||||||
popup.IsOpen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_sndPreview.Resume();
|
_sndPreview.Resume();
|
||||||
if (SessionLoadingPopup is { } popup)
|
|
||||||
{
|
|
||||||
popup.IsOpen = _sessionLoadingActive;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2038,7 +2009,7 @@ public partial class MainWindow : Window
|
|||||||
ContentToolbar.IsVisible = false;
|
ContentToolbar.IsVisible = false;
|
||||||
ConsolePanel.IsVisible = false;
|
ConsolePanel.IsVisible = false;
|
||||||
LaunchBar.IsVisible = false;
|
LaunchBar.IsVisible = false;
|
||||||
HideSessionLoading();
|
SessionLoadingPopup.IsOpen = false;
|
||||||
UpdateSessionBarVisibility();
|
UpdateSessionBarVisibility();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2138,7 +2109,7 @@ public partial class MainWindow : Window
|
|||||||
GameView.IsVisible = false;
|
GameView.IsVisible = false;
|
||||||
GameView.IsHitTestVisible = true;
|
GameView.IsHitTestVisible = true;
|
||||||
SessionBarPopup.IsOpen = false;
|
SessionBarPopup.IsOpen = false;
|
||||||
HideSessionLoading();
|
SessionLoadingPopup.IsOpen = false;
|
||||||
AnimateLibraryBlur(0, clearWhenComplete: true);
|
AnimateLibraryBlur(0, clearWhenComplete: true);
|
||||||
MainContent.Margin = new Thickness(32, 24, 32, 20);
|
MainContent.Margin = new Thickness(32, 24, 32, 20);
|
||||||
ContentToolbar.IsVisible = true;
|
ContentToolbar.IsVisible = true;
|
||||||
@@ -2222,14 +2193,7 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
SessionLoadingTitle.Text = title;
|
SessionLoadingTitle.Text = title;
|
||||||
SessionLoadingDetail.Text = detail;
|
SessionLoadingDetail.Text = detail;
|
||||||
_sessionLoadingActive = true;
|
SessionLoadingPopup.IsOpen = true;
|
||||||
SessionLoadingPopup.IsOpen = IsActive && WindowState != WindowState.Minimized;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HideSessionLoading()
|
|
||||||
{
|
|
||||||
_sessionLoadingActive = false;
|
|
||||||
SessionLoadingPopup.IsOpen = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReturnToLibraryWhileStopping()
|
private void ReturnToLibraryWhileStopping()
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Random;
|
||||||
|
|
||||||
|
public static class RandomExports
|
||||||
|
{
|
||||||
|
private const int RandomErrorInvalid = unchecked((int)0x817C0016);
|
||||||
|
private const int MaxRandomBytes = 64;
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "PI7jIZj4pcE",
|
||||||
|
ExportName = "sceRandomGetRandomNumber",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libSceRandom")]
|
||||||
|
public static int RandomGetRandomNumber(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var destination = ctx[CpuRegister.Rdi];
|
||||||
|
var size = ctx[CpuRegister.Rsi];
|
||||||
|
if ((destination == 0 && size != 0) || size > MaxRandomBytes)
|
||||||
|
{
|
||||||
|
return ctx.SetReturn(RandomErrorInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> bytes = stackalloc byte[(int)size];
|
||||||
|
RandomNumberGenerator.Fill(bytes);
|
||||||
|
return ctx.Memory.TryWrite(destination, bytes)
|
||||||
|
? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK)
|
||||||
|
: ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Random;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests.Random;
|
||||||
|
|
||||||
|
public sealed class RandomExportsTests
|
||||||
|
{
|
||||||
|
private const ulong BaseAddress = 0x1000;
|
||||||
|
private const int RandomErrorInvalid = unchecked((int)0x817C0016);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRandomNumberWritesRequestedBytes()
|
||||||
|
{
|
||||||
|
var memory = new FakeCpuMemory(BaseAddress, 64);
|
||||||
|
var ctx = CreateContext(memory, BaseAddress, 64);
|
||||||
|
|
||||||
|
Assert.Equal(0, RandomExports.RandomGetRandomNumber(ctx));
|
||||||
|
|
||||||
|
var bytes = new byte[64];
|
||||||
|
Assert.True(memory.TryRead(BaseAddress, bytes));
|
||||||
|
Assert.NotEqual(new byte[64], bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0, 1)]
|
||||||
|
[InlineData(BaseAddress, 65)]
|
||||||
|
public void GetRandomNumberRejectsInvalidArguments(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
var memory = new FakeCpuMemory(BaseAddress, 64);
|
||||||
|
var ctx = CreateContext(memory, address, size);
|
||||||
|
|
||||||
|
Assert.Equal(RandomErrorInvalid, RandomExports.RandomGetRandomNumber(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRandomNumberAcceptsEmptyRequest()
|
||||||
|
{
|
||||||
|
var memory = new FakeCpuMemory(BaseAddress, 64);
|
||||||
|
var ctx = CreateContext(memory, 0, 0);
|
||||||
|
|
||||||
|
Assert.Equal(0, RandomExports.RandomGetRandomNumber(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRandomNumberReportsUnmappedDestination()
|
||||||
|
{
|
||||||
|
var memory = new FakeCpuMemory(BaseAddress, 64);
|
||||||
|
var ctx = CreateContext(memory, BaseAddress + 64, 1);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT,
|
||||||
|
RandomExports.RandomGetRandomNumber(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CpuContext CreateContext(
|
||||||
|
ICpuMemory memory,
|
||||||
|
ulong destination,
|
||||||
|
ulong size)
|
||||||
|
{
|
||||||
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||||
|
ctx[CpuRegister.Rdi] = destination;
|
||||||
|
ctx[CpuRegister.Rsi] = size;
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user