Skip to content

feat: AI Frame Generation menu in sidebar#129

Merged
Producdevity merged 10 commits into
Producdevity:masterfrom
The412Banner:feat/ai-framegen-pr
May 22, 2026
Merged

feat: AI Frame Generation menu in sidebar#129
Producdevity merged 10 commits into
Producdevity:masterfrom
The412Banner:feat/ai-framegen-pr

Conversation

@The412Banner
Copy link
Copy Markdown
Contributor

@The412Banner The412Banner commented May 20, 2026

Summary

Adds an AI Frame Generation switch + settings dialog to the in-game sidebar (SidebarPerformanceFragment / winemu_sidebar_hub_type_fragment). Toggling the switch writes byte 2 of the 10-byte mmap'd gamescope.control file that libGameScopeVK.so polls. Gear button opens a dialog with 6 presets (Eco / Flow / Bal / Boost / Clear / Max) and a flow-scale slider; settings persist to SharedPreferences and re-apply on every WineActivity.onCreate/onResume so they survive game restarts and resumes.

Origin

This is a port of work I originally added to my Bannerhub-Lite fork, with additional fixes contributed by @teldommm (Seldom) on top — see The412Banner/Bannerhub-Lite#6 for the upstream fix series:

  • fix: apply FrameGen settings on WineActivity.onResume + hide gear when disabled
  • Add blue close button (dialog can only be dismissed via the explicit Close button)
  • feat: migrate FrameGen to Performance tab + add GameScope V2 driver support

All of those refinements are folded into this single squashed commit.

Why it works against stock imagefs

gamehub-lite-api's imagefs.zst v1.4.1 already ships libGameScopeVK.so with frame interpolation compiled in (strings: Frame interpolation backend created, GAMESCOPE_CONTROL_PATH, GameScope control enabled). The IPC is env-var-driven, so the runtime is fully in place — only the UI + mmap writer + Vulkan ICD JSON writer were missing.

No .so bundling, no imagefs change, no API endpoint change.

What's added

  • extension/: 5 Java sources (BhFrameGenSettings, BhFrameGenWriter, BhFrameGenWiring, BhFrameGenDialog, BhVulkanIcdWriter); all paths use ctx.getPackageName() so they auto-adapt across the 5 package variants
  • Smali injections (2): SidebarPerformanceFragment.onResume() override calling BhFrameGenWiring.bindFromFragment(this); WineActivity.onCreate insertion of BhFrameGenWriter.applyFromPrefs(p0) after invoke-super
  • Resource patches: 3 IDs, 2 strings, 1 layout row after crt_switch
  • Build pipeline: build-debug.yml adds Android SDK setup + javac + d8 step gated on hashFiles('extension/*.java') != '' so it's a no-op for branches without the extension; patch.sh gains inject_extension_dex() that probes for the next-free classesN.dex slot in the rebuilt APK and zips the compiled dex in before align/sign (no-op if EXTENSION_DEX_PATH unset)

Verified

  • CI green on the fork via the patched build-debug.yml
  • Built APK contains all 5 Bh* classes in classes11.dex
  • Device-tested on real hardware: sidebar shows the row, toggle works, gear opens preset dialog, frame interpolation activates in-game, settings survive game restart and Home/resume

Test plan

  • Install built APK, launch any game via GameHub Lite
  • Open sidebar — confirm Frame Generation row appears on the Performance / HubType fragment
  • Toggle ON — gear icon appears next to the switch
  • Tap gear — dialog opens with 6 preset buttons, flow-scale slider, blue Close button
  • Pick a preset, close dialog — confirm frame interpolation is active
  • Press Home, resume game — settings still applied (onResume re-apply)
  • Toggle OFF — gear hides immediately

Summary by CodeRabbit

  • New Features

    • Frame Generation dialog: toggle, presets, adjustable flow scale with live feedback and persistent settings
    • RTS touch overlay: on-screen touch controls can be enabled/disabled
  • UI/UX Updates

    • Sidebar: Frame Generation switch and gear/settings button (appears when enabled)
    • Launcher texts updated (app name shown as “GameHub Lite”) and currency display changed to $
  • Stability

    • Settings persist and are applied at app launch

Review Change Stack

Adds an AI Frame Generation switch + settings dialog to the in-game
sidebar (SidebarPerformanceFragment / winemu_sidebar_hub_type_fragment).
Toggling the switch writes byte 2 of the 10-byte mmap'd gamescope.control
file that libGameScopeVK.so polls. Gear button opens a dialog with 6
presets (Eco / Flow / Bal / Boost / Clear / Max) and a flow-scale slider;
settings persist to SharedPreferences and re-apply on every
WineActivity.onCreate so they survive game restarts.

