Merge branch 'aarch64-main' of https://github.com/theofficialgman/Wiicompiled into pr/50

This commit is contained in:
patchzyy
2026-08-30 22:09:50 +02:00
9 changed files with 158 additions and 16 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ $llvmReadobj = [System.IO.Path]::GetFullPath($LlvmReadobjPath)
$systemDlls = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
@(
'advapi32.dll', 'authz.dll', 'bcrypt.dll', 'combase.dll', 'comdlg32.dll', 'crypt32.dll',
'advapi32.dll', 'authz.dll', 'bcrypt.dll', 'bcryptprimitives.dll', 'combase.dll', 'comdlg32.dll', 'crypt32.dll',
'd3d11.dll', 'd3d12.dll', 'dbghelp.dll', 'dcomp.dll', 'dwrite.dll', 'dwmapi.dll',
'dxgi.dll', 'gdi32.dll', 'imm32.dll',
'iphlpapi.dll', 'kernel32.dll', 'mf.dll', 'mfplat.dll', 'mfreadwrite.dll', 'mfuuid.dll',
+6 -3
View File
@@ -16,6 +16,7 @@ if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
$repoRoot = [IO.Path]::GetFullPath($RepositoryRoot)
$launcher = Join-Path $repoRoot 'Launcher'
$setup = Join-Path $launcher 'WiiCompiled.Setup.Windows'
$common = Join-Path $launcher 'WiiCompiled.Setup.Common'
$failures = [Collections.Generic.List[string]]::new()
function Add-Failure([string]$Message) { $failures.Add($Message) }
@@ -48,9 +49,11 @@ function Compare-Set([string[]]$Expected, [string[]]$Actual, [string]$ExpectedNa
$pins = Get-MkwProjectPins (Join-Path $repoRoot 'projects\mkwii\recomp.yml')
# --- The Retro-WFC endpoint: recomp.yml owns it; the installer host pins the same string so a
# --- redirected or rewritten endpoint cannot be fetched from.
$inputValidation = Read-SourceFile (Join-Path $setup 'InputValidation.cs') 'InputValidation.cs'
$hostUri = Get-CapturedValue $inputValidation 'CurrentRetroWfcPayloadUri\s*=\s*"([^"]+)"' `
# --- redirected or rewritten endpoint cannot be fetched from. The literal lives in
# --- WiiCompiled.Setup.Common (shared with WiiCompiled.Setup.Linux) - InputValidation.cs only
# --- re-exports it as `= RetroWfcPayload.CurrentRetroWfcPayloadUri;`, no literal to capture there.
$retroWfcPayload = Read-SourceFile (Join-Path $common 'RetroWfcPayload.cs') 'RetroWfcPayload.cs'
$hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"([^"]+)"' `
'The host Retro-WFC endpoint constant'
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
Regular → Executable
+48 -9
View File
@@ -21,6 +21,42 @@ set -euo pipefail
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
workspace=$(cd "$script_dir/.." && pwd)
# `uname -m` reports the *kernel's* architecture, which can differ from userspace - an aarch64
# kernel can run a 32-bit armhf userland (as shipped by 32-bit Raspberry Pi OS), same as an x86_64
# kernel can run an i686 one. What matters here is which userspace binaries (dotnet, appimagetool)
# will actually run, so this reads the ELF header of this script's own running bash interpreter -
# real userspace - rather than trusting the kernel's self-report. /proc/$$/exe (not /proc/self/exe:
# that would resolve inside the readlink subprocess below, to readlink itself, not to bash) is this
# shell's own PID. EI_CLASS (byte 4: 1=32-bit, 2=64-bit) and e_machine (bytes 18-19: 3=EM_386,
# 40=EM_ARM, 62=EM_X86_64, 183=EM_AARCH64) are read as plain little-endian bytes, which every
# real-world x86/ARM Linux userland uses; ELF's big-endian encoding is a non-issue here since no
# Linux distro ships a big-endian x86 or ARM userland.
elf_exe=$(readlink -f "/proc/$$/exe")
elf_class=$(od -An -t u1 -j 4 -N 1 "$elf_exe" | tr -d ' ')
elf_machine_lo=$(od -An -t u1 -j 18 -N 1 "$elf_exe" | tr -d ' ')
elf_machine_hi=$(od -An -t u1 -j 19 -N 1 "$elf_exe" | tr -d ' ')
elf_machine=$(( elf_machine_hi * 256 + elf_machine_lo ))
# Mirrors the host-architecture detection NodToolProvider.cs already does (RuntimeInformation.
# OSArchitecture) so this script's own dotnet RID and appimagetool selection agree with the
# nodtool binary that same code path resolves below. local-build.sh needs no such mapping itself:
# it just drives the native CMake configure, which already accepts x86_64 or aarch64 natively
# (see runtime/CMakeLists.txt's CMAKE_SYSTEM_PROCESSOR check).
case "$elf_class:$elf_machine" in
2:62)
dotnet_rid=linux-x64
appimagetool_arch=x86_64
;;
2:183)
dotnet_rid=linux-arm64
appimagetool_arch=aarch64
;;
*)
echo "build-appimage.sh: unsupported userspace architecture (ELF class $elf_class, machine $elf_machine) - WiiCompiled requires a 64-bit x86_64 or aarch64 userland" >&2
exit 1
;;
esac
output_dir="$workspace/Launcher/dist"
appimagetool_override=""
@@ -40,10 +76,10 @@ appdir="$workspace/Launcher/artifacts/appimage-build/AppDir"
rm -rf "$appdir"
mkdir -p "$appdir/usr/bin" "$appdir/workspace/Launcher"
echo "Publishing the installer (self-contained linux-x64)..."
echo "Publishing the installer (self-contained $dotnet_rid)..."
publish_tmp="$workspace/Launcher/artifacts/appimage-build/publish"
rm -rf "$publish_tmp"
dotnet publish "$workspace/Launcher/WiiCompiled.Setup.Linux" -c Release -r linux-x64 \
dotnet publish "$workspace/Launcher/WiiCompiled.Setup.Linux" -c Release -r "$dotnet_rid" \
--self-contained -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true \
-o "$publish_tmp"
cp "$publish_tmp/WiiCompiled.Setup.Linux" "$appdir/usr/bin/wiicompiled-setup"
@@ -52,10 +88,10 @@ chmod +x "$appdir/usr/bin/wiicompiled-setup"
# Published as a self-contained binary too, so an AppImage user never needs a `dotnet` SDK on
# PATH at all - local-build.sh is told about it via --translator-bin and skips its own
# dotnet-build-from-source step entirely (see local-build.sh's translator resolution branch).
echo "Publishing the translator (self-contained linux-x64)..."
echo "Publishing the translator (self-contained $dotnet_rid)..."
translator_publish_tmp="$workspace/Launcher/artifacts/appimage-build/publish-translator"
rm -rf "$translator_publish_tmp"
dotnet publish "$workspace/translator/src/Translator.Cli" -c Release -r linux-x64 \
dotnet publish "$workspace/translator/src/Translator.Cli" -c Release -r "$dotnet_rid" \
--self-contained -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true \
-o "$translator_publish_tmp"
cp "$translator_publish_tmp/Translator.Cli" "$appdir/usr/bin/translator-cli"
@@ -155,11 +191,13 @@ PY
echo "Resolving appimagetool..."
appimagetool="$appimagetool_override"
if [[ -z "$appimagetool" ]]; then
appimagetool="$workspace/Launcher/artifacts/appimagetool"
# Cache path is arch-tagged so a workspace shared or synced across an x86_64 and an aarch64
# machine never picks up the wrong architecture's cached binary.
appimagetool="$workspace/Launcher/artifacts/appimagetool-$appimagetool_arch"
if [[ ! -x "$appimagetool" ]]; then
echo "Downloading appimagetool..."
echo "Downloading appimagetool ($appimagetool_arch)..."
mkdir -p "$(dirname "$appimagetool")"
curl -fsSL "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" \
curl -fsSL "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$appimagetool_arch.AppImage" \
-o "$appimagetool"
chmod +x "$appimagetool"
fi
@@ -169,5 +207,6 @@ mkdir -p "$output_dir"
echo "Packaging..."
# appimagetool detects the target architecture from the first ELF executable it finds in the
# AppDir; AppRun here is a shell script, not ELF, so ARCH must be set explicitly.
ARCH=x86_64 "$appimagetool" "$appdir" "$output_dir/WiiCompiled-Setup-x86_64.AppImage"
echo "Built: $output_dir/WiiCompiled-Setup-x86_64.AppImage"
output_name="WiiCompiled-Setup-$appimagetool_arch.AppImage"
ARCH="$appimagetool_arch" "$appimagetool" "$appdir" "$output_dir/$output_name"
echo "Built: $output_dir/$output_name"
+41 -1
View File
@@ -329,12 +329,52 @@ void apply_port_preferences() noexcept {
}
}
#if defined(_WIN32)
// Ports are explicit assignments. SDL may choose a player index at connection
// time, but accepting it would make a newly connected controller silently take
// over a game port before the user assigns it in the controller menu.
// over a game port before the user assigns it in the controller menu - which
// matters here because a manually-assigned WUP-028 adapter port (see
// wup028_adapter.cpp, Windows-only) could otherwise collide with one SDL
// auto-claimed. Elsewhere, with no WUP-028 port to collide with, the original
// auto-claim behavior below is restored instead.
void ensure_player_index(GameController& controller) noexcept {
assign_player_index(controller, -1);
}
#else
// SDL only hands out a player index when the device already had a gamepad mapping
// at connect time, so anything mapped later (the setup wizard) stays at -1.
void ensure_player_index(GameController& controller) noexcept {
const int32_t player = SDL_GetGamepadPlayerIndex(controller.m_controller);
if (player >= 0) {
controller.m_playerIndex = player;
return;
}
if (controller.m_playerIndex >= 0) {
return;
}
ensure_port_preferences_loaded();
const auto claim = [&](bool skipConfiguredPorts) {
for (int32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
if (skipConfiguredPorts && g_portPreferences[port].state != PortPreferenceState::Unset) {
continue;
}
const bool taken = std::any_of(g_GameControllers.begin(), g_GameControllers.end(), [&](const auto& entry) {
return entry.second.m_controller != controller.m_controller && effective_player_index(entry.second) == port;
});
if (!taken) {
assign_player_index(controller, port);
return true;
}
}
return false;
};
// Explicitly configured ports are only used as a last resort so a hot-plugged
// controller cannot steal the port its preferred device will claim.
if (!claim(true)) {
claim(false);
}
}
#endif
} // namespace
GameController* get_controller_for_player(uint32_t player) noexcept {
+9
View File
@@ -237,6 +237,15 @@ set(MKW_CPU_BASELINE_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/host_cpu_baseline.cpp
list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE}
${MKW_CPU_BASELINE_SOURCE})
# WUP-028 (official GameCube adapter) support talks to the adapter over WinUSB, which only
# exists on Windows - SDL3 already exposes the same hardware as a normal joystick on Linux/macOS,
# so this file has nothing to do there. Every call site into it is separately gated behind
# #if defined(_WIN32) (see wup028_adapter.cpp's own header comment), so it's safe to simply not
# compile it at all on other platforms rather than build a stub implementation.
if(NOT WIN32)
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/wup028_adapter.cpp")
endif()
# The translator emits the complete, content-addressed source graph. Consuming
# this one manifest keeps configure independent of the 28k generated function
# files and of optional Retro Rewind artifacts such as code.map.
+6
View File
@@ -63,9 +63,11 @@ struct RuntimeUserConfig {
// comma-separated SDL-style physical button names ("south", or
// "dpad_up,left_shoulder") as values; pressing either bound button counts.
std::array<std::optional<std::string>, 12> controllerButtons;
#ifdef _WIN32
// One-based physical WUP-028 adapter port assigned to each game port.
// Zero or a missing value means the adapter does not own that game port.
std::array<uint32_t, 4> gameCubeAdapterPorts{};
#endif
};
namespace RuntimeConfigFile {
@@ -365,12 +367,14 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.controllerButtons[index] =
FindConfigValue<std::string>(document, "controller", buttonKeys[index]);
}
#ifdef _WIN32
for (size_t index = 0; index < config.gameCubeAdapterPorts.size(); ++index) {
const std::string key = "adapter_port_" + std::to_string(index + 1);
if (auto value = FindConfigUint(document, "controller", key); value && *value <= 4) {
config.gameCubeAdapterPorts[index] = *value;
}
}
#endif
config.widescreen = FindConfigValue<bool>(document, "video", "widescreen");
config.windowPosX = FindConfigInt(document, "video", "window_x");
@@ -634,6 +638,7 @@ inline bool SetControllerButton(size_t index, std::string value) {
return WriteSetting("controller", kControllerButtonKeys[index], FormatString(value));
}
#ifdef _WIN32
inline int GameCubeAdapterPort(size_t gamePort) {
if (gamePort >= Get().gameCubeAdapterPorts.size()) return -1;
const uint32_t physicalPort = Get().gameCubeAdapterPorts[gamePort];
@@ -646,6 +651,7 @@ inline bool SetGameCubeAdapterPort(size_t gamePort, int physicalPort) {
Mutable().gameCubeAdapterPorts[gamePort] = storedPort;
return WriteSetting("controller", "adapter_port_" + std::to_string(gamePort + 1), std::to_string(storedPort));
}
#endif
inline bool SetAudioVolume(float value) {
value = std::clamp(value, 0.0f, 1.0f);
+11 -1
View File
@@ -1,7 +1,9 @@
#include "hle_stubs.h"
#include "memory.h"
#include "hle/controller_status_contract.h"
#ifdef _WIN32
#include "wup028_adapter.h"
#endif
#include <algorithm>
#include <cstdio>
@@ -34,7 +36,9 @@ void WritePadStatus(uint32_t base, const PADStatus& status) {
extern "C" uint32_t PAD__Init_HLE()
{
#if defined(_WIN32)
Wup028Adapter::Initialize();
#endif
return PADInit() ? 1u : 0u;
}
PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ());
@@ -46,8 +50,9 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr)
}
PADStatus statuses[PAD_CHANMAX]{};
std::array<PADStatus, PAD_CHANMAX> adapterStatuses{};
uint32_t rumbleMask = PADRead(statuses);
#if defined(_WIN32)
std::array<PADStatus, PAD_CHANMAX> adapterStatuses{};
if (Wup028Adapter::Read(adapterStatuses) && !PADIsInputBlocked()) {
for (uint32_t port = 0; port < PAD_CHANMAX; ++port) {
if (adapterStatuses[port].err == PAD_ERR_NONE) {
@@ -56,6 +61,7 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr)
}
}
}
#endif
try {
for (uint32_t i = 0; i < PAD_CHANMAX; ++i) {
@@ -84,8 +90,12 @@ PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), (
extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command)
{
#if defined(_WIN32)
if (!Wup028Adapter::SetRumble(static_cast<uint32_t>(chan), command == PAD_MOTOR_RUMBLE)) {
PADControlMotor(chan, command);
}
#else
PADControlMotor(chan, command);
#endif
}
PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command));
+8
View File
@@ -1377,7 +1377,9 @@ int RuntimeMain(int argc, char** argv) {
}
aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait);
GxGuestWrite::InstallAuroraHooks();
#if defined(_WIN32)
Wup028Adapter::Initialize();
#endif
UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width,
auroraInfo.windowSize.native_fb_height);
settings_overlay::InitializeRuntimeSettings();
@@ -1419,7 +1421,9 @@ int RuntimeMain(int argc, char** argv) {
// Shutdown fiber system
Fiber::GuestFiberManager::Shutdown();
WindowPlacementPersistence::Flush(true);
#if defined(_WIN32)
Wup028Adapter::Shutdown();
#endif
aurora_shutdown();
SetRuntimeExitCodeImpl(0);
ShutdownProcessTranscript();
@@ -1437,7 +1441,9 @@ int RuntimeMain(int argc, char** argv) {
SetRuntimeExitCodeImpl(1);
Fiber::GuestFiberManager::Shutdown();
WindowPlacementPersistence::Flush(true);
#if defined(_WIN32)
Wup028Adapter::Shutdown();
#endif
aurora_shutdown();
ShutdownProcessTranscript();
return 1;
@@ -1449,7 +1455,9 @@ int RuntimeMain(int argc, char** argv) {
SetRuntimeExitCodeImpl(1);
Fiber::GuestFiberManager::Shutdown();
WindowPlacementPersistence::Flush(true);
#if defined(_WIN32)
Wup028Adapter::Shutdown();
#endif
aurora_shutdown();
ShutdownProcessTranscript();
return 1;
+28 -1
View File
@@ -1,5 +1,7 @@
#include "settings_overlay.h"
#ifdef _WIN32
#include "wup028_adapter.h"
#endif
#include "audio_backend.h"
#include "controller_mapping_wizard.h"
#include "game_graphics_options.h"
@@ -313,6 +315,12 @@ void ApplyConfiguredMappings() {
}
void DrawGameCubeAdapterInfo() {
// The official GameCube adapter is Windows-only (see wup028_adapter.cpp); on Linux/macOS,
// Wup028Adapter is a permanently-disconnected stub, and SDL3 already exposes the same
// hardware as a normal joystick, so this menu would only ever show "Searching" and four
// perpetually-empty adapter ports - confusing clutter for a feature that can't do anything
// on this platform. Skip it entirely rather than render a menu that never has content.
#if defined(_WIN32)
ImGui::Separator();
if (!ImGui::BeginMenu("GameCube adapter info")) return;
@@ -338,6 +346,7 @@ void DrawGameCubeAdapterInfo() {
}
}
ImGui::EndMenu();
#endif
}
void DrawControllerSettings() {
@@ -351,13 +360,20 @@ void DrawControllerSettings() {
ImGui::Separator();
const uint32_t selectedGamePort = static_cast<uint32_t>(g_controllerPort);
#if defined(_WIN32)
const int adapterAssignment = Wup028Adapter::GetPortAssignment(selectedGamePort);
if (adapterAssignment >= 0) {
ImGui::Text("Assigned: GameCube adapter port %d", adapterAssignment + 1);
} else {
} else
#endif
{
const char* currentName = PADGetName(selectedGamePort);
ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None");
}
#if defined(_WIN32)
// Windows-only, same reasoning as DrawGameCubeAdapterInfo() above: on other platforms
// adapterAssignment is always -1 and every port would always read "(empty)", so this submenu
// would never have anything real to offer.
if (ImGui::BeginMenu("Assign GameCube adapter port")) {
if (ImGui::MenuItem("None", nullptr, adapterAssignment < 0)) {
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
@@ -381,10 +397,13 @@ void DrawControllerSettings() {
}
ImGui::EndMenu();
}
#endif
if (ImGui::MenuItem("Unassign controller")) {
PADClearPort(selectedGamePort);
#if defined(_WIN32)
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
#endif
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
}
ImGui::Separator();
@@ -392,7 +411,9 @@ void DrawControllerSettings() {
const uint32_t controllerCount = PADCount();
if (controllerCount == 0) {
ImGui::TextDisabled("No controller connected");
#if defined(_WIN32)
DrawGameCubeAdapterInfo();
#endif
return;
}
@@ -401,8 +422,10 @@ void DrawControllerSettings() {
const char* name = PADGetNameForControllerIndex(index);
ImGui::PushID(static_cast<int>(index));
if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) {
#if defined(_WIN32)
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
#endif
PADSetPortForIndex(index, selectedGamePort);
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
ApplyConfiguredMappings();
@@ -416,7 +439,9 @@ void DrawControllerSettings() {
PADButtonMapping* mappings = PADGetButtonMappings(static_cast<uint32_t>(g_controllerPort), &mappingCount);
if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) {
ImGui::TextDisabled("Assign a controller to edit its buttons");
#if defined(_WIN32)
DrawGameCubeAdapterInfo();
#endif
return;
}
@@ -556,7 +581,9 @@ void DrawControllerSettings() {
ImGui::TextUnformatted(kControllerButtons[i].label);
ImGui::PopID();
}
#if defined(_WIN32)
DrawGameCubeAdapterInfo();
#endif
}
void DrawAudioSettings() {