Add Xbox UWP port
@@ -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 = {
|
||||
|
||||
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -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
|
||||
```
|
||||
@@ -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})
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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."
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||