Add Xbox UWP port

This commit is contained in:
Caorthann
2026-08-01 20:29:33 +01:00
parent 8fbe819493
commit dacd9abe73
29 changed files with 722 additions and 10 deletions
+3
View File
@@ -46,3 +46,6 @@ mobile/dist/
# per-machine iOS bundle-id pin (see scripts/build_ios.sh)
mobile/ios/bundle_id.local
# Xbox UWP build output
/ports/uwp/build/
@@ -726,7 +726,12 @@ static ControllerMapping_t *SDL_CreateMappingForWGIController(SDL_JoystickGUID g
}
SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
#ifdef __WINRT__
/* Xbox WGI exposes the canonical SDL axis and hat layout. */
SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,", sizeof(mapping_string));
#else
SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:b10,dpdown:b12,dpleft:b13,dpright:b11,leftx:a1,lefty:a0~,rightx:a3,righty:a2~,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string));
#endif
return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT);
}
@@ -731,10 +731,19 @@ static int WGI_JoystickOpen(SDL_Joystick *joystick, int device_index)
__x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->gamecontroller, &wireless);
}
/* Initialize the joystick capabilities */
joystick->nbuttons = state->nbuttons;
joystick->naxes = state->naxes;
joystick->nhats = state->nhats;
#ifdef __WINRT__
if (hwdata->gamepad) {
joystick->nbuttons = 10;
joystick->naxes = 6;
joystick->nhats = 1;
} else
#endif
{
/* Initialize the joystick capabilities */
joystick->nbuttons = state->nbuttons;
joystick->naxes = state->naxes;
joystick->nhats = state->nhats;
}
joystick->epowerlevel = wireless ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
if (wireless && hwdata->battery) {
@@ -872,6 +881,51 @@ static void WGI_JoystickUpdate(SDL_Joystick *joystick)
{
struct joystick_hwdata *hwdata = joystick->hwdata;
HRESULT hr;
#ifdef __WINRT__
if (hwdata->gamepad) {
struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading reading;
static const __x_ABI_CWindows_CGaming_CInput_CGamepadButtons button_masks[] = {
GamepadButtons_A, GamepadButtons_B, GamepadButtons_X, GamepadButtons_Y,
GamepadButtons_LeftShoulder, GamepadButtons_RightShoulder,
GamepadButtons_View, GamepadButtons_Menu,
GamepadButtons_LeftThumbstick, GamepadButtons_RightThumbstick
};
Uint8 hat = SDL_HAT_CENTERED;
Uint8 i;
hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(hwdata->gamepad, &reading);
if (SUCCEEDED(hr) && (!reading.Timestamp || reading.Timestamp != hwdata->timestamp)) {
const DOUBLE left_x = SDL_max(-1.0, SDL_min(1.0, reading.LeftThumbstickX));
const DOUBLE left_y = SDL_max(-1.0, SDL_min(1.0, reading.LeftThumbstickY));
const DOUBLE right_x = SDL_max(-1.0, SDL_min(1.0, reading.RightThumbstickX));
const DOUBLE right_y = SDL_max(-1.0, SDL_min(1.0, reading.RightThumbstickY));
const DOUBLE left_trigger = SDL_max(0.0, SDL_min(1.0, reading.LeftTrigger));
const DOUBLE right_trigger = SDL_max(0.0, SDL_min(1.0, reading.RightTrigger));
SDL_PrivateJoystickAxis(joystick, 0, (Sint16)(left_x * 32767.0));
SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-left_y * 32767.0));
SDL_PrivateJoystickAxis(joystick, 2, (Sint16)(left_trigger * 65535.0 - 32768.0));
SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(right_x * 32767.0));
SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(-right_y * 32767.0));
SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(right_trigger * 65535.0 - 32768.0));
for (i = 0; i < (Uint8)SDL_arraysize(button_masks); ++i) {
SDL_PrivateJoystickButton(joystick, i,
(reading.Buttons & button_masks[i]) ? SDL_PRESSED : SDL_RELEASED);
}
if (reading.Buttons & GamepadButtons_DPadUp) hat |= SDL_HAT_UP;
if (reading.Buttons & GamepadButtons_DPadDown) hat |= SDL_HAT_DOWN;
if (reading.Buttons & GamepadButtons_DPadLeft) hat |= SDL_HAT_LEFT;
if (reading.Buttons & GamepadButtons_DPadRight) hat |= SDL_HAT_RIGHT;
SDL_PrivateJoystickHat(joystick, 0, hat);
hwdata->timestamp = reading.Timestamp;
}
return;
}
#endif
UINT32 nbuttons = SDL_min(joystick->nbuttons, SDL_MAX_UINT8);
boolean *buttons = NULL;
UINT32 nhats = SDL_min(joystick->nhats, SDL_MAX_UINT8);
@@ -994,7 +1048,38 @@ static void WGI_JoystickQuit(void)
static SDL_bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
{
#ifdef __WINRT__
WindowsGamingInputControllerState *state = &wgi.controllers[device_index];
if (state->type != SDL_JOYSTICK_TYPE_GAMECONTROLLER) {
return SDL_FALSE;
}
SDL_zero(*out);
out->a = (SDL_InputMapping){ EMappingKind_Button, 0 };
out->b = (SDL_InputMapping){ EMappingKind_Button, 1 };
out->x = (SDL_InputMapping){ EMappingKind_Button, 2 };
out->y = (SDL_InputMapping){ EMappingKind_Button, 3 };
out->leftshoulder = (SDL_InputMapping){ EMappingKind_Button, 4 };
out->rightshoulder = (SDL_InputMapping){ EMappingKind_Button, 5 };
out->back = (SDL_InputMapping){ EMappingKind_Button, 6 };
out->start = (SDL_InputMapping){ EMappingKind_Button, 7 };
out->leftstick = (SDL_InputMapping){ EMappingKind_Button, 8 };
out->rightstick = (SDL_InputMapping){ EMappingKind_Button, 9 };
out->dpup = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_UP };
out->dpdown = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_DOWN };
out->dpleft = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_LEFT };
out->dpright = (SDL_InputMapping){ EMappingKind_Hat, SDL_HAT_RIGHT };
out->leftx = (SDL_InputMapping){ EMappingKind_Axis, 0 };
out->lefty = (SDL_InputMapping){ EMappingKind_Axis, 1 };
out->lefttrigger = (SDL_InputMapping){ EMappingKind_Axis, 2 };
out->rightx = (SDL_InputMapping){ EMappingKind_Axis, 3 };
out->righty = (SDL_InputMapping){ EMappingKind_Axis, 4 };
out->righttrigger = (SDL_InputMapping){ EMappingKind_Axis, 5 };
return SDL_TRUE;
#else
return SDL_FALSE;
#endif
}
SDL_JoystickDriver SDL_WGI_JoystickDriver = {
Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+112
View File
@@ -0,0 +1,112 @@
# Gen1Recomp Xbox UWP build notes
This is the Xbox Dev Mode package for Gen1Recomp.
The rough shape is:
- `Gen1RecompUWP.exe` starts LÖVE through SDL's WinRT wrapper
- [love-xbox-uwp](https://github.com/caorthann-celt/love-xbox-uwp) provides the LÖVE 11.5 UWP backend and LuaJIT
- SDL2 is built from the copy already carried by Gen1Recomp
- ANGLE provides OpenGL ES over D3D11
- vcpkg provides the audio, font, video, and compression libraries
## What You Need
The tested toolchain is:
- Visual Studio 2022 17.14
- MSVC v143 x64/x86 build tools
- C++ Universal Windows Platform tools
- Windows 11 SDK `10.0.26100.0`
- CMake 3.24 or newer
- Git
- vcpkg with the `x64-uwp` triplet
Use Visual Studio Installer to add **Universal Windows Platform development**, the v143 C++ tools, CMake tools for Windows, and Windows SDK `10.0.26100.0`.
## Checkouts
Keep the game, LÖVE backend, and vcpkg in separate folders. From the parent of the existing Gen1Recomp checkout:
```powershell
git clone --branch gen1recomp --single-branch `
https://github.com/caorthann-celt/love-xbox-uwp.git `
love-xbox-uwp
git clone https://github.com/microsoft/vcpkg.git vcpkg
```
The LÖVE checkout must stay on the `gen1recomp` branch.
## vcpkg
Bootstrap vcpkg and install the UWP libraries used by LÖVE:
```powershell
Set-Location vcpkg
./bootstrap-vcpkg.bat
./vcpkg.exe install `
freetype:x64-uwp `
openal-soft:x64-uwp `
libtheora:x64-uwp `
libvorbis:x64-uwp `
libogg:x64-uwp `
zlib:x64-uwp
Set-Location ..
```
## Environment
Set the three build roots from the parent folder:
```powershell
$env:LOVE_UWP_ROOT = (Resolve-Path './love-xbox-uwp').Path
$env:VCPKG_ROOT = (Resolve-Path './vcpkg').Path
$gameRoot = (Resolve-Path './Gen1Recomp-UWP').Path
```
## Build SDL2
Start from a Visual Studio 2022 UWP developer prompt, or initialize `VsDevCmd.bat` for x64 UWP. Then build and install SDL2:
```powershell
Set-Location "$gameRoot\ports\uwp"
./scripts/build-sdl2-angle.ps1
$env:UWP_SDL2_ANGLE_ROOT = (Resolve-Path './build/sdl2-install').Path
```
## Build LÖVE
Configure the backend once, then build the Release libraries and DLLs:
```powershell
cmake -S $env:LOVE_UWP_ROOT -B "$env:LOVE_UWP_ROOT/build/uwp-x64-angle" `
-G "Visual Studio 17 2022" -A x64 `
-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 `
"-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" `
-DVCPKG_TARGET_TRIPLET=x64-uwp `
"-DLOVE_UWP_SDL_ROOT=$env:UWP_SDL2_ANGLE_ROOT" `
-DLOVE_UWP_LUAJIT=ON -DLOVE_UWP_ANGLE=ON
cmake --build "$env:LOVE_UWP_ROOT/build/uwp-x64-angle" `
--config Release --parallel
```
## Build the MSIX
Build the game package from `ports/uwp`:
```powershell
Set-Location "$gameRoot\ports\uwp"
cmake --preset uwp-release
cmake --build --preset uwp-release
```
The build creates `gen1recomp.love`, links the UWP host, and stages LÖVE, LuaJIT, SDL2, ANGLE, and the vcpkg runtime DLLs.
## Build Output
The Release package lands under:
```text
ports\uwp\build\release\AppPackages\Gen1RecompUWP
```
+110
View File
@@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.24)
project(Gen1RecompUWP LANGUAGES CXX)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
message(FATAL_ERROR "Configure with a WindowsStore preset.")
endif()
foreach(required_env LOVE_UWP_ROOT UWP_SDL2_ANGLE_ROOT VCPKG_ROOT)
if(NOT DEFINED ENV{${required_env}} OR "$ENV{${required_env}}" STREQUAL "")
message(FATAL_ERROR "Set ${required_env} before configuring.")
endif()
endforeach()
file(TO_CMAKE_PATH "$ENV{LOVE_UWP_ROOT}" LOVE_UWP_ROOT)
file(TO_CMAKE_PATH "$ENV{UWP_SDL2_ANGLE_ROOT}" UWP_SDL2_ANGLE_ROOT)
file(TO_CMAKE_PATH "$ENV{VCPKG_ROOT}" VCPKG_ROOT)
get_filename_component(GAME_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
set(ANGLE_RUNTIME_ROOT "${CMAKE_CURRENT_LIST_DIR}/third_party/angle")
set(LOVE_BUILD_ROOT "${LOVE_UWP_ROOT}/build/uwp-x64-angle" CACHE PATH "LÖVE UWP build directory")
set(LOVE_CONFIG "RelWithDebInfo" CACHE STRING "Configuration used for the LÖVE UWP backend")
set(LOVE_OUTPUT_ROOT "${LOVE_BUILD_ROOT}/${LOVE_CONFIG}")
if(LOVE_CONFIG STREQUAL "Release" OR LOVE_CONFIG STREQUAL "Debug")
set(LOVE_LIBRARY "${LOVE_OUTPUT_ROOT}/liblove.lib")
set(LOVE_RUNTIME "${LOVE_OUTPUT_ROOT}/love.dll")
else()
set(LOVE_LIBRARY "${LOVE_OUTPUT_ROOT}/libliblove.lib")
set(LOVE_RUNTIME "${LOVE_OUTPUT_ROOT}/liblove.dll")
endif()
set(REQUIRED_FILES
"${LOVE_OUTPUT_ROOT}/lovestatic.lib"
"${LOVE_LIBRARY}"
"${LOVE_RUNTIME}"
"${LOVE_OUTPUT_ROOT}/lua51.lib"
"${LOVE_OUTPUT_ROOT}/lua51.dll"
"${UWP_SDL2_ANGLE_ROOT}/include/SDL2/SDL.h"
"${UWP_SDL2_ANGLE_ROOT}/lib/SDL2.lib"
"${UWP_SDL2_ANGLE_ROOT}/bin/SDL2.dll"
"${ANGLE_RUNTIME_ROOT}/libEGL.dll"
"${ANGLE_RUNTIME_ROOT}/libGLESv2.dll"
"${ANGLE_RUNTIME_ROOT}/d3dcompiler_47.dll"
)
foreach(path IN LISTS REQUIRED_FILES)
if(NOT EXISTS "${path}")
message(FATAL_ERROR "Missing UWP dependency: ${path}")
endif()
endforeach()
set(GAME_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/gen1recomp.love")
set_source_files_properties("${GAME_ARCHIVE}" PROPERTIES GENERATED TRUE)
add_custom_target(gen1recomp_love ALL
COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File
"${CMAKE_CURRENT_LIST_DIR}/scripts/package-game.ps1"
-SourceRoot "${GAME_ROOT}" -Output "${GAME_ARCHIVE}"
BYPRODUCTS "${GAME_ARCHIVE}"
VERBATIM
)
add_executable(${PROJECT_NAME} WIN32 "app/main.cpp")
add_dependencies(${PROJECT_NAME} gen1recomp_love)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
VS_SDK_REFERENCES "Microsoft.VCLibs, Version=14.0"
)
target_include_directories(${PROJECT_NAME} PRIVATE "${UWP_SDL2_ANGLE_ROOT}/include/SDL2")
target_link_libraries(${PROJECT_NAME} PRIVATE
"${LOVE_OUTPUT_ROOT}/lovestatic.lib"
"${LOVE_LIBRARY}"
"${UWP_SDL2_ANGLE_ROOT}/lib/SDL2.lib"
"${LOVE_OUTPUT_ROOT}/lua51.lib"
WindowsApp.lib
)
set(PACKAGE_ROOT_FILES
"${CMAKE_CURRENT_LIST_DIR}/Package.appxmanifest"
"${GAME_ARCHIVE}"
"${LOVE_RUNTIME}"
"${LOVE_OUTPUT_ROOT}/lua51.dll"
"${UWP_SDL2_ANGLE_ROOT}/bin/SDL2.dll"
"${ANGLE_RUNTIME_ROOT}/libEGL.dll"
"${ANGLE_RUNTIME_ROOT}/libGLESv2.dll"
"${ANGLE_RUNTIME_ROOT}/d3dcompiler_47.dll"
)
set(VCPKG_RUNTIME_NAMES
brotlicommon.dll brotlidec.dll bz2.dll fmt.dll freetype.dll libpng16.dll
OpenAL32.dll theora.dll theoradec.dll vorbis.dll vorbisfile.dll ogg.dll z.dll
)
foreach(name IN LISTS VCPKG_RUNTIME_NAMES)
set(runtime "${VCPKG_ROOT}/installed/x64-uwp/bin/${name}")
if(NOT EXISTS "${runtime}")
message(FATAL_ERROR "Missing UWP runtime DLL: ${name}")
endif()
list(APPEND PACKAGE_ROOT_FILES "${runtime}")
endforeach()
set_source_files_properties(${PACKAGE_ROOT_FILES} PROPERTIES
VS_COPY_TO_OUT_DIR Always
VS_DEPLOYMENT_CONTENT TRUE
VS_DEPLOYMENT_LOCATION "."
)
file(GLOB PACKAGE_ASSETS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/Assets/*.png")
set_source_files_properties(${PACKAGE_ASSETS} PROPERTIES
VS_DEPLOYMENT_CONTENT TRUE
VS_DEPLOYMENT_LOCATION "Assets"
)
target_sources(${PROJECT_NAME} PRIVATE ${PACKAGE_ROOT_FILES} ${PACKAGE_ASSETS})
+49
View File
@@ -0,0 +1,49 @@
{
"version": 6,
"configurePresets": [
{
"name": "uwp-common",
"hidden": true,
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "WindowsStore",
"CMAKE_SYSTEM_VERSION": "10.0",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"VCPKG_TARGET_TRIPLET": "x64-uwp"
}
},
{
"name": "uwp-relwithdebinfo",
"inherits": "uwp-common",
"displayName": "Gen1Recomp Xbox UWP (RelWithDebInfo)",
"binaryDir": "${sourceDir}/build/relwithdebinfo",
"cacheVariables": {
"LOVE_CONFIG": "RelWithDebInfo"
}
},
{
"name": "uwp-release",
"inherits": "uwp-common",
"displayName": "Gen1Recomp Xbox UWP (Release)",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"LOVE_CONFIG": "Release"
}
}
],
"buildPresets": [
{
"name": "uwp-relwithdebinfo",
"configurePreset": "uwp-relwithdebinfo",
"configuration": "RelWithDebInfo",
"jobs": 8
},
{
"name": "uwp-release",
"configurePreset": "uwp-release",
"configuration": "Release",
"jobs": 8
}
]
}
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
<Identity Name="Gen1RecompUWP" Publisher="CN=Caorthann" Version="0.3.0.0" />
<mp:PhoneIdentity PhoneProductId="5074018d-a46b-4dd3-b7fa-93a1e39858ad" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Gen1Recomp</DisplayName>
<PublisherDisplayName>Gen1Recomp</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Gen1RecompUWP.App">
<uap:VisualElements
DisplayName="Gen1Recomp"
Description="Gen1Recomp for Xbox Dev Mode"
BackgroundColor="#101820"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile
Square71x71Logo="Assets\SmallTile.png"
Wide310x150Logo="Assets\WideTile.png"
Square310x310Logo="Assets\LargeTile.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#101820" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<Capability Name="internetClientServer" />
<Capability Name="codeGeneration" />
<uap:Capability Name="removableStorage" />
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="broadFileSystemAccess" />
<rescap:Capability Name="expandedResources" />
</Capabilities>
</Package>
+31
View File
@@ -0,0 +1,31 @@
#include <Windows.h>
#include <SDL.h>
#include <string>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Storage.h>
extern "C" int SDL_main(int argc, char **argv);
namespace
{
int runLove(int, char **)
{
std::wstring packagePath = winrt::Windows::ApplicationModel::Package::Current()
.InstalledLocation().Path().c_str();
std::string gamePath = winrt::to_string(packagePath + L"\\gen1recomp.love");
char executable[] = "Gen1RecompUWP";
char fused[] = "--fused";
char *loveArgv[] = {executable, gamePath.data(), fused, nullptr};
return SDL_main(3, loveArgv);
}
} // namespace
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
SDL_SetHint(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, "1");
OutputDebugStringA("Gen1Recomp UWP startup\n");
return SDL_WinRTRunApp(runLove, nullptr);
}
+24
View File
@@ -0,0 +1,24 @@
param(
[string]$Configuration = "Release",
[string]$WindowsSdkVersion = "10.0"
)
$ErrorActionPreference = "Stop"
$portRoot = Split-Path -Parent $PSScriptRoot
$gameRoot = (Resolve-Path (Join-Path $portRoot "..\..")).Path
$sourceRoot = Join-Path $gameRoot "mobile\android\love\src\jni\SDL2"
$buildRoot = Join-Path $portRoot "build\sdl2"
$installRoot = Join-Path $portRoot "build\sdl2-install"
cmake -S $sourceRoot -B $buildRoot `
-G "Visual Studio 17 2022" -A x64 `
-DCMAKE_SYSTEM_NAME=WindowsStore `
"-DCMAKE_SYSTEM_VERSION=$WindowsSdkVersion" `
"-DCMAKE_INSTALL_PREFIX=$installRoot" `
-DSDL_SHARED=ON -DSDL_STATIC=OFF `
-DSDL_OPENGL=OFF -DSDL_OPENGLES=ON -DSDL_VULKAN=OFF
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
cmake --build $buildRoot --config $Configuration --target INSTALL --parallel
exit $LASTEXITCODE
+49
View File
@@ -0,0 +1,49 @@
param(
[Parameter(Mandatory = $true)][string]$SourceRoot,
[Parameter(Mandatory = $true)][string]$Output
)
$ErrorActionPreference = 'Stop'
$source = (Resolve-Path -LiteralPath $SourceRoot).Path
$required = @('main.lua', 'conf.lua', 'tools/rom_manifest.json')
$allowedRoots = @('assets/', 'data/', 'mods/', 'src/', 'tools/rom_manifest')
$forbidden = '(?i)(^|/)(assets/generated|data/generated|build|dist|ports|mobile|tests?)(/|$)|\.(gb|gbc|sav|srm)$'
$tracked = & git -C $source ls-files
if ($LASTEXITCODE -ne 0) { throw 'git ls-files failed' }
$entries = $tracked |
ForEach-Object { $_.Replace('\', '/') } |
Where-Object {
$path = $_
(($required -contains $path) -or ($allowedRoots | Where-Object { $path.StartsWith($_) })) -and
$path -notmatch $forbidden
} |
Sort-Object -Unique
foreach ($path in $required) {
if ($entries -notcontains $path) { throw "Required game file is missing from archive input: $path" }
}
$parent = Split-Path -Parent $Output
New-Item -ItemType Directory -Force -Path $parent | Out-Null
if (Test-Path -LiteralPath $Output) { Remove-Item -LiteralPath $Output -Force }
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = [System.IO.Compression.ZipFile]::Open($Output, 'Create')
try {
foreach ($relative in $entries) {
$full = Join-Path $source $relative
if (-not (Test-Path -LiteralPath $full -PathType Leaf)) { throw "Tracked file is missing: $relative" }
$entry = $archive.CreateEntry($relative, [System.IO.Compression.CompressionLevel]::Optimal)
$entry.LastWriteTime = [DateTimeOffset]'2000-01-01T00:00:00Z'
$input = [System.IO.File]::OpenRead($full)
$outputStream = $entry.Open()
try { $input.CopyTo($outputStream) } finally { $outputStream.Dispose(); $input.Dispose() }
}
} finally {
$archive.Dispose()
}
Write-Host "Created $Output with $($entries.Count) tracked, ROM-free entries."
+9
View File
@@ -0,0 +1,9 @@
# UWP dependencies
The ANGLE UWP runtime is committed under `angle`. The package also uses:
- `LOVE_UWP_ROOT`: [caorthann-celt/love-xbox-uwp](https://github.com/caorthann-celt/love-xbox-uwp) on the `gen1recomp` branch.
- `UWP_SDL2_ANGLE_ROOT`: SDL2 built by `scripts/build-sdl2-angle.ps1`.
- `VCPKG_ROOT`: the UWP codec, font, compression, and audio runtimes used by LÖVE.
SDL headers, import library, and DLL must come from the same build. Runtime DLLs stay inside the installed package.
+89
View File
@@ -0,0 +1,89 @@
# This is the official list of The ANGLE Project Authors
# for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization
# Email addresses for individuals are tracked elsewhere to avoid spam.
Google Inc.
TransGaming Inc.
3DLabs Inc. Ltd.
Adobe Systems Inc.
Autodesk, Inc.
BlackBerry Limited
Cable Television Laboratories, Inc.
Collabora, Ltd.
Cloud Party, Inc.
Igalia, S.L.
Imagination Technologies Ltd.
Intel Corporation
LunarG, Inc.
Mozilla Corporation
Turbulenz
Klarälvdalens Datakonsult AB
Microsoft Corporation
Microsoft Open Technologies, Inc.
NVIDIA Corporation
Opera Software ASA
The Qt Company Ltd.
Advanced Micro Devices, Inc.
LG Electronics, Inc.
IBM Inc.
AdaptVis GmbH
Samsung Electronics, Inc.
Arm Ltd.
Broadcom Inc.
Facebook, Inc.
The Khronos Group, Inc.
Numfum GmbH
Yandex LLC
Rive
Institute of Software, Chinese Academy of Sciences
Guangdong OPPO Mobile Telecommunications Corp., Ltd
Qualcomm Innovation Center, Inc.
Jacek Caban
Mark Callow
Ginn Chen
Tibor den Ouden
Régis Fénéon
James Hauxwell
Sam Hocevar
Pierre Leveille
Jonathan Liu
Boying Lu
Aitor Moreno
Yuri O'Donnell
Josh Soref
Ma Aiguo
Maks Naumov
Jinyoung Hur
Sebastian Bergstein
James Ross-Gowan
Nickolay Artamonov
Ihsan Akmal
Andrei Volykhin
Jérôme Duval
Руслан Ижбулатов
Thomas Miller
Till Rathmann
Nick Shaforostov
Jaime Bernardo
Le Hoang Quyen
Lu Yahan
Ethan Lee
Renaud Lepage
Artem Bolgar
Wander Lairson Costa
Stephan Hartmann
SeongHwan Park
Xiaopeng Li
Akihiko Odaki
Ho Cheung
Tao Wang
Phan Quang Minh
Hongchen Yan
Andrew Sumsion
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2018 The ANGLE Project Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
// Ltd., nor the names of their contributors may be used to endorse
// or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
+13
View File
@@ -0,0 +1,13 @@
# ANGLE UWP runtime
`libEGL.dll` and `libGLESv2.dll` were built for x64 UWP from [SternXD/angle](https://github.com/SternXD/angle) commit `45b0b1e03400b7a10aaa9a077e196d1abcddafce`. They report ANGLE version `2.1.25011` and source hash `45b0b1e03400`.
`d3dcompiler_47.dll` is the x64 Direct3D HLSL compiler redistributable from Windows SDK `10.0.26100.7705`.
SHA-256:
- `d3dcompiler_47.dll`: `A05F99734F7C4822FEFC12B367AF21FD0976ED6608752FB1E1E80B6ECE7ECBBB`
- `libEGL.dll`: `FE2AC107D0B6CAAE917E755064B430E4ECC52495B12E44EC7B0218E10504ECD1`
- `libGLESv2.dll`: `C8A7E08E0C34E534A781DD8784065CC0608D6E497ADB451F6EEF348E594A00CF`
ANGLE's upstream `LICENSE` and `AUTHORS` files are included beside its binaries.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -259,7 +259,8 @@ function Music.play(data, song, loop, ctx)
pcall(loopSrc.setLooping, loopSrc, wantLoop)
applyVolume(loopSrc)
applyFilter(loopSrc)
else
elseif not isChip then
-- ChipAudio owns the loop policy for its queueable source.
pcall(src.setLooping, src, wantLoop)
end
applyVolume(src)
+7
View File
@@ -31,6 +31,11 @@ local CacheFs = {}
local SEP = package.config:sub(1, 1)
local function isUWP()
return love and love.system and love.system.getOS
and love.system.getOS() == "UWP"
end
-- Cache-relative paths are prefixed with this before every read/write, so a
-- Blue/Yellow import lands under its GameVersion.cachePrefix (blue/, yellow/)
-- while a Red import keeps the historical root. The launcher sets it per
@@ -52,6 +57,7 @@ local mkdirFn = nil
local function resolveMkdir()
if mkdirFn ~= nil then return mkdirFn end
mkdirFn = false
if isUWP() then return mkdirFn end
local ok, ffi = pcall(require, "ffi")
if not ok then return mkdirFn end
if ffi.os == "Windows" then
@@ -83,6 +89,7 @@ local rmdirFn = nil
local function resolveRmdir()
if rmdirFn ~= nil then return rmdirFn end
rmdirFn = false
if isUWP() then return rmdirFn end
local ok, ffi = pcall(require, "ffi")
if not ok then return rmdirFn end
if ffi.os == "Windows" then
+20 -5
View File
@@ -8,6 +8,11 @@ local SafeArea = require("src.core.SafeArea")
local RomImporter = {}
RomImporter.__index = RomImporter
local function isUWP()
return love and love.system and love.system.getOS
and love.system.getOS() == "UWP"
end
-- love.system.pickFile is a NATIVE BRIDGE, not part of LÖVE: it exists only on
-- builds that compiled one (Android, and iOS builds patched by
-- mobile/ios/patch_love_src.py). A build without it must fall back to the
@@ -990,6 +995,7 @@ end
-- import-only run all skip the release check so headless and CI runs never spin
-- up the background worker or reach out to the network.
local function updaterAllowed()
if isUWP() then return false end
if not Platform.networkValidated() then return false end
if not (love.filesystem.isFused and love.filesystem.isFused()) then return false end
if os.getenv("POKEPORT_AUTOPILOT") or os.getenv("POKEPORT_DRIVER") then return false end
@@ -1034,6 +1040,7 @@ function RomImporter.new(onComplete, opts)
mobileFileBridge = mobileFileBridge,
android = android,
ios = mobileOS == "iOS",
nativePicker = romImportMode == "native-picker",
-- One startup poll pass on both mobiles. iOS: files dropped through the
-- Files app are swept into the save dir before Lua boots (GRBootstrap) with
-- no love.focus event necessarily following. Android: the SAF picker is a
@@ -1493,7 +1500,7 @@ function RomImporter:chooseMod()
self:rescanModsAction()
return
end
if self.ios and love.system.getPickedFile then
if self.nativePicker and love.system.getPickedFile then
self.iosPendingKind = "mod"
if not pickFile("mod") then
self.iosPendingKind = nil
@@ -1569,7 +1576,7 @@ function RomImporter:chooseSaveImport(version)
self:rescanSavesAction(version)
return
end
if self.ios and love.system.getPickedFile then
if self.nativePicker and love.system.getPickedFile then
self.iosPendingKind = "sav"
self.iosPendingVersion = version
if not pickFile("sav") then
@@ -1687,7 +1694,7 @@ function RomImporter:choose(version)
self:rescanAction(self.chooseVersion)
return
end
if self.ios and love.system.getPickedFile then
if self.nativePicker and love.system.getPickedFile then
self.iosPendingKind = "rom"
if not pickFile("rom") then
self.iosPendingKind = nil
@@ -1889,7 +1896,7 @@ function RomImporter:update(dt)
end)
end
end
if self.ios and love.system.getPickedFile and self.workState ~= "working" then
if self.nativePicker and love.system.getPickedFile and self.workState ~= "working" then
local path = love.system.getPickedFile()
if path then
local kind = self.iosPendingKind or "rom"
@@ -1898,10 +1905,18 @@ function RomImporter:update(dt)
self.iosPendingVersion = nil
if kind == "mod" then
self:_installMod(path)
if isUWP() and self.modNotice and self.modNotice.ok then
os.remove(path)
end
elseif kind == "sav" then
self:_importSave(version or self:_savedropTarget(), path)
local target = version or self:_savedropTarget()
self:_importSave(target, path)
if isUWP() and self.saveNotice[target] and self.saveNotice[target].ok then
os.remove(path)
end
else
self:startPath(path)
if isUWP() then os.remove(path) end
end
elseif love.system.getPickError then
local errorText = love.system.getPickError()
+5
View File
@@ -99,6 +99,11 @@ local cache = { status = "idle" } -- newest snapshot from the worker
local function ensureWorker()
if workerReady ~= nil then return workerReady end
if love and love.system and love.system.getOS
and love.system.getOS() == "UWP" then
workerReady = false
return false
end
if not (love and love.thread and love.thread.newThread) then
workerReady = false
return false
+27
View File
@@ -28,6 +28,8 @@ love.system = love.system or {}
local saved = {
getOS = love.system.getOS,
pickFile = love.system.pickFile,
getPickedFile = love.system.getPickedFile,
getPickError = love.system.getPickError,
getDirectoryItems = love.filesystem.getDirectoryItems,
getInfo = love.filesystem.getInfo,
read = love.filesystem.read,
@@ -135,6 +137,29 @@ contract:choose("red")
check(picks == 2,
"choose() still reopens the picker per call (#420/#442 contract, got " .. picks .. ")")
-- 7. UWP consumes the absolute LocalState path returned by its native picker
-- and removes the temporary copy only after a successful import.
local removedPath
local savedOsRemove = os.remove
os.remove = function(path) removedPath = path; return true end
love.system.getPickedFile = function()
love.system.getPickedFile = function() return nil end
return [[C:\LocalState\picked_mod.zip]]
end
love.system.getPickError = function() return nil end
local uwp = importer("UWP")
uwp.iosPendingKind = "mod"
uwp._installMod = function(self, source)
self._installed = source
self.modNotice = { ok = true, text = "Installed test-mod" }
end
uwp:update(0)
check(uwp._installed == [[C:\LocalState\picked_mod.zip]],
"UWP passes the picker path to the mod installer")
check(removedPath == [[C:\LocalState\picked_mod.zip]],
"UWP removes the temporary picker copy after installation")
os.remove = savedOsRemove
local touchForwardsToImporter = {
Android = true,
iOS = true,
@@ -161,6 +186,8 @@ check(not touch._flex,
love.system.getOS = saved.getOS
love.system.pickFile = saved.pickFile
love.system.getPickedFile = saved.getPickedFile
love.system.getPickError = saved.getPickError
love.filesystem.getDirectoryItems = saved.getDirectoryItems
love.filesystem.getInfo = saved.getInfo
love.filesystem.read = saved.read