Why it works against stock imagefs:
gamehub-lite-api's imagefs.zst v1.4.1 already ships libGameScopeVK.so
with frame interpolation compiled in (strings: "Frame interpolation
backend created", "GAMESCOPE_CONTROL_PATH", "GameScope control enabled").
The IPC is env-var-driven, so the runtime is fully in place — only the
UI + mmap writer + Vulkan ICD JSON writer were missing. No .so bundling,
no imagefs change, no API endpoint change.

What's added:
- extension/: 5 Java sources (BhFrameGenSettings, BhFrameGenWriter,
  BhFrameGenWiring, BhFrameGenDialog, BhVulkanIcdWriter); all paths use
  ctx.getPackageName() so they auto-adapt across the 5 package variants
- Smali injections (2): SidebarPerformanceFragment.onResume() override
  calling BhFrameGenWiring.bindFromFragment(this); WineActivity.onCreate
  insertion of BhFrameGenWriter.applyFromPrefs(p0) after invoke-super
- Resource patches: 3 IDs, 2 strings, 1 layout row after crt_switch
- Build pipeline: build-debug.yml adds Android SDK setup + javac + d8
  step gated on hashFiles('extension/*.java') != '' so it's a no-op for
  branches without the extension; patch.sh gains inject_extension_dex()
  that probes for next-free classesN.dex slot in the rebuilt APK and
  zips the compiled dex in before align/sign (no-op if
  EXTENSION_DEX_PATH unset)

Verified:
- CI green via the patched build-debug.yml
- Built APK contains all 5 Bh* classes in classes11.dex
- Device-tested on real hardware: sidebar shows the row, toggle works,
  frame interpolation activates in-game
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Walkthrough

Adds an AI frame-generation settings subsystem (settings model, dialog, sidebar wiring, control-file writer, and Vulkan ICD selector), integrates an RTS touch overlay into WineActivity with input hooks, extends CI to compile/inject an extension dex into the APK, and updates UI resources and the patch manifest.

Changes

Frame Generation Settings System

Layer / File(s) Summary
Frame Generation Settings Model and Persistence
extension/BhFrameGenSettings.java, extension/BhFrameGenWriter.java, extension/BhVulkanIcdWriter.java
BhFrameGenSettings defines presets and persists values via SharedPreferences. BhFrameGenWriter writes a 10-byte gamescope.control via mmap and exposes apply/update entrypoints; BhVulkanIcdWriter idempotently manages per-package Vulkan ICD JSON.
Frame Generation Settings Dialog UI
extension/BhFrameGenDialog.java
Programmatically constructs a right-aligned, dimmed settings panel with preset and flow-scale SeekBars, persists/exports changes via BhFrameGenWriter, and exposes a safe show(Context) helper.
Frame Generation Sidebar Wiring & Fragment Hook
extension/BhFrameGenWiring.java, patches/diffs/smali_classes4/com/xj/winemu/sidebar/SidebarPerformanceFragment.smali.patch
Reflectively binds the sidebar switch and gear button, syncs persisted enabled state, registers toggle handlers that persist changes through BhFrameGenWriter, and invokes wiring on fragment resume.
Layout / IDs / Strings
patches/diffs/res/layout/winemu_sidebar_hub_type_fragment.xml.patch, patches/diffs/res/values/ids.xml.patch, patches/diffs/res/values/strings.xml.patch
Adds a frame-generation switch container and gear button to the sidebar layout, new resource IDs for frame-gen and RTS controls, and localized string entries for the new UI keys.

CI Build Pipeline and Extension Dex Injection

Layer / File(s) Summary
Build Pipeline Extension
.github/workflows/build-debug.yml, patch.sh
Workflow now includes extension/** in trigger paths and pins several actions to commit SHAs; conditionally provisions Android SDK and compiles extension/*.java to a classes.dex (javac -> d8) exported as EXTENSION_DEX_PATH. patch.sh adds compile_extension_dex and inject_extension_dex to optionally insert that dex into the next free classesN.dex slot in unsigned.apk.

RTS Touch Overlay Integration and Performance Management

Layer / File(s) Summary
RTS Overlay View and Activity Integration
patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch
Adds RtsTouchOverlayView instance/static references, creates/attaches overlay above controls, initializes visibility from InputControlsManager, toggles/restores trackpad via WinUIBridge on enable/disable, calls BhFrameGenWriter.applyFromPrefs() during onCreate, and clears static reference in onDestroy.

Patch Manifest Updates

Layer / File(s) Summary
Patch target manifest
patches/files_to_patch.txt
Registers new layout and smali patch targets (sidebar layouts, UI controllers, input manager) and reorders entries in the patch manifest.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Producdevity

Poem

🐰✨ A rabbit taps the seekbar bright,
presets hum under rounded light,
Vulkan writes its little file,
RTS overlays dance for a while,
CI stitches dex to ship by night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: AI Frame Generation menu in sidebar' directly and clearly describes the primary feature addition—an AI Frame Generation UI (switch and settings dialog) integrated into the game sidebar.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch (1)

286-319: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Close PerformanceHintManager$Session after reporting work duration.

The created hint session (v3) is never closed. PerformanceHintManager.Session implements AutoCloseable and holds native system resources that must be explicitly released via close(). Failing to close leaks binder objects and system memory, particularly problematic across activity recreations. Add proper cleanup in a try-finally or exception handler after reportActualWorkDuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch` around
lines 286 - 319, The PerformanceHintManager.Session returned by
PerformanceHintManager.createHintSession (stored in v3) is never closed; wrap
the createHintSession/reportActualWorkDuration sequence in a try/finally (or
equivalent error-path handling) and always call Session.close() on the v3
instance in the finally block to release native resources; locate the
createHintSession call and the subsequent reportActualWorkDuration invocation
and insert guaranteed cleanup (invoke close on the
PerformanceHintManager$Session) regardless of success or exceptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-debug.yml:
- Line 61: Replace the floating tag "uses: android-actions/setup-android@v3"
with a pinned full commit SHA for the android-actions/setup-android action;
locate the desired stable commit SHA on the android-actions/setup-android
repository (e.g., from the v3 release tag), and update the workflow entry to
"uses: android-actions/setup-android@<commit-sha>" so the CI uses an immutable
reference; optionally add a short comment noting which release/tag that SHA
corresponds to for future updates.

In `@extension/BhFrameGenDialog.java`:
- Line 90: Replace all hardcoded user-facing strings in BhFrameGenDialog (e.g.,
the title.setText("AI Frame Generation") call and the other hardcoded strings at
lines referenced) with lookups to string resources instead of inline literals;
add or reuse a helper like
getStringByName/getContext().getString(R.string.some_name) to fetch resources
and pass the fallback for safety, then change calls such as title.setText(...),
button.setText(...), and any label.setText(...) to use those resource lookups
(use unique resource names like bh_title_ai_frame_generation, bh_button_accept,
etc.), and ensure the helper is available in the class context so all
occurrences (title, the button labels, dialog body text) use resource lookups
rather than hardcoded text.

In `@extension/BhFrameGenWriter.java`:
- Around line 93-96: resolveControlPath currently builds the control file path
with a hardcoded "/data/data/" prefix; instead obtain the app storage root from
the Context (use ctx.getFilesDir() or equivalent) and construct the final path
by appending "usr/etc/gamescope.control" relative to that directory so it works
for multi-user/work profiles; update the resolveControlPath method to use
ctx.getFilesDir() as the base (or new File(ctx.getFilesDir(), ...)) rather than
concatenating "/data/data/" + pkg.

In `@extension/BhVulkanIcdWriter.java`:
- Around line 119-121: The dataPath(Context ctx) helper currently hardcodes
"/data/data/" + ctx.getPackageName(); change it to resolve the app data root
from the Context (e.g., use ctx.getApplicationInfo().dataDir or an equivalent
Context-derived API) so the ICD path is correct for different users/profiles;
update the dataPath method to return the application dataDir (falling back to
ctx.getPackageName() concatenation only if dataDir is null) and ensure callers
of dataPath(Context) continue to work with the returned stable path.

In `@patch.sh`:
- Around line 341-364: The script currently uses zip and unzip in
inject_extension_dex() but does not verify they are installed; update
check_dependencies() to explicitly verify both zip and unzip are present (e.g.,
using command -v or which) and call process failure (print error and exit
non‑zero) if either is missing so the build fails early with a clear message;
keep references to inject_extension_dex(), EXTENSION_DEX_PATH and WORK_DIR so
reviewers can locate the affected logic and ensure inject_extension_dex() can
assume those binaries exist afterwards.

In `@patches/diffs/res/layout/winemu_sidebar_hub_type_fragment.xml.patch`:
- Line 9: The layout references drawable `@drawable/winemu_ic_setting_focus_white`
for the ImageButton with id btn_frame_gen_settings but that drawable is not
included in this patch; ensure the drawable exists in the base APK or add it to
this PR (or change the reference to an existing drawable) so the layout can
inflate; locate the ImageButton by its id btn_frame_gen_settings in
winemu_sidebar_hub_type_fragment.xml and either add the missing resource named
winemu_ic_setting_focus_white to the project's drawable resources or update the
android:src to a valid existing drawable or themed attribute before merging.

---

Outside diff comments:
In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch`:
- Around line 286-319: The PerformanceHintManager.Session returned by
PerformanceHintManager.createHintSession (stored in v3) is never closed; wrap
the createHintSession/reportActualWorkDuration sequence in a try/finally (or
equivalent error-path handling) and always call Session.close() on the v3
instance in the finally block to release native resources; locate the
createHintSession call and the subsequent reportActualWorkDuration invocation
and insert guaranteed cleanup (invoke close on the
PerformanceHintManager$Session) regardless of success or exceptions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d9aea3e1-a4e9-48bb-ad85-9d5fc37352de

📥 Commits

Reviewing files that changed from the base of the PR and between c3055e5 and f62711c.

📒 Files selected for processing (13)
  • .github/workflows/build-debug.yml
  • extension/BhFrameGenDialog.java
  • extension/BhFrameGenSettings.java
  • extension/BhFrameGenWiring.java
  • extension/BhFrameGenWriter.java
  • extension/BhVulkanIcdWriter.java
  • patch.sh
  • patches/diffs/res/layout/winemu_sidebar_hub_type_fragment.xml.patch
  • patches/diffs/res/values/ids.xml.patch
  • patches/diffs/res/values/strings.xml.patch
  • patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch
  • patches/diffs/smali_classes4/com/xj/winemu/sidebar/SidebarPerformanceFragment.smali.patch
  • patches/files_to_patch.txt

Comment thread .github/workflows/build-debug.yml Outdated
Comment thread extension/BhFrameGenDialog.java Outdated
Comment thread extension/BhFrameGenWriter.java
Comment thread extension/BhVulkanIcdWriter.java
Comment thread patch.sh Outdated
Comment thread patches/diffs/res/layout/winemu_sidebar_hub_type_fragment.xml.patch
inject_extension_dex() uses both `unzip` (to probe the next-free classesN.dex
slot in the rebuilt APK) and `zip` (to add the compiled extension dex back in)
but neither was validated up-front. Without them the build fails late and
opaquely. Adding both to check_dependencies() so the missing-deps banner names
the actual problem.

Addresses CodeRabbit Producdevity#5 on Producdevity#129.
Replace floating @vn tags with full commit SHAs. The trailing comment
records which release tag the SHA corresponds to so future updates can
re-pin to a newer release without re-resolving by hand.

  actions/checkout            v4 → 34e1148… (v4.3.1)
  actions/setup-java          v4 → c1e3236… (v4.8.0)
  android-actions/setup-android v3 → 9fc6c4e… (v3.2.2)
  actions/upload-artifact     v4 → ea165f8… (v4.6.2)

Addresses CodeRabbit Producdevity#1 on Producdevity#129; resolves the zizmor unpinned-uses
policy hit at line 61 and applies the same hardening to the three
other previously-unpinned references in the same workflow.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-debug.yml:
- Line 28: Update the GitHub Actions checkout step that currently uses
"actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" to explicitly set
persist-credentials: false so the workflow does not leave the GITHUB_TOKEN in
the local git config; locate the checkout step in the workflow and add the
persist-credentials: false input under that step to disable credential
persistence for subsequent steps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d3021b6-d706-4bae-b64d-67ab809dc209

📥 Commits

Reviewing files that changed from the base of the PR and between 10a3db0 and 3871b97.

📒 Files selected for processing (1)
  • .github/workflows/build-debug.yml

Comment thread .github/workflows/build-debug.yml
@The412Banner
Copy link
Copy Markdown
Contributor Author

Both CodeRabbit items from earlier are addressed:

  • 10a3db0fcheck_dependencies() now validates zip and unzip (used in inject_extension_dex())
  • 3871b975 — all 4 GitHub Actions in build-debug.yml pinned to commit SHAs (actions/checkout, actions/setup-java, android-actions/setup-android, actions/upload-artifact) with trailing tag comments for human readability

Both CI jobs (Build Debug APK + Validate Patches) green on the latest commit.


Separately, CI is flagging that patches/ have drifted from canonical form, and uploading a refreshed-patches artifact. I reproduced this on a clean checkout of master with no other changes — same 1 out of 39 hunks FAILED for patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch, so it's pre-existing and not introduced by this PR. Happy to send a separate small cleanup PR that:

  1. Replaces the drifted .patch files with the refreshed-patches artifact contents
  2. Manually fixes the one truly-failing WineActivity hunk so all 39 apply cleanly (not 38 + 1 rejected via fuzz)

After that the drift annotation should go silent and the patches become deterministic again. Want me to open that as a follow-up?

@Producdevity
Copy link
Copy Markdown
Owner

Producdevity commented May 21, 2026

Skipping dex injection is safe only when nothing in the patched APK references the extension classes. In this PR the smali hooks are unconditional, so a normal documented local build (./patch.sh or RELEASE=true ./patch.sh) succeeds without EXTENSION_DEX_PATH but produces an APK missing BhFrameGenWriter / BhFrameGenWiring, which should crash when those hooks run.

Could we either compile extension/*.java inside patch.sh when the directory exists, or make patch.sh fail if extension sources/hooks are present but no extension dex was injected?

I am open for discussing the solution, but this will break things on managed sub branches

@The412Banner

@Producdevity
Copy link
Copy Markdown
Owner

I think there is an CI issue introduced: this PR adds extension/*.java as APK runtime code, but the workflow path filters don’t include extension/**.

This PR still runs CI because it also touches patch.sh, .github/workflows/build-debug.yml, and patches/**, but a follow-up that only fixes BhFrameGen*.java would skip the debug APK build entirely. Since the workflow now compiles those files into the injected dex, can we add extension/** to both the push.paths and pull_request.paths lists in this PR? @The412Banner

@Producdevity
Copy link
Copy Markdown
Owner

git diff --check reports trailing whitespace in patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch. Can we strip the trailing whitespace so the patch stays clean? @The412Banner

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch (1)

261-279: 💤 Low value

Thermal headroom result is fetched but never used.

PowerManager.getThermalHeadroom(I)F is called but the float result is discarded. If this is placeholder code for future use, consider removing it to avoid confusion; if it's meant to influence behavior, the result needs to be stored/checked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch` around
lines 261 - 279, The call to PowerManager.getThermalHeadroom(I)F in WineActivity
is discarding its float result; either remove this unused call or capture and
use the returned thermal headroom value: after invoking getThermalHeadroom on
the PowerManager obtained via getSystemService, store the float result and then
handle it (e.g., log it, compare against a threshold and branch, or otherwise
influence behavior) so the call is not a no-op; ensure the SDK_INT check around
the block remains and update the registers/locals used (the result of
getThermalHeadroom) accordingly.
patch.sh (1)

369-383: 💤 Low value

Consider using find instead of ls for safer path handling.

Static analysis flags these ls usages (SC2012). While the current code works for controlled SDK paths, using find would be more robust against edge cases with unusual characters in paths.

♻️ Suggested refactor
     local android_jar
-    android_jar=$(ls -1 "$sdk_root"/platforms/android-*/android.jar 2>/dev/null | sort -V | tail -1)
+    android_jar=$(find "$sdk_root/platforms" -maxdepth 2 -name 'android.jar' 2>/dev/null | sort -V | tail -1)
     if [ -z "$android_jar" ] || [ ! -f "$android_jar" ]; then
         print_error "android.jar not found under $sdk_root/platforms/."
         print_error "Install a platform, e.g. sdkmanager 'platforms;android-34'."
         return 1
     fi

     local d8_bin
