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
38 changes: 38 additions & 0 deletions .github/workflows/async-get.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Async mod HTTP and file downloads
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
transport:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y love
- name: Exercise real worker requests against a local server
shell: python
run: |
import os, pathlib, shutil, subprocess, urllib.request, zipfile
host = os.environ['RUNNER_OS']
if host == 'Linux':
executable = shutil.which('love')
else:
suffix = 'macos' if host == 'macOS' else 'win64'
target = pathlib.Path(os.environ['RUNNER_TEMP']) / 'love-get-check'
target.mkdir()
archive = target / 'runtime.zip'
urllib.request.urlretrieve(f'https://github.com/love2d/love/releases/download/11.5/love-11.5-{suffix}.zip', archive)
with zipfile.ZipFile(archive) as z: z.extractall(target)
executable = next(target.rglob('love.app/Contents/MacOS/love')) if host == 'macOS' else next(target.rglob('lovec.exe'))
if host == 'macOS': executable.chmod(0o755)
subprocess.run(['python', 'tools/check_async_get.py', '--love', str(executable), '--tls'], check=True)
71 changes: 71 additions & 0 deletions .github/workflows/mod-native-http.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Native bounded HTTP
on:
pull_request:
paths:
- 'src/mods/LogReport.lua'
- 'src/mods/Net.lua'
- 'src/mods/Manifest.lua'
- 'src/mods/Loader.lua'
- 'src/net/**'
- 'src/core/HostShell.lua'
- 'mobile/ios/native/GRHTTPBridge.swift'
- 'mobile/ios/patch_love_src.py'
- 'mobile/android/love/src/main/java/org/love2d/android/BoundedHttp.java'
- 'mobile/android/love/src/main/java/org/love2d/android/GameActivity.java'
- 'mobile/android/love/src/jni/love/src/modules/system/wrap_System.cpp'
- 'tests/native_http/**'
- 'tests/engine/log_reporting.lua'
- 'tests/engine/bounded_http.lua'
- 'tools/qualify_native_http.py'
- '.github/workflows/mod-native-http.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: native-bounded-http-${{ github.ref }}
cancel-in-progress: true
jobs:
android-bridge-build:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Install native Android build dependencies
run: '"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "platforms;android-34" "build-tools;34.0.0" "ndk;25.2.9519653"'
- name: Compile the Android HTTP adapter and native Lua binding
working-directory: mobile/android
run: bash ./gradlew --no-daemon :love:compileEmbedDebugJavaWithJavac :love:externalNativeBuildEmbedDebug
native-protocols:
runs-on: macos-15
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- run: brew install luajit
- run: luajit tests/engine/log_reporting.lua && luajit tests/engine/async_get_queue.lua && luajit tests/engine/bounded_http.lua
- name: Verify the pinned host-test dependency
shell: bash
run: |
set -euo pipefail
curl --fail --location https://repo.maven.apache.org/maven2/org/json/json/20240303/json-20240303.jar -o "$RUNNER_TEMP/json-20240303.jar"
printf '%s %s\n' 3cf6cd6892e32e2b4c1c39e0f52f5248a2f5b37646fdfbb79a66b46b618414ed "$RUNNER_TEMP/json-20240303.jar" | shasum -a 256 -c -
- name: Exercise native adapter logic and typecheck iOS
shell: bash
run: |
python3 tools/qualify_native_http.py --swiftc "$(xcrun --find swiftc)" --sdk "$(xcrun --sdk macosx --show-sdk-path)" --ios-sdk "$(xcrun --sdk iphoneos --show-sdk-path)" --java-home "$JAVA_HOME" --json-jar "$RUNNER_TEMP/json-20240303.jar" --output "$RUNNER_TEMP/native-http"
- uses: actions/upload-artifact@v4
if: always()
with:
name: native-bounded-http
path: |
${{ runner.temp }}/native-http/*.json
${{ runner.temp }}/native-http/*.log
if-no-files-found: warn
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.
60 changes: 60 additions & 0 deletions docs/mod-log-reporting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Fixed-destination log reporting and bounded native HTTP

Declare a fixed HTTPS `log_url` and the `network` permission in the manifest.
API 2/3 reject invalid or ungranted declarations before executing the mod; legacy
declarations warn and discard an invalid destination.

```lua
local job, why = mod:postLog("diagnostic text", {format="text"})
-- Later: poll and release through the same interface as GET.
if job then
local result = mod.fetch:poll(job)
if result.status ~= "pending" then mod.fetch:release(job) end
end
```

The only option is `format`, either `text` (default) or `json`. Input is at most
65,536 bytes. JSON wraps it in `{ts, mod, format, body}`; its encoded payload is
bounded separately at 512 KiB. Unknown keys and invalid values submit nothing.
The endpoint and mod identity are captured when the API is created. Manifest
mutation, options and redirects cannot change the log destination. POST does not
follow redirects. No server response body reaches the mod, including error and
late/cancelled results. Public log results expose status, progress, HTTP status
and a stable failure diagnostic. The mod's existing network permission applies.

GET and logs share the existing three-worker queue, global 16-job ceiling and
four-pending-requests-per-mod ceiling. Queue time counts towards the timeout.
Log requests use 20 seconds; other internal requests cap at 30 seconds. Released
or timed-out jobs retain their physical worker slot until completion. Poll reads
only worker results and performs no network, pipe or filesystem reads.

Desktop POST reuses the bounded curl path. Payloads reside in worker-owned files
and never enter shell syntax; those files are removed after the request. Curl
configuration is disabled and explicit HTTPS protocol limits remain enforced.

The new mobile `boundedHttpVersion()` / `httpRequestBounded()` protocol carries
binary payloads as bounded Base64 fields. Android reuses the existing JNI route
with a mandatory bounded envelope and a separately validated URLConnection
adapter; legacy cloud HTTP is unchanged. iOS uses a separate Foundation adapter
and private ephemeral sessions, without cookie/credential storage. Both adapters
retain platform TLS verification, bound body accumulation, refuse non-HTTP URLs
and HTTPS downgrades, and cap GET redirects at five. A POST is always HTTPS and
cannot redirect. iOS retains native task slots until terminal delegate callbacks;
Android closes the current connection on the absolute request deadline.

An HTTP failure, timeout or cancellation does not prove that a server did not
accept the submitted log. The service does not retry POST automatically.

Verification includes real owned desktop TLS transfers, literal bytes, expanded
JSON payloads, reply suppression, redirect refusal and existing GET/file-download
regressions. `tools/qualify_native_http.py` executes the production Swift and Java
adapter logic with URLProtocol/URLConnection fixtures and typechecks iOS. Those
fixtures do not exercise mobile TLS stacks or physical devices. Native packaging,
real mobile TLS/device acceptance, and resource behavior under suspension remain
separate gates.

The fixed Switch runtime currently has no bounded HTTP binding; the service
reports unavailable there and does not attempt a subprocess. Its TLS/runtime
adapter still needs integration and qualification. A native source build and
matching Lua payload are required for the mobile protocol. This change does not
establish full mod gameplay or complete platform parity.
28 changes: 28 additions & 0 deletions docs/modding.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Native modding

## Background HTTP GET

A mod declaring `"network"` receives `mod.fetch:get(url, options)`, `:poll(handle)`,
`:cancel(handle)`, `:release(handle)`, `:available()` and `:capabilities()`.
Handles belong to their creating mod. Poll returns a detached table with
`status` (`pending`, `ok`, `error` or `cancelled`), `body`, `err`,
`receivedBytes` and, when known, `httpStatus`. Always release finished handles.
Rollback and unload release a mod's requests automatically.

GET transport runs in three LÖVE workers. The engine caps active/queued work at
16 requests, with four pending requests per mod. `maxSeconds` defaults to 20
and caps at 30, including queue time; `maxBytes` defaults to 4 MiB and caps at
16 MiB. `boundedHttpsGet = 1` requires HTTPS throughout redirects. Desktop
capabilities advertise this constraint. Desktop curl is required; mobile
bridges are not yet supported by this API. Remote URLs and headers are passed
through a private curl config file, outside shell syntax.

Cancel discards the result immediately. A curl already executing may occupy
its worker until its bounded timeout; releasing a handle cannot bypass the
global worker limit. Shutdown waits for those bounded operations. This adds
GET support; it does not implement POST, arbitrary requests or `mod.job`.
The older download-to-file API still performs a synchronous first poll.


The modding book lives on the
[project wiki](https://github.com/bryanthaboi/gen1recomp/wiki).

Expand Down Expand Up @@ -277,3 +301,7 @@ The launcher hands the link to the operating system to open, so only `http://`
and `https://` links are accepted. Any other value, such as a `file:` link or
a custom scheme, is dropped with a warning. The mod itself still loads; it
just gets no button.

With `network` and a fixed HTTPS `log_url`, `mod:postLog` sends bounded one-way
diagnostics through the shared workers. See [log reporting](mod-log-reporting.md)
for payload, ownership and native platform boundaries.
30 changes: 30 additions & 0 deletions docs/native-download-worker-retirement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Physical download worker retirement in transport QA

Prepared on the existing offered HTTP/logging PR273 head523bf0bd. Production
transport and request/queue bounds are unchanged. The actual worker fixture
forwards original responses before reporting native request completion on a
QA-only channel. Before filling the queue, it waits for preceding physical
responses; a separate real-worker barrier proves that cancellation retains a
credit until physical retirement and returns exactly one credit afterward.
The existing cold-start/publication barriers, overflow, cancellation,
file-preservation and shutdown checks remain active.

This also carries the independently qualified newer cold-start/publication
test synchronization into this older offer. Its existing45-second external
fixture process deadline remains unchanged. A newer fork composition passes
the actual official LOVE11.5 TLS fixture (1512checks), all949/4 official
ROM-free suites and70CI checks. Those receipts do not qualify this older
combined host. Its own committed source `d8c7deb0` independently passes the
actual official LOVE11.5 owned-loopback TLS fixture:1521 checks/595pump ticks
in6.98seconds, with the original45-second process deadline unchanged. All134
regular runtime files match the official archive byte-for-byte. Receipt:
`upstream-download-retirement-1793247736116883000/receipt.json`. The installed
storage helper manages the ephemeral fixture, processes and unique profile;
only Python process/storage ownership is instrumented, not Lua assertions,
worker responses or transport behavior. No GPU, release,
all-sprite-download or selected actor-art acceptance is claimed.

Integration/tests by Roxas2712 are offered under
https://github.com/Roxas2712/Gen2Recomped/blob/main/LICENSE-ROXAS2712.md,
including its specific Cedric / UNDERdecoded permission with visible credit.
Existing authors' rights, source notices and prior grants remain applicable.
3 changes: 3 additions & 0 deletions main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,9 @@ function love.quit()
if package.loaded["src.translation.Service"] then
pcall(package.loaded["src.translation.Service"].shutdown)
end
if package.loaded["src.net.Fetch"] then
pcall(package.loaded["src.net.Fetch"].shutdown)
end
end

function love.filedropped(file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "wrap_System.h"
#include "common/android.h"
#include "sdl/System.h"
#include <cstring>

namespace love
{
Expand Down Expand Up @@ -199,6 +200,24 @@ int w_httpRequest(lua_State *L)
return 1;
}

// Bounded worker transport added by Roxas2712; shares the existing JNI route.
int w_httpRequestBounded(lua_State *L)
{
size_t size = 0;
const char *json = luaL_checklstring(L, 1, &size);
if (size > 1024 * 1024 || std::memchr(json, 0, size) != nullptr)
lua_pushliteral(L, "{\"error\":\"invalid bounded request\"}");
else
{
std::string envelope = "{\"boundedEnvelope\":";
envelope.append(json, size);
envelope.push_back('}');
luax_pushstring(L, instance()->httpRequest(envelope.c_str()));
}
return 1;
}
int w_boundedHttpVersion(lua_State *L) { lua_pushinteger(L, 1); return 1; }

int w_walkerNotify(lua_State *L)
{
const char *json = luaL_checkstring(L, 1);
Expand Down Expand Up @@ -263,6 +282,8 @@ static const luaL_Reg functions[] =
{ "mkdirs", w_mkdirs },
{ "walkerNotify", w_walkerNotify },
{ "httpRequest", w_httpRequest },
{ "httpRequestBounded", w_httpRequestBounded },
{ "boundedHttpVersion", w_boundedHttpVersion },
{ "syncHealthSteps", w_syncHealthSteps },
{ "restartApp", w_restartApp },
{ "httpDownload", w_httpDownload },
Expand Down
Loading