mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-28 21:49:42 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6be44dd750 |
@@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SharpEmuVersion>0.0.3-hotfix-1</SharpEmuVersion>
|
||||
<SharpEmuVersion>0.0.3</SharpEmuVersion>
|
||||
<Version>$(SharpEmuVersion)</Version>
|
||||
|
||||
<RepoRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)'))</RepoRoot>
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ path = [
|
||||
"global.json",
|
||||
"**/packages.lock.json",
|
||||
"scripts/ps5_names.txt",
|
||||
"src/SharpEmu.LibAtrac9/**",
|
||||
"src/SharpEmu.GUI/Languages/**",
|
||||
"src/SharpEmu.ShaderCompiler.Metal/Templates/**",
|
||||
"tests/SharpEmu.ShaderCompiler.Metal.Tests/Goldens/**",
|
||||
@@ -20,7 +21,7 @@ SPDX-FileCopyrightText = "SharpEmu Emulator Project"
|
||||
SPDX-License-Identifier = "GPL-2.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = "src/SharpEmu.LibAtrac9/**"
|
||||
path = "src/SharpEmu.GUI/Atrac9/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2018 Alex Barney"
|
||||
SPDX-License-Identifier = "MIT"
|
||||
|
||||
+249
-14
@@ -1,32 +1,267 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Application composition root. Shared resources and styles are included in
|
||||
cascade order so individual launcher views do not redefine global visuals.
|
||||
-->
|
||||
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:SharpEmu.GUI"
|
||||
x:Class="SharpEmu.GUI.App"
|
||||
RequestedThemeVariant="Dark">
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="avares://SharpEmu.GUI/Themes/Tokens.axaml" />
|
||||
<ResourceInclude Source="avares://SharpEmu.GUI/Themes/Templates/SettingRow.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
<Color x:Key="SystemAccentColor">#7C5CFC</Color>
|
||||
|
||||
<LinearGradientBrush x:Key="BgBrush" StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#12151F" />
|
||||
<GradientStop Offset="0.55" Color="#0D1017" />
|
||||
<GradientStop Offset="1" Color="#0B0D14" />
|
||||
</LinearGradientBrush>
|
||||
|
||||
<SolidColorBrush x:Key="ChromeBrush" Color="#090C12" />
|
||||
<SolidColorBrush x:Key="CardBrush" Color="#141924" />
|
||||
<SolidColorBrush x:Key="CardBorderBrush" Color="#232B3A" />
|
||||
<SolidColorBrush x:Key="ElevatedBrush" Color="#1B2230" />
|
||||
<SolidColorBrush x:Key="TextBrush" Color="#E8ECF4" />
|
||||
<SolidColorBrush x:Key="MutedBrush" Color="#8B94A7" />
|
||||
<SolidColorBrush x:Key="FaintBrush" Color="#5A6478" />
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="#7C5CFC" />
|
||||
<SolidColorBrush x:Key="AccentHoverBrush" Color="#8F73FF" />
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#E5484D" />
|
||||
<SolidColorBrush x:Key="DangerHoverBrush" Color="#F2555A" />
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#46C46B" />
|
||||
<SolidColorBrush x:Key="InfoBrush" Color="#58A6FF" />
|
||||
<SolidColorBrush x:Key="TileHoverBrush" Color="#1A2130" />
|
||||
<SolidColorBrush x:Key="TileSelectedBrush" Color="#212A3F" />
|
||||
|
||||
<ControlTheme x:Key="{x:Type local:SettingRow}" TargetType="local:SettingRow">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock x:Name="PART_Label" Text="{TemplateBinding Label}" FontSize="13" />
|
||||
<TextBlock Text="{TemplateBinding Description}" FontSize="11"
|
||||
Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Description, RelativeSource={RelativeSource TemplatedParent},
|
||||
Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<ToggleSwitch OnContent="Override" OffContent="Override" MinWidth="0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{TemplateBinding ShowOverride}"
|
||||
IsChecked="{Binding IsOverridden, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
|
||||
<ContentPresenter x:Name="PART_Slot" Content="{TemplateBinding Content}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
|
||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Base.axaml" />
|
||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Surfaces.axaml" />
|
||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Buttons.axaml" />
|
||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Inputs.axaml" />
|
||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Console.axaml" />
|
||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Library.axaml" />
|
||||
<Style Selector="Window">
|
||||
<Setter Property="FontFamily" Value="Inter, Segoe UI, sans-serif" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.card">
|
||||
<Setter Property="Background" Value="{StaticResource CardBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Padding" Value="16" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.pill">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="CornerRadius" Value="999" />
|
||||
<Setter Property="Padding" Value="10,3" />
|
||||
</Style>
|
||||
|
||||
<!-- Session status/hotkey badges: the title-id pill geometry with a
|
||||
tinted fill so state (RUNNING) and keys (F11) read at a glance. -->
|
||||
<Style Selector="Border.badge">
|
||||
<Setter Property="CornerRadius" Value="999" />
|
||||
<Setter Property="Padding" Value="8,2" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="Border.badge.running">
|
||||
<Setter Property="Background" Value="#1E46C46B" />
|
||||
<Setter Property="BorderBrush" Value="#5546C46B" />
|
||||
</Style>
|
||||
<Style Selector="Border.badge.key">
|
||||
<Setter Property="Background" Value="#1E58A6FF" />
|
||||
<Setter Property="BorderBrush" Value="#5558A6FF" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.sectionTitle">
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="LetterSpacing" Value="1.5" />
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.fieldLabel">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedBrush}" />
|
||||
<Setter Property="Margin" Value="0,0,0,6" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.accent">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="22,10" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AccentHoverBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.danger">
|
||||
<Setter Property="Background" Value="{StaticResource DangerBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="22,10" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.danger:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource DangerHoverBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.ghost">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
<Setter Property="Padding" Value="12,7" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ToggleButton.ghost">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
<Setter Property="Padding" Value="12,7" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton.ghost:checked /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Top-level page switcher (Library / Options): plain transparent
|
||||
buttons, not TabItem, so there is no Fluent selected-tab underline.
|
||||
The active page is conveyed by brightness alone; LB/RB gamepad
|
||||
hints flank the pair. -->
|
||||
<Style Selector="Button.segment">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedBrush}" />
|
||||
<Setter Property="FontSize" Value="22" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Padding" Value="6,4" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.segment:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
<Style Selector="Button.segment.active">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Gamepad shoulder-button hint chip (LB/RB, L1/R1). -->
|
||||
<Style Selector="Border.padHint">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="Padding" Value="8,3" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ContextMenu">
|
||||
<Setter Property="Background" Value="{StaticResource CardBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="Padding" Value="6" />
|
||||
</Style>
|
||||
<Style Selector="ContextMenu MenuItem">
|
||||
<Setter Property="Padding" Value="10,7" />
|
||||
<Setter Property="CornerRadius" Value="7" />
|
||||
</Style>
|
||||
<Style Selector="ContextMenu Separator">
|
||||
<Setter Property="Background" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="8,4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox.console">
|
||||
<Setter Property="Background" Value="#0B0E14" />
|
||||
<Setter Property="FontFamily" Value="Cascadia Mono, Consolas, Courier New, monospace" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.console ListBoxItem">
|
||||
<Setter Property="Padding" Value="10,1" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
</Style>
|
||||
|
||||
<!-- Cover-art library grid -->
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem">
|
||||
<Setter Property="Padding" Value="10" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="14" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="RenderTransform" Value="translateY(0px)" />
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.12" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:pointerover">
|
||||
<Setter Property="RenderTransform" Value="translateY(-3px)" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource TileHoverBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource TileSelectedBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource TileSelectedBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentHoverBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.coverShadow">
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="BoxShadow" Value="0 6 14 0 #55000000" />
|
||||
</Style>
|
||||
<Style Selector="Border.coverClip">
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -242,9 +242,7 @@ public partial class MainWindow : Window
|
||||
EnvLogNpToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle("SHARPEMU_LOG_NP", EnvLogNpToggle.IsChecked == true);
|
||||
EnvGuestImageCpuSyncToggle.IsCheckedChanged += (_, _) =>
|
||||
SetEnvironmentToggle(
|
||||
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
||||
EnvGuestImageCpuSyncToggle.IsChecked == true);
|
||||
SetGuestImageCpuSync(EnvGuestImageCpuSyncToggle.IsChecked == true);
|
||||
LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged();
|
||||
|
||||
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
|
||||
@@ -855,8 +853,10 @@ public partial class MainWindow : Window
|
||||
EnvLogDirectMemoryToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_DIRECT_MEMORY");
|
||||
EnvLogIoToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_IO");
|
||||
EnvLogNpToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_NP");
|
||||
EnvGuestImageCpuSyncToggle.IsChecked =
|
||||
_settings.EnvironmentToggles.Contains("SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||
EnvGuestImageCpuSyncToggle.IsChecked = IsEnvironmentEnabled(
|
||||
_settings.EnvironmentToggles,
|
||||
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
||||
defaultValue: true);
|
||||
WindowModeBox.SelectedIndex = ChoiceIndex(_settings.WindowMode, "Windowed", "Borderless", "Exclusive");
|
||||
LoadHostDisplayOptions();
|
||||
ScalingModeBox.SelectedIndex = ChoiceIndex(_settings.ScalingMode, "Fit", "Cover", "Stretch", "Integer");
|
||||
@@ -1083,6 +1083,40 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private void SetGuestImageCpuSync(bool enabled)
|
||||
{
|
||||
const string name = "SHARPEMU_GUEST_IMAGE_CPU_SYNC";
|
||||
_settings.EnvironmentToggles.RemoveAll(entry =>
|
||||
string.Equals(entry, name, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(entry, name + "=0", StringComparison.OrdinalIgnoreCase));
|
||||
if (!enabled)
|
||||
{
|
||||
_settings.EnvironmentToggles.Add(name + "=0");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsEnvironmentEnabled(
|
||||
IEnumerable<string> entries,
|
||||
string name,
|
||||
bool defaultValue)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (string.Equals(entry, name + "=0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.Equals(entry, name, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(entry, name + "=1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private string SelectedLogLevel()
|
||||
{
|
||||
return LogLevelBox.SelectedIndex switch
|
||||
|
||||
@@ -241,7 +241,7 @@ public sealed class PerGameSettingsDialog : Window
|
||||
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, global.HdrMode, "Auto");
|
||||
foreach (var (name, box) in _envBoxes)
|
||||
{
|
||||
box.IsChecked = IsEnvironmentEnabled(global.EnvironmentToggles, name, defaultValue: false);
|
||||
box.IsChecked = IsEnvironmentEnabled(global.EnvironmentToggles, name, defaultValue: name == "SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||
}
|
||||
|
||||
if (existing is null)
|
||||
@@ -295,7 +295,7 @@ public sealed class PerGameSettingsDialog : Window
|
||||
_envRow.IsOverridden = true;
|
||||
foreach (var (name, box) in _envBoxes)
|
||||
{
|
||||
box.IsChecked = IsEnvironmentEnabled(env, name, defaultValue: false);
|
||||
box.IsChecked = IsEnvironmentEnabled(env, name, defaultValue: name == "SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,10 +396,17 @@ public sealed class PerGameSettingsDialog : Window
|
||||
|
||||
private List<string> BuildEnvironmentEntries()
|
||||
{
|
||||
return _envBoxes
|
||||
.Where(entry => entry.Box.IsChecked == true)
|
||||
const string guestImageCpuSync = "SHARPEMU_GUEST_IMAGE_CPU_SYNC";
|
||||
var entries = _envBoxes
|
||||
.Where(entry => entry.Name != guestImageCpuSync && entry.Box.IsChecked == true)
|
||||
.Select(entry => entry.Name)
|
||||
.ToList();
|
||||
if (_envBoxes.First(entry => entry.Name == guestImageCpuSync).Box.IsChecked != true)
|
||||
{
|
||||
entries.Add(guestImageCpuSync + "=0");
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static bool IsEnvironmentEnabled(
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Window and text defaults shared by all launcher views.
|
||||
-->
|
||||
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style Selector="Window">
|
||||
<Setter Property="FontFamily" Value="Inter, Segoe UI, sans-serif" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.sectionTitle">
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="LetterSpacing" Value="1.5" />
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.fieldLabel">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedBrush}" />
|
||||
<Setter Property="Margin" Value="0,0,0,6" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -1,84 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Shared launcher button variants and page switcher styles.
|
||||
-->
|
||||
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style Selector="Button.accent">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="22,10" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AccentHoverBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.danger">
|
||||
<Setter Property="Background" Value="{StaticResource DangerBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="22,10" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.danger:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource DangerHoverBrush}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.ghost">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
<Setter Property="Padding" Value="12,7" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ToggleButton.ghost">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
<Setter Property="Padding" Value="12,7" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton.ghost:checked /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Top-level page switcher (Library / Options): plain transparent
|
||||
buttons, not TabItem, so there is no Fluent selected-tab underline.
|
||||
The active page is conveyed by brightness alone; LB/RB gamepad
|
||||
hints flank the pair. -->
|
||||
<Style Selector="Button.segment">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedBrush}" />
|
||||
<Setter Property="FontSize" Value="22" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Padding" Value="6,4" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="Button.segment:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
<Style Selector="Button.segment.active">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Console list typography and compact item spacing.
|
||||
-->
|
||||
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style Selector="ListBox.console">
|
||||
<Setter Property="Background" Value="#0B0E14" />
|
||||
<Setter Property="FontFamily" Value="Cascadia Mono, Consolas, Courier New, monospace" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.console ListBoxItem">
|
||||
<Setter Property="Padding" Value="10,1" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -1,29 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Shared text input and context-menu styles.
|
||||
-->
|
||||
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ContextMenu">
|
||||
<Setter Property="Background" Value="{StaticResource CardBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="Padding" Value="6" />
|
||||
</Style>
|
||||
<Style Selector="ContextMenu MenuItem">
|
||||
<Setter Property="Padding" Value="10,7" />
|
||||
<Setter Property="CornerRadius" Value="7" />
|
||||
</Style>
|
||||
<Style Selector="ContextMenu Separator">
|
||||
<Setter Property="Background" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="8,4" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -1,37 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Cover-art library item states and motion.
|
||||
-->
|
||||
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem">
|
||||
<Setter Property="Padding" Value="10" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="14" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="RenderTransform" Value="translateY(0px)" />
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.12" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:pointerover">
|
||||
<Setter Property="RenderTransform" Value="translateY(-3px)" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource TileHoverBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource TileSelectedBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.tileGrid ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource TileSelectedBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentHoverBrush}" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -1,55 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Shared card, badge, hint and cover surfaces.
|
||||
-->
|
||||
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style Selector="Border.card">
|
||||
<Setter Property="Background" Value="{StaticResource CardBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Padding" Value="16" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.pill">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="CornerRadius" Value="999" />
|
||||
<Setter Property="Padding" Value="10,3" />
|
||||
</Style>
|
||||
|
||||
<!-- Session status/hotkey badges: the title-id pill geometry with a
|
||||
tinted fill so state (RUNNING) and keys (F11) read at a glance. -->
|
||||
<Style Selector="Border.badge">
|
||||
<Setter Property="CornerRadius" Value="999" />
|
||||
<Setter Property="Padding" Value="8,2" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="Border.badge.running">
|
||||
<Setter Property="Background" Value="#1E46C46B" />
|
||||
<Setter Property="BorderBrush" Value="#5546C46B" />
|
||||
</Style>
|
||||
<Style Selector="Border.badge.key">
|
||||
<Setter Property="Background" Value="#1E58A6FF" />
|
||||
<Setter Property="BorderBrush" Value="#5558A6FF" />
|
||||
</Style>
|
||||
|
||||
<!-- Gamepad shoulder-button hint chip (LB/RB, L1/R1). -->
|
||||
<Style Selector="Border.padHint">
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="Padding" Value="8,3" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.coverShadow">
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="BoxShadow" Value="0 6 14 0 #55000000" />
|
||||
</Style>
|
||||
<Style Selector="Border.coverClip">
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -1,34 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Control theme for the shared launcher settings row.
|
||||
-->
|
||||
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:SharpEmu.GUI">
|
||||
<ControlTheme x:Key="{x:Type local:SettingRow}" TargetType="local:SettingRow">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2" Margin="0,0,16,0">
|
||||
<TextBlock x:Name="PART_Label" Text="{TemplateBinding Label}" FontSize="13" />
|
||||
<TextBlock Text="{TemplateBinding Description}" FontSize="11"
|
||||
Foreground="{StaticResource MutedBrush}" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Description, RelativeSource={RelativeSource TemplatedParent},
|
||||
Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<ToggleSwitch OnContent="Override" OffContent="Override" MinWidth="0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{TemplateBinding ShowOverride}"
|
||||
IsChecked="{Binding IsOverridden, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
|
||||
<ContentPresenter x:Name="PART_Slot"
|
||||
Content="{TemplateBinding Content}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -1,32 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
Shared colors and brushes used throughout the launcher.
|
||||
-->
|
||||
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Color x:Key="SystemAccentColor">#7C5CFC</Color>
|
||||
|
||||
<LinearGradientBrush x:Key="BgBrush" StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#12151F" />
|
||||
<GradientStop Offset="0.55" Color="#0D1017" />
|
||||
<GradientStop Offset="1" Color="#0B0D14" />
|
||||
</LinearGradientBrush>
|
||||
|
||||
<SolidColorBrush x:Key="ChromeBrush" Color="#090C12" />
|
||||
<SolidColorBrush x:Key="CardBrush" Color="#141924" />
|
||||
<SolidColorBrush x:Key="CardBorderBrush" Color="#232B3A" />
|
||||
<SolidColorBrush x:Key="ElevatedBrush" Color="#1B2230" />
|
||||
<SolidColorBrush x:Key="TextBrush" Color="#E8ECF4" />
|
||||
<SolidColorBrush x:Key="MutedBrush" Color="#8B94A7" />
|
||||
<SolidColorBrush x:Key="FaintBrush" Color="#5A6478" />
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="#7C5CFC" />
|
||||
<SolidColorBrush x:Key="AccentHoverBrush" Color="#8F73FF" />
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#E5484D" />
|
||||
<SolidColorBrush x:Key="DangerHoverBrush" Color="#F2555A" />
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#46C46B" />
|
||||
<SolidColorBrush x:Key="InfoBrush" Color="#58A6FF" />
|
||||
<SolidColorBrush x:Key="TileHoverBrush" Color="#1A2130" />
|
||||
<SolidColorBrush x:Key="TileSelectedBrush" Color="#212A3F" />
|
||||
</ResourceDictionary>
|
||||
@@ -87,10 +87,13 @@ public static unsafe class GuestImageWriteTracker
|
||||
|
||||
private static RangeSnapshot _rangeSnapshot = RangeSnapshot.Empty;
|
||||
|
||||
// CPU-written guest image synchronization is the compatible default. A few
|
||||
// titles (currently GTA V) require the lower-overhead watch-only path and
|
||||
// opt out explicitly with SHARPEMU_GUEST_IMAGE_CPU_SYNC=0.
|
||||
private static readonly bool _enabled =
|
||||
string.Equals(
|
||||
!string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_CPU_SYNC"),
|
||||
"1",
|
||||
"0",
|
||||
StringComparison.Ordinal);
|
||||
private static readonly (bool Wildcard, ulong[] Addresses) _lifetimeTraceFilter =
|
||||
ParseAddressList(Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_IMAGE_ADDRS"));
|
||||
|
||||
@@ -42,15 +42,6 @@ public static class AudioOut2Exports
|
||||
// with ContextMemoryAlignment (0x100).
|
||||
private const int PortStateSize = 0x20;
|
||||
private const int SpeakerInfoSize = 0x20;
|
||||
|
||||
private static readonly string _stackOutBufferModes =
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_AUDIO_OUT2_STACK_WRITES") ?? "1";
|
||||
|
||||
private static bool AllowStackOut(string which) =>
|
||||
string.Equals(_stackOutBufferModes, "1", StringComparison.Ordinal) ||
|
||||
_stackOutBufferModes
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Contains(which, StringComparer.OrdinalIgnoreCase);
|
||||
private const int PortParamSize = 0x40;
|
||||
private const int AttributeEntrySize = 0x18;
|
||||
private const uint PortAttributeIdPcm = 0;
|
||||
@@ -60,7 +51,6 @@ public static class AudioOut2Exports
|
||||
private static int _nextPortId;
|
||||
private static long _pushTraceCount;
|
||||
private static long _submitTraceCount;
|
||||
private static long _submitSkipTraceCount;
|
||||
private static long _attributePcmTraceCount;
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, byte> SpeakerArrays = new();
|
||||
@@ -136,9 +126,6 @@ public static class AudioOut2Exports
|
||||
public uint SamplingFrequency { get; }
|
||||
public uint GrainSamples { get; }
|
||||
public ulong PcmAddress;
|
||||
|
||||
public int PcmPending;
|
||||
|
||||
}
|
||||
|
||||
// Two host streams: primary FMOD context (menus) and everything else
|
||||
@@ -207,19 +194,6 @@ public static class AudioOut2Exports
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
var contextMemorySize = (ulong)AudioOut2ContextMemorySize;
|
||||
Span<byte> param = stackalloc byte[AudioOut2ContextParamSize];
|
||||
if (ctx.Memory.TryRead(paramAddress, param))
|
||||
{
|
||||
var queueDepth = BinaryPrimitives.ReadUInt32LittleEndian(param[0x0C..]);
|
||||
if (queueDepth == 0)
|
||||
{
|
||||
queueDepth = 4;
|
||||
}
|
||||
|
||||
contextMemorySize = checked(0x10000UL + (queueDepth * 0x590UL));
|
||||
}
|
||||
|
||||
// Heap: {size, alignment} (16 bytes), matching sceAudioPropagationSystemQueryMemory.
|
||||
// Stack: SIZE ONLY as a full ulong (8 bytes). Writing alignment at +8 is how
|
||||
// [rbp-0x30] became 0x100 on GTA V Enhanced. Do NOT shrink this to uint32 —
|
||||
@@ -228,10 +202,10 @@ public static class AudioOut2Exports
|
||||
if (IsGuestStackAddress(memoryInfoAddress))
|
||||
{
|
||||
Span<byte> sizeOnly = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(sizeOnly, contextMemorySize);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(sizeOnly, AudioOut2ContextMemorySize);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] audio_out2.context-query-memory stack-size-only " +
|
||||
$"out=0x{memoryInfoAddress:X} size=0x{contextMemorySize:X}");
|
||||
$"out=0x{memoryInfoAddress:X} size=0x{AudioOut2ContextMemorySize:X}");
|
||||
return ctx.Memory.TryWrite(memoryInfoAddress, sizeOnly)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
@@ -239,11 +213,11 @@ public static class AudioOut2Exports
|
||||
|
||||
Span<byte> memoryInfo = stackalloc byte[0x10];
|
||||
memoryInfo.Clear();
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x00..], contextMemorySize);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x00..], AudioOut2ContextMemorySize);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x08..], AudioOut2ContextMemoryAlignment);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] audio_out2.context-query-memory out=0x{memoryInfoAddress:X} " +
|
||||
$"size=0x{contextMemorySize:X} align=0x{AudioOut2ContextMemoryAlignment:X}");
|
||||
$"size=0x{AudioOut2ContextMemorySize:X} align=0x{AudioOut2ContextMemoryAlignment:X}");
|
||||
return ctx.Memory.TryWrite(memoryInfoAddress, memoryInfo)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
@@ -351,10 +325,7 @@ public static class AudioOut2Exports
|
||||
{
|
||||
if (Contexts.TryGetValue(ctx[CpuRegister.Rdi], out var state))
|
||||
{
|
||||
if (!TrySubmitContextAudio(ctx, state))
|
||||
{
|
||||
state.PaceAdvance();
|
||||
}
|
||||
state.PaceAdvance();
|
||||
}
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
@@ -371,16 +342,14 @@ public static class AudioOut2Exports
|
||||
// uint64 write into a stack slot at [rbp-0x14] next to the canary at
|
||||
// [rbp-0x10] zeroed the canary low half and killed Bink Snd @ eboot+0xAE36.
|
||||
var outLevelAddress = ctx[CpuRegister.Rsi];
|
||||
var outAvailableAddress = ctx[CpuRegister.Rdx];
|
||||
if (outLevelAddress == 0)
|
||||
{
|
||||
outLevelAddress = outAvailableAddress;
|
||||
outAvailableAddress = 0;
|
||||
outLevelAddress = ctx[CpuRegister.Rdx];
|
||||
}
|
||||
|
||||
Span<byte> level = stackalloc byte[sizeof(uint)];
|
||||
if (outLevelAddress != 0)
|
||||
{
|
||||
Span<byte> level = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(level, 0);
|
||||
if (!ctx.Memory.TryWrite(outLevelAddress, level))
|
||||
{
|
||||
@@ -388,20 +357,6 @@ public static class AudioOut2Exports
|
||||
}
|
||||
}
|
||||
|
||||
if (outAvailableAddress != 0 &&
|
||||
outAvailableAddress != outLevelAddress &&
|
||||
IsWritableOutBuffer(outAvailableAddress))
|
||||
{
|
||||
var available = Contexts.TryGetValue(ctx[CpuRegister.Rdi], out var context)
|
||||
? context.QueueDepth
|
||||
: 4u;
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(level, available);
|
||||
if (!ctx.Memory.TryWrite(outAvailableAddress, level))
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
@@ -463,7 +418,6 @@ public static class AudioOut2Exports
|
||||
}
|
||||
|
||||
port.PcmAddress = BinaryPrimitives.ReadUInt64LittleEndian(pcm);
|
||||
Volatile.Write(ref port.PcmPending, port.PcmAddress != 0 ? 1 : 0);
|
||||
var n = Interlocked.Increment(ref _attributePcmTraceCount);
|
||||
if (n <= 8 || n % 500 == 0)
|
||||
{
|
||||
@@ -521,14 +475,13 @@ public static class AudioOut2Exports
|
||||
// Handle encodes only the low type byte; PortState keeps the full type
|
||||
// so object ports (0x01xx) can still be filtered at submit time.
|
||||
var handle = 0x2000_0000UL | ((ulong)(portType & 0xFF) << 16) | portId;
|
||||
var portState = new PortState(
|
||||
Ports[handle] = new PortState(
|
||||
handle,
|
||||
contextHandle,
|
||||
portType,
|
||||
dataFormat,
|
||||
samplingFrequency,
|
||||
grainSamples);
|
||||
Ports[handle] = portState;
|
||||
if (!TryWriteUInt64(ctx, outPortAddress, handle))
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
@@ -559,8 +512,7 @@ public static class AudioOut2Exports
|
||||
// caller frames / canaries (state=0x7FFFDE1FF688 right before fail).
|
||||
// Heap outs still get a real state blob even when the handle wasn't
|
||||
// minted by PortCreate — this title synthesizes port ids itself.
|
||||
if (IsGuestStackAddress(stateAddress) &&
|
||||
!(AllowStackOut("portstate") && Ports.ContainsKey(portHandle)))
|
||||
if (IsGuestStackAddress(stateAddress))
|
||||
{
|
||||
TraceAudioOut2(
|
||||
$"port-get-state skip-stack handle=0x{portHandle:X} state=0x{stateAddress:X}");
|
||||
@@ -593,48 +545,6 @@ public static class AudioOut2Exports
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4dq2rblWlg0",
|
||||
ExportName = "sceAudioOut2ContextSetAttributes",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut2")]
|
||||
public static int AudioOut2ContextSetAttributes(CpuContext ctx)
|
||||
{
|
||||
var attributeAddress = ctx[CpuRegister.Rsi];
|
||||
var count = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||
return SetReturn(
|
||||
ctx,
|
||||
count != 0 && attributeAddress == 0
|
||||
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
|
||||
: 0);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "bkBN+CMLwRc",
|
||||
ExportName = "sceAudioOut2GetSystemState",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut2")]
|
||||
public static int AudioOut2GetSystemState(CpuContext ctx)
|
||||
{
|
||||
var stateAddress = ctx[CpuRegister.Rdi];
|
||||
if (stateAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (IsGuestStackAddress(stateAddress) && !AllowStackOut("systemstate"))
|
||||
{
|
||||
TraceAudioOut2($"get-system-state skip-stack out=0x{stateAddress:X}");
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
Span<byte> state = stackalloc byte[0x40];
|
||||
state.Clear();
|
||||
return ctx.Memory.TryWrite(stateAddress, state)
|
||||
? SetReturn(ctx, 0)
|
||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
// rdi=out buffer, rsi=type/flag (not a pointer). Fixed-size write only.
|
||||
[SysAbiExport(
|
||||
Nid = "DImz2Ft9E2g",
|
||||
@@ -649,7 +559,8 @@ public static class AudioOut2Exports
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (IsGuestStackAddress(infoAddress) && !AllowStackOut("speaker"))
|
||||
// Same rule as PortGetState — never bulk-write speaker info onto the stack.
|
||||
if (IsGuestStackAddress(infoAddress))
|
||||
{
|
||||
TraceAudioOut2($"get-speaker-info skip-stack out=0x{infoAddress:X}");
|
||||
return SetReturn(ctx, 0);
|
||||
@@ -877,8 +788,25 @@ public static class AudioOut2Exports
|
||||
return false;
|
||||
}
|
||||
|
||||
// Serialize submits per process so ArrayPool buffers stay private; primary
|
||||
// and secondary devices still receive independent PCM (no digital sum).
|
||||
lock (HostSubmitGate)
|
||||
{
|
||||
if (!TryPickMainBed(context.Handle, out var mainPort))
|
||||
{
|
||||
mainPort = null;
|
||||
}
|
||||
|
||||
if (!TryPickAuxBed(context.Handle, out var auxPort))
|
||||
{
|
||||
auxPort = null;
|
||||
}
|
||||
|
||||
if (mainPort is null && auxPort is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mix = ArrayPool<float>.Shared.Rent(frames * 2);
|
||||
var source = ArrayPool<byte>.Shared.Rent(frames * 16 * sizeof(float));
|
||||
var output = ArrayPool<byte>.Shared.Rent(frames * AudioPcmConversion.OutputFrameSize);
|
||||
@@ -886,11 +814,16 @@ public static class AudioOut2Exports
|
||||
{
|
||||
mix.AsSpan(0, frames * 2).Clear();
|
||||
var mixedPorts = 0;
|
||||
foreach (var port in Ports.Values)
|
||||
ulong lastPort = 0;
|
||||
uint lastFormat = 0;
|
||||
var lastChannels = 0;
|
||||
|
||||
// At most one MAIN/BGM + one AUX on this context. Menus stay on
|
||||
// MAIN; intro/Bink rides AUX without stacking every bed.
|
||||
for (var bed = 0; bed < 2; bed++)
|
||||
{
|
||||
if (port.ContextHandle != context.Handle ||
|
||||
port.PcmAddress == 0 ||
|
||||
Interlocked.Exchange(ref port.PcmPending, 0) == 0 ||
|
||||
var port = bed == 0 ? mainPort : auxPort;
|
||||
if (port is null ||
|
||||
!TryDecodeDataFormat(port.DataFormat, out var ch, out var bps, out var isFloat))
|
||||
{
|
||||
continue;
|
||||
@@ -917,6 +850,9 @@ public static class AudioOut2Exports
|
||||
isFloat,
|
||||
additive: mixedPorts > 0);
|
||||
mixedPorts++;
|
||||
lastPort = port.Handle;
|
||||
lastFormat = port.DataFormat;
|
||||
lastChannels = ch;
|
||||
}
|
||||
|
||||
if (mixedPorts == 0)
|
||||
@@ -926,11 +862,22 @@ public static class AudioOut2Exports
|
||||
|
||||
var outputSpan = output.AsSpan(0, frames * AudioPcmConversion.OutputFrameSize);
|
||||
var peak = 0f;
|
||||
var any = false;
|
||||
for (var frame = 0; frame < frames; frame++)
|
||||
{
|
||||
var left = Math.Clamp(mix[frame * 2], -1f, 1f);
|
||||
var right = Math.Clamp(mix[(frame * 2) + 1], -1f, 1f);
|
||||
peak = Math.Max(peak, Math.Max(Math.Abs(left), Math.Abs(right)));
|
||||
var framePeak = Math.Max(Math.Abs(left), Math.Abs(right));
|
||||
if (framePeak > peak)
|
||||
{
|
||||
peak = framePeak;
|
||||
}
|
||||
|
||||
if (framePeak > 1e-7f)
|
||||
{
|
||||
any = true;
|
||||
}
|
||||
|
||||
BinaryPrimitives.WriteInt16LittleEndian(
|
||||
outputSpan[(frame * AudioPcmConversion.OutputFrameSize)..],
|
||||
FloatToPcm16(left));
|
||||
@@ -939,6 +886,13 @@ public static class AudioOut2Exports
|
||||
FloatToPcm16(right));
|
||||
}
|
||||
|
||||
if (!any)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind primary only after a real grain so silent early contexts
|
||||
// do not steal the menu device.
|
||||
var backend = ResolveContextBackend(context, out var backendName);
|
||||
if (backend is null)
|
||||
{
|
||||
@@ -946,11 +900,12 @@ public static class AudioOut2Exports
|
||||
}
|
||||
|
||||
var n = Interlocked.Increment(ref _submitTraceCount);
|
||||
if (n <= 8 || n % 500 == 0)
|
||||
if (n <= 8 || n % 200 == 0)
|
||||
{
|
||||
TraceAudioOut2(
|
||||
$"context-submit#{n} handle=0x{context.Handle:X} frames={frames} " +
|
||||
$"ports={mixedPorts} peak={peak:F4} backend={backendName}");
|
||||
$"ports={mixedPorts} lastPort=0x{lastPort:X} format=0x{lastFormat:X} " +
|
||||
$"ch={lastChannels} peak={peak:F4} backend={backendName}");
|
||||
}
|
||||
|
||||
return backend.Submit(outputSpan);
|
||||
@@ -964,6 +919,75 @@ public static class AudioOut2Exports
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryPickMainBed(ulong contextHandle, out PortState? chosen)
|
||||
{
|
||||
chosen = null;
|
||||
var chosenScore = int.MinValue;
|
||||
foreach (var port in Ports.Values)
|
||||
{
|
||||
if (port.ContextHandle != contextHandle ||
|
||||
port.PcmAddress == 0 ||
|
||||
IsObjectPort(port.PortType) ||
|
||||
!IsMainOrBgmPort(port.PortType) ||
|
||||
!TryDecodeDataFormat(port.DataFormat, out var channels, out _, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var score = channels switch
|
||||
{
|
||||
2 => 300,
|
||||
1 => 200,
|
||||
8 => 100,
|
||||
_ => 50,
|
||||
};
|
||||
if ((port.PortType & 0xFF) == 0)
|
||||
{
|
||||
score += 20; // MAIN over BGM
|
||||
}
|
||||
|
||||
if (score > chosenScore)
|
||||
{
|
||||
chosenScore = score;
|
||||
chosen = port;
|
||||
}
|
||||
}
|
||||
|
||||
return chosen is not null;
|
||||
}
|
||||
|
||||
private static bool TryPickAuxBed(ulong contextHandle, out PortState? chosen)
|
||||
{
|
||||
chosen = null;
|
||||
var chosenScore = int.MinValue;
|
||||
foreach (var port in Ports.Values)
|
||||
{
|
||||
if (port.ContextHandle != contextHandle ||
|
||||
port.PcmAddress == 0 ||
|
||||
IsObjectPort(port.PortType) ||
|
||||
(port.PortType & 0xFF) != 6 ||
|
||||
!TryDecodeDataFormat(port.DataFormat, out var channels, out _, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var score = channels switch
|
||||
{
|
||||
2 => 300,
|
||||
1 => 200,
|
||||
8 => 100,
|
||||
_ => 50,
|
||||
};
|
||||
if (score > chosenScore)
|
||||
{
|
||||
chosenScore = score;
|
||||
chosen = port;
|
||||
}
|
||||
}
|
||||
|
||||
return chosen is not null;
|
||||
}
|
||||
|
||||
private static bool IsMainOrBgmPort(ushort portType)
|
||||
{
|
||||
var kind = portType & 0xFF;
|
||||
|
||||
@@ -8459,13 +8459,15 @@ internal static unsafe class VulkanVideoPresenter
|
||||
/// </summary>
|
||||
private void DrainGuestImageCpuSync()
|
||||
{
|
||||
var syncEnabled = SharpEmu.HLE.GuestImageWriteTracker.Enabled;
|
||||
HashSet<ulong>? dirtyAddresses = null;
|
||||
List<(ulong Address, uint Width, uint Height, ulong ByteCount)>? extents = null;
|
||||
if (syncEnabled)
|
||||
if (!SharpEmu.HLE.GuestImageWriteTracker.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Interlocked.Exchange(ref _cpuWrittenGuestImageSyncRequested, 0);
|
||||
|
||||
HashSet<ulong>? dirtyAddresses = null;
|
||||
List<(ulong Address, uint Width, uint Height, ulong ByteCount)>? extents = null;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_guestImageExtents.Count > 0)
|
||||
@@ -8534,8 +8536,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (_textureCache.Count == 0)
|
||||
{
|
||||
if (dirtyAddresses is not null)
|
||||
@@ -14508,26 +14508,27 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
if (!tookPresentation)
|
||||
{
|
||||
// Upstream also replays the last host splash here after an
|
||||
// embedded-surface resize. That path is gone with the SDL
|
||||
// window: there is no host surface to resize around, and the
|
||||
// swapchain recreate above already covers SDL's own resize.
|
||||
//
|
||||
// A render-loop tick with no newer flip is normal. Warn only when
|
||||
// an actual queued presentation is waiting on unfinished guest work.
|
||||
if (SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.IsActive ||
|
||||
ShouldTracePresentedGuestImageContentsForDiagnostics())
|
||||
var hasPendingPresentation =
|
||||
HasPendingGuestPresentation(_presentedSequence);
|
||||
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TracePresentNotTaken(
|
||||
_presentedSequence,
|
||||
hasPendingPresentation);
|
||||
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TraceGpuWaitSnapshot();
|
||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
||||
hasPendingPresentation &&
|
||||
_presentNotTakenLoggedSequence != _presentedSequence)
|
||||
{
|
||||
var hasPendingPresentation =
|
||||
HasPendingGuestPresentation(_presentedSequence);
|
||||
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TracePresentNotTaken(
|
||||
_presentedSequence,
|
||||
hasPendingPresentation);
|
||||
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TraceGpuWaitSnapshot();
|
||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
||||
hasPendingPresentation &&
|
||||
_presentNotTakenLoggedSequence != _presentedSequence)
|
||||
{
|
||||
_presentNotTakenLoggedSequence = _presentedSequence;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] vk.present_not_taken seq={_presentedSequence} " +
|
||||
"— presentation submitted but its required guest work isn't complete; nothing shown.");
|
||||
}
|
||||
_presentNotTakenLoggedSequence = _presentedSequence;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] vk.present_not_taken seq={_presentedSequence} " +
|
||||
"— presentation submitted but its required guest work isn't complete; nothing shown.");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user