From 617a87a47b284e42459ee68f335eb2af5ed92114 Mon Sep 17 00:00:00 2001 From: bryanthaboi Date: Tue, 28 Jul 2026 12:14:58 -0400 Subject: [PATCH] anbernic --- README.md | 34 +++++++ build-rg34xxsp.sh | 45 +++++---- main.lua | 6 +- src/import/RomImporter.lua | 183 +++++++++++++++++++++++++++++++++++-- 4 files changed, 243 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index fd3a7ac3..a88c01f4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,40 @@ The packaged app contains neither a ROM nor pre-extracted game data. Music, sound effects, and cries are synthesized while the game runs from compact audio channel programs copied out of the verified ROM. +## Anbernic RG34XXSP (Stock OS 64-bit MOD) + +Download `gen1recomp-*-rg34xxsp-stockos64-mod.zip` from +[Releases](https://github.com/bryanthaboi/gen1recomp/releases). This build +targets **Stock OS 64-bit MOD** on the RG34XXSP with PortMaster installed +(TF1). + +1. Unzip the release on your computer. You get `Gen1recomp.sh` and a + `gen1recomp/` folder. +2. Copy **both** onto the SD card under **`Roms/PORTS/`** so the layout is: + + ``` + Roms/PORTS/Gen1recomp.sh + Roms/PORTS/gen1recomp/ + ``` + + On the device that path is `/mnt/mmc/Roms/PORTS/`. Keep the launcher and + the `gen1recomp/` folder as siblings — do not nest the `.sh` inside the + folder. +3. Put your legal US Red and/or Blue `.gb` files inside the game folder: + + ``` + Roms/PORTS/gen1recomp/lovegame/ + ``` + + Example: `Roms/PORTS/gen1recomp/lovegame/Pokemon - Red Version.gb` +4. Eject the card, boot the handheld, open **Ports → Gen1recomp**. +5. On the launcher, move the cursor with the D-pad or left stick, press **A** + to click. Choose the Red or Blue tab, then **Choose ROM** — with no file + picker on stock OS, that scans `lovegame/` for the `.gb` you dropped in. + +After import, saves and the ROM-derived cache stay next to the game on the +SD card (`portable.txt`). + ## Controls diff --git a/build-rg34xxsp.sh b/build-rg34xxsp.sh index 80d2efac..efa9db73 100755 --- a/build-rg34xxsp.sh +++ b/build-rg34xxsp.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Build a PortMaster-style aarch64 port of gen1recomp for Anbernic RG34XXSP -# stock OS (Allwinner H700, 64-bit Stock OS / Stock OS Mod + PortMaster). +# on Stock OS 64-bit MOD (H700; cbepx-me StockOS MOD / official V1.0.6 base +# + PortMaster). # # The stock Anbernic firmware resolves ports relative to the launch script # (roms/PORTS/...), not PortMaster's /$directory/ports/... path. This pack @@ -11,10 +12,10 @@ # ./build-rg34xxsp.sh [--version X.Y.Z] # # Output: -# dist/rg34xxsp/gen1recomp-rg34xxsp.zip +# dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip # # Install on device: -# 1. Flash / run 64-bit Stock OS (or Stock OS Mod) with PortMaster installed. +# 1. Flash / run Stock OS 64-bit MOD with PortMaster installed. # 2. Unzip into the SD card's roms/PORTS/ folder so you have: # roms/PORTS/Gen1recomp.sh # roms/PORTS/gen1recomp/... @@ -31,6 +32,10 @@ WORK="$HERE/work/rg34xxsp" DIST="$ROOT/dist/rg34xxsp" APP_NAME="gen1recomp" +# Artifact suffix matches the CFW this port targets (Anbernic H700 Stock OS +# 64-bit MOD for RG34XXSP). Release uploads stage it as +# gen1recomp--rg34xxsp-stockos64-mod.zip. +ARTIFACT_SUFFIX="rg34xxsp-stockos64-mod" PORT_DIR_NAME="gen1recomp" LAUNCHER_NAME="Gen1recomp.sh" LOVE_VERSION="11.5" @@ -235,7 +240,7 @@ cat > "$PORT_ROOT/port.json" < "$PORT_ROOT/gameinfo.xml" < "$PORT_ROOT/README.md" <<'EOF' -## gen1recomp (RG34XXSP / Anbernic stock OS) +## gen1recomp (RG34XXSP / Stock OS 64-bit MOD) -Native LÖVE 11.5 port of gen1recomp for Anbernic RG34XXSP (H700) 64-bit stock OS -with PortMaster. +Native LÖVE 11.5 port of gen1recomp for Anbernic RG34XXSP on +**Stock OS 64-bit MOD** (H700, PortMaster). ### Install -1. Use **64-bit Stock OS** (or Stock OS Mod) with PortMaster installed. +1. Use **Stock OS 64-bit MOD** with PortMaster installed (TF1). 2. Unzip so `Gen1recomp.sh` and the `gen1recomp/` folder sit in `roms/PORTS/`. 3. Copy a legal US Pokemon Red or Blue `.gb` into `gen1recomp/lovegame/`. 4. Refresh Ports / restart EmulationStation and launch **Gen1recomp**. +### Controls (launcher) + +| Input | Action | +|--|--| +| D-pad / left stick | Move cursor | +| A | Click | +| L1 / R1 | Switch tabs | +| Right stick | Scroll lists | +| Start / Select | Play or Choose ROM | + +In-game controls use the normal PortMaster / SDL pad map (rebind under OPTIONS → CONTROLS). + ### First run Stock OS has no zenity file picker. Put the `.gb` in `lovegame/`, then press @@ -286,23 +303,19 @@ Canonical SHA-1 (1 MiB US carts only): - Red: `ea9bcae617fdf159b045185467ae58b2e4a48b9a` - Blue: `d7037c83e1ae5b39bde3c30787637ba1d4c48ce2` -### Controls - -Physical controls map through PortMaster / SDL. Rebind in-game under -OPTIONS → CONTROLS. - ### Thanks LÖVE runtime binaries from [PortMaster](https://portmaster.games/). +Stock OS 64-bit MOD: [cbepx-me](https://github.com/cbepx-me/Anbernic-H700-RG-xx-StockOS-Modification). EOF # --------------------------------------------------------------- zip -ZIP_OUT="$DIST/$APP_NAME-rg34xxsp.zip" -rm -f "$ZIP_OUT" +ZIP_OUT="$DIST/$APP_NAME-$ARTIFACT_SUFFIX.zip" +rm -f "$ZIP_OUT" "$DIST/$APP_NAME-rg34xxsp.zip" say "packing $ZIP_OUT" (cd "$PORT_ROOT" && zip -q -9 -r "$ZIP_OUT" \ "$LAUNCHER_NAME" "$PORT_DIR_NAME" port.json gameinfo.xml README.md) say "done." say "artifact: $ZIP_OUT ($(du -h "$ZIP_OUT" | cut -f1))" -say "copy onto the RG34XXSP SD card under roms/PORTS/, then drop your .gb into gen1recomp/lovegame/" +say "copy onto the RG34XXSP SD card under roms/PORTS/ (Stock OS 64-bit MOD), then drop your .gb into gen1recomp/lovegame/" diff --git a/main.lua b/main.lua index 897ade18..a2437c75 100644 --- a/main.lua +++ b/main.lua @@ -315,19 +315,19 @@ end function love.gamepadpressed(joystick, button) if editorMode then return end - if Importer then return end + if Importer then return Importer:gamepadpressed(joystick, button) end Game:gamepadpressed(joystick, button) end function love.gamepadreleased(joystick, button) if editorMode then return end - if Importer then return end + if Importer then return Importer:gamepadreleased(joystick, button) end Game:gamepadreleased(joystick, button) end function love.gamepadaxis(joystick, axis, value) if editorMode then return end - if Importer then return end + if Importer then return Importer:gamepadaxis(joystick, axis, value) end Game:gamepadaxis(joystick, axis, value) end diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index c6166796..4d7d6979 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -519,6 +519,14 @@ function RomImporter.new(onComplete, opts) -- Android SAF create-document: which game's SAVE FILES card should show -- "Save exported." when export_done.flag appears on focus. androidPendingExportVersion = nil, + -- Virtual pointer for handhelds / gamepads (Anbernic stock OS has no + -- mouse). D-pad + left stick move it; A clicks; shoulders cycle tabs; + -- right stick scrolls the save-slot / mods lists. + _padCursor = { x = 0, y = 0 }, + _padCursorActive = false, + _padAxis = { leftx = 0, lefty = 0, righty = 0 }, + _padDir = {}, + _padInited = false, }, RomImporter) for _, version in ipairs(GameVersion.ORDER) do @@ -571,6 +579,15 @@ function RomImporter.new(onComplete, opts) end end + -- On Linux handhelds a gamepad is usually already connected at boot; arm + -- the virtual cursor immediately so the player does not have to press a + -- button before seeing something move. + if self.launcher and love.system.getOS() == "Linux" + and love.joystick and love.joystick.getJoystickCount + and love.joystick.getJoystickCount() > 0 then + self:_activatePadCursor() + end + return self end @@ -1004,6 +1021,7 @@ end function RomImporter:update(dt) self.pulse = self.pulse + dt + self:_updatePadCursor(dt) if self.workState ~= "working" or not self.worker then return end local started = love.timer.getTime() repeat @@ -1020,6 +1038,125 @@ function RomImporter:update(dt) until love.timer.getTime() - started >= 0.008 end +-- ------- gamepad virtual cursor (handheld / PortMaster) -------------------- +local PAD_DEAD = 0.28 +local PAD_SPEED = 560 -- px/s at full stick deflection +local PAD_DPAD_SPEED = 420 + +function RomImporter:_activatePadCursor() + if self._padCursorActive then return end + local w, h = love.graphics.getDimensions() + if not self._padInited then + self._padCursor.x = w * 0.5 + self._padCursor.y = h * 0.45 + self._padInited = true + end + self._padCursorActive = true +end + +function RomImporter:_cycleTab(delta) + local order = { "red", "blue", "yellow", "mods" } + local idx = 1 + for i, id in ipairs(order) do + if id == self.tab then idx = i; break end + end + idx = ((idx - 1 + delta) % #order) + 1 + self.tab = order[idx] + self._slotPress = nil + self._modPress = nil +end + +function RomImporter:_updatePadCursor(dt) + -- Real mouse motion yields the pad cursor so desktop users keep a normal + -- pointer after bumping a stick once. + local mx, my = love.mouse.getPosition() + if self._lastMouseX and self._padCursorActive then + if math.abs(mx - self._lastMouseX) > 3 or math.abs(my - self._lastMouseY) > 3 then + self._padCursorActive = false + end + end + self._lastMouseX, self._lastMouseY = mx, my + + local ax = self._padAxis.leftx or 0 + local ay = self._padAxis.lefty or 0 + local dx, dy = 0, 0 + if math.abs(ax) > PAD_DEAD then dx = dx + ax end + if math.abs(ay) > PAD_DEAD then dy = dy + ay end + if self._padDir.dpleft then dx = dx - 1 end + if self._padDir.dpright then dx = dx + 1 end + if self._padDir.dpup then dy = dy - 1 end + if self._padDir.dpdown then dy = dy + 1 end + + if dx ~= 0 or dy ~= 0 then + self:_activatePadCursor() + local mag = math.sqrt(dx * dx + dy * dy) + if mag > 1 then dx, dy = dx / mag, dy / mag end + local speed = (math.abs(ax) > PAD_DEAD or math.abs(ay) > PAD_DEAD) + and PAD_SPEED or PAD_DPAD_SPEED + local w, h = love.graphics.getDimensions() + local nx = self._padCursor.x + dx * speed * dt + local ny = self._padCursor.y + dy * speed * dt + self._padCursor.x = math.max(0, math.min(w, nx)) + self._padCursor.y = math.max(0, math.min(h, ny)) + end + + -- Right stick scrolls the active list (save slots or mods). + local ry = self._padAxis.righty or 0 + if math.abs(ry) > PAD_DEAD then + self:_activatePadCursor() + local step = -ry * 480 * dt + if self.tab == "mods" then + local maxS = self._modMax or 0 + if maxS > 0 then + local next = (self.modScroll or 0) + step + self.modScroll = math.max(0, math.min(maxS, next)) + end + elseif self.tab == "red" or self.tab == "blue" then + local maxS = (self._slotMax and self._slotMax[self.tab]) or 0 + if maxS > 0 then + local next = (self.slotScroll[self.tab] or 0) + step + self.slotScroll[self.tab] = math.max(0, math.min(maxS, next)) + end + end + end +end + +function RomImporter:gamepadpressed(_, button) + self:_activatePadCursor() + if button == "a" then + -- Instant click at the virtual pointer (same path as a mouse/touch tap). + self:mousepressed(self._padCursor.x, self._padCursor.y, 1) + elseif button == "leftshoulder" then + self:_cycleTab(-1) + elseif button == "rightshoulder" then + self:_cycleTab(1) + elseif button == "dpup" or button == "dpdown" + or button == "dpleft" or button == "dpright" then + self._padDir[button] = true + elseif button == "start" or button == "back" then + -- Start / Select: Play if ready, else Choose ROM on the active game tab. + if self.workState == "working" then return end + local version = self.tab + if version == "red" or version == "blue" then + if self.ready[version] then self:play(version) else self:choose(version) end + end + end +end + +function RomImporter:gamepadreleased(_, button) + if button == "dpup" or button == "dpdown" + or button == "dpleft" or button == "dpright" then + self._padDir[button] = nil + end +end + +function RomImporter:gamepadaxis(_, axis, value) + if axis == "leftx" or axis == "lefty" or axis == "righty" then + self._padAxis[axis] = value + if math.abs(value) > PAD_DEAD then self:_activatePadCursor() end + end +end + -- Player pressed Play on a game whose ROM is imported: hand off to boot. function RomImporter:play(version) if self.workState == "working" then return end @@ -1214,12 +1351,17 @@ function RomImporter:draw() local pulse = self.pulse self._s = s - -- Hover state (desktop only -- touch has no cursor). Panel methods read the - -- pointer + set self._anyHover through self:_hover; the cursor is set at the - -- end. Reset the per-frame hit rects so a tab with no controls (mods) cannot + -- Hover state. Desktop mouse, or the gamepad virtual cursor on handhelds + -- (Android stays touch-only -- no hover). Panel methods read the pointer + + -- set self._anyHover through self:_hover; the cursor is set at the end. + -- Reset the per-frame hit rects so a tab with no controls (mods) cannot -- inherit last frame's game-panel buttons. - self._mx, self._my = love.mouse.getPosition() - self._hoverEnabled = not self.android + if self._padCursorActive then + self._mx, self._my = self._padCursor.x, self._padCursor.y + else + self._mx, self._my = love.mouse.getPosition() + end + self._hoverEnabled = self._padCursorActive or not self.android self._anyHover = false self.romButtonRect = nil self.playButtonRect = nil @@ -1578,7 +1720,8 @@ function RomImporter:draw() self:_updateSlotDrag() -- pointer cursor over any interactive element (desktop only) - if self._hoverEnabled and love.mouse.isCursorSupported and love.mouse.isCursorSupported() then + if self._hoverEnabled and not self._padCursorActive + and love.mouse.isCursorSupported and love.mouse.isCursorSupported() then if self._anyHover then self.handCursor = self.handCursor or love.mouse.getSystemCursor("hand") love.mouse.setCursor(self.handCursor) @@ -1586,6 +1729,34 @@ function RomImporter:draw() resetPointerCursor(self) end end + + -- Gamepad virtual cursor (drawn last so it sits above the CRT overlay). + if self._padCursorActive then + local x, y = self._padCursor.x, self._padCursor.y + local hot = self._anyHover + love.graphics.push("all") + love.graphics.origin() + love.graphics.setLineWidth(1) + -- Drop shadow + love.graphics.setColor(0, 0, 0, 0.45) + love.graphics.polygon("fill", + x + 2, y + 2, x + 2, y + 22, x + 8, y + 16, x + 14, y + 26, + x + 18, y + 24, x + 11, y + 14, x + 20, y + 14) + -- Pointer body + if hot then + love.graphics.setColor(0.25, 0.95, 0.55, 1) + else + love.graphics.setColor(1, 1, 1, 1) + end + love.graphics.polygon("fill", + x, y, x, y + 20, x + 6, y + 14, x + 12, y + 24, + x + 16, y + 22, x + 9, y + 12, x + 18, y + 12) + love.graphics.setColor(0.05, 0.07, 0.12, 1) + love.graphics.polygon("line", + x, y, x, y + 20, x + 6, y + 14, x + 12, y + 24, + x + 16, y + 22, x + 9, y + 12, x + 18, y + 12) + love.graphics.pop() + end end local function inside(r, x, y)