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 sound effects, and cries are synthesized while the game runs from compact
audio channel programs copied out of the verified ROM. 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 ## Controls
+29 -16
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build a PortMaster-style aarch64 port of gen1recomp for Anbernic RG34XXSP # 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 # The stock Anbernic firmware resolves ports relative to the launch script
# (roms/PORTS/...), not PortMaster's /$directory/ports/... path. This pack # (roms/PORTS/...), not PortMaster's /$directory/ports/... path. This pack
@@ -11,10 +12,10 @@
# ./build-rg34xxsp.sh [--version X.Y.Z] # ./build-rg34xxsp.sh [--version X.Y.Z]
# #
# Output: # Output:
# dist/rg34xxsp/gen1recomp-rg34xxsp.zip # dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip
# #
# Install on device: # 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: # 2. Unzip into the SD card's roms/PORTS/ folder so you have:
# roms/PORTS/Gen1recomp.sh # roms/PORTS/Gen1recomp.sh
# roms/PORTS/gen1recomp/... # roms/PORTS/gen1recomp/...
@@ -31,6 +32,10 @@ WORK="$HERE/work/rg34xxsp"
DIST="$ROOT/dist/rg34xxsp" DIST="$ROOT/dist/rg34xxsp"
APP_NAME="gen1recomp" 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" PORT_DIR_NAME="gen1recomp"
LAUNCHER_NAME="Gen1recomp.sh" LAUNCHER_NAME="Gen1recomp.sh"
LOVE_VERSION="11.5" LOVE_VERSION="11.5"
@@ -235,7 +240,7 @@ cat > "$PORT_ROOT/port.json" <<EOF
"attr": { "attr": {
"title": "gen1recomp", "title": "gen1recomp",
"desc": "Native LÖVE2D recreation of Pokemon Red and Blue. Supply your own legal US Red or Blue ROM.", "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"], "genres": ["adventure", "rpg"],
"porter": ["gen1recomp"], "porter": ["gen1recomp"],
"image": {}, "image": {},
@@ -263,18 +268,30 @@ cat > "$PORT_ROOT/gameinfo.xml" <<EOF
EOF EOF
cat > "$PORT_ROOT/README.md" <<'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 Native LÖVE 11.5 port of gen1recomp for Anbernic RG34XXSP on
with PortMaster. **Stock OS 64-bit MOD** (H700, PortMaster).
### Install ### 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/`. 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/`. 3. Copy a legal US Pokemon Red or Blue `.gb` into `gen1recomp/lovegame/`.
4. Refresh Ports / restart EmulationStation and launch **Gen1recomp**. 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 ### First run
Stock OS has no zenity file picker. Put the `.gb` in `lovegame/`, then press 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` - Red: `ea9bcae617fdf159b045185467ae58b2e4a48b9a`
- Blue: `d7037c83e1ae5b39bde3c30787637ba1d4c48ce2` - Blue: `d7037c83e1ae5b39bde3c30787637ba1d4c48ce2`
### Controls
Physical controls map through PortMaster / SDL. Rebind in-game under
OPTIONS → CONTROLS.
### Thanks ### Thanks
LÖVE runtime binaries from [PortMaster](https://portmaster.games/). 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 EOF
# --------------------------------------------------------------- zip # --------------------------------------------------------------- zip
ZIP_OUT="$DIST/$APP_NAME-rg34xxsp.zip" ZIP_OUT="$DIST/$APP_NAME-$ARTIFACT_SUFFIX.zip"
rm -f "$ZIP_OUT" rm -f "$ZIP_OUT" "$DIST/$APP_NAME-rg34xxsp.zip"
say "packing $ZIP_OUT" say "packing $ZIP_OUT"
(cd "$PORT_ROOT" && zip -q -9 -r "$ZIP_OUT" \ (cd "$PORT_ROOT" && zip -q -9 -r "$ZIP_OUT" \
"$LAUNCHER_NAME" "$PORT_DIR_NAME" port.json gameinfo.xml README.md) "$LAUNCHER_NAME" "$PORT_DIR_NAME" port.json gameinfo.xml README.md)
say "done." say "done."
say "artifact: $ZIP_OUT ($(du -h "$ZIP_OUT" | cut -f1))" 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) function love.gamepadpressed(joystick, button)
if editorMode then return end if editorMode then return end
if Importer then return end if Importer then return Importer:gamepadpressed(joystick, button) end
Game:gamepadpressed(joystick, button) Game:gamepadpressed(joystick, button)
end end
function love.gamepadreleased(joystick, button) function love.gamepadreleased(joystick, button)
if editorMode then return end if editorMode then return end
if Importer then return end if Importer then return Importer:gamepadreleased(joystick, button) end
Game:gamepadreleased(joystick, button) Game:gamepadreleased(joystick, button)
end end
function love.gamepadaxis(joystick, axis, value) function love.gamepadaxis(joystick, axis, value)
if editorMode then return end 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) Game:gamepadaxis(joystick, axis, value)
end 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 -- Android SAF create-document: which game's SAVE FILES card should show
-- "Save exported." when export_done.flag appears on focus. -- "Save exported." when export_done.flag appears on focus.
androidPendingExportVersion = nil, 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) }, RomImporter)
for _, version in ipairs(GameVersion.ORDER) do for _, version in ipairs(GameVersion.ORDER) do
@@ -571,6 +579,15 @@ function RomImporter.new(onComplete, opts)
end end
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 return self
end end
@@ -1004,6 +1021,7 @@ end
function RomImporter:update(dt) function RomImporter:update(dt)
self.pulse = self.pulse + dt self.pulse = self.pulse + dt
self:_updatePadCursor(dt)
if self.workState ~= "working" or not self.worker then return end if self.workState ~= "working" or not self.worker then return end
local started = love.timer.getTime() local started = love.timer.getTime()
repeat repeat
@@ -1020,6 +1038,125 @@ function RomImporter:update(dt)
until love.timer.getTime() - started >= 0.008 until love.timer.getTime() - started >= 0.008
end 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. -- Player pressed Play on a game whose ROM is imported: hand off to boot.
function RomImporter:play(version) function RomImporter:play(version)
if self.workState == "working" then return end if self.workState == "working" then return end
@@ -1214,12 +1351,17 @@ function RomImporter:draw()
local pulse = self.pulse local pulse = self.pulse
self._s = s self._s = s
-- Hover state (desktop only -- touch has no cursor). Panel methods read the -- Hover state. Desktop mouse, or the gamepad virtual cursor on handhelds
-- pointer + set self._anyHover through self:_hover; the cursor is set at the -- (Android stays touch-only -- no hover). Panel methods read the pointer +
-- end. Reset the per-frame hit rects so a tab with no controls (mods) cannot -- 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. -- inherit last frame's game-panel buttons.
self._mx, self._my = love.mouse.getPosition() if self._padCursorActive then
self._hoverEnabled = not self.android 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._anyHover = false
self.romButtonRect = nil self.romButtonRect = nil
self.playButtonRect = nil self.playButtonRect = nil
@@ -1578,7 +1720,8 @@ function RomImporter:draw()
self:_updateSlotDrag() self:_updateSlotDrag()
-- pointer cursor over any interactive element (desktop only) -- 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 if self._anyHover then
self.handCursor = self.handCursor or love.mouse.getSystemCursor("hand") self.handCursor = self.handCursor or love.mouse.getSystemCursor("hand")
love.mouse.setCursor(self.handCursor) love.mouse.setCursor(self.handCursor)
@@ -1586,6 +1729,34 @@ function RomImporter:draw()
resetPointerCursor(self) resetPointerCursor(self)
end end
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 end
local function inside(r, x, y) local function inside(r, x, y)