mirror of
https://github.com/xenios-jp/XeniOS.git
synced 2026-07-11 15:19:09 -07:00
[macOS/UI] Add ability to launch with Rosetta2 on Apple Silicon
Reworks the quick settings UI to remove Audio section that's no longer very meaningful and adds platform specific toggles there instead on macOS and Linux.
This commit is contained in:
@@ -112,6 +112,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if XE_PLATFORM_MAC
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include "xenia/apu/apu_flags.h"
|
||||
#include "xenia/apu/audio_system.h"
|
||||
#include "xenia/cpu/backend/backend.h"
|
||||
@@ -278,6 +282,11 @@ DEFINE_bool(
|
||||
"Enable the Metal performance HUD (MTL_HUD_ENABLED=1) when launching "
|
||||
"games in separate processes.",
|
||||
"MacOS");
|
||||
DEFINE_bool(
|
||||
use_rosetta, false,
|
||||
"Launch games under Rosetta 2 (x86_64) instead of native arm64.\n"
|
||||
"Only meaningful on Apple Silicon hosts running a universal app bundle.",
|
||||
"MacOS");
|
||||
#endif
|
||||
DEFINE_bool(disable_game_window_mouse, false,
|
||||
"Disables mouse interactions in the game window, including "
|
||||
@@ -312,6 +321,25 @@ struct WxToolbarState {
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
#if XE_PLATFORM_MAC
|
||||
// execv of a universal binary inherits the parent's arch; arch(1) is the
|
||||
// only way to switch (e.g. Rosetta-running game spawning the native arm64
|
||||
// UI back). Returns "-arm64" / "-x86_64" when a switch is needed, or null
|
||||
// when default exec already picks the right slice.
|
||||
const char* RosettaArchFlagIfSwitchNeeded(bool want_rosetta) {
|
||||
int translated = 0;
|
||||
size_t sz = sizeof(translated);
|
||||
// Missing sysctl => assume native; only Rosetta children set this to 1.
|
||||
sysctlbyname("sysctl.proc_translated", &translated, &sz, nullptr, 0);
|
||||
const bool parent_rosetta = (translated != 0);
|
||||
if (parent_rosetta == want_rosetta) {
|
||||
return nullptr;
|
||||
}
|
||||
return want_rosetta ? "-x86_64" : "-arm64";
|
||||
}
|
||||
#endif
|
||||
|
||||
// The first tool flips between "Open" (no title) and "Back" (title running).
|
||||
constexpr int kToolIdOpenBack = 11000;
|
||||
constexpr int kToolIdSettings = 11001;
|
||||
@@ -733,8 +761,13 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||
std::string gamemode_cmd;
|
||||
|
||||
#if XE_PLATFORM_LINUX
|
||||
// unsetenv on the false branch: the env var is inherited across the
|
||||
// UI <-> title spawn chain, so a stale "1" from a previous launch
|
||||
// would otherwise outlive the unchecked cvar.
|
||||
if (cvars::use_mangohud) {
|
||||
setenv("MANGOHUD", "1", 1);
|
||||
} else {
|
||||
unsetenv("MANGOHUD");
|
||||
}
|
||||
if (cvars::use_gamemode) {
|
||||
gamemode_cmd = "gamemoderun";
|
||||
@@ -743,8 +776,17 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||
#if XE_PLATFORM_MAC
|
||||
if (cvars::use_metal_hud) {
|
||||
setenv("MTL_HUD_ENABLED", "1", 1);
|
||||
} else {
|
||||
unsetenv("MTL_HUD_ENABLED");
|
||||
}
|
||||
// Empty host_path = returning to the UI/game list; the per-title
|
||||
// rosetta override shouldn't follow us back, so use native there.
|
||||
const bool want_rosetta = !host_path.empty() && cvars::use_rosetta;
|
||||
const char* arch_flag = RosettaArchFlagIfSwitchNeeded(want_rosetta);
|
||||
#else
|
||||
const char* arch_flag = nullptr;
|
||||
#endif
|
||||
const bool use_arch_wrapper = (arch_flag != nullptr);
|
||||
|
||||
arg_storage.push_back(executable_path.string());
|
||||
|
||||
@@ -790,6 +832,10 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||
if (!gamemode_cmd.empty()) {
|
||||
argv.push_back(gamemode_cmd.c_str());
|
||||
}
|
||||
if (use_arch_wrapper) {
|
||||
argv.push_back("arch");
|
||||
argv.push_back(arch_flag);
|
||||
}
|
||||
for (const std::string& arg : arg_storage) {
|
||||
argv.push_back(arg.c_str());
|
||||
}
|
||||
@@ -797,6 +843,8 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||
|
||||
if (!gamemode_cmd.empty()) {
|
||||
execvp(gamemode_cmd.c_str(), const_cast<char**>(argv.data()));
|
||||
} else if (use_arch_wrapper) {
|
||||
execvp("arch", const_cast<char**>(argv.data()));
|
||||
} else {
|
||||
execv(executable_path.c_str(), const_cast<char**>(argv.data()));
|
||||
}
|
||||
@@ -3458,8 +3506,11 @@ void EmulatorWindow::LaunchTitleInNewProcess(
|
||||
std::string gamemode_cmd;
|
||||
|
||||
#if XE_PLATFORM_LINUX
|
||||
// See OnEmulatorInitialized: env vars survive the UI<->title spawn chain.
|
||||
if (cvars::use_mangohud) {
|
||||
setenv("MANGOHUD", "1", 1);
|
||||
} else {
|
||||
unsetenv("MANGOHUD");
|
||||
}
|
||||
if (cvars::use_gamemode) {
|
||||
gamemode_cmd = "gamemoderun";
|
||||
@@ -3469,8 +3520,18 @@ void EmulatorWindow::LaunchTitleInNewProcess(
|
||||
#if XE_PLATFORM_MAC
|
||||
if (cvars::use_metal_hud) {
|
||||
setenv("MTL_HUD_ENABLED", "1", 1);
|
||||
} else {
|
||||
unsetenv("MTL_HUD_ENABLED");
|
||||
}
|
||||
const char* arch_flag = RosettaArchFlagIfSwitchNeeded(cvars::use_rosetta);
|
||||
#else
|
||||
const char* arch_flag = nullptr;
|
||||
#endif
|
||||
const bool use_arch_wrapper = (arch_flag != nullptr);
|
||||
if (use_arch_wrapper) {
|
||||
argv.push_back("arch");
|
||||
argv.push_back(arch_flag);
|
||||
}
|
||||
|
||||
argv.push_back(executable_path.c_str());
|
||||
|
||||
@@ -3507,6 +3568,8 @@ void EmulatorWindow::LaunchTitleInNewProcess(
|
||||
|
||||
if (!gamemode_cmd.empty()) {
|
||||
execvp(gamemode_cmd.c_str(), const_cast<char**>(argv.data()));
|
||||
} else if (use_arch_wrapper) {
|
||||
execvp("arch", const_cast<char**>(argv.data()));
|
||||
} else {
|
||||
execv(executable_path.c_str(), const_cast<char**>(argv.data()));
|
||||
}
|
||||
@@ -3616,7 +3679,12 @@ xe::X_STATUS EmulatorWindow::RunTitle(
|
||||
}
|
||||
|
||||
#if XE_PLATFORM_LINUX || XE_PLATFORM_MAC
|
||||
// In-process launch is unstable here; spawn a fresh process.
|
||||
// In-process launch is unstable here; spawn a fresh process. Apply the
|
||||
// title's overrides first — LaunchTitleInNewProcess reads cvars (e.g.
|
||||
// use_rosetta, use_gamemode) before fork, so the child needs the
|
||||
// overridden values inherited from the parent.
|
||||
config::ReloadConfig();
|
||||
config::LoadGameConfigForFile(abs_path);
|
||||
LaunchTitleInNewProcess(abs_path);
|
||||
return X_STATUS_SUCCESS;
|
||||
#endif
|
||||
|
||||
@@ -247,26 +247,49 @@ void QuickSettingsDialog::Build() {
|
||||
main_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 6);
|
||||
}
|
||||
|
||||
// ---- Audio ----
|
||||
#if XE_PLATFORM_LINUX
|
||||
// ---- Linux launch options ----
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Audio"));
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Linux"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
auto* grid = new wxFlexGridSizer(2, 6, 8);
|
||||
grid->AddGrowableCol(1, 1);
|
||||
|
||||
auto* apu = add_combo(box, "apu", find_options("apu"));
|
||||
auto* apu_label = add_label(box, _("Audio Backend:"));
|
||||
remember_label("apu", apu_label);
|
||||
add_form_row(grid, apu_label, apu);
|
||||
auto* mangohud = add_check(box, "use_mangohud");
|
||||
auto* mangohud_label = add_label(box, _("MangoHUD overlay:"));
|
||||
remember_label("use_mangohud", mangohud_label);
|
||||
add_form_row(grid, mangohud_label, mangohud);
|
||||
|
||||
auto* volume = add_spin(box, "volume", 0, 100);
|
||||
auto* volume_label = add_label(box, _("Default Volume:"));
|
||||
remember_label("volume", volume_label);
|
||||
add_form_row(grid, volume_label, volume);
|
||||
auto* gamemode = add_check(box, "use_gamemode");
|
||||
auto* gamemode_label = add_label(box, _("Feral GameMode:"));
|
||||
remember_label("use_gamemode", gamemode_label);
|
||||
add_form_row(grid, gamemode_label, gamemode);
|
||||
|
||||
box_sizer->Add(grid, 1, wxEXPAND | wxALL, 6);
|
||||
main_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 6);
|
||||
}
|
||||
#elif XE_PLATFORM_MAC
|
||||
// ---- macOS launch options ----
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("macOS"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
auto* grid = new wxFlexGridSizer(2, 6, 8);
|
||||
grid->AddGrowableCol(1, 1);
|
||||
|
||||
auto* metal_hud = add_check(box, "use_metal_hud");
|
||||
auto* metal_hud_label = add_label(box, _("Metal performance HUD:"));
|
||||
remember_label("use_metal_hud", metal_hud_label);
|
||||
add_form_row(grid, metal_hud_label, metal_hud);
|
||||
|
||||
auto* rosetta = add_check(box, "use_rosetta");
|
||||
auto* rosetta_label = add_label(box, _("Run under Rosetta 2 (x86_64):"));
|
||||
remember_label("use_rosetta", rosetta_label);
|
||||
add_form_row(grid, rosetta_label, rosetta);
|
||||
|
||||
box_sizer->Add(grid, 1, wxEXPAND | wxALL, 6);
|
||||
main_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 6);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- Other ----
|
||||
{
|
||||
@@ -549,11 +572,6 @@ void QuickSettingsDialog::Save() {
|
||||
static_cast<int64_t>(std::stoull(opt.pending_value))));
|
||||
} catch (...) {
|
||||
}
|
||||
} else if (name == "volume") {
|
||||
try {
|
||||
apply(var, toml::value(std::stoi(opt.pending_value)));
|
||||
} catch (...) {
|
||||
}
|
||||
} else if (name == "license_mask") {
|
||||
try {
|
||||
apply(var, toml::value(std::stoi(opt.pending_value)));
|
||||
@@ -561,7 +579,9 @@ void QuickSettingsDialog::Save() {
|
||||
}
|
||||
} else if (name == "fullscreen" || name == "present_letterbox" ||
|
||||
name == "discord" || name == "use_dedicated_xma_thread" ||
|
||||
name == "in_process_title_relaunch") {
|
||||
name == "in_process_title_relaunch" || name == "use_mangohud" ||
|
||||
name == "use_gamemode" || name == "use_metal_hud" ||
|
||||
name == "use_rosetta") {
|
||||
apply(var, toml::value(opt.pending_value == "true"));
|
||||
} else if (ui::FindIntCvarEnumOptions(name)) {
|
||||
// Dropdown int cvar: convert the selected option name to its id.
|
||||
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Combine two single-arch Xenia-edge.app bundles into one universal bundle.
|
||||
# Run after both arm64 and x86_64 builds complete. Similar to the lipo step
|
||||
# in .github/workflows/CI.yml but meant for interactive dev use.
|
||||
#
|
||||
# Usage:
|
||||
# tools/build-universal-macos.sh [arm64_app] [x86_64_app]
|
||||
# Defaults assume the conventional layout: build/ for arm64, build-x64/ for
|
||||
# x86_64, Release config.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
arm64_app=${1:-build/bin/macOS/Release/Xenia-edge.app}
|
||||
x86_64_app=${2:-build-x64/bin/macOS/Release/Xenia-edge.app}
|
||||
|
||||
for app in "$arm64_app" "$x86_64_app"; do
|
||||
if [[ ! -d "$app" ]]; then
|
||||
echo "error: app bundle not found: $app" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
arm64_exe="$arm64_app/Contents/MacOS/Xenia-edge"
|
||||
x86_64_exe="$x86_64_app/Contents/MacOS/Xenia-edge"
|
||||
|
||||
# Returns a path containing only the requested arch. If the input is already
|
||||
# thin and matches, returns the original path; if it's fat (e.g. left over from
|
||||
# a previous run of this script that lipo'd in place), extracts the slice to a
|
||||
# temp file so the next lipo doesn't see duplicate archs.
|
||||
thin_input() {
|
||||
local exe=$1
|
||||
local want=$2
|
||||
local out=$3
|
||||
local archs
|
||||
archs=$(lipo -archs "$exe")
|
||||
if [[ "$archs" != *"$want"* ]]; then
|
||||
echo "error: $exe is '$archs', expected '$want'." >&2
|
||||
echo " Reconfigure that build dir with -DCMAKE_OSX_ARCHITECTURES=$want." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$archs" == "$want" ]]; then
|
||||
printf -v "$out" '%s' "$exe"
|
||||
else
|
||||
local tmp
|
||||
tmp=$(mktemp -t Xenia-edge."$want")
|
||||
lipo -thin "$want" "$exe" -output "$tmp"
|
||||
printf -v "$out" '%s' "$tmp"
|
||||
fi
|
||||
}
|
||||
thin_input "$arm64_exe" arm64 arm64_input
|
||||
thin_input "$x86_64_exe" x86_64 x86_64_input
|
||||
|
||||
# Only the main Mach-O is merged
|
||||
# Fail loud if anything else diverges so we don't silently create a
|
||||
# bundle where resources don't match one of the slices.
|
||||
# xenia.log is a runtime drop (the app cwd is Contents/MacOS), and
|
||||
# _CodeSignature is regenerated by the codesign call below.
|
||||
if ! diff -r --brief \
|
||||
--exclude=Xenia-edge \
|
||||
--exclude=xenia.log \
|
||||
--exclude=_CodeSignature \
|
||||
"$arm64_app/Contents" "$x86_64_app/Contents"; then
|
||||
echo "error: bundles differ outside Contents/MacOS/Xenia-edge" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp=$(mktemp -t Xenia-edge.universal)
|
||||
lipo -create "$arm64_input" "$x86_64_input" -output "$tmp"
|
||||
mv "$tmp" "$arm64_exe"
|
||||
|
||||
info=$(lipo -info "$arm64_exe")
|
||||
echo "$info"
|
||||
if ! grep -q arm64 <<<"$info" || ! grep -q x86_64 <<<"$info"; then
|
||||
echo "error: lipo result missing an arch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# lipo invalidates the linker-applied ad-hoc signature; arm64 refuses to
|
||||
# launch unsigned, so re-sign ad-hoc.
|
||||
codesign --force --sign - "$arm64_app"
|
||||
|
||||
echo "universal bundle: $arm64_app"
|
||||
Reference in New Issue
Block a user