Compare commits

..

1 Commits

Author SHA1 Message Date
bryanthaboi 0e40a7a1f4 Merge pull request #1408 from bryanthaboi/dev 2026-08-16 10:14:26 -04:00
1591 changed files with 149477 additions and 294846 deletions
-1
View File
@@ -1 +0,0 @@
* @bryanthaboi
+10 -16
View File
@@ -5,10 +5,6 @@ body:
- type: markdown
attributes:
value: |
**Turn off all mods before filing.** Disable everything in the launcher's MODS
tab, confirm the bug still happens, then open this. Bugs that only show up with
mods on belong with the mod author, not here.
A screenshot is worth more than any description. If you can grab one, grab one.
If you genuinely can't, that's fine, but then the details below need to be thorough
enough that someone can find the bug without ever seeing your screen.
@@ -55,24 +51,22 @@ body:
validations:
required: true
- type: checkboxes
id: mods_off
- type: dropdown
id: mods_enabled
attributes:
label: Mods off
description: >
Turn off every mod in the launcher's MODS tab and reproduce the bug
before submitting. Do not file vanilla bugs with mods still enabled.
label: Were any mods on
description: Check the MODS tab in the launcher if you're not sure.
options:
- label: I turned off all mods and can still reproduce this
required: true
- "No"
- "Yes"
validations:
required: true
- type: input
id: mods_which
attributes:
label: Which mods (if you first noticed this with any on)
description: >
Optional. If you originally hit this with mods enabled, list them —
but only after you've confirmed it still happens with all of them off.
label: Which mods (if any were on)
description: List the enabled mods. Leave blank if none were on.
placeholder: nuzlocke 1.0.0, running-shoes 0.3
validations:
required: false
+118
View File
@@ -0,0 +1,118 @@
name: Feature request
description: Ask for something new in the engine, launcher, or platform — not a content/gameplay mod.
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Use this for **engine / launcher / platform** work (ports, video options, save
tooling, networking, mod API seams, docs).
If what you want is a gameplay, cosmetic, audio, or QoL change that a Lua mod
could ship — running shoes, alternate sprites, day/night, shiny indicators,
Gen 2-like battle toggles, soundtrack packs — open a
**[Mod request](https://github.com/bryanthaboi/gen1recomp/issues/new?template=mod_request.yml)**
instead.
"Can we add X" on its own is hard to act on. Say what you want, why you want it,
and how you picture it working.
- type: input
id: summary
attributes:
label: One line summary
description: What you want, in a sentence.
placeholder: Add Linux AppImage releases next to the macOS and Windows builds
validations:
required: true
- type: dropdown
id: game
attributes:
label: Which game is this about
description: Pick every version it applies to. Use N/A if it isn't game-specific.
multiple: true
options:
- Red
- Blue
- Yellow
- Gold
- N/A
validations:
required: true
- type: input
id: discord
attributes:
label: Discord username (optional)
description: >
So maintainers can ping you on Discord if they need a quick follow-up.
Leave blank if you'd rather keep everything on GitHub.
placeholder: yourname
validations:
required: false
- type: textarea
id: what
attributes:
label: What do you want
description: >
Describe it properly. What is it, where does it live (launcher, options,
engine), what does the player see or do. If it changes something that already
exists, say what it does today and what it should do instead.
placeholder: |
Ship a Linux AppImage on each release, same version as the macOS/Windows builds,
with the same save folder layout and mod discovery path.
validations:
required: true
- type: textarea
id: why
attributes:
label: Why is this worth doing
description: >
What's annoying or missing right now. What does this fix. If it's just because you
think it would be fun, say that, it's a real answer.
placeholder: |
LÖVE already runs on Linux; without a packaged build, players have to assemble
it themselves and miss release notes / update checks.
validations:
required: true
- type: textarea
id: how
attributes:
label: How should it work
description: >
The specifics. Which menu, what happens in the edge cases. If you don't
know, say what you'd expect as a player and leave the rest open.
placeholder: |
- GitHub Releases asset next to the .dmg / .exe
- Same options.lua / mods/ layout as desktop
- Documented in the README install section
validations:
required: true
- type: dropdown
id: scope
attributes:
label: Does this change how the original game plays
description: >
Some requests are quality of life, some change the actual game. Both are fine,
it just helps to know which one you're asking for.
options:
- Quality of life, original game is untouched
- Changes how the game plays
- Not sure
validations:
required: true
- type: textarea
id: extra
attributes:
label: Anything else
description: >
Reference screenshots, how another game does it, related issues. Leave blank
if nothing comes to mind.
validations:
required: false
+130
View File
@@ -0,0 +1,130 @@
name: Mod request
description: Ask for a gameplay, cosmetic, audio, or QoL change that belongs as a Lua mod.
labels: ["mod request"]
body:
- type: markdown
attributes:
value: |
This tracker is for ideas that should ship as **mods**, not as core engine
features — alternate sprites, running shoes, day/night, shiny indicators,
soundtrack packs, Gen 2-like battle toggles, map cosmetics, bag QoL, etc.
The engine already exposes a lot of this through registries and hooks
([modding wiki](https://github.com/bryanthaboi/gen1recomp/wiki)).
Use a **Feature request** instead for launcher / ports / video options /
networking / save tooling / new API seams.
- type: input
id: summary
attributes:
label: One line summary
description: What the mod should do, in a sentence.
placeholder: Hold B to run at bike speed on the overworld
validations:
required: true
- type: dropdown
id: game
attributes:
label: Which game is this for
description: Pick every version the mod should cover. Use N/A if it isn't game-specific.
multiple: true
options:
- Red
- Blue
- Yellow
- Gold
- N/A
validations:
required: true
- type: input
id: discord
attributes:
label: Discord username (optional)
description: >
So maintainers or mod authors can ping you on Discord if they pick this up.
Leave blank if you'd rather keep everything on GitHub.
placeholder: yourname
validations:
required: false
- type: textarea
id: what
attributes:
label: What should the mod do
description: >
Describe the player-facing behavior. What changes, where, what does the
player see or press. If it toggles from Options or a START-menu entry, say so.
placeholder: |
Hold B while walking outdoors to move at bike speed. Release to walk again.
Same places the bike is allowed; no effect in battles or menus.
validations:
required: true
- type: textarea
id: why
attributes:
label: Why is this worth doing as a mod
description: >
Why optional/modded rather than a core option. Who wants it on, who wants
vanilla left alone.
placeholder: |
Great for replaying and backtracking, but some people want a strict Gen 1
pace. A mod (or an opt-in mod option) keeps both camps happy.
validations:
required: true
- type: textarea
id: how
attributes:
label: How should it work
description: >
Buttons, menus, edge cases, whether it needs new art/audio. If you know a
hook or registry that fits (movement.speed, pokemon.sprite, rulesets, …),
mention it — otherwise leave it open.
placeholder: |
- Hold B on the overworld
- Same step timing as the bike
- Disabled where the bike is disabled
- Prefer hooks:wrap("movement.speed") if that still fits
validations:
required: true
- type: dropdown
id: vanilla
attributes:
label: With the mod off, is vanilla unchanged
options:
- Yes — parity when disabled
- No — it would replace something always-on
- Not sure
validations:
required: true
- type: dropdown
id: category
attributes:
label: Best-fit mod category
description: Same taxonomy as example mods (BALANCE, GRAPHICS, AUDIO, …).
options:
- GAMEPLAY / QoL
- GRAPHICS
- AUDIO
- BALANCE / ruleset
- CONTENT (maps, encounters, trainers)
- UI / TOOL
- TOTAL_CONVERSION-ish
- Not sure
validations:
required: true
- type: textarea
id: extra
attributes:
label: Anything else
description: >
Reference screenshots, other games/hacks that do it, related issues, or
"I'd like to try writing this myself." Leave blank if nothing comes to mind.
validations:
required: false
+1 -4
View File
@@ -120,7 +120,7 @@ jobs:
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics|cache_fs_gold_nx_load)_test\.lua$|tests/engine/platform_nx)'; then
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics)_test\.lua$|tests/engine/platform_nx)'; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
@@ -151,9 +151,6 @@ jobs:
luajit tests/engine/assets_version_fallback_test.lua
luajit tests/engine/nx_generated_guard_test.lua
luajit tests/engine/nx_yellow_boot_test.lua
luajit tests/engine/cache_fs_gold_nx_load_test.lua
luajit tests/engine/cache_fs_blue_mount_test.lua
luajit tests/engine/switch_diagnostics_test.lua
switch-build:
name: Switch fused build
-118
View File
@@ -1,118 +0,0 @@
# AI Disclosure
This is a disclosure of the use of AI in this project.
## AI Use
Anyone who demands the dislosure of how AI was used in an engineering project,
has no idea what AI is, or how it works.
AI was used in this project as a tool. Several contributors used AI in their
commits, and so you will see like 7 commits by Claude or Codex or Cursor.
However those commits were reviewed by human beings, and it was declared that
the exact same fix would have been done by a human, so they were accepted.
AI was not used to make decisions, or to create the project.
If you would like to read more, well then continue reading:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque ante leo, luctus in semper a, maximus ut est. Vivamus nec magna vitae quam luctus suscipit nec eu orci. Vestibulum ut felis a dolor cursus vulputate. Phasellus pharetra elementum sollicitudin. Aenean elementum imperdiet ultrices. In risus mauris, scelerisque sed viverra in, iaculis non eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quam justo, maximus sit amet fringilla in, tristique eget sapien.
Pellentesque rhoncus, erat nec elementum ultricies, augue purus suscipit augue, in condimentum nisl enim vel velit. Etiam at semper turpis. Vestibulum ipsum magna, ultrices non sem ut, hendrerit bibendum mi. Curabitur in varius sapien. Morbi posuere bibendum ex, at ultrices orci. Fusce feugiat ultrices varius. Suspendisse sed ante ligula. Sed dignissim lorem est, nec tristique arcu commodo sed. Cras consectetur sapien dolor, vitae finibus enim lacinia id.
Donec quis magna est. Maecenas dui arcu, venenatis sit amet libero nec, lacinia eleifend leo. Quisque lobortis vulputate lacus a elementum. Proin nec metus lectus. Donec eu auctor sem, at finibus ipsum. Curabitur eget dignissim justo. Donec lobortis leo eu arcu tristique, in volutpat augue eleifend. Morbi lacinia a risus in suscipit. Maecenas suscipit est eu interdum dictum. Cras in nulla imperdiet, dapibus mauris posuere, facilisis velit. Nunc dapibus, leo quis interdum tempor, elit mi mattis dolor, sagittis dictum mi urna sed lectus. Maecenas elementum, mauris id molestie dapibus, diam arcu egestas erat, at tempor justo orci vitae nibh. Etiam sagittis facilisis erat a vulputate. Praesent condimentum ac odio quis sollicitudin.
Fusce vitae orci vestibulum, sagittis dolor non, cursus urna. Morbi eleifend pretium pellentesque. Pellentesque ornare elementum sem in imperdiet. Maecenas dapibus, erat et lobortis porttitor, velit magna auctor odio, quis interdum elit est eu justo. In posuere euismod odio, in porttitor magna iaculis eget. In id quam pulvinar, ultrices dolor in, pellentesque dolor. Nunc varius ante at felis dictum, id porttitor sem efficitur. Integer pretium dignissim commodo. Suspendisse in est a arcu blandit faucibus. Donec quis lacus mollis, tincidunt nunc quis, suscipit nunc. Nunc non arcu dignissim, dignissim sem in, finibus neque. Aliquam non porta eros. Donec et pretium augue, non cursus eros.
Nunc at dignissim nisi. Nam nec metus augue. Proin nulla sapien, tristique a purus vel, vulputate commodo mi. Sed id erat leo. Quisque ullamcorper a nisl id molestie. Aliquam erat volutpat. Donec eget hendrerit mauris. Fusce tincidunt nisl a lorem tincidunt dapibus. Nam volutpat rhoncus tortor.
Nunc et sapien enim. Proin at nunc a nulla maximus consectetur nec eget tortor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci odio, sodales et elementum laoreet, porta at lacus. Maecenas vestibulum lectus risus, pulvinar scelerisque dolor posuere viverra. Proin gravida tellus vitae accumsan dignissim. Nunc non sapien aliquet ex cursus ultricies ac quis diam. Sed luctus feugiat risus eu tincidunt. Duis auctor lacinia fringilla. Donec pretium cursus magna a feugiat. Duis tristique, leo vulputate semper iaculis, est ipsum dapibus lacus, et molestie nulla enim nec ex. Nunc non feugiat neque.
Fusce euismod egestas elit ut pretium. Nulla eros quam, auctor sit amet faucibus eu, scelerisque eu neque. Sed nisi felis, lobortis in sapien a, tempor efficitur nunc. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque ut justo est. Sed maximus, tellus in bibendum posuere, quam augue finibus eros, sed gravida arcu diam quis lectus. Donec eu placerat ligula. Ut quis imperdiet lorem. Maecenas a mi ac augue semper sodales. Curabitur in justo velit. Praesent et felis quis enim porttitor sagittis.
Nulla sed sagittis felis, sit amet placerat tortor. Ut metus est, sollicitudin ac turpis quis, aliquet congue lorem. Fusce auctor erat non convallis aliquet. Nullam sodales rutrum tellus ac malesuada. Quisque sem diam, iaculis in ultricies sit amet, fermentum quis sem. Integer condimentum placerat purus non lacinia. Integer hendrerit ultricies tellus, at dignissim nibh. Suspendisse accumsan eget tortor nec cursus. Proin accumsan rhoncus leo, eget pretium est tristique ac.
Sed feugiat sed diam a porta. Nullam varius lacus at fermentum fringilla. Morbi pharetra scelerisque pharetra. Nulla placerat vitae ligula non efficitur. Suspendisse quam dui, rutrum eget nulla eu, semper eleifend ante. Aenean ut condimentum arcu. Suspendisse auctor metus non sem ornare, vel tincidunt odio vehicula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
Donec vel metus ut libero sollicitudin posuere a nec nulla. Vivamus a scelerisque nisi. Aliquam eu sollicitudin tortor. Aliquam purus eros, blandit et quam et, pretium porttitor sem. Nunc iaculis arcu enim, et maximus sem malesuada in. Morbi nec nunc volutpat, semper diam sit amet, gravida elit. Vestibulum eu turpis vel lacus imperdiet congue. Donec rhoncus auctor sem.
Suspendisse eu lorem non dolor pretium finibus euismod quis dolor. Cras finibus egestas velit, commodo rutrum est placerat sit amet. Pellentesque vitae semper diam, sit amet auctor metus. Sed porttitor porttitor nunc, vel imperdiet neque volutpat quis. Sed hendrerit sapien et lacus imperdiet, nec hendrerit lorem sodales. Integer lobortis rutrum odio at ullamcorper. Aliquam tincidunt magna a tellus suscipit, at porta turpis dapibus. Ut ultricies auctor felis eu feugiat. Sed tempus sem et dictum fringilla. Nunc non pellentesque tortor. Suspendisse pulvinar, arcu ut imperdiet gravida, eros ex mattis mauris, vel ultricies est erat et dui. Praesent porttitor tortor et erat interdum efficitur. Phasellus et luctus lectus, et egestas ante. Praesent ex ipsum, rutrum id efficitur et, vulputate non tortor. Aenean maximus nunc ac purus sodales, et venenatis lacus laoreet.
Cras egestas ultrices dui, at tempor leo varius vitae. Donec porta, nisl nec ornare maximus, est arcu auctor mauris, varius elementum nisl arcu venenatis neque. Aenean metus quam, vestibulum eget justo non, hendrerit dapibus nunc. Vivamus diam ante, mattis sed nulla at, iaculis elementum magna. Sed massa diam, efficitur vel nunc sed, malesuada interdum tortor. Aliquam non neque aliquet ex imperdiet finibus eget ac neque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
Pellentesque fringilla tortor metus, luctus commodo leo gravida et. Fusce nec turpis at lorem rutrum porta vel in justo. Curabitur mattis suscipit felis, id dapibus arcu ornare ut. Proin sapien felis, pulvinar ut tristique vitae, aliquet ac dui. Vestibulum a erat tellus. Vestibulum sagittis dolor eget augue egestas fringilla. Fusce et purus a nunc auctor dapibus vel sed dui. Fusce interdum, libero vel pellentesque rutrum, urna massa iaculis tellus, et aliquam lectus diam vel sem. Donec a nunc et dui semper gravida. Donec posuere, eros eu consequat efficitur, justo metus ullamcorper lectus, et molestie massa ipsum eu sapien. Mauris eu suscipit neque. Morbi convallis sit amet leo a scelerisque. Cras ultrices libero ac mattis accumsan. Nam gravida ligula id erat semper ornare. Duis consequat ut ipsum eu volutpat. Quisque egestas sollicitudin ullamcorper.
Cras pellentesque quam non neque porta fringilla. Integer elementum, augue mattis blandit consequat, enim ipsum finibus ex, quis finibus neque eros eu ex. Fusce at urna justo. Donec erat eros, maximus id mauris vel, rutrum rutrum sapien. Morbi sed rutrum ex. Suspendisse lacus velit, varius ut elementum vitae, finibus non enim. Suspendisse vehicula euismod ipsum, id consequat nulla sodales vel. Morbi eu sem id leo congue dapibus a nec velit. Sed nec neque quam. Etiam rhoncus id nulla id volutpat. Nulla facilisi. Donec non maximus enim. Aenean consequat, sapien sit amet malesuada rutrum, erat sem euismod sapien, et feugiat lectus mauris id velit.
Fusce sodales porttitor gravida. Proin placerat ante nec nibh tempor aliquam. Sed ut diam eu sem fringilla malesuada. Maecenas aliquam risus vel quam dictum, at iaculis nibh pretium. Mauris convallis quam vitae dolor varius suscipit. Etiam nec fermentum dui. Aliquam in magna tincidunt, consectetur quam eget, aliquam purus. Ut dictum aliquet finibus. Pellentesque vel lacinia felis. Nulla malesuada vestibulum varius. Sed quam diam, efficitur id felis in, volutpat bibendum erat. Praesent luctus vulputate urna at interdum. Aenean ac aliquam eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi mollis, nisl vel consequat vehicula, massa tellus porta arcu, vel dictum sapien mauris sit amet nunc. Integer sed maximus neque, ac iaculis urna.
Integer non erat a leo euismod convallis quis eget magna. Morbi gravida ac urna sed ornare. Nunc vehicula mauris accumsan, ornare sem in, egestas mauris. Vestibulum vel vulputate felis. Nulla eu scelerisque diam. Suspendisse ac odio tempor nunc pellentesque hendrerit at a magna. Vivamus ultrices nunc ut orci fermentum pharetra. Nullam laoreet hendrerit ligula ut gravida. Proin scelerisque magna sit amet arcu malesuada, pharetra ultrices est molestie. Nullam pulvinar placerat dui, vitae hendrerit tortor luctus sed. Pellentesque elementum tellus eget arcu pulvinar varius.
Ut placerat, magna vitae tincidunt ultricies, est orci aliquet urna, at luctus augue erat vel ipsum. Fusce odio sem, venenatis vel consequat nec, bibendum sed dolor. Cras a sodales eros. Nullam eget dui congue, vehicula purus ut, condimentum dui. Maecenas libero ipsum, condimentum tincidunt nisi in, sodales lacinia ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
Duis gravida velit ac euismod suscipit. Proin sed ligula erat. Nullam eu ornare massa, non fringilla felis. Curabitur eu erat ex. Quisque sit amet dolor id arcu mattis scelerisque at at eros. In arcu nulla, fermentum non maximus eget, rhoncus in lacus. Nulla sit amet augue eu tortor vulputate congue. Vivamus laoreet condimentum tempus. Maecenas tempor, diam sed laoreet venenatis, mauris arcu lacinia enim, quis facilisis nunc turpis a ligula. Pellentesque quis placerat nisi, sit amet ullamcorper diam. Suspendisse a elementum elit, vel tristique dolor. Nam pretium ante tortor, vel tristique ipsum ultricies ut. Quisque non lectus imperdiet, placerat erat ac, pharetra tellus. In condimentum at magna a posuere. Aliquam et fringilla ipsum. Sed facilisis, nulla a finibus gravida, elit elit vulputate velit, dictum ornare est sapien a nisl.
Mauris eleifend vulputate felis sed mattis. Praesent id velit vitae ex porta pretium. Cras mollis malesuada justo, ut ornare quam placerat ac. Donec lobortis arcu tellus, luctus tempor mi malesuada quis. Maecenas condimentum libero vitae finibus malesuada. Vestibulum sollicitudin fringilla diam eget egestas. Sed vulputate urna nec ipsum maximus hendrerit. Maecenas blandit ex ut massa sodales, vitae tincidunt lorem ullamcorper. Phasellus vitae nisl ornare, cursus sem a, pulvinar arcu. Vestibulum faucibus risus nec tincidunt pellentesque. Pellentesque vel porttitor ex. Vivamus sollicitudin gravida lacus in suscipit. Aliquam urna neque, sodales quis quam ac, suscipit condimentum ante.
Morbi id arcu sit amet sapien ornare gravida eget quis sapien. In hac habitasse platea dictumst. In quis interdum ligula. Donec sed mi vulputate, scelerisque turpis vitae, interdum odio. Proin tristique condimentum arcu, et malesuada tellus convallis in. Fusce egestas maximus magna, sit amet convallis velit porttitor ut. Curabitur venenatis lacus ut blandit convallis. Phasellus scelerisque congue turpis eget vehicula. Nam venenatis mi sit amet rhoncus pretium. Nulla sed odio purus. Phasellus cursus id sapien ut feugiat. Duis et ipsum vel dui tempus porta. Curabitur non tortor consectetur, sollicitudin tellus mollis, sollicitudin lorem.
Ut quis ornare justo. Nunc aliquam, leo sit amet placerat placerat, dui nulla luctus dui, ac iaculis nisl orci id metus. Vestibulum nunc nunc, porta nec dictum id, feugiat et ante. Quisque lobortis, lacus tristique vestibulum rhoncus, massa nulla dignissim massa, at scelerisque massa nunc at velit. Donec eu ipsum nec dui luctus pulvinar et et turpis. Quisque eu neque erat. Donec varius egestas nunc, ut pretium libero tempor ac. Vestibulum pellentesque mi erat, et semper dolor semper vitae. Morbi enim dui, laoreet non venenatis sit amet, dignissim a orci. Sed id odio turpis. Phasellus non rutrum magna. Maecenas placerat arcu ultricies ultrices congue. Nulla quis neque ligula. Etiam in diam commodo, pharetra mauris ac, pretium nisi. Praesent sed nibh nec odio condimentum commodo vel vel lacus.
Pellentesque id libero vitae ex egestas pharetra placerat nec augue. Ut eget lobortis lorem, at vehicula sapien. Aliquam eu tincidunt ligula. Aenean et vestibulum dui, quis porttitor dui. Nullam quis dolor libero. Sed accumsan eros vitae nisi ornare congue. Aliquam nisi sapien, sollicitudin quis odio vel, pharetra maximus urna.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi accumsan felis id urna malesuada, non vulputate velit maximus. Proin urna velit, viverra a metus vel, sollicitudin faucibus nulla. Vivamus ipsum lectus, pharetra sit amet varius vel, ullamcorper nec neque. Suspendisse potenti. Vivamus sit amet justo ac augue placerat hendrerit in eu felis. Integer luctus ex quam, in pellentesque eros interdum sed. Fusce finibus quis neque vitae efficitur. Pellentesque vulputate consectetur egestas. Integer a neque fermentum, tincidunt ligula id, gravida urna. Pellentesque ultrices, leo et suscipit accumsan, lorem nunc porta dui, non congue ligula leo ut urna. Duis vehicula risus in mi eleifend luctus. Duis convallis, mi faucibus pellentesque cursus, libero mauris varius sem, sit amet fermentum massa metus nec tellus. In tortor ligula, faucibus eu nibh id, lobortis viverra erat. Morbi non nisi suscipit, mollis enim at, convallis velit.
Pellentesque consequat imperdiet felis quis scelerisque. Duis aliquam mollis nibh quis tincidunt. Vivamus elit odio, blandit quis volutpat at, blandit nec tortor. Cras maximus ex at odio maximus, dapibus condimentum risus malesuada. Pellentesque viverra orci at ante commodo, quis posuere sapien efficitur. Nunc tristique imperdiet diam elementum lobortis. Fusce velit dui, ultrices id ante pharetra, fermentum egestas augue. Curabitur ante augue, vestibulum non magna quis, feugiat pulvinar diam. Suspendisse sagittis dui a tellus scelerisque, ut tincidunt neque accumsan. Cras pharetra metus vel eros tincidunt, vel tincidunt lacus egestas. Donec eget pellentesque sapien. Cras condimentum in justo pulvinar feugiat. Quisque malesuada ac odio eget rhoncus. Fusce posuere justo sed finibus ornare.
Duis a auctor tortor. Pellentesque lobortis auctor risus, ultrices varius mi cursus quis. Sed dui nulla, mattis sit amet justo mattis, condimentum commodo justo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque hendrerit diam vel lacinia volutpat. Sed et luctus nunc, vitae congue arcu. Aenean placerat tincidunt ipsum. Ut molestie orci eu dapibus viverra. Cras sodales ullamcorper augue, at aliquam ex. Sed et justo augue.
Nullam feugiat risus et turpis faucibus, vel tincidunt nulla consequat. Aliquam libero erat, pellentesque sit amet tellus in, tempor ornare nisl. Donec viverra eget magna non pharetra. Cras sollicitudin, justo ut porttitor venenatis, risus nulla auctor est, at commodo sem urna in mi. Vestibulum mattis sapien vel nibh pulvinar, vel dignissim lacus cursus. Maecenas vel dictum tortor, ac ultricies justo. Praesent quis venenatis nisl. Morbi a diam fringilla, auctor lectus sed, varius est. Praesent faucibus auctor dolor, a commodo nisi mattis id. Fusce porta molestie ultrices. Sed pulvinar, leo ac consectetur hendrerit, erat velit gravida enim, eu blandit ligula justo sit amet neque.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean libero risus, porttitor in congue vel, consequat sed felis. Vivamus eget blandit ante. Vivamus vitae mattis massa. In ut dolor sit amet tellus sollicitudin mattis. Mauris iaculis nisl neque, in gravida lectus ultricies nec. Fusce vehicula vehicula lacinia. Fusce viverra sed nisl id rhoncus. Donec sed porta mi. Nam tempus purus non massa tincidunt iaculis. Morbi viverra massa ut gravida vestibulum.
Proin dapibus mi a libero sagittis, id vehicula nulla iaculis. Proin a enim in tortor tincidunt egestas. Integer finibus neque eu nibh pretium, et pellentesque urna finibus. Integer a sollicitudin mauris, at convallis erat. Nullam sit amet lectus sed turpis commodo efficitur. Nulla nec turpis dapibus, suscipit diam quis, vulputate urna. Aenean sed posuere justo.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed luctus purus nibh, id imperdiet risus pulvinar in. Quisque auctor sem lacinia turpis mollis, nec pretium ipsum suscipit. Aliquam sed metus sagittis, mollis nisi eget, hendrerit libero. Nulla sodales erat semper nisl condimentum, ultricies rhoncus lacus commodo. Ut suscipit libero augue, non vulputate dui tristique vel. Praesent convallis efficitur est, sed tincidunt mauris aliquet in. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
Etiam a vehicula purus. Curabitur lacus erat, ultrices et dictum id, posuere ut risus. Etiam at auctor leo. Pellentesque eleifend est a metus lacinia rutrum. Nullam justo ligula, tempor non facilisis vel, volutpat sed nulla. Integer non dolor consequat, dapibus lectus eu, luctus turpis. Aenean enim erat, sagittis vitae ornare bibendum, faucibus sit amet magna. Phasellus suscipit ultrices faucibus. Sed at risus molestie, viverra ipsum vel, bibendum lectus. Pellentesque molestie vitae risus non viverra. Phasellus eleifend massa id odio sagittis mattis. Nullam velit mauris, viverra quis fermentum sit amet, vestibulum ut ex. Suspendisse imperdiet, sapien sed sagittis pharetra, nisi nibh vulputate metus, quis mattis dolor nisi ut lorem. Praesent vestibulum nibh vulputate lacus pulvinar tempor. Vestibulum vulputate diam ligula, vitae efficitur enim dapibus non. Etiam at ornare enim.
Curabitur aliquet velit enim, euismod faucibus urna euismod sit amet. Vivamus viverra vulputate nulla, ut gravida neque rutrum in. Suspendisse potenti. Nulla vitae neque felis. Etiam eu erat ac nulla ornare volutpat. Quisque ut diam dui. Sed ut massa quis dolor volutpat eleifend. Duis posuere dolor sit amet varius auctor. Donec mollis malesuada erat, eu luctus libero viverra feugiat. Curabitur fermentum velit eu purus fringilla, consequat tincidunt diam rutrum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc volutpat vel est in vehicula. Morbi euismod, tortor ut posuere ullamcorper, velit justo ultricies lorem, vitae tincidunt erat ante vitae sem. Praesent semper, quam eget condimentum finibus, metus leo imperdiet augue, nec fringilla sem nunc id sapien.
Aliquam vestibulum ante porta sem finibus, ut rhoncus elit sodales. Phasellus a lacus congue, sagittis nulla eget, cursus libero. Duis laoreet fringilla faucibus. Aenean gravida lorem sed fringilla facilisis. Pellentesque sodales urna lorem, non rutrum tortor vulputate eget. Duis a enim semper, iaculis sem ac, facilisis urna. Donec iaculis nulla sit amet dignissim volutpat. Mauris cursus dui id feugiat suscipit. Fusce tempor placerat nulla vitae vestibulum. Vivamus imperdiet blandit nulla, in aliquet justo viverra in. Cras malesuada molestie ligula sit amet volutpat. Praesent ornare orci sit amet rutrum eleifend. Ut placerat metus felis, id malesuada justo mollis eget. Etiam mi turpis, pulvinar in pulvinar in, tincidunt in neque.
In venenatis euismod neque, eu convallis diam semper ac. Mauris auctor mi non massa vestibulum viverra. Aenean non turpis sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent gravida feugiat interdum. Vivamus sit amet consequat ligula. Praesent dictum nunc ac sapien ullamcorper consectetur. Ut malesuada blandit neque.
Duis tincidunt mauris sit amet odio dictum accumsan. Suspendisse efficitur nibh magna, ac dapibus augue vulputate nec. Etiam lectus neque, sollicitudin quis sapien vitae, aliquet fermentum mi. Aenean interdum interdum rhoncus. Donec eu libero urna. Donec semper lacus eu nunc scelerisque, vitae viverra quam consectetur. Integer sagittis, nulla congue venenatis auctor, purus justo mollis metus, at rutrum magna arcu nec est. Maecenas felis lorem, consequat non cursus vitae, lobortis vitae nisi. Cras eget magna justo. Nullam sagittis tellus id luctus ultricies. Etiam a arcu efficitur, consectetur libero non, imperdiet turpis. Donec ac velit et nisl semper semper. Duis iaculis interdum nunc sed tempor.
Sed diam odio, sagittis non dignissim nec, accumsan ac diam. Fusce sit amet dui sit amet justo ultrices viverra. Sed vel massa suscipit nibh porttitor laoreet in id nunc. Nam quis libero vitae nunc blandit sollicitudin et a lorem. Duis urna arcu, accumsan sed dignissim sit amet, vulputate at ex. Mauris porttitor libero mauris, vel fringilla diam euismod quis. Sed varius placerat tellus vel efficitur. Phasellus pulvinar gravida magna. Nulla dignissim consectetur finibus.
Nunc quis aliquet nisi. Cras luctus bibendum eros ac dignissim. Aenean suscipit felis vitae elementum eleifend. Proin commodo nunc non diam dignissim, in tincidunt nulla ultrices. Vivamus faucibus quam scelerisque interdum finibus. Sed porttitor vehicula urna, in laoreet arcu condimentum non. Praesent ac lacus diam. Vivamus aliquam euismod risus, luctus dictum lectus sodales et. Proin quis velit ac massa tristique scelerisque.
Sed non dolor efficitur, tincidunt mi eget, sagittis tortor. Quisque at varius felis, at finibus sem. Vestibulum vel lectus tincidunt, pharetra diam sit amet, interdum nulla. Sed ut elit tortor. Nam tincidunt tempus aliquam. Vivamus rhoncus faucibus sapien eget facilisis. Aliquam erat volutpat. Phasellus placerat aliquam lacus, eget ultrices orci pretium hendrerit. Fusce vitae dolor sit amet ante condimentum placerat. Nunc varius risus id tellus mollis, euismod luctus sapien viverra. Donec sodales est vel massa suscipit, eu sollicitudin ante convallis. Curabitur eu condimentum velit. Sed pharetra euismod tincidunt.
Nam at libero eros. Quisque bibendum, ligula quis sagittis ullamcorper, eros leo consectetur ex, quis elementum dolor justo vitae mi. Maecenas et elementum erat, et auctor enim. Nunc at nibh fermentum, ullamcorper mi elementum, facilisis erat. Vestibulum vestibulum leo ut pellentesque placerat. Suspendisse imperdiet nisl vitae justo sodales pellentesque. Interdum et malesuada fames ac ante ipsum primis in faucibus. In faucibus pretium nunc, sed interdum lectus vestibulum quis. Vestibulum luctus viverra ex at efficitur. Etiam ac est lorem. Maecenas mollis, orci at rhoncus congue, nulla leo rutrum dui, et pellentesque orci ligula eget ipsum. Suspendisse fermentum nisi turpis, ut sollicitudin purus imperdiet non. Maecenas vitae quam ornare, porta sem quis, rhoncus neque. Donec mattis purus a erat tristique, ac mollis est convallis. Duis vitae ipsum viverra, condimentum ante vel, sagittis ex. Maecenas placerat odio libero, id interdum turpis fermentum at.
Praesent faucibus nulla eget vehicula accumsan. Nulla elementum ante a nibh venenatis hendrerit. Proin nec nunc mattis, imperdiet nisl rutrum, sollicitudin libero. Nullam bibendum dignissim faucibus. Sed eget tortor vitae sapien cursus faucibus nec et lectus. Duis pharetra non odio id consectetur. Suspendisse at est sem. Nunc mauris ligula, ultrices id ante non, venenatis mattis erat. Vivamus sit amet viverra nisl.
Sed cursus vel nisi in mattis. Nunc porttitor dictum leo ac euismod. Sed blandit ornare nunc id lobortis. Aenean convallis ligula at volutpat commodo. Vestibulum sit amet laoreet urna. Donec et pellentesque orci, ac egestas nulla. In accumsan venenatis porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec scelerisque, metus nec viverra pharetra, dolor libero dictum velit, id ullamcorper enim nisi eu nibh. Aliquam nec dapibus quam. Curabitur vulputate, libero sit amet tempor ullamcorper, libero purus congue quam, nec sollicitudin orci erat non massa. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
Aenean faucibus mollis placerat. Praesent lacinia venenatis turpis eu scelerisque. Nam tempus tortor a varius posuere. Cras ut viverra tortor. Cras facilisis mauris ut ante imperdiet, a malesuada justo luctus. Ut eu enim sit amet arcu porttitor pulvinar ac ac odio. Ut odio neque, molestie vitae ligula quis, dignissim viverra erat. In ullamcorper erat sed elementum varius. Sed in lacus maximus, euismod nisi vitae, tempus mi. Nunc tellus justo, auctor at luctus ac, feugiat sit amet dui.
Donec imperdiet purus lorem, sed venenatis dolor finibus non. Aenean lacus nunc, elementum nec arcu eget, faucibus elementum turpis. Aliquam lacinia massa ac quam efficitur, et tincidunt eros pretium. Fusce condimentum mi vel pharetra egestas. Quisque consectetur nibh vel leo dignissim sollicitudin. Duis ultrices felis ipsum, sed maximus arcu ornare vitae. Curabitur porttitor ligula in turpis facilisis, id venenatis augue ultricies. Phasellus vel dolor id tellus finibus sodales ut quis nisi. Integer id orci cursus erat tincidunt sagittis non in nunc. Pellentesque ligula lacus, vestibulum eu ante vel, facilisis viverra massa. Sed ut tincidunt metus, vel tristique est. Ut et cursus justo. Ut ac porttitor eros, at dictum felis. Phasellus ornare nisi sit amet risus varius, sed sollicitudin nulla ornare. Donec aliquam ipsum urna. Aliquam id bibendum magna, quis venenatis diam.
Duis tempor odio id iaculis egestas. Cras consequat neque ac posuere iaculis. Nulla tempus et nisi eu auctor. Vestibulum metus massa, dignissim ut metus eget, ullamcorper consectetur turpis. Integer vel est tellus. Ut ac vestibulum massa. Pellentesque nec venenatis erat. Nam vel pellentesque lectus.
Morbi a placerat est. Ut eleifend ante ut placerat porta. Donec sagittis semper leo, ut scelerisque nisi imperdiet feugiat. Mauris purus turpis, consequat ut fringilla ac, cursus eget augue. Fusce arcu dolor, sagittis et facilisis ut, scelerisque non lacus. Aliquam sit amet eleifend tellus. Mauris id est luctus, iaculis tortor eget, gravida justo. Suspendisse at tellus nisl. Nullam felis erat, vehicula eu porttitor bibendum, pulvinar et dui. Sed molestie lacus nec sagittis rutrum. Aliquam erat volutpat. Nullam ut aliquet eros. Sed feugiat, massa id pharetra auctor, leo turpis condimentum purus, sit amet volutpat sem nunc sed nisi.
Pellentesque feugiat ipsum at accumsan iaculis. Morbi et dui in lorem commodo hendrerit. Mauris tempor ex mollis mollis blandit. Cras eu turpis feugiat, suscipit velit quis, volutpat magna. Vestibulum varius ligula ut quam mollis, a volutpat nibh lobortis. Sed sodales euismod leo non suscipit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam ultricies sagittis justo, sit amet lacinia eros tincidunt tristique. Etiam faucibus turpis in lacus efficitur, vitae rutrum magna porttitor. Cras finibus eros vel ante semper, facilisis vestibulum erat accumsan. Ut mollis dui ut commodo varius. In et sem malesuada erat egestas pellentesque. Maecenas pulvinar sodales risus, at euismod ligula aliquet in. Quisque fringilla malesuada dui vel cursus. Curabitur eu ex vulputate, pretium elit non, sodales sapien. Integer egestas facilisis odio et pretium.
Integer eleifend, felis vitae faucibus tempus, tellus lectus placerat nunc, eu efficitur lacus mi vel nisl. Mauris commodo pretium feugiat. In aliquet nibh diam, ac egestas mauris consequat ut. Integer cursus, tortor pharetra pellentesque pulvinar, neque risus ultricies felis, et consequat felis eros at eros. Pellentesque fermentum velit ac sodales facilisis. Suspendisse vestibulum metus quis convallis lacinia. Donec in pharetra magna. Proin gravida dolor eget ligula lobortis sagittis.
Nullam consectetur ut massa id ultrices. Fusce consectetur at eros at mollis. Donec nec nibh fringilla, porttitor ipsum eget, aliquam neque. Quisque suscipit tortor in dui commodo, sed venenatis augue cursus. Etiam feugiat purus id justo elementum placerat. Sed interdum dictum nibh at sodales. Maecenas lobortis, metus ac sagittis lacinia, elit arcu varius felis, quis facilisis magna elit in leo. Proin condimentum orci sit amet dignissim imperdiet. Fusce sed iaculis felis. Maecenas sodales non magna vitae rutrum.
Nulla id ex massa. Sed vehicula sed quam non elementum. Aliquam luctus, enim vel molestie posuere, sem arcu laoreet justo, quis finibus nisl justo et magna. Vivamus malesuada elit in aliquam dignissim. Sed at tellus in orci vulputate ullamcorper. Integer magna sem, mattis id hendrerit non, tincidunt in est. Praesent posuere aliquet lobortis. Quisque euismod leo ut nisl pellentesque, et imperdiet dui dapibus.
Sed a erat nec risus pulvinar venenatis. Integer ultrices eros at aliquet efficitur. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque ac maximus ante. Sed sodales, nisi et sagittis accumsan, diam odio consequat elit, et mollis dolor turpis tincidunt metus. Proin justo quam, tincidunt id convallis a, molestie hendrerit massa. Etiam pharetra turpis eu ultrices mollis.
Proin fermentum libero in purus cursus molestie. In varius magna eu ante maximus, eget rutrum felis iaculis. Maecenas hendrerit, diam eget vestibulum vehicula, est quam porta magna, a dictum urna mi ut libero. Vestibulum dictum quis lacus vitae eleifend. Integer sapien libero, pretium vitae euismod eget, semper eget ante. Vivamus mollis elementum odio vel hendrerit. Phasellus tristique, metus eget luctus tincidunt, ex enim faucibus ipsum, at dictum eros urna ac mi. Aenean imperdiet felis eu ultricies egestas. Vivamus fermentum convallis nisi, non sollicitudin felis posuere nec. Ut commodo sit amet felis semper dictum. Aenean accumsan, tellus id blandit aliquet, lorem nibh pellentesque mi, sit amet volutpat erat ligula ut est. Vivamus non posuere velit. Sed vel rutrum diam, non pulvinar nulla. Suspendisse quis gravida lectus, ac accumsan justo. Sed lobortis neque ante, a imperdiet nisl iaculis nec.
+10 -9
View File
@@ -4,12 +4,8 @@ A native LÖVE2D recreation of Poke Red, Blue and Yellow. The engine and map
behavior are hand-written Lua; game data and graphics are decoded from a ROM
supplied by the player.
And before you say, "that's not a recomp", you're wrong. Recomp is an acronym. ***Reverse Engineering Causes Obsessive Mental Problems***
[Click Here for the AI Use Disclosure!](AIDisclosure.md)
> [!CAUTION]
> **We are NOT affiliated with the website `gen1recomp[.]com`** That website is not run by this project, was not authorized by us, and we have no idea who operates it. It is impersonating this project; do not download anything from it, and treat anything it hosts or claims as untrustworthy. Even if the site currently links back to this repository, the people behind it can change its content at any time, so nothing on it should ever be trusted. This GitHub repository and the Discord linked below are the only official sources for this project. Also, as I assumed would eventually happen, the idiot that made that website now pumped it full of adware. Please stay away from that website.
> **We are NOT affiliated with the website `gen1recomp[.]com`** That website is not run by this project, was not authorized by us, and we have no idea who operates it. It is impersonating this project; do not download anything from it, and treat anything it hosts or claims as untrustworthy. Even if the site currently links back to this repository, the people behind it can change its content at any time, so nothing on it should ever be trusted. This GitHub repository and the Discord linked below are the only official sources for this project.
<p align="center"><img src="https://raw.githubusercontent.com/bryanthaboi/gen1recomp/refs/heads/dev/assets/logo/logo.png"></p>
@@ -53,7 +49,7 @@ And before you say, "that's not a recomp", you're wrong. Recomp is an acronym. *
### Watch the latest update video
[![Watch the latest update video](https://img.youtube.com/vi/yi7LkWQPKKM/maxresdefault.jpg)](https://youtu.be/yi7LkWQPKKM)
[![Watch the latest update video](https://img.youtube.com/vi/8IOgqbe4YvA/maxresdefault.jpg)](https://www.youtube.com/watch?v=8IOgqbe4YvA)
This project does not include a ROM, emulate the Game Boy, transpile assembly,
@@ -332,7 +328,7 @@ Maps can be edited in our own build of [Tiled](https://www.mapeditor.org),
and exported back out as a mod; see
[docs/tiled-map-editing.md](docs/tiled-map-editing.md).
## Bugs
## Bugs and Ideas
Found a bug? A warp dropping you somewhere it shouldn't, a battle doing math
that looks wrong, text in the wrong box, anything that does not match the
@@ -341,6 +337,12 @@ original game.
Attach a screenshot if you can. It saves a lot of back and forth, and if you
can't get one, the form asks you to describe what you saw instead.
Thought of a feature that could be good, or a way to improve one that already
exists?
[Open a feature request](https://github.com/bryanthaboi/gen1recomp/issues/new?template=feature_request.yml).
Say what you want, why it is worth doing, and how you picture it working. A
request with real detail is one that can actually get built.
## More
- [Link play](https://github.com/bryanthaboi/gen1recomp/wiki/Guide-Link-Play)
@@ -348,8 +350,7 @@ can't get one, the form asks you to describe what you saw instead.
- [Save editor](https://github.com/bryanthaboi/gen1recomp/wiki/Guide-Save-Editor)
— edit party, boxes, items, events, and Pokédex flags outside the game.
- `docs/architecture.md` — runtime details;
`docs/behavior-porting-notes.md` — formula provenance;
`docs/link-security.md` — what link play defends against, and what it doesn't.
`docs/behavior-porting-notes.md` — formula provenance.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

-10
View File
@@ -1,10 +0,0 @@
# gb_anim -- bundled touch skin
Bezel art and overlay layout from libretro's `common-overlays`
(`gamepads/gb_anim_portrait`), licensed CC-BY-4.0:
https://github.com/libretro/common-overlays
`overlay.cfg` is the upstream `gb_big.cfg`, unmodified. It ships as the
reference skin for the RetroArch-overlay loader in
`src/core/TouchSkin.lua`: a full-device bezel, per-button press art, a
screen viewport, and page switching between the DMG and Color shells.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

-89
View File
@@ -1,89 +0,0 @@
overlays = 2
overlay0_name = "GameBoy"
overlay0_overlay = img/gb_back.png
overlay0_full_screen = true
overlay0_normalized = true
overlay0_range_mod = 1.0
overlay0_alpha_mod = 0.001
overlay0_viewport = "0.0,0.0,1.0,0.505"
overlay0_viewport_fill = true
overlay1_name = "GameBoyColor"
overlay1_overlay = img/gbc_back.png
overlay1_full_screen = true
overlay1_normalized = true
overlay1_range_mod = 1.0
overlay1_alpha_mod = 0.001
overlay1_viewport = "0.0,0.0,1.0,0.505"
overlay1_viewport_fill = true
# GameBoy
overlay0_descs = 18
overlay0_desc0 = "left,0.12778,0.73417,radial,0.09630,0.04635"
overlay0_desc0_overlay = img/gb_left.png
overlay0_desc1 = "right,0.35370,0.73417,radial,0.09630,0.04635"
overlay0_desc1_overlay = img/gb_right.png
overlay0_desc2 = "up,0.24074,0.67063,radial,0.08241,0.05417"
overlay0_desc2_overlay = img/gb_up.png
overlay0_desc3 = "down,0.24074,0.79771,radial,0.08241,0.05417"
overlay0_desc3_overlay = img/gb_down.png
overlay0_desc4 = "left|up,0.09259,0.65188,rect,0.06481,0.03646"
overlay0_desc5 = "right|up,0.38704,0.65188,rect,0.06481,0.03646"
overlay0_desc6 = "left|down,0.09259,0.81750,rect,0.06481,0.03646"
overlay0_desc7 = "right|down,0.38704,0.81750,rect,0.06481,0.03646"
overlay0_desc8 = "a,0.87407,0.72417,radial,0.08889,0.05000"
overlay0_desc8_overlay = img/gb_a_b.png
overlay0_desc9 = "b,0.68148,0.76584,radial,0.08889,0.05000"
overlay0_desc9_overlay = img/gb_a_b.png
overlay0_desc10 = "a|b,0.77037,0.73417,radial,0.02963,0.01667"
overlay0_desc11 = "a|b,0.78518,0.75584,radial,0.02963,0.01667"
overlay0_desc12 = "start,0.66666,0.93000,radial,0.07037,0.03958"
overlay0_desc12_overlay = img/gb_start_select.png
overlay0_desc13 = "select,0.33333,0.93000,radial,0.07037,0.03958"
overlay0_desc13_overlay = img/gb_start_select.png
overlay0_desc14 = "menu_toggle,0.05000,0.52800,radial,0.041296,0.02323"
overlay0_desc14_overlay = img/menu.png
overlay0_desc15 = "overlay_next,0.95000,0.52800,radial,0.041296,0.02323"
overlay0_desc15_overlay = img/rotate.png
overlay0_desc15_next_target = "GameBoyColor"
overlay0_desc16 = "rewind,0.05000,0.97500,radial,0.041296,0.02323"
overlay0_desc16_overlay =
overlay0_desc17 = "hold_fast_forward,0.95000,0.97500,radial,0.041296,0.02323"
overlay0_desc17_overlay =
# GameBoyColor
overlay1_descs = 18
overlay1_desc0 = "left,0.14078,0.73417,radial,0.08530,0.04635"
overlay1_desc0_overlay = img/gbc_left.png
overlay1_desc1 = "right,0.34270,0.73417,radial,0.08530,0.04635"
overlay1_desc1_overlay = img/gbc_right.png
overlay1_desc2 = "up,0.24074,0.67863,radial,0.08241,0.04617"
overlay1_desc2_overlay = img/gbc_up.png
overlay1_desc3 = "down,0.24074,0.78971,radial,0.08241,0.04617"
overlay1_desc3_overlay = img/gbc_down.png
overlay1_desc4 = "left|up,0.09259,0.65188,rect,0.06481,0.03646"
overlay1_desc5 = "right|up,0.38704,0.65188,rect,0.06481,0.03646"
overlay1_desc6 = "left|down,0.09259,0.81750,rect,0.06481,0.03646"
overlay1_desc7 = "right|down,0.38704,0.81750,rect,0.06481,0.03646"
overlay1_desc8 = "a,0.87407,0.72417,radial,0.08889,0.05000"
overlay1_desc8_overlay = img/gbc_a.png
overlay1_desc9 = "b,0.68148,0.76584,radial,0.08889,0.05000"
overlay1_desc9_overlay = img/gbc_b.png
overlay1_desc10 = "a|b,0.77037,0.73417,radial,0.02963,0.01667"
overlay1_desc11 = "a|b,0.78518,0.75584,radial,0.02963,0.01667"
overlay1_desc12 = "start,0.66666,0.93000,radial,0.07037,0.03958"
overlay1_desc12_overlay = img/gbc_start_select.png
overlay1_desc13 = "select,0.33333,0.93000,radial,0.07037,0.03958"
overlay1_desc13_overlay = img/gbc_start_select.png
overlay1_desc14 = "menu_toggle,0.05000,0.52800,radial,0.041296,0.02323"
overlay1_desc14_overlay = img/menu.png
overlay1_desc15 = "overlay_next,0.95000,0.52800,radial,0.041296,0.02323"
overlay1_desc15_overlay = img/rotate.png
overlay1_desc15_next_target = "GameBoy"
overlay1_desc16 = "rewind,0.05000,0.97500,radial,0.041296,0.02323"
overlay1_desc16_overlay =
overlay1_desc17 = "hold_fast_forward,0.95000,0.97500,radial,0.041296,0.02323"
overlay1_desc17_overlay =
-10
View File
@@ -1,10 +0,0 @@
# tv_crt -- bundled desktop bezel
CRT television border from libretro's `common-overlays`
(`borders/tv-integer.cfg` + `borders/img/tv-integer.png`), licensed
CC-BY-4.0: https://github.com/libretro/common-overlays
`overlay.cfg` is the upstream file, unmodified. It is the reference
DESKTOP skin: 1920x1080, `descs = 0` (pure decoration, no touch buttons),
and a `viewport` naming the transparent screen hole, so the Game Boy
picture is fitted into the TV's tube instead of the whole window.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

-6
View File
@@ -1,6 +0,0 @@
overlays = 1
overlay0_overlay = img/tv-integer.png
overlay0_full_screen = true
overlay0_descs = 0
overlay0_viewport = "0.2335,0.0855,0.5335,0.830"
overlay0_viewport_fill = true
+2 -15
View File
@@ -6,30 +6,18 @@ function love.conf(t)
local editor = os.getenv("POKEPORT_EDITOR") == "1"
local developer = os.getenv("POKEPORT_DEV") == "1"
local companion = nil
if arg then
for _, a in ipairs(arg) do
if a == "--editor" then editor = true end
if a == "--developer" then developer = true end
local port, token = a:match("^%-%-display%-companion=(%d+),([%w]+)$")
if port then companion = { port = tonumber(port), token = token } end
end
end
-- main.lua runs in the same Lua state right after conf.lua; stash the
-- decision in a global so it doesn't need to reparse `arg`.
_G.POKEPORT_EDITOR_MODE = editor
_G.POKEPORT_DEV_MODE = developer
_G.POKEPORT_DISPLAY_COMPANION = companion
if companion then
t.identity = "pokemon-love2d-companion"
t.window.title = "gen1recomp Secondary Display"
t.window.width = 640
t.window.height = 576
t.window.minwidth = 160
t.window.minheight = 144
t.window.resizable = true
elseif editor then
if editor then
-- Same identity as the game, deliberately: the editor edits the game's
-- saves and reads the game's ROM cache, both of which live under this
-- folder. A private editor identity would point love.filesystem at an
@@ -63,8 +51,7 @@ function love.conf(t)
end
t.version = love._os == "iOS" and "12.0" or "11.5"
t.window.vsync = 1
t.modules.audio = not companion
t.modules.joystick = not companion
t.modules.joystick = true
t.modules.physics = false
-- love.system is not loaded during love.conf; love._os is set by the
-19
View File
@@ -5,27 +5,8 @@
-- voucher exchange and the BICYCLE/CANCEL price window need more than
-- command rows (#568).
local TextBox = require("src.render.TextBox")
-- data/events/hidden_events.asm:542
local BIKE_DISPLAYS = {
{ 1, 0 }, { 2, 1 }, { 1, 2 }, { 3, 2 }, { 0, 4 }, { 1, 5 },
}
return {
BIKE_SHOP = {
-- engine/events/hidden_events/new_bike.asm:1
onInteract = function(game, ow, fx, fy)
for _, c in ipairs(BIKE_DISPLAYS) do
if c[1] == fx and c[2] == fy then
game.stack:push(TextBox.new(game,
(game.data.text or {})._NewBicycleText or "A shiny new\nBICYCLE!"))
return true
end
end
return false
end,
talk = {
-- BikeShopMiddleAgedWomanText (pokered/scripts/BikeShop.asm):
-- always shows the same flavor line, no branching.
-1
View File
@@ -168,7 +168,6 @@ return {
{ "jump_if_true", "come_see" },
{ "set_flag", "EVENT_GOT_POKEBALLS_FROM_OAK" },
{ "give_item", "POKE_BALL", 5, false },
{ "text_sound", "Get_Key_Item" }, -- OaksLab.asm:1060
{ "show_text", "_OaksLabOak1ReceivedPokeballsText" },
{ "show_text", "_OaksLabGivePokeballsExplanationText" },
{ "jump", "end" },
+3 -23
View File
@@ -702,23 +702,6 @@ M.MT_MOON_B2F = {
return false
end,
talk = {
-- MtMoonB2FSuperNerdText: once beaten his line turns on the fossils
-- (scripts/MtMoonB2F.asm:187), which the header's flat `after` can't hold
TEXT_MTMOONB2F_SUPER_NERD = function(game, ow, npc, done)
if not superNerdBeaten(ow) then
engageSuperNerd(game, ow, done)
return
end
local TextBox = require("src.render.TextBox")
local t = game.data.text
local flags = game.save.flags
local line = (flags.EVENT_GOT_DOME_FOSSIL or flags.EVENT_GOT_HELIX_FOSSIL)
and (t._MtMoonB2FSuperNerdTheresAPokemonLabText
or "Far away, on\nCINNABAR ISLAND,\nthere's a POKéMON\nLAB.")
or (t._MtMoonB2fSuperNerdEachTakeOneText
or "We'll each take\none!\nNo being greedy!")
game.stack:push(TextBox.new(game, line, done))
end,
TEXT_MTMOONB2F_DOME_FOSSIL = mtMoonFossil(
"DOME_FOSSIL", "MTMOONB2F_HELIX_FOSSIL", "EVENT_GOT_DOME_FOSSIL"),
TEXT_MTMOONB2F_HELIX_FOSSIL = mtMoonFossil(
@@ -733,8 +716,7 @@ local function museumClerk(game, ow, done, onDecline)
local t = game.data.text or {}
if game.save.flags.EVENT_BOUGHT_MUSEUM_TICKET then
game.stack:push(TextBox.new(game,
t._Museum1FScientist1TakePlentyOfTimeText
or "Take your time,\nand enjoy it all!", done))
"Take your time,\nand enjoy it all!", done))
return
end
-- scripts/Museum1F.asm:72
@@ -752,12 +734,10 @@ local function museumClerk(game, ow, done, onDecline)
{ money = money }))
elseif yes then
game.stack:push(TextBox.new(game,
t._Museum1FScientist1DontHaveEnoughMoneyText
or "You don't have\nenough money.", onDecline or done, { money = money }))
"You don't have\nenough money.", onDecline or done, { money = money }))
else
game.stack:push(TextBox.new(game,
t._Museum1FScientist1ComeAgainText
or "Come again!", onDecline or done, { money = money }))
"Come again!", onDecline or done, { money = money }))
end
end }))
end
+28 -41
View File
@@ -118,36 +118,33 @@ M.ROUTE_15_GATE_2F = {
M.MT_MOON_POKECENTER = {
talk = {
-- command rows, not a Lua handler: give_pokemon needs a runner to AskName (#1407)
TEXT_MTMOONPOKECENTER_MAGIKARP_SALESMAN = {
{ "check_flag", "EVENT_BOUGHT_MAGIKARP" },
{ "jump_if_true", "no_refunds" },
-- MONEY_BOX goes up between the offer and YesNoChoice -- MtMoonPokecenter.asm:31
{ "text_opts", { money = true } },
{ "ask", "_MtMoonPokecenterMagikarpSalesmanIGotADealText" },
{ "jump_if_false", "declined" },
{ "check_money", 500 },
{ "jump_if_false", "no_money" },
{ "give_pokemon", "MAGIKARP", 5 },
-- MtMoonPokecenter.asm:49 `jr nc, .done`: a refused gift is never charged
{ "jump_if_false", "box_full" },
{ "take_money", 500 },
{ "set_flag", "EVENT_BOUGHT_MAGIKARP" },
{ "text_sound", "Get_Item1" },
{ "show_text", "_GotMonText", { RAM = "MAGIKARP" } },
{ "jump", "end" },
{ "label", "box_full" },
{ "show_text", "_BoxIsFullText" },
{ "jump", "end" },
{ "label", "declined" },
{ "show_text", "_MtMoonPokecenterMagikarpSalesmanNoText" },
{ "jump", "end" },
{ "label", "no_money" },
{ "show_text", "_MtMoonPokecenterMagikarpSalesmanNoMoneyText" },
{ "jump", "end" },
{ "label", "no_refunds" },
{ "show_text", "_MtMoonPokecenterMagikarpSalesmanNoRefundsText" },
},
TEXT_MTMOONPOKECENTER_MAGIKARP_SALESMAN = function(game, ow, npc, done)
local t = text(game)
if game.save.flags.EVENT_BOUGHT_MAGIKARP then
push(game, t._MtMoonPokecenterMagikarpSalesmanNoRefundsText
or "Well, I don't\ngive refunds!", done)
return
end
ask(game, t._MtMoonPokecenterMagikarpSalesmanOfferText
or "MAGIKARP! A\nsteal at ¥500!\nWant one?", function(yes)
if not yes then
push(game, t._MtMoonPokecenterMagikarpSalesmanNoText
or "No? I'm only\nselling today!", done)
return
end
if game.save.money < 500 then
push(game, t._MtMoonPokecenterMagikarpSalesmanNoMoneyText
or "You'll need more\nmoney than that!", done)
return
end
game.save.money = game.save.money - 500
game.save.flags.EVENT_BOUGHT_MAGIKARP = true
local Commands = require("src.script.Commands")
Commands.give_pokemon({ save = game.save, game = game, overworld = ow },
"MAGIKARP", 5)
push(game, t._GotMonText or "{PLAYER} got\n{RAM:wNameBuffer}!", done)
end)
end,
},
}
@@ -516,17 +513,7 @@ M.ROUTE_24 = {
push(game, text(game)._Route24CooltrainerM1YouCouldBecomeATopLeaderText,
done)
else
-- scripts/Route24.asm:125
ow:engageTrainer(npc, function()
if ow:trainerDefeated(npc) then
-- scripts/Route24.asm:62
push(game,
text(game)._Route24CooltrainerM1YouCouldBecomeATopLeaderText,
done)
else
done()
end
end, text(game)._Route24CooltrainerM1DefeatedText, true)
ow:engageTrainer(npc, done)
end
end
if not flags.EVENT_GOT_NUGGET then
+4 -5
View File
@@ -7,9 +7,9 @@ local M = {}
local function text(game) return game.data.text end
local function push(game, s, done, opts)
local function push(game, s, done)
local TextBox = require("src.render.TextBox")
game.stack:push(TextBox.new(game, s, done, opts))
game.stack:push(TextBox.new(game, s, done))
end
-- PrintText on a text_end string returns with the box still drawn and
@@ -236,6 +236,7 @@ M.CINNABAR_GYM = {
if yes == machine.yes then
-- CinnabarGymQuizCorrectText: item jingle, then the gate
-- slides open (SFX_GO_INSIDE) if it was still locked
Sound.play(game.data, "Get_Item1")
push(game, t._CinnabarGymQuizCorrectText
or "You're absolutely\ncorrect!\fGo on through!", function()
if not game.save.flags[gymGateFlag(index)] then
@@ -243,9 +244,7 @@ M.CINNABAR_GYM = {
Sound.play(game.data, "Go_Inside")
end
applyGymGates(game, ow)
end, { preSound = function()
return Sound.play(game.data, "Get_Item1")
end })
end)
return
end
Sound.play(game.data, "Denied")
+6 -6
View File
@@ -17,9 +17,9 @@ local function surfingPikachu(game)
return nil
end
local function push(game, text, done, opts)
local function push(game, text, done)
local TextBox = require("src.render.TextBox")
game.stack:push(TextBox.new(game, text, done, opts))
game.stack:push(TextBox.new(game, text, done))
end
-- the two-variant posters: the surf-capable line once a surfing
@@ -69,11 +69,11 @@ return {
TEXT_SUMMERBEACHHOUSE_PIKACHU = function(game, ow, npc, done)
local t = game.data.text
-- scripts/SummerBeachHouse.asm:68
push(game, t._SummerBeachHousePikachuText or "PIKACHU: Pikaa!",
done, { auto = { wait = true, delay = 0, sound = function()
return require("src.core.Sound").playCry(game.data, "PIKACHU")
end } })
function()
require("src.core.Sound").playCry(game.data, "PIKACHU")
done()
end)
end,
TEXT_SUMMERBEACHHOUSE_POSTER1 = poster(1),
-120
View File
@@ -1,120 +0,0 @@
# Link play: threat model and what the code actually guarantees
Link play is the only part of this game that reads bytes written by
somebody else. This is what it defends against, what it does not, and
where each guarantee lives.
## The boundary
Everything a peer or the relay sends arrives as one JSON object per line.
There is exactly one place it becomes a message:
src/link/Net.lua reads bytes, frames lines, decodes JSON
src/link/Wire.lua rebuilds each line as a typed message
src/link/Session.lua the only path from a transport into a mode
`Session:update` runs `Wire.sanitize` on every message before anything
else sees it. A schema returns a **new** table holding only the fields it
names, at the Lua types it names, so the rest of `src/link/` can read
`msg.slot`, `msg.parts.actives` or `msg.mons[i].dvs.hp` directly and be
right by construction. A message with no schema (a mod's, or a future
build's) keeps a bounded, scalar-only copy of its payload instead of
being dropped.
A message that fails its schema is **dropped and logged**, never fatal.
Latching a terminal failure would hand a hostile peer a cheaper
disconnect than sending nothing at all.
### Why the bounds are loose
Wire's numeric bounds are deliberately wider than the game's own clamps in
`Protocol.unpackMon`. Both peers run identical clamps over identical
packets; a bound that bit an honest value would change one side's copy of
a mon and desync the lockstep. Wire's job is types and sizes. Rules are
`Protocol`'s job, and it keeps its own clamps for the callers that reach
it without a Session (the mod API, `tests/`).
### Containment behind it
Assume something still gets through:
- `Game:step` pcalls the link pump, and pcalls `stack:update` **only
while a link session is active**. On a throw, `Game:breakLink` closes
the connection, unwinds to the overworld and says "The link was
broken." Outside link play the stack is unguarded on purpose: a blanket
pcall would swallow real engine bugs and leave the game silently wrong
instead of loudly broken.
- `Net` caps `rxBuf` at 256KB and its per-frame read at 512KB, so a peer
that never sends a newline ends as a clean disconnect.
- `Json.decode` refuses documents nested past 64 levels, and takes an
optional length cap that the link path passes and the mod-manifest path
does not.
## The relay (`../pokeserver`)
- A line that is not a JSON **object** with a string `type` is dropped
before any handler runs, and `onLine` is wrapped in try/catch.
`server.js` installs `uncaughtException`/`unhandledRejection` handlers:
one bad packet must never take every live match down with the process.
- Line buffers are capped, lines per second are capped, connections per
IP and in total are capped, and an unbound connection that never hosts
or joins is swept after 30s.
- `SERVER_ONLY` is the set of message types the server is the only
legitimate author of (`peer_gone`, `bracket_update`, `match_start`,
`tournament_over`, `spectate`, ...). A peer that sends one has them
dropped rather than forwarded, so a bracket opponent cannot forge a
tournament result or fake "your opponent left".
- Trainer names are reduced to a printable subset and capped at the same
10 characters the game enforces, on the way in, because they are
rendered by the dashboard and broadcast to every participant.
`pokeserver/test/hostile.js` is the regression net for all of that.
## What is NOT defended
**Party legality is trust-the-client.** Online play meets strangers, and
`Handshake.onlineAllowed` is a Lua function in the same VM the mods load
into. It cannot be made tamper-proof in-process, and pretending otherwise
would only cost honest mod authors. What lockstep and
`Protocol.unpackMon`'s recompute-from-species-data *do* guarantee is that
a cheater cannot invent stats, moves, or a shiny: every derived value is
rebuilt locally from real species data. They can send a legal party they
farmed or edited. That is the honest boundary.
What the relay does instead is **observe and record**. It already sees
every `hello`, so it keeps each connection's self-reported
`engineVersion`, `fingerprint` and `linkModified`, compares the two sides
of a room or a live tournament match, and logs and surfaces a
`modded` / `fingerprint_mismatch` / `version_skew` flag on the dashboard.
A patched client can still lie; what it cannot do is lie without the
tournament organizer having a record of it.
Client-side attestation is deliberately not built. This is an
open-source Lua game: it would be theater, and it would break honest
mods.
**The relay has no TLS.** Port 7778 is plaintext, so party contents,
trades and trainer names are visible to anyone on the network path. There
is nothing secret in a Pokemon party, but it is a real property of the
system and not an oversight. Fixing it means a TLS terminator in front of
the relay and a client that speaks it, which is a version break for every
shipped build.
**The dashboard has no default password.** `DASHBOARD_PASSWORD` is
required; with it unset the relay runs and the dashboard simply does not
start. It is still Basic Auth over plain HTTP, so it belongs behind an
IP restriction or an SSH tunnel (`pokeserver/DEPLOY.md`).
## Tests
luajit tests/link_hostile.lua every message type x every wrong type
luajit tests/link_desync_fuzz.lua lockstep fuzz, plus a mutation mode
luajit tests/run_link_tests.lua both of the above, plus the rest
cd ../pokeserver && npm test relay smoke, 16-player bracket, hostile
`tests/link_hostile.lua` builds its corpus from a template per message
type, replaces each field (and several nested ones) with every wrong Lua
type, and drives the survivors through the real trade session, a real
lockstep battle, a real spectator battle, and the tournament screen
**including its draw** -- because the two nastiest payloads are
delayed-fuse ones that crash on render rather than on receipt.
+3 -4
View File
@@ -512,9 +512,8 @@ gains a field instead of the name gaining a prefix.
id under Gen 1's `name` key, which is the one payload difference the
numeric flag space forces.
- *Menus (`src/ui/gen2/`):* `ui.start_menu.items`, `ui.title_menu.items`,
`ui.options.rows`, `ui.party.submenu`, `ui.party.grid_navigation`,
`ui.naming.grid`, `ui.pc.items`, `ui.list_menu`, `transition.style`.
`ui.list_menu` covers Gold's script
`ui.options.rows`, `ui.party.submenu`, `ui.naming.grid`, `ui.pc.items`,
`ui.list_menu`, `transition.style`. `ui.list_menu` covers Gold's script
menus (`ScriptMenu.lua`); the `Chrome.List` widget the START and title
menus draw with does not raise it yet, so those two are composed through
their own hooks only.
@@ -555,7 +554,7 @@ gains a field instead of the name gaining a prefix.
passes `game`; positions 2-4 (mon, row, trigger) match.
- *The frame (`src/core/Game2.lua`):* hooks `input.step`, `input.pointer`,
`render.zones`, `render.compose`, `render.output_enabled`, `render.output`,
`render.letterbox`, `render.hud`, `render.viewport`, `render.window`. Each sits
`render.letterbox`, `render.hud`. Each sits
at the same moment `src/core/Game.lua` and `src/render/Renderer.lua` raise it
-- the logic tick before the pad is read, a pointer the touch overlay gets
first refusal on, the palette zone list handed to the present pass, the
+18 -65
View File
@@ -223,29 +223,20 @@ scripts, battles, and transitions leave the party untouched.
start at the player's current position. Both games expose `bicycle`, `fish`,
`cut`, `surf`, `strength`, `flash`, `dig`, and `teleport`; Gold additionally
exposes `headbutt`, `whirlpool`, `waterfall`, `sweet_scent`, and the
contextual `squirtbottle` key item. Red additionally exposes `softboiled` with
eligible `sources`; each source contains its eligible `targets`. Fishing rows
include the owned rods that are valid choices. The list is empty while the
world is busy, and omits an action whenever its item, move, badge, terrain, or
engine state forbids it.
contextual `squirtbottle` key item. Fishing rows include the owned rods that
are valid choices. The list is empty while the world is busy, and omits an
action whenever its item, move, badge, terrain, or engine state forbids it.
The optional second return is `"world is busy"` during transient input locks
or `"no overworld"` before a playable world exists.
Call `mod.world:useFieldAction(id, opts)` to perform a listed action through
the active game's own field-item path. Fishing accepts `{ rod = "OLD_ROD" }`
and chooses automatically when only one rod is available. Red's `softboiled`
accepts one-based `{ sourceSlot, targetSlot }` values copied from its action
record. Invalid, stale, and busy requests return `nil` plus a reason without
changing game state. Mods do not need generation-specific badge, terrain,
bike, fishing, or field-move
and chooses automatically when only one rod is available. Invalid, stale, and
busy requests return `nil` plus a reason without changing game state. Mods do
not need generation-specific badge, terrain, bike, fishing, or field-move
logic. Action lists are extensible; callers should render the records they
understand and ignore unknown ids rather than assuming a fixed list length.
Red exposes FLY separately because it requires a destination picker:
`mod.world:canFly()` reports whether FLY is eligible at the current location,
and `mod.world:flyTo(mapId)` accepts only a visited destination from the native
Fly town list. Gold does not expose these two methods yet.
## Read-only battle snapshots
`mod.battle:snapshot()` returns `nil` outside a battle and a copied battle
@@ -268,10 +259,10 @@ an optional stock `catchChance` percentage.
`prompt` describes the currently visible choice (`menu`, `moves`, `party`,
`advance`, `safari`, or `mimic`) and is `locked` when another screen or battle
phase owns input. Generation-specific features remain optional: Gen 1 includes
battle medicine, balls, catch previews, Safari balls, and Mimic choices. Gold
exposes balls and their exact stock catch previews; targeted medicine remains
screen-owned and is omitted rather than guessing at its pocketed PACK flow.
Callers should ignore unknown fields and tolerate absent optional ones.
battle medicine, balls, catch previews, Safari balls, and Mimic choices;
Gold currently returns an empty `items` list rather than guessing at its
pocketed PACK flow. Callers should ignore unknown fields and tolerate absent
optional ones.
## Battle menu intents
@@ -287,16 +278,10 @@ The shared Red, Blue, Yellow, and Gold intents are:
- `{ kind = "move", slot = 1..4 }`
- `{ kind = "back" }` while the move menu is active
Red, Blue, and Yellow also expose their generation-specific choices:
- `{ kind = "safari", action = "ball" }` (`bait`, `rock`, and `run` are the
other accepted actions)
- `{ kind = "mimic", index = 1 }` using an entry's snapshot `index`
Menu choices and moves use the same engine methods as the native controls;
`party` and `item` open the native screens rather than exposing or duplicating
their mutable logic. Tutorial, link, forced, stale, and covered battle states
refuse core intents. Use `mod.input` for ordinary text advance.
their mutable logic. Tutorial, link, Safari, forced, stale, and covered battle
states refuse these core intents. Use `mod.input` for ordinary text advance.
## Rendering pipelines
@@ -669,14 +654,11 @@ the wrapper is visible during that same fixed step. The callback receives
`input.pointer` delivers uncaptured gameplay pointer events -- touches and
real mouse input alike. The callback receives `(next, game, ev)` where `ev`
is `{ phase, source, id, x, y, gameX, gameY, insideGame, dx, dy, pressure,
button }`: `phase` is
is `{ phase, source, id, x, y, dx, dy, pressure, button }`: `phase` is
`"pressed"`, `"moved"`, `"released"` or `"cancelled"`; `source` is `"touch"`
or `"mouse"`; `id` is the LÖVE touch id or `"mouse"`; and the coordinates
`x` / `y` are LOVE window units, while `gameX` / `gameY` are local to the
active game viewport and `insideGame` says whether the pointer is inside it.
Without a custom viewport both coordinate pairs are identical. The on-screen
touch controls keep first refusal: a
are LOVE window units, the same space `render.hud`'s viewport and the touch
overlay lay out in. The on-screen touch controls keep first refusal: a
pointer that begins on a virtual control belongs to the pad for its whole
lifecycle and never reaches the hook, while one that begins outside stays
visible even if it later crosses a control. A real mouse reaches the hook
@@ -709,22 +691,6 @@ composited and before touch controls draw. The window-space viewport contains
and `dpiY`, so a tool can use the letterbox margins without drawing over the
playfield or pushing an updating game state.
`render.viewport` lets a layout mod reserve the window-space rectangle in which
the game renders. It receives `(next, ctx)` with the full window's `width`,
`height`, `pixelWidth`, `pixelHeight`, `dpiX`, `dpiY`, and `generation`, and
returns `{ x, y, width, height }`. The engine clamps that rectangle to the
window and makes game layout, safe-area calculations, and rendering use it as
their display. Set `capture = true` to request a composition canvas even when
the rectangle fills the window. With no subscriber, no canvas is allocated and
the normal presentation path is unchanged.
When a viewport is active, `render.window` receives `(next, game, ctx)` after
the game frame has been captured. `ctx` contains its `canvas`, `x`, `y`,
`width`, `height`, the full `windowWidth` / `windowHeight`, `dpiX`, `dpiY`, and
`generation`. Calling `next(game, ctx)` draws the game at the requested origin;
a wrapper may instead compose that canvas with its own UI. Touch controls remain
full-size OS-window chrome and draw after this hook.
`render.compose` wraps the whole-window composite in `Renderer:endFrame`. It
receives `(next, renderer, ctx)`; returning `true` without calling `next` hands
the mod full control of the window, while calling `next` runs the engine's
@@ -733,24 +699,11 @@ the finished `worldCanvas` and `uiCanvas` with their SGB `zones` / `worldZones`,
`worldActive`, the frame metrics (`ww`, `wh`, `pw`, `ph`, `ox`, `oy`, `vpw`,
`vph`, `scale`, `Sx`, `Sy`, `dpiX`, `dpiY`), `renderer:blitCanvas(...)` for a
palette-correct blit of either canvas into an arbitrary screen rect, and the
`secondScreen` bridge (`available()` / `detected()` / `push(...)` /
`pollTouch()` / `setEnabled`) for driving a second physical display.
`detected()` reports a connected target even while its output is being created;
`available()` means it can accept a frame now. `push(imageData, w, h)` retains
the original contract. Its optional `background` (`0xRRGGBB`) and `preference`
arguments request an extended presentation; a preference ending in `:cover`
fills and crops the target, while other values preserve the whole frame.
Android also accepts `handheld` or `secondary` (with an optional `:cover`
suffix) as routing hints; unsupported or unavailable targets fall back to the
other connected display.
`pollTouch()` returns the oldest queued event as `"action,x,y"` in submitted-frame
coordinates, or `nil`.
`secondScreen` bridge (`available()` / `push(imageData, w, h)` / `pollTouch()` /
`setEnabled`) for driving a second physical display. `pollTouch()` returns the
oldest queued event as `"action,x,y"` in submitted-frame coordinates, or `nil`.
This is what lets a mod lay the two passes out as two stacked Game Boy screens,
or push one onto a second screen, without the engine knowing the layout.
On process-capable Windows, Linux and macOS hosts without a native display
bridge, enabling this facade opens a second resizable app window instead. It
uses the same `available`, `detected`, `push`, `pollTouch` and `setEnabled`
contract, so a mod does not need a desktop-specific rendering path.
`render.output_enabled` and `render.output` are the later, whole-window seam
for mods that need the engine's normal composite rather than its separate
+20 -2
View File
@@ -11,10 +11,28 @@ Features intentionally added beyond the original Pokémon Red, Blue, and Yellow
* **Persistent custom options** stored separately from game saves
* **Optional widescreen battle layout**
* **Mobile touch controls** with editable layouts, vibration, and orientation settings
* **Touch skins** in RetroArch overlay format and Delta `.deltaskin` (including PDF-wrapped bezel art), with per-button press states and Super Game Boy borders
* **Translation and custom font support**
* **Built-in save editor** for parties, boxes, items, events, maps, and Pokédex data
* **Tiled map editing tools** for mod authors
* **Pokédex diploma and printer image exports**
* **Community mod browser**
* **Soft reset button combination**
* **Keyboard and controller rebinding**
* **Mod profiles** with separate mod settings and save slots
* **Sandboxed mods**: an installed mod can read only its own folder and write only its own storage
* **Improved launcher and save editor UI**, including background downloads and update checks
* **Direct-launch options** for shortcuts, Steam entries, and handheld frontends
## Gen 2 Specifics
## Pokémon Gold (Gen 2)
* **COLOR, zoom, tilt, GBC FX, and quick save/load**
* **UI that stays fixed while the overworld zooms**
* **Border-block surrounds** for maps smaller than the screen
* **Gold-specific launcher options**
* **Optional widescreen battle layout**
* **Skippable trade animation** with B or START
* **QUIT and EXIT GAME** from the menus
* **Mod manager** with Gen 1 mod adapters, per-game targeting, and `modkit gen2check`
* **Followers** for mods, plus Gen 2-only registries and hooks
* **On-screen touch pad** and controller SELECT for registered items
* **Older mods keep loading** after the sandbox change, through per-mod compat stand-ins for the pre-sandbox globals
-278
View File
@@ -1,278 +0,0 @@
# Touch skins and the Skin Studio
A **skin** replaces the on-screen controls wholesale: a bezel image, a
control layout, and the rectangle the Game Boy screen is drawn into. Engine:
`src/core/TouchSkin.lua` (model, parsers, zip export), `src/core/TouchControls.lua`
(draw and input), `src/render/Renderer.lua` (the screen viewport),
`src/core/DeltaSkin.lua` (Delta `.deltaskin` import and export),
`src/ui/SkinStudio.lua` (the desktop editor). Tests:
`tests/engine/touch_skin_test.lua`, `tests/engine/skin_studio_test.lua`,
`tests/engine/skin_studio_ux.lua`,
`tests/engine/skin_studio_image_import.lua`,
`tests/engine/skin_format_import_test.lua`,
`tests/engine/launcher_skins_tab.lua`,
`tests/engine/launcher_skins_ux.lua`.
Skins are picked in the launcher's **Skins** tab, which also imports them and
opens the studio. `options.touchControls.skin` holds the folder name.
## Formats
Three load: the native `skin.lua`, a RetroArch overlay `.cfg`, and a Delta
`.deltaskin`. `skin.lua` wins when a folder has more than one. The launcher
badges each installed skin with the format it was read from.
**RetroArch overlay `.cfg`.** The libretro `common-overlays` collection loads
as-is. Supported keys:
| Key | Meaning |
| --- | --- |
| `overlays` | page count |
| `overlayN_name` | page name, the target of `next_target` |
| `overlayN_overlay` | bezel image |
| `overlayN_full_screen` | stretch the page to the window |
| `overlayN_rect` | page placement, default `0,0,1,1` |
| `overlayN_aspect_ratio` | design aspect; the overlay letterboxes to it even when full screen |
| `overlayN_range_mod`, `overlayN_alpha_mod` | desc defaults |
| `overlayN_viewport` | `x,y,w,h`, the screen cutout |
| `overlayN_viewport_fill` | parsed; the engine always fits, see below |
| `overlayN_descM` | `binds,x,y,shape,range_x,range_y` |
| `overlayN_descM_overlay` | control art |
| `overlayN_descM_next_target` | page to switch to |
| `overlayN_descM_range_mod`, `_alpha_mod` | per-control overrides |
| `overlayN_descM_reach_x/_y/_up/_down/_left/_right` | hitbox reach |
`x,y` is the centre and `range_x,range_y` are half extents, both normalized.
Hitboxes are `radial` or `rect`. Pipe-separated binds (`left|down`) are one
control that holds both. A `nul` desc is decoration: it draws and never
captures a touch.
The area desc types are expanded rather than ignored: `dpad_area`,
`abxy_area`, `analog_left` and `analog_right` each become eight hitboxes over
the same area, one per 45 degree sector measured from its centre, the way
RetroArch resolves them: there is no neutral middle, and the four corner
sectors fire two inputs. Any `_up` / `_down` / `_left` / `_right` override and
the per-side reach are honoured, and the desc's own art is kept as decoration
over the top. Exporting a cfg folds the eight back into the one area desc they
came from. `retrok_<key>` is a keyboard bind.
Alpha follows RetroArch (`input_driver.c`, `input_overlay_post_poll`): every
image sits at the overlay opacity, and a pressed control's image swaps to
`opacity * alpha_mod`. So `alpha_mod` above 1 lights a control up and below 1
fades it out, and both directions read as a press animation.
**Native `skin.lua`.** This module's own model written back out: one Lua
table, no flat key space, and a separate `imagePressed` per control that a
`.cfg` cannot express. Loaded with an empty environment, so a skin authored by
a stranger cannot reach `love` or `io`. Sizes here are full width and height
rather than RetroArch's half extents, because that is what an editor's numeric
fields mean.
```lua
return {
name = "my_skin",
pages = {
{
name = "main",
image = "img/bezel.png",
fullScreen = true,
viewport = { x = 0.0, y = 0.0, w = 1.0, h = 0.5, fill = false },
controls = {
{ bind = "a", x = 0.87, y = 0.72, w = 0.18, h = 0.10,
shape = "radial", image = "img/a.png", imagePressed = "img/a_down.png" },
},
},
},
}
```
**Delta `.deltaskin`.** A zip (any wrapping folder is stripped) holding an
`info.json` plus its art. The `representations` tree is walked
device / display type / orientation, and every orientation that exists becomes
a page; `page.orient` is the orientation key, so a portrait/landscape pair
auto-rotates like a RetroArch one. Item `frame` rects are top-left plus size in
`mappingSize` points and are converted to the native centre plus half extent;
`extendedEdges` merge per key into the reach fields; `mask: "circle"` becomes a
radial hitbox. A `dpad` or `thumbstick` item expands into the 3x3 grid, so the
corners fire two directions. `screens[1].outputFrame` (or the legacy
`gameScreenFrame`) becomes the screen cutout. A portrait page with neither
keeps `mappingSize` as the overlay aspect, sits at the bottom of the
window, and puts the Game Boy picture in the leftover space above -- the
usual GBA4iOS controller-deck layout. Pages that name a screen rect still
stretch to the window the way Delta does. Host functions map to
engine hotkeys: `menu` to `menu_toggle`, `fastForward` to
`hold_fast_forward`, `toggleFastForward` to `toggle_fast_forward`;
`quickSave` and `quickLoad` have nothing to bind to and drop to decoration.
Both `com.rileytestut.delta.game.*` and Manic's `public.aoshuang.game.*`
identifiers are accepted, and a non Game Boy system warns instead of failing.
PDF artwork is usually a JPEG wrapped so iOS can scale it (Delta's
Image-to-PDF skins, Preview exports, and the like). Import extracts that
JPEG and draws it; a true vector PDF with no embedded image is still refused,
with a message asking for a PNG version. GBA4iOS `.gbcskin` / `.gbaskin` files
are an older, incompatible schema and are refused by name.
## Bindable actions
The eight Game Boy buttons: `a`, `b`, `start`, `select`, `up`, `down`,
`left`, `right`.
Engine hotkeys, handled in `Game:touchSkinHotkey`:
| Bind | Effect |
| --- | --- |
| `overlay_next`, `overlay_previous` | switch page, honouring `next_target` |
| `hold_fast_forward`, `fast_forward` | fast forward while held |
| `toggle_fast_forward` | step the speed option |
| `reset` | soft reset to the title |
| `menu_toggle` | open OPTIONS |
`screenshot`, `pause_toggle` and `exit_emulator` are recognised but have no
handler yet: a control bound to them draws and does nothing. Anything else,
`rewind` included, is not in the bind table at all, so the control falls back
to decoration and never captures a touch.
As an extension to the format, `key:<name>` presses any keyboard key, which is
how a skin button reaches a mod hotkey.
## The screen viewport
`overlayN_viewport` is the cutout the picture is fitted into. The Game Boy
screen keeps its whole-pixel scale and letterboxes inside that rect rather than
stretching to it, so a bezel gets an exact 160x144 picture; `viewport_fill` is
parsed but does not stretch. `overlayN_viewport_expand = true` is an extension
that lets a widescreen bezel take the filling survey-zoom world view instead.
A viewport also implies the faithful-ratio lock. Without it the world pass
expands to fill the cutout and you get more map instead of a Game Boy screen.
Border art often ships with a transparent hole and no `viewport` key. **Detect
screen from bezel** in the studio measures the hole out of the art's alpha
channel and writes the rect.
## Bezels versus pads
A skin whose active page binds nothing is a frame rather than a pad: a TV
surround, a handheld shell, a Super Game Boy border. Those draw on **desktop**
as well, where the touch overlay itself does not, and a gamepad does not hide
them. Anything that binds a button still follows the usual mobile /
`POKEPORT_TOUCH` rule.
## Installing
Four roads, all of them landing in `skins/` in the save directory:
* **Import** on the Skins tab opens the host file picker for a `.zip` or a
`.deltaskin`.
* **Paste a skin link** in the tab's URL row, then **Add**. The download runs
on the fetch pool (`src/net/Fetch.lua`), so the launcher stays live, and the
row shows a spinner until it lands. A link to a bare `overlay.cfg` is wrapped
into an archive on the way in. This is the road that works on a phone, where
there is no file picker to speak of.
* Drop a `.zip` or `.deltaskin` on the launcher window while the Skins tab is
open.
* Copy a folder or archive into `skins/` by hand.
An archive is mounted in place, so there is nothing to unpack. It needs one
`skin.lua`, `.cfg` (`overlay.cfg` is preferred when there are several) or
`info.json`, plus the images it names.
Two ship bundled, both from libretro's `common-overlays` under CC-BY-4.0:
| Skin | Source | Shape |
| --- | --- | --- |
| `gb_anim` | `gamepads/gb_anim_portrait` | handheld shell, working buttons, two pages |
| `tv_crt` | `borders/tv-integer` | CRT television frame, no buttons |
Attribution lives in each folder's `README.md`. `tv_crt` is a photograph of a
real television: CC-BY-4.0 upstream, but treat it as a test asset rather than
shipping branding.
## The studio
Launcher, Skins tab, **Open Skin Studio**, or the gear on any skin row to open
that skin. Desktop only: the launcher does not offer it on Android or iOS,
because it wants a mouse, typed coordinates and room for an inspector.
**Canvas.** A mock device at a chosen preset, so a phone skin is authored at
phone proportions on a desktop monitor.
| Preset | Size |
| --- | --- |
| Phone portrait / landscape | 1080x1920, 1920x1080 |
| Tablet portrait / landscape | 1536x2048, 2048x1536 |
| Steam Deck | 1280x800 |
| Desktop 1080p | 1920x1080 |
| Ultrawide 21:9 | 2560x1080 |
| Super Game Boy border | 256x224 |
The Super Game Boy preset locks the viewport to the real screen window,
160x144 at (48,40), so an SGB border cannot be drawn out of register.
**Editing.** Click a control to select it, drag to move, eight handles to
resize. Arrow keys nudge the selection one canvas pixel, shift-arrow ten. While
a control is dragged it snaps to the centres and edges of the other controls
and of the page itself when it comes within a few pixels, and the guide it
snapped to is drawn. X / Y / W / H are in canvas pixels, so a control can be
typed to the coordinate its art was drawn at. **Back** and **Front** move the
selection through the draw order. Bind, hitbox shape, hit reach and idle and
pressed images are per control; the bezel, the pages and the screen cutout are
per page. The cutout is itself a draggable element with a 10:9 lock.
**Bind** opens a grid of every bind the engine understands: the eight Game Boy
buttons, the diagonal pairs, every hotkey, a few `key:` entries, and
decoration. The COMBINE chips at the top toggle one part at a time, which is
how a pipe bind like `left|down` is built without typing it.
**Undo** and **Redo** in the top bar cover every edit (ctrl+Z / ctrl+Y, or
`u` / shift+`u` without a keyboard modifier). The stack holds the last 50
actions. `L` toggles the bind captions drawn on the canvas.
Each page can **Lock** to portrait or landscape. With **Match canvas** on
(the default), the page list picks a matching mock device and the canvas preset
picks a matching page. Turn Match canvas off to look at a portrait page on a
landscape device. **Pages** opens the page list, where a page is selected,
renamed or deleted.
Starting a new skin, opening another one or closing the studio with unsaved
edits prompts first, with Save first / Discard / Cancel.
A RetroArch overlay whose pages are already named portrait / landscape
(the auto-rotate convention) locks those pages and turns Match canvas on
when you open it. You do not have to click Lock first.
**Art.** The **Bezel**, **Idle art** and **Pressed art** rows open a
thumbnail grid of the images already in the skin folder, with `(none)` first;
the **Import** button there and beside each row opens the host file picker (`src/core/FilePicker.lua`: osascript, PowerShell,
zenity/kdialog) and copies the chosen PNG or JPG into `img/` under the name in
the SKIN field, then assigns it to that slot. Dropping a PNG or JPG on the
window does the same for whichever slot was last touched. A new bezel does not
move the screen cutout: press **Detect screen from bezel** to measure it out of
the art's alpha.
**Testing.** **Test** makes the canvas live: clicking presses real Game Boy
buttons and the footer reports what is held. **Play** saves the skin, selects
it, and boots the game with it.
**Saving.** **Save** writes `skins/<name>/skin.lua` and copies every image the
skin names, so the folder stands alone. **Export** offers three formats, and
the Skins tab's gear offers the same three for any installed skin:
| Export | Contents |
| --- | --- |
| gen1recomp `.zip` | the native `skin.lua`, the images, and the original `.cfg` when it came from one |
| RetroArch `.zip` | an `overlay.cfg` generated from the model, plus the images |
| Delta `.deltaskin` | an `info.json` generated from the model, plus the images |
All three are written store-only (`src/core/SkinZip.lua`) into `skins/_export/`
in the save directory, which is outside the folder the skin list scans, so an
export can never shadow the skin it came from. The notice names the full path
so a phone can find the file in its own file manager. On desktop **Show the
exported file** opens that folder.
## Not implemented
True vector Delta skins (PDF artwork with no embedded JPEG). Those still need
a PDF renderer this engine does not carry, so they are refused with a message
rather than imported half-drawn. PDF files that wrap a JPEG, the usual Delta
skin case, extract on import.
+1 -3
View File
@@ -169,7 +169,6 @@ the NX runtime modules `src/core/NxAssetOverlay.lua`, `src/core/Platform.lua`,
`tests/engine/assets_version_fallback_test.lua`,
`tests/engine/nx_generated_guard_test.lua`,
`tests/engine/nx_yellow_boot_test.lua`,
`tests/engine/cache_fs_gold_nx_load_test.lua`,
`tests/engine/switch_diagnostics_test.lua`, `tests/engine/platform_nx_*`,
or the Switch-related workflow YAML), CI runs:
@@ -180,8 +179,7 @@ or the Switch-related workflow YAML), CI runs:
`luajit tests/switch_transfer_docs_test.lua`, and the NX engine suites
headlessly (`luajit tests/engine/assets_version_fallback_test.lua`,
`luajit tests/engine/nx_generated_guard_test.lua`,
`luajit tests/engine/nx_yellow_boot_test.lua`,
`luajit tests/engine/cache_fs_gold_nx_load_test.lua`).
`luajit tests/engine/nx_yellow_boot_test.lua`).
2. **Fused NRO build** only on the **main** repository
(`bryanthaboi/gen1recomp`), on the self-hosted Mac runner
(`scripts/build_switch.sh --fetch --fused`), and only when the workflow
+9 -13
View File
@@ -3,7 +3,7 @@
Every GitHub Release that includes Switch support ships an SD-ready zip:
`gen1recomp-*-switch.zip`. Extract it at the root of your microSD (install
or update, same steps), launch with **title override**, then import your
own legal `.gb` / `.gbc` ROM.
own legal `.gb` ROM.
> You need a console that can run Switch homebrew (custom firmware / hbmenu).
> This project does not help you set that up.
@@ -91,14 +91,13 @@ Do **not** launch from the Album applet path for normal play.
This project ships **no** game data. On first launch:
1. Put your own legally obtained Pokémon Red, Blue (`.gb`), Yellow, or
Gold (`.gbc`) dump into `switch/gen1recomp/pokemon-love2d/imports/` (the
launcher also shows the live save-dir path). All four can sit in the
1. Put your own legally obtained Pokémon Red, Blue (`.gb`), or Yellow
(`.gbc`) dump into `switch/gen1recomp/pokemon-love2d/imports/` (the
launcher also shows the live save-dir path). All three can sit in the
same folder.
2. Use **Scan again** on that game's tab (Red / Blue / Yellow / Gold).
Rescan matches by ROM SHA-1 for the open tab only. A Red dump never
imports from the Yellow tab (and vice versa). Gold is Beta in the
launcher; a clean US Gold dump is enough to Play.
2. Use **Scan again** on that game's tab (Red / Blue / Yellow). Rescan
matches by ROM SHA-1 for the open tab only. A Red dump never imports
from the Yellow tab (and vice versa).
## 5. Import / Export a raw `.sav`
@@ -110,13 +109,10 @@ SD / FTP, same transfer methods as ROMs. Paths are **per game**:
| Red | `imports/saves/red/` | `exports/red/` |
| Blue | `imports/saves/blue/` | `exports/blue/` |
| Yellow | `imports/saves/yellow/` | `exports/yellow/` |
| Gold | `imports/saves/gold/` | `exports/gold/` |
(Under the save dir `pokemon-love2d/`. The zip already creates these folders.
Gold cart `.sav` import/export is not supported yet -- the folders exist so
MTP browsing matches the other games. Gold progress still saves in-engine.)
(Under the save dir `pokemon-love2d/`. The zip already creates these folders.)
1. Copy a Gen 1 `.sav` (32 KB) into that game's inbox under the save dir
1. Copy a Gen1 `.sav` (32 KB) into that game's inbox under the save dir
([switch-transfer.md](switch-transfer.md)).
2. With the game's ROM already imported, open **that game's tab**
**SAVE FILES****Import save**. Only that folder is scanned.
+4 -4
View File
@@ -22,8 +22,8 @@ Player install (what to download, title override) stays in
| Loose iteration pair | `sdmc:/switch/gen1recomp/gen1recomp.nro` **and** `game.love` beside it |
| ROM inbox | LÖVE save dir → `imports/` (launcher shows the live `getSaveDirectory()` path; under MTP often `1: SD Card/<save identity>/imports/`) |
| Mod zip inbox | Same save dir → `imports/mods/` then MODS → **Scan again** |
| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow\|gold/` then that game's SAVE FILES → **Import save** (Gold cart `.sav` not supported yet) |
| Save exports | Same save dir → `exports/red\|blue\|yellow\|gold/` (pull after **Export save**; Gold cart `.sav` not supported yet) |
| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow/` then that game's SAVE FILES → **Import save** |
| Save exports | Same save dir → `exports/red\|blue\|yellow/` (pull after **Export save**; MTP / SD / FTP) |
| Opt-in diagnostics | Empty `switch-debug.txt` in the save dir → `switch.log` |
| Lua error log | `lua-error.log` in the save dir |
@@ -54,8 +54,8 @@ macOS, not a Mac-only requirement.
3. Create `switch/gen1recomp/` if needed; extract the release zip at SD root
(or copy NRO / `game.love` for loose).
4. For ROMs/mods/saves, open the save-dir `imports/`, `imports/mods/`,
`imports/saves/<red|blue|yellow|gold>/`, or `exports/<red|blue|yellow|gold>/`
path the launcher prints.
`imports/saves/<red|blue|yellow>/`, or `exports/<red|blue|yellow>/` path the
launcher prints.
5. Wait for the queue; refresh; exit MTP responder; title-override launch.
macOS clients often create AppleDouble sidecars (`._Something.zip`,
-65
View File
@@ -1,65 +0,0 @@
# Tiled map editing (mod authoring)
`tools/tiled_export.py` turns the imported ROM cache into a
[Tiled](https://www.mapeditor.org) workspace, so maps can be edited in a
real map editor and exported back out as a mod. The original had no map
editor at all; the port's own map data is plain Lua, which is what makes
this a data path rather than an asset path.
Editing is done in our own Tiled build,
[bryanthaboi/tiled_gen1recomp](https://github.com/bryanthaboi/tiled_gen1recomp/releases),
which ships the `gen1-mod-export` extension the workspace relies on. Grab it
from that repo's releases; upstream Tiled opens the workspace but cannot
export a mod out of it.
```sh
python3 tools/tiled_export.py # -> build/tiled/ (gitignored)
```
Then open `build/tiled/gen1.tiled-project` in that build of Tiled.
- **The overworld is one surface.** All 222 maps become `maps/*.tmj`, and
`kanto.world` places the 36 connected overworld maps at their real
connection offsets. That world is pre-loaded (seeded into the workspace's
Tiled session), so opening any one overworld map draws its neighbors around
it and you scroll and edit straight across the seams. Everything else is a
double-click away in Tiled's project panel.
- **Extending Kanto wires both ends.** A connection lives on both maps, so
hooking a new map onto a base map also emits the return connection as a
patch on that base map, keeping its other directions intact. The return
offset is derived, not guessed: all 78 vanilla reciprocal pairs satisfy
`back.offset == -offset`.
- **A Tiled tile is a gen1 block.** Each of the 24 tilesets becomes a Tiled
tileset whose tiles are its 32x32 blocks, composited from the 8x8 sheet,
so a tile layer *is* the map's `blocks` array. Warps, signs and objects
sit on the 16px cell grid in object layers, which is the grid the engine
addresses them on.
- **Collision is visible.** View > Show Tile Collision Shapes draws the real
walkability: a rectangle covers each cell whose feet tile is not in the
tileset's `walkable` list, which is the rule `src/world/Map.lua` applies.
- **Maps are shown in their real colors.** Each map is atlased in the SGB
palette it renders with, so Cerulean is blue and Lavender is purple in the
editor exactly as in game. Vanilla resolves that through a cascade with
interiors inheriting the last outdoor map, so the workspace mirrors the
cascade and walks the warp graph to colour interiors. Changing a map's
`palette` exports `palette = "..."` on the record, which beats the cascade,
and the editor offers the real palette names as a dropdown.
- **New blocks and new tilesets.** `blocksets/*.tmj` show a tileset's blocks
as raw 8x8 tiles, four by four, so new blocks can be composed there;
per-tile flags on `tilesets/tiles_*.tsj` become `walkable`, `waterTiles`,
`doorTiles` and the rest.
- **Export is a diff, not a fork of the data.** The `gen1-mod-export`
extension (shipped in `tiled_gen1recomp`) writes either one map file or a whole
loadable mod folder. An edited vanilla map diffs against the imported data
and emits `mod.content.maps:patch` carrying *only* the fields that moved, so
a mod covers the parts it changes and leaves the rest to the base game; a
new map gets `:register` at an index of 1000 or above. An unchanged map
exports nothing at all. Exports pass `tools/modkit.py validate` and `lint`.
- **Or the whole record, on request.** Ticking `exactExport` on a map switches
it to `mod.content.maps:override`, pinning the map to exactly what the
editor shows. It is off by default because an override wins outright over
any other mod patching that map, where a patch composes.
No ROM-derived art travels into an exported mod: a tileset still drawing on
the player's own imported sheet references that path rather than shipping the
pixels, and only a sheet the author supplied is copied in.
-1
View File
@@ -120,7 +120,6 @@ bundled game, in that case.
## Known limitations
- **`love.run` persists across handoff.** By the time `chainload` runs, the
bundled `love.run` has already returned its stepper to LOVE; redefining the
global `love.run` from the payload's `main.lua` does not affect the loop
+20 -227
View File
@@ -8,11 +8,6 @@
-- opens the editor on that slot's file, and restores the launcher when
-- the editor's Close button is pressed (openEditor / closeEditor below)
if POKEPORT_DISPLAY_COMPANION then
return require("src.render.DesktopCompanion").install(
POKEPORT_DISPLAY_COMPANION)
end
local editorMode = os.getenv("POKEPORT_EDITOR") == "1" or POKEPORT_EDITOR_MODE == true
local SwitchDiagnostics = require("src.debug.SwitchDiagnostics")
@@ -20,7 +15,6 @@ local LaunchOptions = require("src.core.LaunchOptions")
local NxDisplay = require("src.core.NxDisplay")
local PlatformHooks = require("src.core.PlatformHooks")
local HostDisplay = require("src.core.HostDisplay")
local GameViewport = require("src.render.GameViewport")
-- Lua errors: persist a redacted trace in the save dir and surface a hint.
do
@@ -36,7 +30,7 @@ do
end
end
local Game, EditorApp, Importer, TouchEditor, Studio
local Game, EditorApp, Importer, TouchEditor
-- #887: quit-to-launcher state, shared by love.load and love.quit (both need
-- it, so it is declared here rather than next to love.quit).
@@ -255,115 +249,7 @@ function closeTouchControlsEditor()
end
end
-- ------------------------------------------------------------ skin studio
local studioHost
local closeSkinStudio
local bootGame
local function openSkinStudio(version, skinId)
local SkinStudio = require("src.ui.SkinStudio")
if not SkinStudio.available_desktop() then return end
studioHost = Importer
if Importer and Importer.prepareOverlayHandoff then
Importer:prepareOverlayHandoff()
end
Importer = nil
Studio = SkinStudio
Studio.load({
version = version,
skinId = skinId,
onClose = function() closeSkinStudio() end,
onPlay = function(v)
closeSkinStudio()
Importer = nil
bootGame(v or version)
end,
})
end
function closeSkinStudio()
if Studio and Studio.unload then Studio.unload() end
Studio = nil
Importer = studioHost
studioHost = nil
if Importer and Importer.resumeAfterOverlay then
Importer:resumeAfterOverlay()
end
end
local function makeLauncher()
local RomImporter = require("src.import.RomImporter")
local forceImport = os.getenv("POKEPORT_FORCE_IMPORT") == "1"
return RomImporter.new(function(version)
Importer = nil
bootGame(version)
end, {
launcher = true,
forceImport = forceImport,
onEditSave = openEditor,
onEditTouchControls = openTouchControlsEditor,
onOpenSkinStudio = require("src.ui.SkinStudio").available_desktop()
and openSkinStudio or nil,
})
end
local function returnToLauncher()
if not Game then return end
pcall(function() require("src.core.Music").stop() end)
pcall(function() require("src.core.Sound").stop() end)
if package.loaded["src.core.ChipAudio"] then
pcall(package.loaded["src.core.ChipAudio"].shutdown)
end
if package.loaded["src.core.DiscordPresence"] then
pcall(package.loaded["src.core.DiscordPresence"].shutdown)
end
if package.loaded["src.core.gen2.Clock"] then
pcall(package.loaded["src.core.gen2.Clock"].shutdown)
end
if package.loaded["src.net.Gen1Tls"] then
pcall(package.loaded["src.net.Gen1Tls"].shutdown)
end
if love.audio and love.audio.stop then
pcall(love.audio.stop)
end
local GameVersion = require("src.core.GameVersion")
local currentVersion = GameVersion.get()
if currentVersion then
require("src.import.CacheFs").unmountVersion(currentVersion)
end
require("src.core.Data"):unloadGenerated()
local Runtime = require("src.mods.Runtime")
if Runtime.reset then
Runtime.reset()
end
Game = nil
autopilot = nil
driverCo = nil
local Input = require("src.core.Input")
local TouchControls = require("src.core.TouchControls")
Input:reset()
TouchControls:reset()
require("src.core.Orientation").applyOptions(
require("src.core.SaveData").loadOptions())
local preload = require("src.mods.LauncherMods").translationStrings()
if preload then require("src.core.Strings").load({ strings = preload }) end
if love.window and love.window.setTitle then
local Version = require("src.core.Version")
love.window.setTitle(Version.title("Gen 1 Recompilation Project"))
end
Importer = makeLauncher()
end
function bootGame(version)
local function bootGame(version)
-- The launcher hands us the chosen game (Red / Blue / Yellow / Gold);
-- scripted and headless runs fall back to POKEPORT_VERSION, then Red.
-- Set the active version and overlay its extracted cache BEFORE anything
@@ -454,7 +340,7 @@ function love.load(args)
-- Apply the persisted Android orientation lock (#592) before the launcher
-- shows: SDL created the window with no orientation hint, so without this
-- the launcher would rotate freely until options are applied at boot.
-- the launcher would rotate freely until Game:applyOptions runs at boot.
-- No-op on desktop / iOS / when options.lua does not exist yet.
require("src.core.Orientation").applyOptions(
require("src.core.SaveData").loadOptions())
@@ -514,8 +400,8 @@ function love.load(args)
-- (#767) only pays off if something fills that catalog this early, and no
-- restart could: the ordering is the same on every launch. Read the
-- enabled mods' string catalogs -- data only, no entry chunk -- so a
-- translation reaches the launcher too. The active game's loader replaces
-- this with the real merged catalog once a version boots.
-- translation reaches the launcher too. Game:load replaces this with the
-- real merged catalog once a version boots.
do
local preload = require("src.mods.LauncherMods").translationStrings()
if preload then require("src.core.Strings").load({ strings = preload }) end
@@ -556,7 +442,15 @@ function love.load(args)
-- by its SHA-1 (GameVersion.forSha1); pressing Play boots that game (Gold
-- goes to its own service owner, src/core/Game2.lua -- docs/gold-phase1.md).
-- Edit on a save row opens the bundled editor on that slot (openEditor).
Importer = makeLauncher()
Importer = RomImporter.new(function(version)
Importer = nil
bootGame(version)
end, {
launcher = true,
forceImport = forceImport,
onEditSave = openEditor,
onEditTouchControls = openTouchControlsEditor,
})
end
function love.update(dt)
@@ -566,9 +460,7 @@ function love.update(dt)
NxDisplay.sync()
if editorMode then return EditorApp.update(dt) end
if TouchEditor then return TouchEditor.update(dt) end
if Studio then return Studio.update(dt) end
if Importer then return Importer:update(dt) end
if not Game then return end
-- Scripted runs (autopilot / POKEPORT_DRIVER) observe and act exactly
-- once per Game:update, so they must keep a 1:1 relationship with the
@@ -611,36 +503,24 @@ end
function love.draw()
if editorMode then
GameViewport.reset()
HostDisplay.beginFrame("editor", EditorApp)
local result = EditorApp.draw()
HostDisplay.endFrame("editor", EditorApp)
return result
end
if TouchEditor then
GameViewport.reset()
HostDisplay.beginFrame("touch_editor", TouchEditor)
local result = TouchEditor.draw()
HostDisplay.endFrame("touch_editor", TouchEditor)
return result
end
if Studio then
HostDisplay.beginFrame("skin_studio", Studio)
local result = Studio.draw()
HostDisplay.endFrame("skin_studio", Studio)
return result
end
if Importer then
GameViewport.reset()
HostDisplay.beginFrame("launcher", Importer)
local result = Importer:draw()
HostDisplay.endFrame("launcher", Importer)
return result
end
if not Game then
GameViewport.reset()
return
end
if not Game then return end
HostDisplay.beginFrame("game", Game)
Game:draw()
@@ -663,16 +543,13 @@ end
function love.keypressed(key, scancode, isrepeat)
if editorMode then return EditorApp.keypressed(key) end
if TouchEditor then return TouchEditor.keypressed(key) end
if Studio then return Studio.keypressed(key) end
if Importer then return Importer:keypressed(key) end
if not Game then return end
Game:keypressed(key)
end
function love.keyreleased(key)
if editorMode or TouchEditor or Studio then return end
if editorMode or TouchEditor then return end
if Importer then return end
if not Game then return end
Game:keyreleased(key)
end
@@ -690,9 +567,7 @@ function love.gamepadpressed(joystick, button)
end
return
end
if Studio then return end
if Importer then return Importer:gamepadpressed(joystick, button) end
if not Game then return end
Game:gamepadpressed(joystick, button)
end
@@ -710,9 +585,7 @@ function love.gamepadreleased(joystick, button)
end
return
end
if Studio then return end
if Importer then return Importer:gamepadreleased(joystick, button) end
if not Game then return end
Game:gamepadreleased(joystick, button)
end
@@ -730,9 +603,7 @@ function love.gamepadaxis(joystick, axis, value)
end
return
end
if Studio then return end
if Importer then return Importer:gamepadaxis(joystick, axis, value) end
if not Game then return end
Game:gamepadaxis(joystick, axis, value)
end
@@ -750,9 +621,7 @@ function love.joystickpressed(joystick, button)
end
return
end
if Studio then return end
if Importer then return Importer:joystickpressed(joystick, button) end
if not Game then return end
Game:joystickpressed(joystick, button)
end
@@ -770,9 +639,7 @@ function love.joystickreleased(joystick, button)
end
return
end
if Studio then return end
if Importer then return Importer:joystickreleased(joystick, button) end
if not Game then return end
Game:joystickreleased(joystick, button)
end
@@ -790,9 +657,7 @@ function love.joystickaxis(joystick, axis, value)
end
return
end
if Studio then return end
if Importer then return Importer:joystickaxis(joystick, axis, value) end
if not Game then return end
Game:joystickaxis(joystick, axis, value)
end
@@ -810,25 +675,21 @@ function love.joystickhat(joystick, hat, direction)
end
return
end
if Studio then return end
if Importer then return Importer:joystickhat(joystick, hat, direction) end
if not Game then return end
Game:joystickhat(joystick, hat, direction)
end
function love.joystickadded(joystick)
SwitchDiagnostics.onJoystickEvent("joystickadded", joystick)
if editorMode or TouchEditor or Studio then return end
if editorMode or TouchEditor then return end
if Importer then return end
if not Game then return end
Game:joystickadded(joystick)
end
function love.joystickremoved(joystick)
SwitchDiagnostics.onJoystickEvent("joystickremoved", joystick)
if editorMode or TouchEditor or Studio then return end
if editorMode or TouchEditor then return end
if Importer then return end
if not Game then return end
Game:joystickremoved(joystick)
end
@@ -837,79 +698,29 @@ end
-- unfocused, so reset input on either transition rather than trust it.
function love.focus(f)
if editorMode or TouchEditor then return end
if Studio then
if Studio.focus then Studio.focus(f) end
return
end
if Importer then
require("src.core.Input"):reset()
if Importer.focus then Importer:focus(f) end
return
end
if not Game then return end
Game:focus(f)
end
-- v is true when the window becomes visible again, false on minimize.
function love.visible(v)
if editorMode or TouchEditor then return end
if Studio then
if Studio.visible then Studio.visible(v) end
return
end
if Importer then
require("src.core.Input"):reset()
return
end
if not Game then return end
Game:visible(v)
end
function love.lowmemory()
if editorMode or TouchEditor or Studio or Importer then return end
if editorMode or TouchEditor or Importer then return end
if Game then Game:onResume() end
end
love.handlers = love.handlers or {}
function love.handlers.audiosuspend()
local ChipAudio = package.loaded["src.core.ChipAudio"]
if ChipAudio then pcall(ChipAudio.setSuspended, true) end
end
function love.handlers.audioreset()
local ChipAudio = package.loaded["src.core.ChipAudio"]
if ChipAudio then
pcall(ChipAudio.setSuspended, false)
pcall(ChipAudio.rebuildPlayback)
end
local Music = package.loaded["src.core.Music"]
if Music then pcall(Music.onDeviceReset) end
local Sound = package.loaded["src.core.Sound"]
if Sound then pcall(Sound.onDeviceReset) end
end
function love.handlers.intent_game(version)
if type(version) ~= "string" or version == "" then return end
version = version:lower():gsub("^%s+", ""):gsub("%s+$", "")
local GameVersion = require("src.core.GameVersion")
if GameVersion.VERSIONS and not GameVersion.VERSIONS[version] then return end
local RomImporter = require("src.import.RomImporter")
if not RomImporter.isReady(version) then return end
local currentVersion = GameVersion.get()
if Game and currentVersion == version then
return
end
if Game then
returnToLauncher()
end
Importer = nil
bootGame(version)
end
function love.touchpressed(id, x, y, dx, dy, pressure)
if editorMode then
-- iOS synthesizes mousepressed for the primary touch; forwarding here
@@ -927,14 +738,12 @@ function love.touchpressed(id, x, y, dx, dy, pressure)
if love.system.getOS() == "iOS" then return end
return TouchEditor.touchpressed(id, x, y)
end
if Studio then return end
if Importer then
-- Both mobiles: FlexLove scroll needs the real touch stream. Clicks are
-- polled inside the view; the istouch filter on mousepressed still drops
-- Android's synthesized mouse twin so Import cannot double-fire (#553).
return Importer:touchpressed(id, x, y, dx, dy, pressure)
end
if not Game then return end
Game:touchpressed(id, x, y, dx, dy, pressure)
end
@@ -944,11 +753,9 @@ function love.touchmoved(id, x, y, dx, dy, pressure)
if love.system.getOS() == "iOS" then return end
return TouchEditor.touchmoved(id, x, y)
end
if Studio then return end
if Importer then
return Importer:touchmoved(id, x, y, dx, dy, pressure)
end
if not Game then return end
Game:touchmoved(id, x, y, dx, dy, pressure)
end
@@ -958,11 +765,9 @@ function love.touchreleased(id, x, y, dx, dy, pressure)
if love.system.getOS() == "iOS" then return end
return TouchEditor.touchreleased(id, x, y)
end
if Studio then return end
if Importer then
return Importer:touchreleased(id, x, y, dx, dy, pressure)
end
if not Game then return end
Game:touchreleased(id, x, y, dx, dy, pressure)
end
@@ -972,9 +777,7 @@ function love.wheelmoved(x, y)
return
end
if TouchEditor then return end
if Studio then return Studio.wheelmoved(x, y) end
if Importer then return end
if not Game then return end
Game:wheelmoved(x, y)
end
@@ -1011,7 +814,6 @@ function love.mousepressed(x, y, button, istouch)
if love.system.getOS() == "Android" then return end
return TouchEditor.mousepressed(x, y, button)
end
if Studio then return Studio.mousepressed(x, y, button) end
if Importer then
-- love.touchpressed already forwards the primary touch into FlexLove for
-- scroll. LÖVE ALSO synthesizes a mouse press for that same touch; if both
@@ -1046,7 +848,6 @@ function love.mousereleased(x, y, button, istouch)
if love.system.getOS() == "Android" then return end
return TouchEditor.mousereleased(x, y, button)
end
if Studio then return Studio.mousereleased(x, y, button) end
if Importer then return end
if editorMode and EditorApp.mousereleased then
return EditorApp.mousereleased(x, y, button)
@@ -1064,7 +865,6 @@ function love.mousemoved(x, y, dx, dy, istouch)
if love.system.getOS() == "Android" then return end
return TouchEditor.mousemoved(x, y)
end
if Studio then return Studio.mousemoved(x, y) end
if editorMode or Importer then return end
if mouseTouch then
if Game and love.mouse.isDown(1) then Game:touchmoved("mouse", x, y) end
@@ -1075,7 +875,6 @@ end
function love.textinput(text)
if TouchEditor then return end
if Studio then return Studio.textinput(text) end
if Importer then return Importer:textinput(text) end
if editorMode and EditorApp.textinput then
return EditorApp.textinput(text)
@@ -1115,16 +914,11 @@ function love.quit()
-- docs/modding.md's core.quit_to_launcher entry) may veto returning to
-- this Lua launcher via that hook. Vanilla behavior (used when no mod
-- claims the hook) is exactly the condition below.
local isAndroid = (love.system and love.system.getOS and love.system.getOS() == "Android")
local wouldReturnToLauncher = PlatformHooks.quitToLauncher(function()
return Game and not Importer and not quitToLauncher and not scripted
and (isAndroid or not launchedIntoGame)
and not launchedIntoGame
end)
if wouldReturnToLauncher then
if isAndroid then
returnToLauncher()
return true -- abort this quit; the restart lands back in the launcher
end
quitToLauncher = true
-- Tell the fresh boot to ignore any boot-straight-into-a-game option this
-- once, so the restart really does land in the launcher (#887). A failed
@@ -1158,7 +952,6 @@ function love.filedropped(file)
if editorMode and EditorApp and EditorApp.filedropped then
return EditorApp.filedropped(file)
end
if Studio then return Studio.filedropped(file) end
if Importer then Importer:filedropped(file) end
end
@@ -29,18 +29,14 @@
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:icon="@drawable/love"
android:label="${NAME}" >
<meta-data
android:name="android.allow_multiple_resumed_activities"
android:value="true" />
<activity
android:name="org.love2d.android.GameActivity"
android:exported="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:label="${NAME}"
android:launchMode="singleTask"
android:launchMode="singleInstance"
android:screenOrientation="${ORIENTATION}"
android:resizeableActivity="false"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
@@ -53,15 +49,5 @@
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
</activity>
<activity
android:name="org.love2d.android.GameActivity$SecondaryActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleTask"
android:resizeableActivity="false"
android:screenOrientation="${ORIENTATION}"
android:taskAffinity="${applicationId}.secondary"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
</application>
</manifest>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -3,9 +3,4 @@
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="shortcut_red">#E53935</color>
<color name="shortcut_blue">#1E88E5</color>
<color name="shortcut_yellow">#FDD835</color>
<color name="shortcut_gold">#D4AF37</color>
</resources>
@@ -45,11 +45,6 @@
// own, which can name a different volume on merged / adopted-SD storage.
#include "filesystem/Filesystem.h"
#include "common/Module.h"
#include "audio/Audio.h"
#include "audio/openal/Audio.h"
#include "event/Event.h"
namespace love
{
namespace android
@@ -283,70 +278,6 @@ bool restartApp()
return result;
}
bool updateAppShortcuts(const std::vector<std::string> &versions)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
if (activity == nullptr)
return false;
jmethodID method = env->GetStaticMethodID(activity, "updateAppShortcuts", "([Ljava/lang/String;)Z");
if (method == nullptr)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return false;
}
jclass stringClass = env->FindClass("java/lang/String");
jobjectArray array = env->NewObjectArray((jsize) versions.size(), stringClass, nullptr);
for (size_t i = 0; i < versions.size(); ++i)
{
jstring jstr = env->NewStringUTF(versions[i].c_str());
env->SetObjectArrayElement(array, (jsize) i, jstr);
env->DeleteLocalRef(jstr);
}
jboolean result = env->CallStaticBooleanMethod(activity, method, array);
env->DeleteLocalRef(array);
env->DeleteLocalRef(stringClass);
env->DeleteLocalRef(activity);
return result;
}
std::string getLaunchGame()
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
if (activity == nullptr)
return "";
jmethodID method = env->GetStaticMethodID(activity, "getLaunchGame", "()Ljava/lang/String;");
if (method == nullptr)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return "";
}
jstring jgame = (jstring) env->CallStaticObjectMethod(activity, method);
if (jgame == nullptr)
{
env->DeleteLocalRef(activity);
return "";
}
const char *str = env->GetStringUTFChars(jgame, nullptr);
std::string result = (str != nullptr) ? str : "";
if (str != nullptr)
env->ReleaseStringUTFChars(jgame, str);
env->DeleteLocalRef(jgame);
env->DeleteLocalRef(activity);
return result;
}
bool httpDownload(const char *url, const char *destPath, const char *userAgent, const char *accept)
{
if (url == nullptr || destPath == nullptr)
@@ -394,153 +325,6 @@ bool httpDownload(const char *url, const char *destPath, const char *userAgent,
return result;
}
bool httpPost(const char *url, const char *body, int bodyLen, const char *contentType, const char *userAgent)
{
if (url == nullptr || body == nullptr || bodyLen < 0)
return false;
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
// Same resolution rule as httpDownload: the activity's own class via
// SDL_AndroidGetActivity, never FindClass -- this bridge is called off
// the main thread (love.thread workers), whose class loader cannot see
// app classes.
jobject activityObj = (jobject) SDL_AndroidGetActivity();
if (activityObj == nullptr)
return false;
jclass activity = env->GetObjectClass(activityObj);
env->DeleteLocalRef(activityObj);
// Old APK / new liblove skew: report "no transport" the same way a
// missing curl does, instead of aborting on a missing method (#597).
jmethodID method = env->GetStaticMethodID(activity, "httpPost",
"(Ljava/lang/String;[BLjava/lang/String;Ljava/lang/String;)Z");
if (method == nullptr)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return false;
}
jstring jurl = env->NewStringUTF(url);
// raw bytes across the bridge: a log ring can carry arbitrary UTF-8,
// and a jstring would run it through modified UTF-8
jbyteArray jbody = env->NewByteArray(bodyLen);
if (jbody != nullptr)
env->SetByteArrayRegion(jbody, 0, bodyLen, (const jbyte*) body);
jstring jct = contentType != nullptr ? env->NewStringUTF(contentType) : nullptr;
jstring jua = userAgent != nullptr ? env->NewStringUTF(userAgent) : nullptr;
jboolean result = env->CallStaticBooleanMethod(activity, method, jurl, jbody, jct, jua);
env->DeleteLocalRef(jurl);
if (jbody != nullptr)
env->DeleteLocalRef(jbody);
if (jct != nullptr)
env->DeleteLocalRef(jct);
if (jua != nullptr)
env->DeleteLocalRef(jua);
env->DeleteLocalRef(activity);
return result;
}
bool httpRequest(const char *url, const char *method,
const char *const *headerPairs, int headerPairCount,
const char *body, int bodyLen, const char *userAgent, std::string &out)
{
out.clear();
if (url == nullptr)
return false;
if (headerPairCount < 0 || (headerPairCount > 0 && headerPairs == nullptr))
return false;
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
// Same resolution rule as httpDownload: the activity's own class via
// SDL_AndroidGetActivity, never FindClass for an app class -- save sync
// runs on a love.thread worker, whose class loader cannot see them.
jobject activityObj = (jobject) SDL_AndroidGetActivity();
if (activityObj == nullptr)
return false;
jclass activity = env->GetObjectClass(activityObj);
env->DeleteLocalRef(activityObj);
// Old APK / new liblove skew: report "no transport" instead of aborting
// on a missing method (#597).
jmethodID method_id = env->GetStaticMethodID(activity, "httpRequest",
"(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[BLjava/lang/String;)[B");
if (method_id == nullptr)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return false;
}
jobjectArray jheaders = nullptr;
if (headerPairCount > 0)
{
// java/lang/String, unlike an app class, resolves from any thread.
jclass stringClass = env->FindClass("java/lang/String");
if (stringClass == nullptr)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return false;
}
jheaders = env->NewObjectArray((jsize) headerPairCount, stringClass, nullptr);
env->DeleteLocalRef(stringClass);
if (jheaders == nullptr)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return false;
}
for (int i = 0; i < headerPairCount; i++)
{
jstring field = env->NewStringUTF(headerPairs[i] != nullptr ? headerPairs[i] : "");
env->SetObjectArrayElement(jheaders, (jsize) i, field);
if (field != nullptr)
env->DeleteLocalRef(field);
}
}
jstring jurl = env->NewStringUTF(url);
jstring jmethod = env->NewStringUTF(method != nullptr ? method : "GET");
// raw bytes across the bridge, as httpPost does: a request body is JSON
// carrying a base64 save, and a jstring would run it through modified UTF-8
jbyteArray jbody = nullptr;
if (body != nullptr && bodyLen >= 0)
{
jbody = env->NewByteArray((jsize) bodyLen);
if (jbody != nullptr && bodyLen > 0)
env->SetByteArrayRegion(jbody, 0, (jsize) bodyLen, (const jbyte*) body);
}
jstring jua = env->NewStringUTF(userAgent != nullptr ? userAgent : "gen1recomp");
jobject result = env->CallStaticObjectMethod(activity, method_id, jurl, jmethod,
jheaders, jbody, jua);
env->DeleteLocalRef(jurl);
env->DeleteLocalRef(jmethod);
if (jheaders != nullptr)
env->DeleteLocalRef(jheaders);
if (jbody != nullptr)
env->DeleteLocalRef(jbody);
env->DeleteLocalRef(jua);
env->DeleteLocalRef(activity);
if (result == nullptr)
return false;
jbyteArray bytes = (jbyteArray) result;
jsize length = env->GetArrayLength(bytes);
if (length > 0)
{
out.resize((size_t) length);
env->GetByteArrayRegion(bytes, 0, length, (jbyte*) &out[0]);
}
env->DeleteLocalRef(result);
return true;
}
/*
* TLS sockets. Same resolution rule as httpDownload above -- the activity's
* own class, never FindClass -- and the same tolerance for an old APK: a
@@ -1396,68 +1180,6 @@ void love_android_secondary_enable(int on)
env->DeleteLocalRef(activity);
}
extern "C" __attribute__((visibility("default")))
void love_android_secondary_target(int target)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jmethodID method = env->GetStaticMethodID(activity,
"setSecondaryDisplayTarget", "(I)V");
if (method)
env->CallStaticVoidMethod(activity, method, target);
else
env->ExceptionClear();
env->DeleteLocalRef(activity);
}
extern "C" __attribute__((visibility("default")))
int love_android_secondary_detected()
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jmethodID method = env->GetStaticMethodID(activity,
"hasSecondaryDisplayCandidate", "()Z");
jboolean detected = JNI_FALSE;
if (method)
detected = env->CallStaticBooleanMethod(activity, method);
else
env->ExceptionClear();
env->DeleteLocalRef(activity);
return detected ? 1 : 0;
}
extern "C" __attribute__((visibility("default")))
int love_android_present_secondary(const void *rgba, int width, int height,
unsigned int background, int cover)
{
if (!rgba || width <= 0 || height <= 0)
return 0;
jlong size = (jlong) width * (jlong) height * 4;
if (size <= 0)
return 0;
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jmethodID method = env->GetStaticMethodID(activity, "presentSecondaryFrame",
"(Ljava/nio/ByteBuffer;IIIZ)Z");
if (!method)
{
env->ExceptionClear();
env->DeleteLocalRef(activity);
return 0;
}
jobject frame = env->NewDirectByteBuffer((void *) rgba, size);
if (!frame)
{
env->DeleteLocalRef(activity);
return 0;
}
jboolean shown = env->CallStaticBooleanMethod(activity, method, frame,
width, height, (jint) background, cover ? JNI_TRUE : JNI_FALSE);
env->DeleteLocalRef(frame);
env->DeleteLocalRef(activity);
return shown ? 1 : 0;
}
extern "C" __attribute__((visibility("default")))
const char *love_android_poll_secondary_touch()
{
@@ -1487,98 +1209,4 @@ const char *love_android_poll_secondary_touch()
return event.empty() ? nullptr : event.c_str();
}
static love::audio::openal::Audio *love_android_openal_audio()
{
love::audio::Audio *audio = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
if (audio == nullptr)
return nullptr;
const char *name = audio->getName();
if (name == nullptr || strcmp(name, "love.audio.openal") != 0)
return nullptr;
return (love::audio::openal::Audio *) audio;
}
extern "C" JNIEXPORT void JNICALL
Java_org_love2d_android_GameActivity_nativeAudioFocusLost(JNIEnv *env, jclass cls)
{
(void) env;
(void) cls;
love::audio::openal::pushAudioSuspendEvent();
love::audio::openal::Audio *audio = love_android_openal_audio();
if (audio != nullptr)
audio->pauseContext();
}
extern "C" JNIEXPORT void JNICALL
Java_org_love2d_android_GameActivity_nativeAudioFocusGained(JNIEnv *env, jclass cls)
{
(void) env;
(void) cls;
love::audio::openal::Audio *audio = love_android_openal_audio();
if (audio == nullptr)
return;
audio->resumeContext();
if (!audio->isDeviceConnected())
audio->reopenDevice();
love::audio::openal::pushAudioResetEvent();
}
extern "C" JNIEXPORT void JNICALL
Java_org_love2d_android_GameActivity_nativeAudioDeviceChanged(JNIEnv *env, jclass cls)
{
(void) env;
(void) cls;
love::audio::openal::Audio *audio = love_android_openal_audio();
if (audio == nullptr)
return;
audio->pauseContext();
audio->reopenDevice();
audio->resumeContext();
love::audio::openal::pushAudioResetEvent();
}
static void pushGameIntentEvent(const char *game)
{
auto eventmodule = love::Module::getInstance<love::event::Event>(love::Module::M_EVENT);
if (eventmodule == nullptr || game == nullptr)
return;
std::vector<love::Variant> args;
args.push_back(love::Variant(std::string(game)));
love::event::Message *msg = new love::event::Message("intent_game", args);
eventmodule->push(msg);
msg->release();
}
extern "C" JNIEXPORT void JNICALL
Java_org_love2d_android_GameActivity_nativeOnGameIntent(JNIEnv *env, jclass cls, jstring game)
{
(void) cls;
if (game == nullptr)
return;
const char *str = env->GetStringUTFChars(game, nullptr);
if (str != nullptr)
{
pushGameIntentEvent(str);
env->ReleaseStringUTFChars(game, str);
}
}
#endif // LOVE_ANDROID
@@ -90,16 +90,6 @@ bool syncHealthSteps();
**/
bool restartApp();
/**
* Dynamic App Shortcuts: updates Android ShortcutManager with ready game versions.
**/
bool updateAppShortcuts(const std::vector<std::string> &versions);
/**
* Returns the game version requested via initial launch Intent (if any).
**/
std::string getLaunchGame();
/**
* Blocking HTTPS GET into destPath (GameActivity.httpDownload). Android has
* no curl binary, so this is the transport src/core/HostShell.lua uses there
@@ -108,29 +98,6 @@ std::string getLaunchGame();
**/
bool httpDownload(const char *url, const char *destPath, const char *userAgent, const char *accept);
/**
* Blocking HTTPS POST of a raw byte body (GameActivity.httpPost). The
* mirror of httpDownload for mod.postLog log sends, which need POST and
* have no curl on Android. contentType / userAgent may be null. Returns
* whether the server accepted the send (2xx).
**/
bool httpPost(const char *url, const char *body, int bodyLen, const char *contentType, const char *userAgent);
/**
* Blocking HTTPS request with a method, headers and a byte body
* (GameActivity.httpRequest). What save sync needs and neither of the two
* above can give it: PUT, per-request auth headers, and the response body of
* a 4xx as well as a 2xx. headerPairs is a flat name, value array of
* headerPairCount entries; body/userAgent may be null. `out` receives the
* Java side's envelope -- a head line of "STATUS <code>" or "ERROR <text>",
* a newline, then the raw response bytes. False means the platform has no
* such bridge at all (an old APK under a newer liblove), which the Lua side
* reports as "update the app" rather than as a failed request.
**/
bool httpRequest(const char *url, const char *method,
const char *const *headerPairs, int headerPairCount,
const char *body, int bodyLen, const char *userAgent, std::string &out);
/**
* TLS client sockets (GameActivity.tls*, implemented by TlsSocket.java).
* LuaSocket, which is what LOVE ships, does TCP only, so wss:// is otherwise
@@ -22,7 +22,6 @@
#include "common/delay.h"
#include "RecordingDevice.h"
#include "sound/Decoder.h"
#include "event/Event.h"
#include <cstdlib>
#include <iostream>
@@ -31,10 +30,6 @@
#include "common/ios.h"
#endif
#ifndef ALC_CONNECTED
#define ALC_CONNECTED 0x313
#endif
namespace love
{
namespace audio
@@ -42,35 +37,9 @@ namespace audio
namespace openal
{
static const int DISCONNECT_CHECK_INTERVAL = 200;
static void pushAudioEvent(const char *name)
{
auto eventmodule = Module::getInstance<event::Event>(Module::M_EVENT);
if (eventmodule == nullptr)
return;
event::Message *msg = new event::Message(name);
eventmodule->push(msg);
msg->release();
}
void pushAudioSuspendEvent()
{
pushAudioEvent("audiosuspend");
}
void pushAudioResetEvent()
{
pushAudioEvent("audioreset");
}
Audio::PoolThread::PoolThread(Audio *audio, Pool *pool)
: audio(audio)
, pool(pool)
Audio::PoolThread::PoolThread(Pool *pool)
: pool(pool)
, finish(false)
, paused(false)
{
threadName = "AudioPool";
}
@@ -82,8 +51,6 @@ Audio::PoolThread::~PoolThread()
void Audio::PoolThread::threadFunction()
{
int disconnectCheck = 0;
while (true)
{
{
@@ -94,23 +61,7 @@ void Audio::PoolThread::threadFunction()
}
}
if (paused.load())
{
disconnectCheck = 0;
sleep(5);
continue;
}
pool->update();
if (audio != nullptr && ++disconnectCheck >= DISCONNECT_CHECK_INTERVAL)
{
disconnectCheck = 0;
if (!audio->isDeviceConnected() && audio->reopenDevice())
pushAudioResetEvent();
}
sleep(5);
}
}
@@ -121,11 +72,6 @@ void Audio::PoolThread::setFinish()
finish = true;
}
void Audio::PoolThread::setPaused(bool paused)
{
this->paused.store(paused);
}
ALenum Audio::getFormat(int bitDepth, int channels)
{
if (bitDepth != 8 && bitDepth != 16)
@@ -153,8 +99,6 @@ Audio::Audio()
, pool(nullptr)
, poolThread(nullptr)
, distanceModel(DISTANCE_INVERSE_CLAMPED)
, alcReopenDeviceSOFT(nullptr)
, reopenChecked(false)
{
// Before opening new device, check if recording
// is requested.
@@ -245,6 +189,13 @@ Audio::Audio()
throw;
}
poolThread = new PoolThread(pool);
poolThread->start();
#ifdef LOVE_IOS
love::ios::initAudioSessionInterruptionHandler();
#endif
#ifdef LOVE_ANDROID
bool hasPauseDeviceExt = alcIsExtensionPresent(device, "ALC_SOFT_pause_device") == ALC_TRUE;
alcDevicePauseSOFT = hasPauseDeviceExt
@@ -254,13 +205,6 @@ Audio::Audio()
? (LPALCDEVICERESUMESOFT) alcGetProcAddress(device, "alcDeviceResumeSOFT")
: nullptr;
#endif
poolThread = new PoolThread(this, pool);
poolThread->start();
#ifdef LOVE_IOS
love::ios::initAudioSessionInterruptionHandler();
#endif
}
Audio::~Audio()
@@ -370,9 +314,6 @@ std::vector<love::audio::Source*> Audio::pause()
void Audio::pauseContext()
{
if (poolThread != nullptr)
poolThread->setPaused(true);
#ifdef LOVE_ANDROID
if (alcDevicePauseSOFT)
alcDevicePauseSOFT(device);
@@ -409,52 +350,6 @@ void Audio::resumeContext()
if (context && alcGetCurrentContext() != context)
alcMakeContextCurrent(context);
#endif
if (poolThread != nullptr)
poolThread->setPaused(false);
}
bool Audio::reopenDevice()
{
if (device == nullptr)
return false;
thread::Lock lock(deviceMutex);
if (!reopenChecked)
{
reopenChecked = true;
if (alcIsExtensionPresent(device, "ALC_SOFT_reopen_device") == ALC_TRUE)
alcReopenDeviceSOFT = (LPALCREOPENDEVICESOFT) alcGetProcAddress(device, "alcReopenDeviceSOFT");
}
if (alcReopenDeviceSOFT == nullptr)
return false;
alcGetError(device);
return alcReopenDeviceSOFT(device, nullptr, nullptr) == ALC_TRUE;
}
bool Audio::isDeviceConnected()
{
if (device == nullptr)
return false;
thread::Lock lock(deviceMutex);
if (alcIsExtensionPresent(device, "ALC_EXT_disconnect") != ALC_TRUE)
return true;
ALCint connected = 1;
alcGetError(device);
alcGetIntegerv(device, ALC_CONNECTED, 1, &connected);
if (alcGetError(device) != ALC_NO_ERROR)
return true;
return connected != 0;
}
void Audio::setVolume(float volume)
@@ -22,7 +22,6 @@
#define LOVE_AUDIO_OPENAL_AUDIO_H
// STD
#include <atomic>
#include <queue>
#include <map>
#include <vector>
@@ -98,8 +97,6 @@ public:
std::vector<love::audio::Source*> pause();
void pauseContext();
void resumeContext();
bool reopenDevice();
bool isDeviceConnected();
void setVolume(float volume);
float getVolume() const;
@@ -158,7 +155,6 @@ private:
class PoolThread: public thread::Threadable
{
protected:
Audio *audio;
Pool *pool;
// Set this to true when the thread should finish.
@@ -166,16 +162,13 @@ private:
// will read from it.
volatile bool finish;
std::atomic<bool> paused;
// finish lock
love::thread::MutexRef mutex;
public:
PoolThread(Audio *audio, Pool *pool);
PoolThread(Pool *pool);
virtual ~PoolThread();
void setFinish();
void setPaused(bool paused);
void threadFunction();
};
@@ -184,13 +177,6 @@ private:
DistanceModel distanceModel;
//float metersPerUnit = 1.0;
#ifndef ALC_SOFT_reopen_device
typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs);
#endif
LPALCREOPENDEVICESOFT alcReopenDeviceSOFT;
bool reopenChecked;
love::thread::MutexRef deviceMutex;
#ifdef LOVE_ANDROID
# ifndef ALC_SOFT_pause_device
typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device);
@@ -202,9 +188,6 @@ private:
#endif
}; // Audio
void pushAudioSuspendEvent();
void pushAudioResetEvent();
#ifdef ALC_EXT_EFX
// Effect objects
extern LPALGENEFFECTS alGenEffects;
@@ -245,25 +245,6 @@ bool System::restartApp() const
#endif
}
bool System::updateShortcuts(const std::vector<std::string> &versions) const
{
#ifdef LOVE_ANDROID
return love::android::updateAppShortcuts(versions);
#else
LOVE_UNUSED(versions);
return false;
#endif
}
std::string System::getLaunchGame() const
{
#ifdef LOVE_ANDROID
return love::android::getLaunchGame();
#else
return "";
#endif
}
bool System::httpDownload(const char *url, const char *destPath,
const char *userAgent, const char *accept) const
{
@@ -278,42 +259,6 @@ bool System::httpDownload(const char *url, const char *destPath,
#endif
}
bool System::httpPost(const char *url, const char *body, int bodyLen,
const char *contentType, const char *userAgent) const
{
#ifdef LOVE_ANDROID
return love::android::httpPost(url, body, bodyLen, contentType, userAgent);
#else
LOVE_UNUSED(url);
LOVE_UNUSED(body);
LOVE_UNUSED(bodyLen);
LOVE_UNUSED(contentType);
LOVE_UNUSED(userAgent);
return false;
#endif
}
bool System::httpRequest(const char *url, const char *method,
const char *const *headerPairs, int headerPairCount,
const char *body, int bodyLen, const char *userAgent,
std::string &out) const
{
#ifdef LOVE_ANDROID
return love::android::httpRequest(url, method, headerPairs, headerPairCount,
body, bodyLen, userAgent, out);
#else
LOVE_UNUSED(url);
LOVE_UNUSED(method);
LOVE_UNUSED(headerPairs);
LOVE_UNUSED(headerPairCount);
LOVE_UNUSED(body);
LOVE_UNUSED(bodyLen);
LOVE_UNUSED(userAgent);
out.clear();
return false;
#endif
}
int System::tlsOpen(const char *host, int port) const
{
#ifdef LOVE_ANDROID
@@ -143,9 +143,6 @@ public:
**/
virtual bool restartApp() const;
virtual bool updateShortcuts(const std::vector<std::string> &versions) const;
virtual std::string getLaunchGame() const;
/**
* Blocking HTTPS GET into an absolute host path (Android only; false
* elsewhere). Android has no curl, which is what every other platform
@@ -154,26 +151,6 @@ public:
virtual bool httpDownload(const char *url, const char *destPath,
const char *userAgent = nullptr, const char *accept = nullptr) const;
/**
* Blocking HTTPS POST of a raw byte body (Android only; false
* elsewhere). The mirror of httpDownload for mod.postLog log sends,
* which need POST and have no curl on Android (#597).
**/
virtual bool httpPost(const char *url, const char *body, int bodyLen,
const char *contentType = nullptr, const char *userAgent = nullptr) const;
/**
* Blocking HTTPS request with a method, headers and a byte body (Android
* only; false elsewhere). Save sync needs PUT, auth headers and the body
* of a 4xx, none of which the two bridges above can express. headerPairs
* is a flat name, value array; `out` receives the response envelope
* ("STATUS <code>" or "ERROR <text>", a newline, then the raw body).
**/
virtual bool httpRequest(const char *url, const char *method,
const char *const *headerPairs, int headerPairCount,
const char *body, int bodyLen, const char *userAgent,
std::string &out) const;
/**
* TLS client sockets (Android only; every call fails elsewhere, where
* LuaSec or another provider is the answer). Non-blocking by contract:
@@ -22,9 +22,6 @@
#include "wrap_System.h"
#include "sdl/System.h"
#include <string>
#include <vector>
namespace love
{
namespace system
@@ -142,68 +139,6 @@ int w_httpDownload(lua_State *L)
return 1;
}
int w_httpPost(lua_State *L)
{
const char *url = luaL_checkstring(L, 1);
size_t bodyLen = 0;
const char *body = luaL_checklstring(L, 2, &bodyLen);
const char *ct = luaL_optstring(L, 3, nullptr);
const char *ua = luaL_optstring(L, 4, nullptr);
luax_pushboolean(L, instance()->httpPost(url, body, (int) bodyLen, ct, ua));
return 1;
}
/*
* love.system.httpRequest(url, method, headers, body, userAgent) -> envelope
*
* `headers` is a flat array of alternating header name and value strings, so
* it maps straight onto the Java bridge's String[] without any parsing here.
* The single return is the response envelope -- a head line of
* "STATUS <code>" or "ERROR <text>", a newline, then the raw body -- or nil
* where the build has no bridge, which src/core/HostShell.lua turns into an
* "update the app" notice rather than a failed request.
*/
int w_httpRequest(lua_State *L)
{
const char *url = luaL_checkstring(L, 1);
const char *method = luaL_optstring(L, 2, "GET");
std::vector<std::string> fields;
if (!lua_isnoneornil(L, 3))
{
luaL_checktype(L, 3, LUA_TTABLE);
size_t count = luax_objlen(L, 3);
for (size_t i = 1; i <= count; i++)
{
lua_rawgeti(L, 3, (int) i);
const char *field = lua_tostring(L, -1);
fields.push_back(field != nullptr ? field : "");
lua_pop(L, 1);
}
}
std::vector<const char *> pairs;
for (size_t i = 0; i < fields.size(); i++)
pairs.push_back(fields[i].c_str());
size_t bodyLen = 0;
const char *body = nullptr;
if (!lua_isnoneornil(L, 4))
body = luaL_checklstring(L, 4, &bodyLen);
const char *ua = luaL_optstring(L, 5, nullptr);
std::string out;
bool ok = instance()->httpRequest(url, method,
pairs.empty() ? nullptr : &pairs[0], (int) pairs.size(),
body, (int) bodyLen, ua, out);
if (!ok)
{
lua_pushnil(L);
return 1;
}
lua_pushlstring(L, out.data(), out.size());
return 1;
}
int w_hasBackgroundMusic(lua_State *L)
{
lua_pushboolean(L, instance()->hasBackgroundMusic());
@@ -283,34 +218,6 @@ int w_tlsClose(lua_State *L)
return 0;
}
int w_updateShortcuts(lua_State *L)
{
if (!lua_istable(L, 1))
return luaL_error(L, "Expected table of game version strings");
std::vector<std::string> versions;
int len = (int) luax_objlen(L, 1);
for (int i = 1; i <= len; ++i)
{
lua_rawgeti(L, 1, i);
if (lua_isstring(L, -1))
versions.push_back(lua_tostring(L, -1));
lua_pop(L, 1);
}
luax_pushboolean(L, instance()->updateShortcuts(versions));
return 1;
}
int w_getLaunchGame(lua_State *L)
{
std::string game = instance()->getLaunchGame();
if (game.empty())
lua_pushnil(L);
else
luax_pushstring(L, game);
return 1;
}
static const luaL_Reg functions[] =
{
{ "getOS", w_getOS },
@@ -325,11 +232,7 @@ static const luaL_Reg functions[] =
{ "createFile", w_createFile },
{ "syncHealthSteps", w_syncHealthSteps },
{ "restartApp", w_restartApp },
{ "updateShortcuts", w_updateShortcuts },
{ "getLaunchGame", w_getLaunchGame },
{ "httpDownload", w_httpDownload },
{ "httpPost", w_httpPost },
{ "httpRequest", w_httpRequest },
{ "tlsOpen", w_tlsOpen },
{ "tlsStatus", w_tlsStatus },
{ "tlsSend", w_tlsSend },
@@ -1,40 +0,0 @@
---
name: Bug report
about: Create a report to help us improve Oboe
title: ''
labels: bug
assignees: ''
---
Android version(s):
Android device(s):
Oboe version:
App name used for testing:
(Please try to reproduce the issue using the OboeTester or an Oboe sample.)
**Short description**
(Please only report one bug per Issue. Do not combine multiple bugs.)
**Steps to reproduce**
**Expected behavior**
**Actual behavior**
**Device**
Please list which devices have this bug.
If device specific, and you are on Linux or a Macintosh, connect the device and please share the result for the following script. This gets properties of the device.
```
for p in \
ro.product.brand ro.product.manufacturer ro.product.model \
ro.product.device ro.product.cpu.abi ro.build.description \
ro.hardware ro.hardware.chipname ro.arch "| grep aaudio";
do echo "$p = $(adb shell getprop $p)"; done
```
**Any additional context**
If applicable, please attach a few seconds of an uncompressed recording of the sound in a WAV or AIFF file.
-36
View File
@@ -1,36 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
#Workaround for https://github.com/dependabot/dependabot-core/issues/6888#issuecomment-1539501116
registries:
maven-google:
type: maven-repository
url: "https://dl.google.com/dl/android/maven2/"
updates:
#Check for updates to Github Actions
- package-ecosystem: "github-actions"
directory: "/" #Location of package manifests
target-branch: "main"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "dependencies/github-actions"
schedule:
interval: "daily"
#Check updates for Gradle dependencies
- package-ecosystem: "gradle"
registries:
- maven-google
directory: "/" #Location of package manifests
target-branch: "main"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "dependencies/gradle"
schedule:
interval: "daily"
@@ -1,38 +0,0 @@
name: Build CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: build samples and apps
uses: github/codeql-action/init@v3
with:
languages: cpp
- run: |
pushd samples
chmod +x gradlew
./gradlew -q clean bundleDebug
popd
pushd apps/OboeTester
chmod +x gradlew
./gradlew -q clean bundleDebug
popd
pushd apps/fxlab
chmod +x gradlew
./gradlew -q clean bundleDebug
popd
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
@@ -1,24 +0,0 @@
name: Update Docs
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1.9.8
with:
doxyfile-path: "./Doxyfile"
working-directory: "."
- name: Deploy
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/reference
@@ -4,5 +4,3 @@
.cxx/
.idea
build
.logpile
+17 -14
View File
@@ -1,21 +1,32 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
#
# Module
#
LOCAL_MODULE := oboe
LOCAL_ARM_NEON := true
#
# Flags
#
LOCAL_CFLAGS := -Wall -Wextra-semi -Wshadow -Wshadow-field
LOCAL_CPPFLAGS := -std=c++17
LOCAL_CPPFLAGS := -std=c++14
#
# Include paths
#
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include \
$(LOCAL_PATH)/src
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
#
# Source files
#
LOCAL_SRC_FILES := \
src/aaudio/AAudioLoader.cpp \
src/aaudio/AudioStreamAAudio.cpp \
src/common/AdpfWrapper.cpp \
src/common/AudioSourceCaller.cpp \
src/common/AudioStream.cpp \
src/common/AudioStreamBuilder.cpp \
@@ -25,11 +36,8 @@ LOCAL_SRC_FILES := \
src/common/FixedBlockReader.cpp \
src/common/FixedBlockWriter.cpp \
src/common/LatencyTuner.cpp \
src/common/OboeExtensions.cpp \
src/common/SourceFloatCaller.cpp \
src/common/SourceI16Caller.cpp \
src/common/SourceI24Caller.cpp \
src/common/SourceI32Caller.cpp \
src/common/Utilities.cpp \
src/common/QuirksManager.cpp \
src/fifo/FifoBuffer.cpp \
@@ -37,26 +45,17 @@ LOCAL_SRC_FILES := \
src/fifo/FifoControllerBase.cpp \
src/fifo/FifoControllerIndirect.cpp \
src/flowgraph/FlowGraphNode.cpp \
src/flowgraph/ChannelCountConverter.cpp \
src/flowgraph/ClipToRange.cpp \
src/flowgraph/Limiter.cpp \
src/flowgraph/ManyToMultiConverter.cpp \
src/flowgraph/MonoBlend.cpp \
src/flowgraph/MonoToMultiConverter.cpp \
src/flowgraph/MultiToManyConverter.cpp \
src/flowgraph/MultiToMonoConverter.cpp \
src/flowgraph/RampLinear.cpp \
src/flowgraph/SampleRateConverter.cpp \
src/flowgraph/SinkFloat.cpp \
src/flowgraph/SinkI16.cpp \
src/flowgraph/SinkI24.cpp \
src/flowgraph/SinkI32.cpp \
src/flowgraph/SinkI8_24.cpp \
src/flowgraph/SourceFloat.cpp \
src/flowgraph/SourceI16.cpp \
src/flowgraph/SourceI24.cpp \
src/flowgraph/SourceI32.cpp \
src/flowgraph/SourceI8_24.cpp \
src/flowgraph/resampler/IntegerRatio.cpp \
src/flowgraph/resampler/LinearResampler.cpp \
src/flowgraph/resampler/MultiChannelResampler.cpp \
@@ -76,6 +75,10 @@ LOCAL_SRC_FILES := \
src/common/Trace.cpp \
src/common/Version.cpp
#
# Libraries related
#
LOCAL_LDLIBS := -llog
# Build
include $(BUILD_STATIC_LIBRARY)
@@ -9,7 +9,6 @@ project(oboe)
set (oboe_sources
src/aaudio/AAudioLoader.cpp
src/aaudio/AudioStreamAAudio.cpp
src/common/AdpfWrapper.cpp
src/common/AudioSourceCaller.cpp
src/common/AudioStream.cpp
src/common/AudioStreamBuilder.cpp
@@ -19,38 +18,26 @@ set (oboe_sources
src/common/FixedBlockReader.cpp
src/common/FixedBlockWriter.cpp
src/common/LatencyTuner.cpp
src/common/OboeExtensions.cpp
src/common/SourceFloatCaller.cpp
src/common/SourceI16Caller.cpp
src/common/SourceI24Caller.cpp
src/common/SourceI32Caller.cpp
src/common/Utilities.cpp
src/common/QuirksManager.cpp
src/fifo/FifoBuffer.cpp
src/fifo/FifoController.cpp
src/fifo/FifoControllerBase.cpp
src/fifo/FifoControllerIndirect.cpp
src/flowgraph/FlowGraphNode.cpp
src/flowgraph/ChannelCountConverter.cpp
src/flowgraph/FlowGraphNode.cpp
src/flowgraph/ClipToRange.cpp
src/flowgraph/Limiter.cpp
src/flowgraph/ManyToMultiConverter.cpp
src/flowgraph/MonoBlend.cpp
src/flowgraph/MonoToMultiConverter.cpp
src/flowgraph/MultiToManyConverter.cpp
src/flowgraph/MultiToMonoConverter.cpp
src/flowgraph/RampLinear.cpp
src/flowgraph/SampleRateConverter.cpp
src/flowgraph/SinkFloat.cpp
src/flowgraph/SinkI16.cpp
src/flowgraph/SinkI24.cpp
src/flowgraph/SinkI32.cpp
src/flowgraph/SinkI8_24.cpp
src/flowgraph/SourceFloat.cpp
src/flowgraph/SourceI16.cpp
src/flowgraph/SourceI24.cpp
src/flowgraph/SourceI32.cpp
src/flowgraph/SourceI8_24.cpp
src/flowgraph/resampler/IntegerRatio.cpp
src/flowgraph/resampler/LinearResampler.cpp
src/flowgraph/resampler/MultiChannelResampler.cpp
@@ -83,23 +70,18 @@ target_include_directories(oboe
# Enable -Ofast
target_compile_options(oboe
PRIVATE
-std=c++17
-std=c++14
-Wall
-Wextra-semi
-Wshadow
-Wshadow-field
"$<$<CONFIG:RELEASE>:-Ofast>"
"$<$<CONFIG:DEBUG>:-O3>"
-Ofast
"$<$<CONFIG:DEBUG>:-Werror>")
# Enable logging of D,V for debug builds
target_compile_definitions(oboe PUBLIC $<$<CONFIG:DEBUG>:OBOE_ENABLE_LOGGING=1>)
option(OBOE_DO_NOT_DEFINE_OPENSL_ES_CONSTANTS "Do not define OpenSLES constants" OFF)
target_compile_definitions(oboe PRIVATE $<$<BOOL:${OBOE_DO_NOT_DEFINE_OPENSL_ES_CONSTANTS}>:DO_NOT_DEFINE_OPENSL_ES_CONSTANTS=1>)
target_link_libraries(oboe PRIVATE log OpenSLES)
target_link_options(oboe PRIVATE "-Wl,-z,max-page-size=16384")
# When installing oboe put the libraries in the lib/<ABI> folder e.g. lib/arm64-v8a
install(TARGETS oboe
@@ -107,4 +89,4 @@ install(TARGETS oboe
ARCHIVE DESTINATION lib/${ANDROID_ABI})
# Also install the headers
install(DIRECTORY include/oboe DESTINATION include)
install(DIRECTORY include/oboe DESTINATION include)
@@ -0,0 +1 @@
Please see the CONTRIBUTING.md file for more information.
+2 -2
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = "Oboe"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER =
PROJECT_NUMBER = 1.2
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -58,7 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = ./docs
OUTPUT_DIRECTORY = docs
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
+1
View File
@@ -0,0 +1 @@
Please see the README.md file for more information.
+13 -14
View File
@@ -1,4 +1,4 @@
# Oboe [![Build CI](https://github.com/google/oboe/workflows/Build%20CI/badge.svg)](https://github.com/google/oboe/actions)
# Oboe [![Build Status](https://travis-ci.org/google/oboe.svg?branch=master)](https://travis-ci.org/google/oboe)
[![Introduction to Oboe video](docs/images/getting-started-video.jpg)](https://www.youtube.com/watch?v=csfHAbr5ilI&list=PLWz5rJ2EKKc_duWv9IPNvx9YBudNMmLSa)
@@ -9,36 +9,35 @@ Oboe is a C++ library which makes it easy to build high-performance audio apps o
- Chooses the audio API (OpenSL ES on API 16+ or AAudio on API 27+) which will give the best audio performance on the target Android device
- Automatic latency tuning
- Modern C++ allowing you to write clean, elegant code
- Workarounds for some known issues
- [Used by popular apps and frameworks](https://github.com/google/oboe/wiki/AppsUsingOboe)
- [Used by popular apps and frameworks](docs/AppsUsingOboe.md)
## Documentation
## Requirements
To build Oboe you'll need a compiler which supports C++14 and the Android header files. The easiest way to obtain these is by downloading the Android NDK r17 or above. It can be installed using Android Studio's SDK manager, or via [direct download](https://developer.android.com/ndk/downloads/).
## API Documentation
- [Getting Started Guide](docs/GettingStarted.md)
- [Full Guide to Oboe](docs/FullGuide.md)
- [API reference](https://google.github.io/oboe)
- [API reference](https://google.github.io/oboe/reference)
- [Tech Notes](docs/notes/)
- [History of Audio features/bugs by Android version](docs/AndroidAudioHistory.md)
- [Migration guide for apps using OpenSL ES](docs/OpenSLESMigration.md)
- [Frequently Asked Questions](docs/FAQ.md) (FAQ)
- [Wiki](https://github.com/google/oboe/wiki)
- [Our roadmap](https://github.com/google/oboe/milestones) - Vote on a feature/issue by adding a thumbs up to the first comment.
### Community
- Reddit: [r/androidaudiodev](https://www.reddit.com/r/androidaudiodev/)
- StackOverflow: [#oboe](https://stackoverflow.com/questions/tagged/oboe)
## Testing
- [**OboeTester** app for measuring latency, glitches, etc.](apps/OboeTester/docs)
- [Oboe unit tests](tests)
- [**OboeTester** app for measuring latency, glitches, etc.](https://github.com/google/oboe/tree/master/apps/OboeTester/docs)
- [Oboe unit tests](https://github.com/google/oboe/tree/master/tests)
## Videos
- [Getting started with Oboe](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_duWv9IPNvx9YBudNMmLSa)
- [Low Latency Audio - Because Your Ears Are Worth It](https://www.youtube.com/watch?v=8vOf_fDtur4) (Android Dev Summit '18)
- [Real-time audio with the 100 oscillator synthesizer](https://www.youtube.com/watch?v=J04iPJBkAKs) (DroidCon Berlin '18)
- [Winning on Android](https://www.youtube.com/watch?v=tWBojmBpS74) - How to optimize an Android audio app. (ADC '18)
- [Real-Time Processing on Android](https://youtu.be/hY9BrS2uX-c) (ADC '19)
## Sample code and apps
- Sample apps can be found in the [samples directory](samples).
- A complete "effects processor" app called FXLab can be found in the [apps/fxlab folder](apps/fxlab).
- Also check out the [Rhythm Game codelab](https://developer.android.com/codelabs/musicalgame-using-oboe?hl=en#0).
- Also check out the [Rhythm Game codelab](https://codelabs.developers.google.com/codelabs/musicalgame-using-oboe/index.html#0).
### Third party sample code
- [Ableton Link integration demo](https://github.com/jbloit/AndroidLinkAudio) (author: jbloit)
@@ -6,8 +6,6 @@
/build/
.idea/
/app/build/
/app/release/
/app/debug/
/app/app.iml
*.iml
/app/externalNativeBuild/
@@ -1,14 +1,11 @@
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -std=c++17 -fvisibility=hidden")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -std=c++14")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
link_directories(${CMAKE_CURRENT_LIST_DIR}/..)
# Increment this number when adding files to OboeTester => 105
# The change in this file will help Android Studio resync
# and generate new build files that reference the new code.
file(GLOB_RECURSE app_native_sources src/main/cpp/*)
### Name must match loadLibrary() call in MainActivity.java
@@ -33,4 +30,5 @@ include_directories(
# link to oboe
target_link_libraries(oboetester log oboe atomic)
target_link_options(oboetester PRIVATE "-Wl,-z,max-page-size=16384")
# bump 2 to resync CMake
@@ -1,17 +1,18 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 34
compileSdkVersion 28
defaultConfig {
applicationId = "com.mobileer.oboetester"
applicationId = "com.google.sample.oboe.manualtest"
minSdkVersion 23
targetSdkVersion 34
versionCode 91
versionName "2.7.2"
targetSdkVersion 28
// Also update the version in the AndroidManifest.xml file.
versionCode 32
versionName "1.5.24"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++17"
cppFlags "-std=c++14"
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
}
}
@@ -30,15 +31,14 @@ android {
path "CMakeLists.txt"
}
}
namespace 'com.mobileer.oboetester'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.core:core-ktx:1.9.0"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
testImplementation 'junit:junit:4.13-beta-3'
implementation 'com.android.support:appcompat-v7:28.0.0'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
@@ -1,138 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
<uses-feature
android:name="android.hardware.audio.output"
android:required="true" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.midi"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.sample.oboe.manualtest"
android:versionCode="32"
android:versionName="1.5.24">
<!-- versionCode and versionName also have to be updated in build.gradle -->
<uses-feature android:name="android.hardware.microphone" android:required="true" />
<uses-feature android:name="android.hardware.audio.output" android:required="true" />
<uses-feature android:name="android.software.midi" android:required="true" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- debug-writing file need external storage writing -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:requestLegacyExternalStorage="true"
android:banner="@mipmap/ic_launcher">
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:name="com.google.sample.oboe.manualtest.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:exported="true">
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".TestOutputActivity"
android:label="@string/title_activity_test_output"
android:screenOrientation="portrait" />
<activity
android:name=".TestInputActivity"
android:label="@string/title_activity_test_input"
android:screenOrientation="portrait" />
<activity
android:name=".TapToToneActivity"
android:label="@string/title_activity_output_latency"
android:screenOrientation="portrait" />
<activity
android:name=".RecorderActivity"
android:label="@string/title_activity_recorder"
android:screenOrientation="portrait" />
<activity
android:name=".EchoActivity"
android:label="@string/title_activity_echo"
android:screenOrientation="portrait" />
<activity
android:name=".RoundTripLatencyActivity"
android:label="@string/title_activity_rt_latency"
android:screenOrientation="portrait" />
<activity
android:name=".ManualGlitchActivity"
android:label="@string/title_activity_glitches"
android:screenOrientation="portrait" />
<activity
android:name=".AutomatedGlitchActivity"
android:label="@string/title_activity_auto_glitches"
android:screenOrientation="portrait" />
<activity
android:name=".TestDisconnectActivity"
android:label="@string/title_test_disconnect"
android:screenOrientation="portrait" />
<activity
android:name=".DeviceReportActivity"
android:label="@string/title_report_devices"
android:screenOrientation="portrait" />
<activity
android:name=".TestDataPathsActivity"
android:label="@string/title_data_paths"
android:screenOrientation="portrait" />
<activity
android:name=".ExtraTestsActivity"
android:exported="true"
android:label="@string/title_extra_tests"
android:screenOrientation="portrait" />
<activity
android:name=".ExternalTapToToneActivity"
android:label="@string/title_external_tap"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.TestOutputActivity"
android:label="@string/title_activity_test_output"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".TestPlugLatencyActivity"
android:label="@string/title_plug_latency"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.TestInputActivity"
android:label="@string/title_activity_test_input"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".TestErrorCallbackActivity"
android:label="@string/title_error_callback"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.TapToToneActivity"
android:label="@string/title_activity_output_latency"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".TestRouteDuringCallbackActivity"
android:label="@string/title_route_during_callback"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.RecorderActivity"
android:label="@string/title_activity_recorder"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".DynamicWorkloadActivity"
android:label="@string/title_dynamic_load"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.EchoActivity"
android:label="@string/title_activity_echo"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".TestColdStartLatencyActivity"
android:label="@string/title_cold_start_latency"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.RoundTripLatencyActivity"
android:label="@string/title_activity_rt_latency"
android:screenOrientation="portrait">
</activity>
<activity
android:name=".TestRapidCycleActivity"
android:label="@string/title_rapid_cycle"
android:exported="true"
android:screenOrientation="portrait" />
android:name="com.google.sample.oboe.manualtest.ManualGlitchActivity"
android:label="@string/title_activity_glitches"
android:screenOrientation="portrait">
</activity>
<activity
android:name="com.google.sample.oboe.manualtest.AutoGlitchActivity"
android:label="@string/title_activity_glitches"
android:screenOrientation="portrait">
</activity>
<activity
android:name="com.google.sample.oboe.manualtest.TestDisconnectActivity"
android:label="@string/title_test_disconnect"
android:screenOrientation="portrait">
</activity>
<service
android:name=".MidiTapTester"
android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
android:exported="true">
android:name="com.google.sample.oboe.manualtest.AudioMidiTester"
android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
<intent-filter>
<action android:name="android.media.midi.MidiDeviceService" />
</intent-filter>
@@ -142,21 +104,16 @@
android:resource="@xml/service_device_info" />
</service>
<service
android:name=".AudioForegroundService"
android:foregroundServiceType="mediaPlayback|microphone"
android:exported="false">
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
</manifest>

Some files were not shown because too many files have changed in this diff Show More