Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2415fab
vendor(iq80-leveldb): pure-Java leveldb fork with per-thread snappy s…
jeremybernstein Jun 2, 2026
47cb266
build(html5): add leveldb + snappy + webkit deps, FeatureGate flag
jeremybernstein Jun 2, 2026
b4f8c9d
feat(runtime): GameRuntime split + WebView suspend on activity backgr…
jeremybernstein Jun 2, 2026
f0cf848
feat(html5): WebView host + per-engine pack/shim system
jeremybernstein Jun 2, 2026
cf3bb11
feat(html5): engine fingerprinting
jeremybernstein Jun 2, 2026
142b4f3
feat(html5): cloud save sync -- chromium leveldb ↔ steam ufs, fs.brid…
jeremybernstein Jun 2, 2026
5d31477
feat(html5): runtime-aware launch + gamepad/touch/keyboard input
jeremybernstein Jun 2, 2026
83a104e
feat(html5): engine packs -- c2/c3/gms/godot/nwjs/tyrano/electron/rmmv
jeremybernstein Jun 2, 2026
e945c31
feat(html5): library/dialog/settings UI + opt-in service + Reclaim Wi…
jeremybernstein Jun 2, 2026
68e3f6b
feat(html5): cross-chapter wiring + snappy android jni init
jeremybernstein Jun 2, 2026
6af80ff
refactor(html5): declarative pack registration + typed engine/source ids
jeremybernstein Jun 2, 2026
acbf284
i18n(html5): translate 79 new runtime strings into 14 locales
jeremybernstein Jun 3, 2026
405291d
feat(html5): ANGLE-override advisory snackbar for terra titles
jeremybernstein Jun 6, 2026
81d18bc
test(html5): commit leveldb fixture .log data files + mark fixtures b…
jeremybernstein Jun 10, 2026
c122ef3
fix(html5): exit to library on godot engine-quit wedge
jeremybernstein Jun 13, 2026
ece062d
fix(html5): report container language on navigator.language + Steam A…
jeremybernstein Jun 18, 2026
87b74b8
feat(html5): cache OMORI decrypt key on the WebViewContainer (cold-bo…
jeremybernstein Jun 21, 2026
579136c
fix(html5): self-classify rmmv save substrate at runtime + scrub leve…
jeremybernstein Jun 25, 2026
d69d37e
fix(html5): scope chromium-profile sync per store for CrossCode; rena…
jeremybernstein Jun 25, 2026
c7a73cf
feat(html5): html5-side extensions to the extracted Steam/GOG/input f…
jeremybernstein Sep 14, 2026
1cc4266
i18n(html5): translate runtime strings into Turkish
jeremybernstein Nov 6, 2026
47e491a
build(html5): point leveldb and snappy-java submodules at the GameNat…
jeremybernstein Nov 14, 2026
3bf48be
docs: note the submodule checkout in the build steps
Nov 12, 2026
1062f59
fix(html5): keep runtime routing off the main thread
Nov 12, 2026
ee8b803
fix(html5): reclaimWineStorage refuses a wine container itself
Nov 12, 2026
14cda93
build(html5): keep the snappy classes leveldb and snappy-java look up…
Nov 12, 2026
076d509
fix(html5): config import keeps the game's runtime and local html5 paths
Nov 12, 2026
7826862
fix(html5): reset to defaults keeps an html5 game on the webview runtime
Nov 12, 2026
bad9024
fix(html5): keep remote documents out of the game WebView
Nov 12, 2026
3edf144
feat(html5): game_exited for webview sessions, game_runtime_switched
Nov 12, 2026
b3ed58c
feat(html5): seed on-screen controls from the default wine layout
Nov 12, 2026
0f6453c
refactor(html5): drop the Controller tab from Edit Container for html…
Nov 12, 2026
19df066
fix(html5): explicit null check in withDocumentPolicy
Nov 12, 2026
85ea8a7
test(html5): ContainerUtilsHtml5OverridesTest sets up DownloadService…
Nov 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# leveldb/IndexedDB save-sync test fixtures are byte-exact binary databases.
# CURRENT/LOG look like text to git but leveldb requires them unmodified; EOL
# normalization on some CI checkouts corrupts them. force binary, never normalize.
app/src/test/resources/html5-saves/** -text
6 changes: 6 additions & 0 deletions .github/workflows/adhoc-signed-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: gh pr checkout ${{ inputs.pr }}
- name: Check out leveldb + snappy submodules
run: |
# after the PR checkout so the pins match the PR; PRs that predate these submodules don't have them
if git config -f .gitmodules submodule.iq80-leveldb/upstream.path >/dev/null; then
git submodule update --init --recursive iq80-leveldb/upstream snappy-java/upstream
fi
- name: Compute artifact suffix
id: meta
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/app-release-signed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Check out leveldb + snappy submodules
run: git submodule update --init --recursive iq80-leveldb/upstream snappy-java/upstream
- name: Add swap
run: |
sudo fallocate -l 8G /mnt/swapfile.ci
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pluvia-pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
steps:
- name: Checking out GameNative
uses: actions/checkout@v4
- name: Check out leveldb + snappy submodules
run: git submodule update --init --recursive iq80-leveldb/upstream snappy-java/upstream
- name: Setup Java 17
uses: actions/setup-java@v4
with:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tagged-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ jobs:

- uses: actions/checkout@v4

- name: Check out leveldb + snappy submodules
run: git submodule update --init --recursive iq80-leveldb/upstream snappy-java/upstream

- name: set up Java 17
uses: actions/setup-java@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ proguard/

# Log Files
*.log
# but leveldb test fixtures keep their write-ahead .log files -- that IS the DB data, not a log
!app/src/test/resources/html5-saves/**/*leveldb/*.log

# Built application files
*.apk
Expand Down
8 changes: 8 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@
[submodule "app/src/main/cpp/lsfg-vk-android"]
path = app/src/main/cpp/lsfg-vk-android
url = https://github.com/GameNative/lsfg-vk-android.git
[submodule "iq80-leveldb/upstream"]
path = iq80-leveldb/upstream
url = https://github.com/GameNative/leveldb.git
branch = gamenative
[submodule "snappy-java/upstream"]
path = snappy-java/upstream
url = https://github.com/GameNative/snappy-java.git
branch = gamenative
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,12 @@ Own a broken game and want to donate a key so someone can fix it? Email [support

Most of the time you don't need this — if you just want to play, grab the release above. This is for contributors.

1. Build it like any normal Android Studio project. Ask on Discord if you get stuck.
2. **SteamGridDB API key (optional):** to pull game artwork for custom games, add your key to `local.properties`:
1. After cloning, fetch the submodules (the vendored leveldb and snappy-java are built from them):
```sh
git submodule update --init --recursive
```
2. Build it like any normal Android Studio project. Ask on Discord if you get stuck.
3. **SteamGridDB API key (optional):** to pull game artwork for custom games, add your key to `local.properties`:
```properties
STEAMGRIDDB_API_KEY=your_api_key_here
```
Expand Down
19 changes: 19 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ android {
// https://developer.android.com/ndk/downloads
ndkVersion = "27.3.13750724"

// build libsnappyjava.so from source: the maven jar's prebuilt is 4 KB-aligned, 64-bit libs need 16 KB.
// System.loadLibrary finds it because PluviaApp sets org.xerial.snappy.use.systemlib=true on device.
externalNativeBuild {
cmake {
path = file("../snappy-java/upstream/android/CMakeLists.txt")
version = "3.22.1"
}
}

signingConfigs {
create("pluvia") {
if (keystoreProperties != null) {
Expand Down Expand Up @@ -461,6 +470,14 @@ dependencies {
implementation(libs.xz)
implementation(libs.jzlib)

// leveldb for html5 save sync. pure-java iq80 over leveldbjni (no arm64 natives) because we need a
// custom Java comparator (idb_cmp1). VENDORED fork, see iq80-leveldb/NOTICE.md.
implementation(project(":iq80-leveldb"))
implementation(project(":snappy-java"))
// host-test only: desktop natives stripped from the shipped :snappy-java artifact. Robolectric needs
// them because PluviaApp skips use.systemlib off-device.
testImplementation(project(path = ":snappy-java", configuration = "desktopNatives"))

// Jetpack Compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.bundles.compose)
Expand All @@ -474,6 +491,7 @@ dependencies {
// Support
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.webkit)
implementation(libs.apng)
implementation(libs.brotli.dec)
implementation(libs.datastore.preferences)
Expand Down Expand Up @@ -514,6 +532,7 @@ dependencies {
testImplementation(libs.zstd.jni)
testImplementation(libs.orgJson)
testImplementation(libs.mockwebserver)
testImplementation(libs.rhino) // JVM JS engine -- executes pure shims (path.js, require-dispatcher.js) in tests

// Add PostHog Android SDK dependency
implementation("com.posthog:posthog-android:3.8.0")
Expand Down
11 changes: 11 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,14 @@
# Samsung Performance SDK (bundled stub jar, referenced by powercontrol Samsung driver)
-keep class com.samsung.sdk.sperf.** { *; }
-dontwarn com.samsung.sdk.sperf.**

# leveldb (html5 save sync) picks its snappy implementation by class name in Snappy's static init and
# instantiates it with newInstance(); nothing else references XerialSnappy. IQ80Snappy is not kept:
# org.iq80.snappy is compile-only, so that lookup always fails and falls through to xerial.
-keep class org.iq80.leveldb.util.Snappy$XerialSnappy { <init>(); }
# snappy-java's native code (SnappyNative.cpp) binds the native methods by name and calls throw_error
# back by name through GetMethodID; nothing in bytecode calls throw_error.
-keep class org.xerial.snappy.SnappyNative {
native <methods>;
public void throw_error(int);
}
23 changes: 23 additions & 0 deletions app/src/main/assets/html5/packs/c3-patches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"byAppId": {
"STEAM_2738490": {
"saves": {
"sync": {
"mechanism": "leveldb-origin-rewrite"
}
}
},
"GOG_1516178466": {
"workerShim": true,
"patches": [
{"type": "response-body-replace", "pathPattern": "(scripts/(c3runtime|main|c3main)|c3runtime|c3main)\\.js$", "find": "exportType:\"html5\"", "replace": "exportType:\"nwjs\""}
],
"saves": {
"sync": {
"mechanism": "opfs-mirror",
"pcOrigin": "file://"
}
}
}
}
}
15 changes: 15 additions & 0 deletions app/src/main/assets/html5/packs/c3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"engine": "pack:c3",
"entryPoint": "index.html",
"packShimPlacement": "prepend",
"wideViewport": true,
"patches": [
{"type": "response-body-replace", "pathPattern": "(scripts/(c3runtime|main|c3main)|c3runtime|c3main)\\.js$", "find": "exportType:\"nwjs\"", "replace": "exportType:\"html5\""},
{"type": "response-body-replace", "pathPattern": "scripts/c3runtime\\.js$", "find": "if(opts.lowLatency){attribs[\"desynchronized\"]=true;attribs[\"preserveDrawingBuffer\"]=", "replace": "if(opts.lowLatency){attribs[\"desynchronized\"]=false;attribs[\"preserveDrawingBuffer\"]="},
{"type": "response-body-replace", "pathPattern": "scripts/main\\.js$", "find": "this._isNWjs = (iRuntime.GetExportType() === \"nwjs\");", "replace": "this._isNWjs = true;"},
{"type": "response-body-replace", "pathPattern": "scripts/c3runtime\\.js$", "find": "GoToLayout(layout){if(this._runtime.IsLoading())return;", "replace": "GoToLayout(layout){if(this._runtime.IsLoading()){var __s=this,__l=layout;setTimeout(function(){C3.Plugins.System.Acts.GoToLayout.call(__s,__l)},100);return;}"}
],
"shims": ["nw-noop"],
"workerShim": false,
"overlay": "default"
}
29 changes: 29 additions & 0 deletions app/src/main/assets/html5/packs/electron.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"engine": "pack:electron",
"entryPoint": "index.html",
"wideViewport": true,
"shims": ["webgl1-npot-fbo-compat", "pack-electron"],
"overlay": "default",
"gamepadKeySynthesisMap": {
"GAMEPAD_BUTTON_A": "KEY_SPACE",
"GAMEPAD_BUTTON_B": "KEY_ESC",
"GAMEPAD_BUTTON_X": "KEY_X",
"GAMEPAD_BUTTON_Y": "KEY_Y",
"GAMEPAD_BUTTON_L1": "KEY_Q",
"GAMEPAD_BUTTON_R1": "KEY_E",
"GAMEPAD_BUTTON_START": "KEY_ESC",
"GAMEPAD_BUTTON_SELECT": "KEY_SHIFT_L",
"GAMEPAD_DPAD_UP": "KEY_W",
"GAMEPAD_DPAD_DOWN": "KEY_S",
"GAMEPAD_DPAD_LEFT": "KEY_A",
"GAMEPAD_DPAD_RIGHT": "KEY_D",
"GAMEPAD_LEFT_THUMB_UP": "KEY_W",
"GAMEPAD_LEFT_THUMB_DOWN": "KEY_S",
"GAMEPAD_LEFT_THUMB_LEFT": "KEY_A",
"GAMEPAD_LEFT_THUMB_RIGHT": "KEY_D",
"GAMEPAD_RIGHT_THUMB_UP": "KEY_UP",
"GAMEPAD_RIGHT_THUMB_DOWN": "KEY_DOWN",
"GAMEPAD_RIGHT_THUMB_LEFT": "KEY_LEFT",
"GAMEPAD_RIGHT_THUMB_RIGHT": "KEY_RIGHT"
}
}
5 changes: 5 additions & 0 deletions app/src/main/assets/html5/packs/gms.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"engine": "pack:gms",
"entryPoint": "index.html",
"packShimPlacement": "append"
}
4 changes: 4 additions & 0 deletions app/src/main/assets/html5/packs/godot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"engine": "pack:godot",
"entryPoint": "index.html"
}
13 changes: 13 additions & 0 deletions app/src/main/assets/html5/packs/nwjs-patches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"byAppId": {
"GOG_1252295864": {
"saves": {
"sync": {
"mechanism": "leveldb-origin-rewrite",
"chromiumProfileSubdir": "User Data/Default",
"syncChromiumProfile": true
}
}
}
}
}
11 changes: 11 additions & 0 deletions app/src/main/assets/html5/packs/nwjs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"engine": "pack:nwjs",
"entryPoint": "index.html",
"packShimPlacement": "append",
"shims": ["nw-noop"],
"saves": {
"sync": {
"chromiumProfileSubdir": "User Data/Default"
}
}
}
15 changes: 15 additions & 0 deletions app/src/main/assets/html5/packs/rmmv-patches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"byAppId": {
"STEAM_1150690": {
"patches": [
{"type": "asset-decrypt", "kind": "omori-aes-ctr"},
{"type": "response-body-replace", "pathPattern": "js/rpg_managers\\.js$", "find": "fs.readFileSync(base + \"/\" + this._path + name)", "replace": "null"},
{"type": "response-body-replace", "pathPattern": "js/rpg_managers\\.js$", "find": "script.innerHTML = applySteamLibrary(buff).toString()", "replace": "script.src = url; script.async = false"},
{"type": "response-body-replace", "pathPattern": "js/plugins/.*\\.OMORI$", "find": "if(!!err) {throw new Error(err)}", "replace": "if(!!err) {return;}"},
{"type": "response-body-replace", "pathPattern": "js/plugins/.*\\.OMORI$", "find": "static _applyCanvasFilter() {\r\n\t\t\tGraphics._renderer.clear();", "replace": "static _applyCanvasFilter() {\r\n\t\t\tif (Graphics._renderer && Graphics._renderer.clear) Graphics._renderer.clear();"},
{"type": "response-body-replace", "pathPattern": "js/rpg_managers\\.js$", "find": " } catch (e) {\r\n this.catchException(e);", "replace": " } catch (e) {\r\n try { console.error('[gamenative] SceneManager.run caught:', e && (e.stack || e.message || String(e))); } catch (_e) {}\r\n this.catchException(e);"},
{"type": "response-body-replace", "pathPattern": "js/plugins/.*\\.OMORI$", "find": " else this._realScale = 0.5;", "replace": " /* gamenative: don't snap to 0.5 on android viewports where computed scale < 1 */"}
]
}
}
}
17 changes: 17 additions & 0 deletions app/src/main/assets/html5/packs/rmmv.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"engine": "pack:rmmv",
"entryPoint": "index.html",
"packShimPlacement": "append",
"input": {"mode": "native-controller"},
"patches": [
{"type": "audio-ext-remap", "fromExt": ".rpgmvo", "toExt": ".ogg"},
{"type": "asset-decrypt", "kind": "rpgmv-xor"}
],
"shims": ["nw-noop", "pack-rmmv"],
"overlay": "default",
"fsBridgeOnly": true,
"gamepadKeySynthesisMap": {
"GAMEPAD_BUTTON_START": "KEY_X",
"GAMEPAD_BUTTON_SELECT": "KEY_SHIFT_L"
}
}
11 changes: 11 additions & 0 deletions app/src/main/assets/html5/packs/tyrano.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"engine": "pack:tyrano",
"entryPoint": "index.html",
"packShimPlacement": "append",
"shims": ["nw-noop", "pack-electron"],
"saves": {
"sync": {
"chromiumProfileSubdir": "User Data/Default"
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/assets/html5/packs/unity.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"engine": "pack:unity",
"entryPoint": "index.html",
"contentEncodedCompression": true,
"fillCanvas": true
}
Loading
Loading