This commit is contained in:
bryanthaboi
2026-07-28 12:14:58 -04:00
parent 8539a6b268
commit 617a87a47b
4 changed files with 243 additions and 25 deletions
+34
View File
@@ -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
+29 -16
View File
@@ -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-<ver>-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" <<EOF
"attr": {
"title": "gen1recomp",
"desc": "Native LÖVE2D recreation of Pokemon Red and Blue. Supply your own legal US Red or Blue ROM.",
"inst": "Copy a canonical US Red or Blue .gb into gen1recomp/lovegame/, then launch and press Choose ROM. Requires 64-bit Stock OS with PortMaster.",
"inst": "Requires Anbernic RG34XXSP Stock OS 64-bit MOD with PortMaster. Copy a canonical US Red or Blue .gb into gen1recomp/lovegame/, then launch and press Choose ROM.",
"genres": ["adventure", "rpg"],
"porter": ["gen1recomp"],
"image": {},
@@ -263,18 +268,30 @@ cat > "$PORT_ROOT/gameinfo.xml" <<EOF
EOF
cat > "$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/"
+3 -3
View File
@@ -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
+177 -6
View File
@@ -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)