Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/android-love-runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Android LÖVE runtime host
on:
pull_request:
paths:
- 'tools/mobile-runtime-check/**'
- 'tools/runtime-upvalue-check/**'
- 'mobile/android/**'
- '.github/workflows/android-love-runtime.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: android-love-runtime-${{ github.ref }}
cancel-in-progress: true
jobs:
actual-host:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Install the pinned Android toolchain
run: >-
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install
'platforms;android-34' 'build-tools;34.0.0' 'ndk;25.2.9519653'
- name: Build matching x86-64 VM candidates
run: >-
python3 tools/mobile-runtime-check/build_android.py
--ndk "$ANDROID_HOME/ndk/25.2.9519653" --abi x86_64
--output '${{ runner.temp }}/android-vm/candidate'
- name: Build actual LÖVE apps in an owned source copy
run: >-
python3 tools/mobile-runtime-check/qualify_android_love.py build
--build '${{ runner.temp }}/android-vm/candidate'
--output '${{ runner.temp }}/android-love/candidate'
- name: Enable the owned emulator's KVM access
run: sudo chmod a+rw /dev/kvm
- name: Execute the actual JNI, LÖVE, thread and FFI host
uses: ReactiveCircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d
with:
api-level: 30
arch: x86_64
target: default
avd-name: gen2-love-runtime-qa
emulator-port: 5554
script: >-
python3 tools/mobile-runtime-check/qualify_android_love.py run
--serial emulator-5554
--build '${{ runner.temp }}/android-love/candidate'
--output '${{ runner.temp }}/android-love-execution/candidate'
- uses: actions/upload-artifact@v4
if: always()
with:
name: android-love-runtime-host
path: |
${{ runner.temp }}/android-vm/candidate/report.json
${{ runner.temp }}/android-vm/candidate/*.log
${{ runner.temp }}/android-love/candidate/report.json
${{ runner.temp }}/android-love/candidate/*.log
${{ runner.temp }}/android-love/candidate/*.apk
${{ runner.temp }}/android-love-execution/candidate/*.json
${{ runner.temp }}/android-love-execution/candidate/*.log
if-no-files-found: warn
retention-days: 7
68 changes: 68 additions & 0 deletions .github/workflows/android-runtime-capacity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Android runtime capacity candidates
on:
pull_request:
paths:
- 'tools/mobile-runtime-check/**'
- 'tools/runtime-upvalue-check/check.lua'
- 'tools/runtime-upvalue-check/c_api.c'
- 'mobile/android/love/src/jni/LuaJIT-2.1/**'
- '.github/workflows/android-runtime-capacity.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: android-runtime-capacity-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
abi: [arm64-v8a, armeabi-v7a, x86, x86_64]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install host multilib and the pinned Android toolchain
run: |
sudo apt-get update
sudo apt-get install -y gcc-multilib
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install 'ndk;25.2.9519653'
- name: Build stock and extended candidates with matching boundary probes
run: >-
python3 tools/mobile-runtime-check/build_android.py
--ndk "$ANDROID_HOME/ndk/25.2.9519653"
--abi '${{ matrix.abi }}'
--output '${{ runner.temp }}/android-runtime/candidate'
- name: Enable the owned emulator's KVM access
if: matrix.abi == 'x86_64'
run: sudo chmod a+rw /dev/kvm
- name: Execute the target VM and C API probes
if: matrix.abi == 'x86_64'
uses: ReactiveCircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d
with:
api-level: 30
arch: x86_64
target: default
avd-name: gen2-runtime-qa
emulator-port: 5554
script: >-
python3 tools/mobile-runtime-check/check_android_target.py
--serial emulator-5554 --abi x86_64
--build '${{ runner.temp }}/android-runtime/candidate'
--output '${{ runner.temp }}/android-runtime-target/candidate'
- uses: actions/upload-artifact@v4
if: always()
with:
name: android-runtime-${{ matrix.abi }}
path: |
${{ runner.temp }}/android-runtime/candidate/report.json
${{ runner.temp }}/android-runtime/candidate/*.log
${{ runner.temp }}/android-runtime/candidate/artifacts/
${{ runner.temp }}/android-runtime-target/candidate/*.json
${{ runner.temp }}/android-runtime-target/candidate/*.log
if-no-files-found: error
retention-days: 7
88 changes: 88 additions & 0 deletions LICENSE-ROXAS2712.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Roxas2712 — Fork Contributions

Version 1.0 · Effective 25 October 2026

Copyright © 2026 Roxas2712, to the extent of rights held.
All rights reserved except as expressly granted below or already granted.

## 1. Scope and ownership

This notice applies only to original contributions made by Roxas2712
(GitHub: Roxas2712) to `Roxas2712/Gen2Recomped`, including original additions
within modified files, insofar as he owns the relevant rights. Git history,
pull-request diffs and source provenance identify contributions; a commit
author or a whole-file change alone does not establish ownership of every
part of that file.

It does not claim ownership of Cedric's work, third-party engine work,
other contributors' work, third-party dependencies, or ROM-derived
content. Their applicable licences and notices remain in force. It does not
grant permission to distribute the complete combined fork where another
rights holder's permission is required.

## 2. General permission and reserved rights

For contributions covered by this notice and not already licensed more
broadly, you may read and study the published source and make private,
non-commercial modifications and builds for your own use. This grants no
general permission to redistribute, sublicense, sell, or offer those
contributions as a hosted service. Other uses require Roxas2712's
written permission, except for the specific permission below and rights
available independently under applicable law.

Keep the relevant copyright, licence and attribution notices. Do not
represent Roxas2712's contributions as exclusively authored by someone
else. Public source availability alone does not put them in the public
domain or grant an open-source licence.

## 3. Specific permission for Cedric / UNDERdecoded

Roxas2712 expressly grants Cedric (Ced; GitHub: UNDERdecoded /
UNDERdecodedHD) a non-exclusive, worldwide, perpetual, royalty-free
permission to use, reproduce, integrate, modify and distribute the covered
contributions that Roxas2712 submits or expressly offers through pull
requests to `UNDERdecoded/Gen2Recomped`, for Cedric's Gen2Recomped /
RetroRecompiled project. This includes its renamed continuation maintained
by Cedric, its official builds, and its commercial operation.

Cedric may license the incorporated contributions to recipients as part of
that project, subject to preserving this permission's applicable credit and
notice requirements. This permission is not a copyright assignment, is not
exclusive, and does not cover unpublished work, separate repositories,
ModCompass service operation, credentials, or content not offered in those
pull requests. Roxas2712 retains his own rights to his contributions.

For uses relying on this permission:

- retain the relevant source copyright and licence notices;
- give visible credit in the project's README or equivalent public project
page, and in the application's existing About / Credits view, if present;
- use at least the following credit, or a substantially equivalent one:

> Contributions by Roxas2712 —
> https://github.com/Roxas2712

A maintained consolidated contributor credit is sufficient; a separate
credit on every screen or for every change is not required. Adapted
contributions must not be attributed exclusively to another author. Other
contributors' credits must also remain intact.

## 4. Earlier licences and permissions remain effective

This notice does not revoke, narrow, or retrospectively add conditions to
any permission already validly granted, including MIT / GPL grants,
separate agreements, or permissions arising from previously submitted
pull requests. Where an earlier grant permits a use independently of this
notice, its existing terms govern that use. For such earlier uses, the
visible credit above is requested to the extent it is not already required.

For subsequent contributions, this is the default statement of Roxas2712's own reserved rights and the specific PR permission, subject to any
applicable existing licence obligations or separately agreed terms. It
does not change another rights holder's licence or guarantee compatibility
of a particular combination of code.

## 5. No warranty

To the extent permitted by applicable law, these contributions are provided
as is, without warranty. No support obligation, endorsement, or rights to
third-party code, game assets, names, or trademarks are granted.
76 changes: 76 additions & 0 deletions tools/mobile-runtime-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Native mobile runtime capacity

Android currently links the vendored `android/<ABI>/libluajit.a` archives.
Source/header edits do not replace those archives. This builder creates new
isolated stock (60 captures) and experimental extended (120 captures) libraries,
matching public headers, an Android interpreter and a linked C API probe for
each selected ABI. It preserves the vendored source, existing binaries and
LuaJIT copyright notice. No application or player data is copied.

The source version is bound to the existing Android headers. Compilation uses
NDK 25.2.9519653 in CI, API 23 by default and Lua-5.2 compatibility in both
variants. Linux uses a matching 32/64-bit host toolchain; macOS supports the
64-bit cross-builds. The command layout follows the upstream
[LuaJIT cross-compilation instructions](https://luajit.org/install.html).

```sh
python3 tools/mobile-runtime-check/build_android.py \
--ndk /path/to/android/ndk/25.2.9519653 \
--abi arm64-v8a --output .local/qa/android-runtime-test/candidate
```

Every candidate includes the existing closure/debug/bytecode probe and a C API
executable linked against the same headers and static library. The builder
checks target ELF architecture and records hashes. Its reports explicitly keep
`targetExecutionQualified` and `releaseSelectable` false: Android execution and
the actual LÖVE/JNI/thread/FFI boundary must be exercised before packaging can
select a candidate. The existing application continues to use its current
runtime until that qualification and selection work is complete.

The workflow also executes both x86-64 candidates on an owned API-30 Android
emulator through `check_android_target.py`. Transfers are hash-checked before
the closure/debug/bytecode/GC and linked C API tests run. The tool requires an
explicit emulator serial, writes only its unique temporary directory, and
removes its own files afterward. ARM and 32-bit execution remain separate
requirements. This standalone VM test does not replace the LÖVE/JNI/thread/FFI
host gate, so its report also keeps release selection disabled.

iOS needs separately built Apple framework slices and interpreter-mode
qualification. Switch needs a rebuilt runtime and target execution. These
build artifacts establish neither full platform parity nor original-mod
startup, rendering or gameplay acceptance. Local QA uses the shared 20 GiB
reserve and owned output leases.

## Actual Android app-host gate

`qualify_android_love.py build` copies the tracked, fully materialized Android
source into one owned QA directory, selects x86-64 there and builds two distinct
test APKs through the existing Gradle/LÖVE/JNI project. Each uses the exact
stock-60 or extended-120 archive and matching headers from a candidate receipt.
The source checkout and normal application runtime are preserved. A fresh
application ID and provider authority prevent overlap with an installed game.
Only the small ROM-free payload is embedded; no mod, cartridge or player save
is copied.

```sh
python3 tools/mobile-runtime-check/qualify_android_love.py build \
--build /path/to/android-runtime/candidate \
--output .local/qa/android-love-build/candidate
python3 tools/mobile-runtime-check/qualify_android_love.py run \
--serial emulator-5554 --build .local/qa/android-love-build/candidate \
--output .local/qa/android-love-run/candidate
```

The build needs the pinned SDK/NDK, JDK 17 and a complete Android checkout.
The run refuses physical devices and existing app installations. It installs
only its fresh test apps on an explicit emulator, checks interpreter/JIT,
closure/debug/bytecode/GC behavior inside the real LÖVE 11.5 host, calls an FFI
callback, executes a separate LÖVE thread VM and verifies filesystem bindings.
Reports are written inside each test app's private directory; only installations
created by that invocation are stopped and uninstalled. Candidate, APK and
source hashes bind the evidence to the actual built inputs.

The `Android LÖVE runtime host` workflow performs this x86-64 acceptance gate.
Its result must be inspected before claiming host qualification. Reports keep
release selection disabled: ARM/32-bit and physical devices, rendering, input,
full original-mod gameplay, iOS and Switch still require their own acceptance.
56 changes: 56 additions & 0 deletions tools/mobile-runtime-check/android-love-main.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-- ROM-free acceptance inside the actual vendored Android LÖVE/JNI host.
local config = require('config')
local Json = require('src.link.Json')
local function publish(value)
value.schema = 1
value.capacity = config.capacity
value.identity = config.identity
value.releaseSelectable = false
local file = assert(io.open(config.report, 'wb'))
assert(file:write(Json.encode(value)))
assert(file:close())
end
function love.load()
local major, minor, revision = love.getVersion()
assert(major == 11 and minor == 5, 'vendored Android LÖVE 11.5 required')
assert(love.filesystem.getIdentity() == config.identity)
assert(jit.arch == 'x64' and jit.status(), 'owned x86-64 Android JIT host required')
local closureReport
local previousPrint, previousArg = print, arg
print = function(line, ...)
if type(line) == 'string' and line:sub(1,17) == 'RUNTIME_UPVALUES ' then
closureReport = Json.decode(line:sub(18))
end
return previousPrint(line, ...)
end
arg = {tostring(config.capacity)}
local ok, why = pcall(assert(love.filesystem.load('check.lua')))
print, arg = previousPrint, previousArg
assert(ok, why)
assert(closureReport and closureReport.passed and closureReport.minimum == config.capacity)
local ffi = require('ffi')
local callback = ffi.cast('double(*)(double)', function(value) return value + .5 end)
assert(callback(2.25) == 2.75, 'actual host FFI callback failed')
callback:free()
local channel = love.thread.newChannel()
local worker = love.thread.newThread('thread.lua')
worker:start(channel, config.capacity)
local result = channel:demand(30)
assert(result and result.ok and result.capacity == config.capacity,
worker:getError() or 'actual host thread VM did not complete')
worker:wait()
assert(not worker:getError(), worker:getError())
assert(love.filesystem.write('filesystem-probe.txt', config.identity))
assert(love.filesystem.read('filesystem-probe.txt') == config.identity)
publish({passed=true, loveVersion={major,minor,revision}, arch=jit.arch,
jitActive=true, ffiCallbackPassed=true, threadVmPassed=true,
filesystemPassed=true, closureReport=closureReport})
print('ANDROID_LOVE_RUNTIME_ABI_PASS', config.capacity, jit.arch)
love.event.quit(0)
end
function love.errorhandler(why)
local message = debug.traceback(tostring(why), 2)
pcall(publish, {passed=false,error=message})
io.stderr:write(message .. '\n')
return function() return 1 end
end
Loading