-    d8_bin=$(ls -1 "$sdk_root"/build-tools/*/d8 2>/dev/null | sort -V | tail -1)
+    d8_bin=$(find "$sdk_root/build-tools" -maxdepth 2 -name 'd8' -type f 2>/dev/null | sort -V | tail -1)
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch`:
- Around line 261-279: The call to PowerManager.getThermalHeadroom(I)F in
WineActivity is discarding its float result; either remove this unused call or
capture and use the returned thermal headroom value: after invoking
getThermalHeadroom on the PowerManager obtained via getSystemService, store the
float result and then handle it (e.g., log it, compare against a threshold and
branch, or otherwise influence behavior) so the call is not a no-op; ensure the
SDK_INT check around the block remains and update the registers/locals used (the
result of getThermalHeadroom) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db8fee53-2c40-4895-b7d2-7c2ba5968838

📥 Commits

Reviewing files that changed from the base of the PR and between 3871b97 and 83b4a9a.

📒 Files selected for processing (3)
  • .github/workflows/build-debug.yml
  • patch.sh
  • patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch

@The412Banner
Copy link
Copy Markdown
Contributor Author

All 3 addressed on 83b4a9a6. Build Debug APK + Validate Patches both ✅ on that SHA.

  • 1fad14aapatch.sh auto-compiles extension/*.java when EXTENSION_DEX_PATH is unset. Mirrors the CI step (javac -source 8 -target 8d8 --release) using ANDROID_SDK_ROOT / ANDROID_HOME. Hard-fails with actionable messages if javac / android.jar / d8 is missing, rather than producing a broken APK. The pre-built-dex path is unchanged, so CI is unaffected. The original silent-skip is kept only for the case where neither a pre-built dex nor extension/ sources are present — so managed sub-branches that have stripped both the sources and the smali hooks still build clean. Verified locally: produced a valid 20.4 KB classes.dex containing all 5 extension classes.

  • 5ed9676cextension/** added to both push.paths and pull_request.paths in build-debug.yml.

  • 83b4a9a6 — Trailing whitespace stripped from WineActivity.smali.patch. git diff --check master..HEAD is clean. 245-byte delta, 911 lines unchanged, every line equals its rstrip() of the original — pure whitespace removal. The Validate Patches workflow's lsdiff syntax/structure check passes on the new file.

Happy to flip the patch.sh fallback to a hard-fail when extension/ is present but no dex was injected (vs. the compile-when-present I went with), if you'd prefer the explicit-fail flavor.

@Producdevity
Copy link
Copy Markdown
Owner

Producdevity commented May 21, 2026

patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch
Thermal headroom result is fetched but never used.

PowerManager.getThermalHeadroom(I)F is called but the float result is discarded. is this intentional? do we need it here?

(edit: resolved)

Walk through every comment from Producdevity's review on PR Producdevity#129
and resolve each (no nitpick skipping):

CodeRabbit findings:
- BhFrameGenDialog.java: route hardcoded dialog strings through string
  resources (title, preset header, flow-scale header, close button,
  preset mode suffix) with safe fallbacks; reuse rts_gesture_settings_close
  for the Close button.
- BhFrameGenWriter.java: build control path from ctx.getFilesDir()
  instead of "/data/data/<pkg>/files/..." so multi-user/work profiles
  resolve correctly.
- BhVulkanIcdWriter.java: dataPath() now returns ctx.getApplicationInfo()
  .dataDir for the same reason.
- build-debug.yml: persist-credentials: false on actions/checkout so
  GITHUB_TOKEN does not survive into the local git config.
- patch.sh: switch ls -1 globbing to find -name for android.jar / d8
  discovery.
- WineActivity.smali.patch:
    * Drop dead getThermalHeadroom() call (result was discarded).
    * Wrap reportActualWorkDuration in a try/catch and close()
      PerformanceHintManager.Session on both success and failure paths.

Java code comment sweep across extension/*.java:
Producdevity asked that comments either explain a non-obvious "why"
or flag that the code is not self-descriptive. Stripped narrating-style
banners, section dividers, planning/phase references, and chat-context
master-map citations; kept comments documenting the on-disk gamescope
protocol layout, the reasons for reflection-based dispatch, idempotency
invariants, and ownership boundaries.

Also removed unused clampInt helper in BhFrameGenWriter.

New string resources added in strings.xml.patch:
  bh_framegen_dialog_flow_scale, bh_framegen_dialog_mode_suffix,
  bh_framegen_dialog_preset
…il loudly on apktool rebuild

WineActivity.onCreate already defines :try_start_0/_1/_2 and
:catch_0/_1, so the previously-added :try_start_0/:catch_0 around
reportActualWorkDuration collided with the existing labels and made
apktool reassembly fail. Rename to :try_start_3 / :try_end_3 /
:catch_2.

While here, drop the `| tail -5` pipe on the apktool rebuild call so
the next failure surfaces the actual error in CI logs rather than the
end of the wrapping stack trace, and fail the script when apktool
exits non-zero (the pipe was masking the exit code).
@The412Banner
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed feedback — sorry about the previous miss on the nitpicks and the comment noise. I walked through every CodeRabbit comment on the PR with the explicit rule that nothing gets skipped, and did a separate pass over extension/*.java with the criteria you laid out (comments must explain a non-obvious why, or flag that the code isn't self-descriptive — no narration, no planning/phase references, no decorative dividers, no chat-context references).

Commit 470b1630 — review sweep. Follow-up 29a4cdd5 fixed a smali try/catch label collision the first commit introduced (the new try block reused :try_start_0/:catch_0, which onCreate already defines, breaking apktool rebuild). CI is green on 29a4cdd5.

CodeRabbit items resolved:

  • BhFrameGenDialog.java (lines 90/117/184/224/298): all five hardcoded user-facing strings now go through a getStringByName(name, fallback) helper. Added bh_framegen_dialog_preset, bh_framegen_dialog_flow_scale, bh_framegen_dialog_mode_suffix to strings.xml.patch; reused the existing bh_framegen_title for the title and rts_gesture_settings_close for the Close button. (One thing to flag: bh_framegen_title was already shipping as "Frame Generation", so the dialog title now matches the sidebar switch label instead of saying "AI Frame Generation". Happy to add a separate bh_framegen_dialog_title resource if you'd rather keep "AI Frame Generation" specifically.)
  • BhFrameGenWriter.java: resolveControlPath now uses ctx.getFilesDir() instead of "/data/data/" + pkg + "/files/...".
  • BhVulkanIcdWriter.java: dataPath() now returns ctx.getApplicationInfo().dataDir.
  • build-debug.yml: actions/checkout step now has with: persist-credentials: false.
  • patch.sh: ls -1 .../platforms/android-*/android.jar and ls -1 .../build-tools/*/d8 switched to find -name ... -type f for safer path handling.
  • WineActivity.smali.patch:
    • The dead getThermalHeadroom(I)F call (result was discarded) is gone — block removed entirely.
    • reportActualWorkDuration is wrapped in :try_start_3 / :try_end_3 / :catch_2 and PerformanceHintManager$Session.close()V is invoked on both success and exception paths so we don't leak the binder.

Java comment cleanup pass:

Stripped from extension/*.java:

  • section dividers (// ── helpers ──, etc.)
  • narrating comments ("Title", "Centered panel", "Click handler", "First-time UI sync", "Sync switch state", "Sync gear visibility", "Outer dim layer", "Bound widgets we need to update from preset changes", etc.)
  • planning-style references ("RTS pattern", "mirrors RTS pattern", "exactly as RTS does")
  • chat-context references (the GAMEHUB_600_MASTER_MAP § 26.8.x citations in BhFrameGenSettings/BhFrameGenWriter headers)
  • narrating Javadoc summaries that just restated the method name ("Apply a preset's defaults to flowScale and model", "Write only the AI model byte", "Convenience launcher for the smali wiring patch", etc.)
  • the unused clampInt helper in BhFrameGenWriter (dead code)

Kept as load-bearing why comments:

  • the 10-byte little-endian protocol table on BhFrameGenWriter (documents the on-disk gamescope.control layout, including which bytes other components own and we leave alone)
  • the reason applyFromPrefsNoContext() exists and resolves the Context via ActivityThread reflection
  • the reason bindFromFragment calls Fragment.getView() reflectively (so the extension doesn't need androidx on its compile classpath)
  • the SidebarSwitchItemView note above invokeSetSwitch (the switch is rendered as an ImageView, not a CompoundButton, hence reflection on setSwitch(boolean))
  • ensureIcdJson deleting the inactive ICD JSON (the why: so the Vulkan loader sees only the selected driver)
  • the Idempotent — onResume can call repeatedly note on bind and ensureIcdJsonForPath

Also dropped | tail -5 on the apktool b call in patch.sh and made it propagate exit codes — that pipe was what swallowed the actual smali-reassembly error from my first push, which is what cost a round-trip here.

Let me know if there's anything I trimmed that you'd want back, or any other tone/style preference for the comments that should be in the next pass.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
extension/BhFrameGenDialog.java (1)

274-276: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use an explicit Locale in formatFloat to avoid comma-decimal in non-en locales.

String.format("%.2f", v) falls back to Locale.getDefault(). On devices with locales such as fr/de/ru, the flow-scale label will render as 0,20 while the rest of the slider semantics (range 0.2..1.0, internal floats) use a dot. For a technical scalar like this, Locale.ROOT (or Locale.US) keeps the displayed value consistent with the underlying numeric contract.

🧰 Proposed fix
-    private static String formatFloat(float v) {
-        return String.format("%.2f", v);
-    }
+    private static String formatFloat(float v) {
+        return String.format(java.util.Locale.ROOT, "%.2f", v);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extension/BhFrameGenDialog.java` around lines 274 - 276, The formatting of
floats in formatFloat currently relies on the default locale which can produce
comma decimals; update formatFloat to use an explicit locale (e.g., Locale.ROOT
or Locale.US) by calling String.format(Locale.ROOT, "%.2f", v) and add the
necessary java.util.Locale import so the displayed flow-scale uses a dot decimal
consistently across locales.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@extension/BhFrameGenDialog.java`:
- Around line 274-276: The formatting of floats in formatFloat currently relies
on the default locale which can produce comma decimals; update formatFloat to
use an explicit locale (e.g., Locale.ROOT or Locale.US) by calling
String.format(Locale.ROOT, "%.2f", v) and add the necessary java.util.Locale
import so the displayed flow-scale uses a dot decimal consistently across
locales.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd428c26-2da8-46fe-9ebd-5d8fa970c75e

📥 Commits

Reviewing files that changed from the base of the PR and between 83b4a9a and 29a4cdd.

📒 Files selected for processing (9)
  • .github/workflows/build-debug.yml
  • extension/BhFrameGenDialog.java
  • extension/BhFrameGenSettings.java
  • extension/BhFrameGenWiring.java
  • extension/BhFrameGenWriter.java
  • extension/BhVulkanIcdWriter.java
  • patch.sh
  • patches/diffs/res/values/strings.xml.patch
  • patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch
💤 Files with no reviewable changes (1)
  • extension/BhFrameGenSettings.java
✅ Files skipped from review due to trivial changes (1)
  • patches/diffs/res/values/strings.xml.patch

…decimal on non-en locales

String.format("%.2f", v) falls back to Locale.getDefault(), which
renders e.g. 0,20 on fr/de/ru devices while the slider range and
preset values use a dot internally. Pin to Locale.ROOT so the
displayed flow-scale matches the underlying numeric contract.

Addresses CodeRabbit outside-diff comment on PR Producdevity#129.
@Producdevity
Copy link
Copy Markdown
Owner

🧹 Nitpick comments (2)

patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch (1)> 261-279: 💤 Low value

Thermal headroom result is fetched but never used.
PowerManager.getThermalHeadroom(I)F is called but the float result is discarded. If this is placeholder code for future use, consider removing it to avoid confusion; if it's meant to influence behavior, the result needs to be stored/checked.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch` around
lines 261 - 279, The call to PowerManager.getThermalHeadroom(I)F in WineActivity
is discarding its float result; either remove this unused call or capture and
use the returned thermal headroom value: after invoking getThermalHeadroom on
the PowerManager obtained via getSystemService, store the float result and then
handle it (e.g., log it, compare against a threshold and branch, or otherwise
influence behavior) so the call is not a no-op; ensure the SDK_INT check around
the block remains and update the registers/locals used (the result of
getThermalHeadroom) accordingly.

patch.sh (1)> 369-383: 💤 Low value

Consider using find instead of ls for safer path handling.
Static analysis flags these ls usages (SC2012). While the current code works for controlled SDK paths, using find would be more robust against edge cases with unusual characters in paths.

♻️ Suggested refactor

     local android_jar
-    android_jar=$(ls -1 "$sdk_root"/platforms/android-*/android.jar 2>/dev/null | sort -V | tail -1)
+    android_jar=$(find "$sdk_root/platforms" -maxdepth 2 -name 'android.jar' 2>/dev/null | sort -V | tail -1)
     if [ -z "$android_jar" ] || [ ! -f "$android_jar" ]; then
         print_error "android.jar not found under $sdk_root/platforms/."
         print_error "Install a platform, e.g. sdkmanager 'platforms;android-34'."
         return 1
     fi

     local d8_bin
