- JavaScript 64.5%
- PHP 17.2%
- CSS 12.1%
- Python 3.2%
- C 1.7%
- Other 1.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq3u1NBXiJfbYvHMJEySUt |
||
| docs/play-2026-09 | ||
| emulatorjs-patch | ||
| harness | ||
| jocor_cartridge | ||
| jocor_eaglercraft | ||
| jocor_flashback | ||
| linkcore | ||
| netplay-server | ||
| tools | ||
| .gitignore | ||
| arcade-netplay-proxy.conf | ||
| check-access.php | ||
| fetch-boxart.py | ||
| fetch-swf.sh | ||
| lib-arcade.js | ||
| package-lock.json | ||
| package.json | ||
| push-file.sh | ||
| README.md | ||
| retry-swf.sh | ||
| rom-list.txt | ||
| shots.js | ||
| test-apps.js | ||
| test-crosscheck.js | ||
| test-invite.js | ||
| test-netplay.js | ||
| test-newuser.js | ||
| test-singleplayer.js | ||
Three game apps for Nextcloud (CT 100)
Built 2026-09-06. These are the sources; the deployed copies under
custom_apps/ on CT 100 are the live ones.
| App | Nav name | What it is |
|---|---|---|
jocor_cartridge |
Cartridge | 69 console ROMs from a Team folder, EmulatorJS, box art, netplay with invites |
jocor_flashback |
Flashback | 1,314 mirrored Flash games played by self-hosted Ruffle |
jocor_eaglercraft |
Eaglercraft | Minecraft 1.12.2 in the browser, framed from eaglercraft.ir |
jocor_arcade was the first version of Cartridge and is gone — disabled,
removed, backed up at
CT100:/root/nc-app-backups/jocor_arcade-superseded-by-cartridge-20260906.tgz.
Not in this tree
- The 3.1 GB of
.swffiles. They live only atCT100:/var/lib/docker/volumes/nextcloud-server_nextcloud/_data/custom_apps/jocor_flashback/games/. Re-mirror withfetch-swf.shthenretry-swf.sh(the second one handles the ~40 games whose file is not named after its slug). A redeploy of Flashback must neverrm -rfthe app directory — copy the subdirectories individually and leavegames/alone. - The ROMs. Team folder id 2 (
Games), grouparcade, read-only.
Running the tests
They drive real Chromium against the live instance over the Funnel URL and log in as real accounts.
cd /root/jocor-games
npm install playwright-core@1.55.0 # if node_modules is absent
P='…' node test-singleplayer.js # Cartridge, one player
P1='…' P2='…' ID=7042 node test-netplay.js # two players, one room
P='…' node test-crosscheck.js # a third account
P='…' node test-apps.js # all three apps
P1='…' P2='…' ID=7025 node test-invite.js # invite → notification → join
ID is a Nextcloud file id from /apps/jocor_cartridge/api/games; it is not
stable across a re-upload of the ROMs. 7025 is Super Mario World.
Accounts: arcade1 (Player One), arcade2 (Player Two), arcadeqa (Arcade QA),
all in group arcade.
Things that cost real time — full detail in the jocor-arcade-app memory note
- Netplay needs an https entrance. On plain http the browser has no WebRTC at all and a room connects to nothing, silently.
- Never check an emulator canvas with
drawImage— WebGL withoutpreserveDrawingBufferreads back solid black, which looks exactly like a dead emulator. Screenshot the element instead. - Nextcloud's root
.htaccessonly passes a fixed extension allowlist through to static files..data(EmulatorJS cores) and.swfare not on it. Both apps carry their own.htaccessto deal with that — Cartridge to allow its cores, Flashback to deny its games (they are served by an authenticated route instead, so the mirror is not public). - NC 34 cannot emit
'unsafe-eval'at all, which is why the EmulatorJS cores are pre-extracted at build time rather than unpacked in the browser.
Who can play what (2026-09-06)
| App | Needs | Why |
|---|---|---|
| Flashback | a Nextcloud login, nothing else | games are app assets, streamed by an authenticated route |
| Eaglercraft | a Nextcloud login, nothing else | it is a frame |
| Cartridge | membership of a group with read on Team folder id 2 | the ROMs are files, so Nextcloud's own permissions decide |
All three apps are enabled instance-wide (enabled: yes, not a group list), so
they appear in everyone's app menu. Only Cartridge can be empty for someone, and
when it is, it says so and points at the fix rather than looking broken.
Team folder Games (id 2) grants read to arcade, admin and
jo-cor-users — between them every path an account currently arrives by,
including self-registration (the registration app puts new sign-ups in
admin). Every human account is also in arcade directly.
A brand-new account created with no groups at all still sees no ROMs. One line fixes it, and it is worth doing at creation time:
occ group:adduser arcade <uid>
check-access.php answers this for the whole instance at once — it asks
LibraryService the same question the app asks, per account:
docker cp check-access.php nextcloud-server:/tmp/ && \
docker exec -u www-data nextcloud-server php /tmp/check-access.php
Two accounts legitimately show no games: chamade-bot-wq912lqz and, until it is
re-enabled, the disabled account craig.
Who can be invited
InviteController::players() lists everyone who can open the game in front of
you, not everyone who shares a group with you. That predicate is deliberate:
the group version broke as soon as the household grew past one group, and it
could list people who had no access to the library. The invite itself is
authorised the same way, so the picker can never offer someone the invite would
then refuse.
Two consequences to expect: a service account with library access (nyx-agent,
via admin) does appear in the list, and an account that has never logged in
does not, because it has no home storage to resolve the ROM against yet.