-    d8_bin=$(ls -1 "$sdk_root"/build-tools/*/d8 2>/dev/null | sort -V | tail -1)
+    d8_bin=$(find "$sdk_root/build-tools" -maxdepth 2 -name 'd8' -type f 2>/dev/null | sort -V | tail -1)

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch`:
- Around line 261-279: The call to PowerManager.getThermalHeadroom(I)F in
WineActivity is discarding its float result; either remove this unused call or
capture and use the returned thermal headroom value: after invoking
getThermalHeadroom on the PowerManager obtained via getSystemService, store the
float result and then handle it (e.g., log it, compare against a threshold and
branch, or otherwise influence behavior) so the call is not a no-op; ensure the
SDK_INT check around the block remains and update the registers/locals used (the
result of getThermalHeadroom) accordingly.

ℹ️ Review info

is this fixed?

@Producdevity
Copy link
Copy Markdown
Owner

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

extension/BhFrameGenDialog.java (1)> 274-276: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use an explicit Locale in formatFloat to avoid comma-decimal in non-en locales.
String.format("%.2f", v) falls back to Locale.getDefault(). On devices with locales such as fr/de/ru, the flow-scale label will render as 0,20 while the rest of the slider semantics (range 0.2..1.0, internal floats) use a dot. For a technical scalar like this, Locale.ROOT (or Locale.US) keeps the displayed value consistent with the underlying numeric contract.

🧰 Proposed fix

-    private static String formatFloat(float v) {
-        return String.format("%.2f", v);
-    }
+    private static String formatFloat(float v) {
+        return String.format(java.util.Locale.ROOT, "%.2f", v);
+    }

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extension/BhFrameGenDialog.java` around lines 274 - 276, The formatting of
floats in formatFloat currently relies on the default locale which can produce
comma decimals; update formatFloat to use an explicit locale (e.g., Locale.ROOT
or Locale.US) by calling String.format(Locale.ROOT, "%.2f", v) and add the
necessary java.util.Locale import so the displayed flow-scale uses a dot decimal
consistently across locales.

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@extension/BhFrameGenDialog.java`:
- Around line 274-276: The formatting of floats in formatFloat currently relies
on the default locale which can produce comma decimals; update formatFloat to
use an explicit locale (e.g., Locale.ROOT or Locale.US) by calling
String.format(Locale.ROOT, "%.2f", v) and add the necessary java.util.Locale
import so the displayed flow-scale uses a dot decimal consistently across
locales.

ℹ️ Review info

is this fixed?

Comment on lines 50 to 52
public static String resolveVkLibraryPath(Context ctx) {
return dataPath(ctx) + "/files/usr/lib/libGameScopeVK.so";
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if a user is on an older firmware (imagefs) version that doesn't have this lib available? or any of the other libs that this PR assumes exists.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat extension/BhVulkanIcdWriter.java
echo "---"
cat extension/BhFrameGenWiring.java
echo "---"
cat extension/BhFrameGenWriter.java

Length of output: 10854


@Producdevity Great question — here's the full picture after reading the code:


Scenario 1 — .so entirely absent (very old imagefs) ✅ Handled

BhFrameGenWiring.bind() gates everything on BhVulkanIcdWriter.isVkAvailable(ctx), which is a simple new File(resolveVkLibraryPath(ctx)).exists() check:

if (!BhVulkanIcdWriter.isVkAvailable(ctx)) {
    if (container != null) container.setVisibility(View.GONE);
    return;
}

The entire sidebar row is hidden and nothing else runs. ensureIcdJson also no-ops when the .so is missing. The only minor side-effect is that BhFrameGenWriter.applyFromPrefs(ctx) is still called unconditionally in WineActivity.onCreate, which will create the gamescope.control file even though nothing will ever read it — harmless, but unnecessary.


Scenario 2 — .so present but old version (pre-imagefs v1.4.1, no FrameGen IPC) ⚠️ Not handled

This is the real gap. isVkAvailable is a blunt file-existence check, not a capability/version check. An older imagefs might ship a libGameScopeVK.so that lacks frame interpolation and the 10-byte IPC protocol entirely. In that case:

  • The UI row will be shown (lib file exists)
  • The user can toggle the switch and open the dialog
  • The control file is written correctly
  • But the old .so silently ignores it — no crash, but the feature does nothing

There's no version probe or symbol check to distinguish an old lib from v1.4.1+.


Suggested mitigations

  1. Guard applyFromPrefs in onCreate behind isVkAvailable — avoids the unnecessary file write on old firmware.

  2. Version/capability check — the cleanest approach would be checking a version marker in the imagefs (e.g. a known file like /files/usr/etc/imagefs.version or a sentinel byte in the .so itself), and surfacing it via isFrameGenCapable(ctx) used alongside isVkAvailable. Would the imagefs ship a version file you could read?

  3. Alternative: gate on control file readability — if libGameScopeVK.so polls the control file, you could check whether the control file is created by the runtime (i.e. by the .so) rather than creating it proactively. But this may complicate the bootstrap flow.

Would you like to open a follow-up issue to track the version-check gap, or address it within this PR?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread extension/BhFrameGenWiring.java Outdated
Comment on lines +60 to +61
/** SidebarSwitchItemView (com.xj.winemu.view) renders the switch as an ImageView,
* not a CompoundButton; driven through its public setSwitch(boolean). */
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to know what it is not? please keep comments concise and only included when the code itself isn't descriptive enough for whatever reason

Comment thread extension/BhFrameGenWiring.java Outdated

/** Bind switch + gear button. Idempotent — onResume can call repeatedly.
* Gear visibility mirrors RTS pattern: gone by default, visible only when ON. */
/** Idempotent — onResume can call repeatedly. */
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of this comment?

Comment thread extension/BhFrameGenSettings.java Outdated
Comment on lines 11 to 12
ECO (0, 0.2f, "Eco", "Lowest overhead, suitable for lower-end devices or battery-sensitive play."),
FLOW (0, 0.4f, "Flow", "Low overhead smoothness boost for most lightweight games."),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use resource strings please?

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch (1)

299-311: 💤 Low value

Optional: the PerformanceHintManager session is created, reports 1 µs of "actual" work, then immediately closed.

reportActualWorkDuration(0x3e8) is 1000 ns ≈ 1 µs and is followed by close() on both paths, so the hint never represents real frame work. If the goal is just to verify the API is reachable, the report call can be dropped; if the goal is to drive thermal/perf hinting for the game thread, the session needs to be held and updated with real per-frame durations. Either way, this block currently does very little besides validating that the binder calls don't throw.

Not blocking — safe to defer or land as-is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch` around
lines 299 - 311, The code creates an android.os.PerformanceHintManager$Session
(v3), calls reportActualWorkDuration(0x3e8) (≈1µs) then immediately close()s the
session, so the hint never represents real work; either remove the artificial
reportActualWorkDuration call if you only need to validate the API, or keep the
session open and call reportActualWorkDuration(J) with real per-frame durations
on each frame and only call close() when the game thread is finished; locate the
session usage around PerformanceHintManager$Session (v3), the
reportActualWorkDuration invocation and the paired close() calls and update
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch`:
- Around line 299-311: The code creates an
android.os.PerformanceHintManager$Session (v3), calls
reportActualWorkDuration(0x3e8) (≈1µs) then immediately close()s the session, so
the hint never represents real work; either remove the artificial
reportActualWorkDuration call if you only need to validate the API, or keep the
session open and call reportActualWorkDuration(J) with real per-frame durations
on each frame and only call close() when the game thread is finished; locate the
session usage around PerformanceHintManager$Session (v3), the
reportActualWorkDuration invocation and the paired close() calls and update
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24865053-5d9e-43da-bfa9-cbd7950e021e

📥 Commits

Reviewing files that changed from the base of the PR and between 83b4a9a and fa0e29a.

📒 Files selected for processing (9)
  • .github/workflows/build-debug.yml
  • extension/BhFrameGenDialog.java
  • extension/BhFrameGenSettings.java
  • extension/BhFrameGenWiring.java
  • extension/BhFrameGenWriter.java
  • extension/BhVulkanIcdWriter.java
  • patch.sh
  • patches/diffs/res/values/strings.xml.patch
  • patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch
💤 Files with no reviewable changes (1)
  • extension/BhFrameGenSettings.java

CodeRabbit (BhFrameGenDialog.java:274-276): already fixed in fa0e29a
(Locale.ROOT in formatFloat) — no further change.

CodeRabbit (WineActivity.smali.patch:261-279): thermal-headroom block
already removed in 470b163 — no further change.

CodeRabbit (WineActivity.smali.patch:299-311): the createHintSession
+ reportActualWorkDuration(1µs) + close block was a smoke test of
the binder API, not a real per-frame perf hint. Holding the session
for live frame durations would require hooking the frame loop and is
out of scope here. Removed the entire block. Side effect: the patch
loses the upstream :cond_0../:cond_1f rename cascade through onCreate
that the block forced, dropping it from 39 hunks to 7 (-674 lines).

Producdevity (BhVulkanIcdWriter.java:52, older imagefs): frame
interpolation in libGameScopeVK.so was introduced in firmware 1.3.6.
On older firmware where the .so is absent, the code already degraded
safely (ICD JSON write short-circuits, control file is harmless,
smali hooks swallow). Added BhVulkanIcdWriter.isVkAvailable(Context)
(mirrors existing isV2Available) and gated BhFrameGenWiring.bind()
on it so the entire @id/frame_gen_container row is hidden on legacy
firmware instead of showing a non-functional switch.

Producdevity (BhFrameGenWiring.java:28): removed the
"Idempotent — onResume can call repeatedly." comment.

Producdevity (BhFrameGenWiring.java:60-61): reworded the comment on
invokeSetSwitch to a single line that just states the why (no compile
classpath access to the patched app's SidebarSwitchItemView). Dropped
the "not a CompoundButton" framing.

Producdevity (BhFrameGenSettings.java:12, resource strings): moved
preset labels and descriptions out of code. Preset now stores
labelResName/descResName (bh_framegen_preset_<preset>_label/_desc)
and exposes getLabel(Context)/getDescription(Context) resolvers that
use the same getIdentifier + English-fallback pattern already in
BhFrameGenDialog.getStringByName. Added 12 entries to
res/values/strings.xml.
@The412Banner
Copy link
Copy Markdown
Contributor Author

Pushed 75e984b1 — addresses all comments since fa0e29ad. Walk-through:

BhFrameGenDialog.java:274-276 (CodeRabbit, "Use explicit Locale in formatFloat") — already fixed in fa0e29ad (pushed at 12:48 UTC, ~2 min before the inline ping). String.format(Locale.ROOT, "%.2f", v) is on the PR head.

WineActivity.smali.patch:261-279 (CodeRabbit nitpick repeat, "Thermal headroom result is fetched but never used") — already removed in 470b1630 (the sweep). getThermalHeadroom is gone from the patch.

WineActivity.smali.patch:299-311 (CodeRabbit, "PerformanceHintManager session reports 1µs then closes") — agreed, that block was a smoke test of the binder API, not a real perf hint. Holding the session for per-frame reportActualWorkDuration calls would require hooking into the frame loop, which is out of scope here. Removed the entire createHintSession/reportActualWorkDuration/close block. Side benefit: WineActivity.smali.patch is now 7 hunks instead of 39 (−674 lines), because my added block was the source of the upstream :cond_0..:cond_1f rename cascade through onCreate. With the block gone, all upstream labels stay at their original numbering.

BhVulkanIcdWriter.java:52 ("what happens if a user is on an older firmware") — frame interpolation in libGameScopeVK.so was introduced in imagefs/firmware 1.3.6, so that's the floor for this feature. On older firmware where the .so is absent the existing code already degrades safely:

  • BhVulkanIcdWriter.ensureIcdJsonForPath() short-circuits at the if (!new File(libPath).exists()) return; guard, so no ICD JSON gets written and the Vulkan loader keeps using imagefs's stock ICD.
  • BhFrameGenWriter writes its 10-byte control file to the app's own filesDir, independent of imagefs — if no lib reads it, the file just sits there.
  • The two smali hooks (WineActivity.onCreateapplyFromPrefs, SidebarPerformanceFragment.onResumebindFromFragment) both wrap in try/catch-swallow, so worst case is a silent no-op, no crash.

To go one better than "silent no-op," I added BhVulkanIcdWriter.isVkAvailable(Context) (mirrors existing isV2Available) and gated BhFrameGenWiring.bind() on it — the entire @id/frame_gen_container row is now hidden on legacy firmware so users don't see a non-functional switch.

BhFrameGenWiring.java:28 ("What is the point of this comment?") — removed.

BhFrameGenWiring.java:60-61 ("why do we need to know what it is not") — reworded to a single line that just states the why: SidebarSwitchItemView is part of the patched app, not on this extension's classpath. (i.e. why reflection is needed at all). Dropped the "not a CompoundButton" framing.

BhFrameGenSettings.java:12 ("can we use resource strings please?") — done. Preset now stores labelResName/descResName strings (bh_framegen_preset_<preset>_label / _desc) and exposes getLabel(Context) / getDescription(Context) resolvers using the same getIdentifier(...) + English-fallback pattern that's already in BhFrameGenDialog.getStringByName. 12 new entries added to res/values/strings.xml.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch (1)

67-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the pref write consistent with bridge availability.

Lines 67-90 write InputControlsManager.h(false, profileId) even when WineActivity.h is null, but the equivalent startup path at Lines 208-232 skips that write unless k0(false) can actually run. That can leave the saved trackpad state out of sync with the real bridge state and break the later restore path.

Also applies to: 208-232

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch` around
lines 67 - 90, The code writes InputControlsManager.h(false, profileId) even
when the WinUIBridge isn't available, causing pref state to diverge; change the
flow so the InputControlsManager.h(false, profileId) call only happens when the
WinUIBridge instance exists and you've invoked WinUIBridge.k0(false) (i.e.,
guard the pref write behind the same bridge-availability check), and apply the
same guard/fix to the other startup path (the block around the earlier 208-232
area); reference WinUIBridge.k0(Z)V, WineActivity.r / WineActivityData.e(), and
InputControlsManager.h(ZLjava/lang/String;)V to locate the affected calls.
🧹 Nitpick comments (1)
extension/BhFrameGenSettings.java (1)

56-65: ⚡ Quick win

Consider clamping flowScale/model on load.

If SharedPreferences ever contains an out-of-range or NaN value (manual edits, downgrade/upgrade, parallel writer bug), it will be passed straight through to the writer and UI (where flowScaleToProgress would clamp the slider but the persisted settings.flowScale would still be bad). A defensive clamp on load (e.g., 0.2f..1.0f for flowScale, 0..1 for model) keeps the in-memory model trustworthy regardless of source.

♻️ Suggested clamp
-        s.flowScale = sp.getFloat("flowScale", s.preset.flowScale);
-        s.model = sp.getInt("model", s.preset.model);
+        float fs = sp.getFloat("flowScale", s.preset.flowScale);
+        if (Float.isNaN(fs) || fs < 0.2f) fs = 0.2f;
+        else if (fs > 1.0f) fs = 1.0f;
+        s.flowScale = fs;
+        int m = sp.getInt("model", s.preset.model);
+        s.model = (m < 0) ? 0 : (m > 1 ? 1 : m);
         return s;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extension/BhFrameGenSettings.java` around lines 56 - 65,
BhFrameGenSettings.load currently trusts stored flowScale and model values;
after reading s.flowScale and s.model (in BhFrameGenSettings.load), defensively
sanitize them: if Float.isNaN(s.flowScale) set to s.preset.flowScale, then clamp
s.flowScale to the allowed range (e.g., Math.max(0.2f, Math.min(1.0f,
s.flowScale))); for s.model validate it’s within allowed integer bounds (e.g.,
0..1) and if out of range reset to s.preset.model or clamp to 0/1; ensure you
reference the fields s.flowScale and s.model and perform these checks
immediately after they are loaded so the in-memory BhFrameGenSettings is always
valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch`:
- Around line 67-90: The code writes InputControlsManager.h(false, profileId)
even when the WinUIBridge isn't available, causing pref state to diverge; change
the flow so the InputControlsManager.h(false, profileId) call only happens when
the WinUIBridge instance exists and you've invoked WinUIBridge.k0(false) (i.e.,
guard the pref write behind the same bridge-availability check), and apply the
same guard/fix to the other startup path (the block around the earlier 208-232
area); reference WinUIBridge.k0(Z)V, WineActivity.r / WineActivityData.e(), and
InputControlsManager.h(ZLjava/lang/String;)V to locate the affected calls.

---

Nitpick comments:
In `@extension/BhFrameGenSettings.java`:
- Around line 56-65: BhFrameGenSettings.load currently trusts stored flowScale
and model values; after reading s.flowScale and s.model (in
BhFrameGenSettings.load), defensively sanitize them: if Float.isNaN(s.flowScale)
set to s.preset.flowScale, then clamp s.flowScale to the allowed range (e.g.,
Math.max(0.2f, Math.min(1.0f, s.flowScale))); for s.model validate it’s within
allowed integer bounds (e.g., 0..1) and if out of range reset to s.preset.model
or clamp to 0/1; ensure you reference the fields s.flowScale and s.model and
perform these checks immediately after they are loaded so the in-memory
BhFrameGenSettings is always valid.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6bcd7ea7-da19-402a-abc9-f9f3951e2254

📥 Commits

Reviewing files that changed from the base of the PR and between fa0e29a and 75e984b.

📒 Files selected for processing (6)
  • extension/BhFrameGenDialog.java
  • extension/BhFrameGenSettings.java
  • extension/BhFrameGenWiring.java
  • extension/BhVulkanIcdWriter.java
  • patches/diffs/res/values/strings.xml.patch
  • patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch

@Producdevity
Copy link
Copy Markdown
Owner

Close PerformanceHintManager$Session after reporting work duration.

The created hint session (v3) is never closed. PerformanceHintManager.Session implements AutoCloseable and holds native system resources that must be explicitly released via close(). Failing to close leaks binder objects and system memory, particularly problematic across activity recreations. Add proper cleanup in a try-finally or exception handler after reportActualWorkDuration.

is this still an issue?

@Producdevity
Copy link
Copy Markdown
Owner

patches/diffs/smali_classes4/com/xj/winemu/WineActivity.smali.patch (1)
67-90:

Keep the pref write consistent with bridge availability.

Lines 67-90 write InputControlsManager.h(false, profileId) even when WineActivity.h is null, but the equivalent startup path at Lines 208-232 skips that write unless k0(false) can actually run. That can leave the saved trackpad state out of sync with the real bridge state and break the later restore path.

Also applies to: 208-232

@Producdevity Producdevity merged commit 0e1ed85 into Producdevity:master May 22, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants