No description
  • JavaScript 68.2%
  • HTML 16.2%
  • PowerShell 15.6%
Find a file
2026-05-18 19:56:23 +02:00
.claude Project initialization 2026-05-18 02:48:37 +02:00
assets Show current name in rename panel and clarify rename errors 2026-05-18 19:54:55 +02:00
web Show current name in rename panel and clarify rename errors 2026-05-18 19:54:55 +02:00
.gitattributes Update .gitattributes 2026-05-18 18:49:25 +02:00
.gitignore Project initialization 2026-05-18 02:48:37 +02:00
API.md Strip menu links and add GlobalRanks highscore system 2026-05-18 03:51:28 +02:00
build.ps1 Strip menu links and add GlobalRanks highscore system 2026-05-18 03:51:28 +02:00
CHANGELOG.md Show current name in rename panel and clarify rename errors 2026-05-18 19:54:55 +02:00
CLAUDE.md Project initialization 2026-05-18 02:48:37 +02:00
README.md Show current name in rename panel and clarify rename errors 2026-05-18 19:54:55 +02:00

Nyancat Lost In Space - Redux

A rework of the 2011 Flash game, playable in any web browser without Flash (via Ruffle) and with a new highscore system backed by GlobalRanks.

The original SWF was exported with JPEXS FFDec into assets/. The menus were stripped of obsolete links/banners and the legacy MyLostGames highscore calls were replaced with a GlobalRanks integration driven by a small JavaScript bridge. build.ps1 recompiles the edited ActionScript back into a SWF that is served by Ruffle.

Repository layout

Path Purpose
assets/NyanCat.swf Original, unmodified game (build input — do not edit)
assets/scripts/ FFDec-exported ActionScript; the edited sources
build.ps1 Recompiles assets/scripts into web/NyanCat.swf
web/index.html Ruffle host page
web/nyan-ranks.js JS bridge: GlobalRanks REST ↔ SWF (ExternalInterface)
web/NyanCat.swf Build output (generated; serve this)
API.md GlobalRanks REST API reference

Prerequisites

Tool Version Notes
JPEXS FFDec recent Provides ffdec-cli.exe; recompiles the AS3
Java (JRE/JDK) 11+ Required by FFDec; verified here with Java 21
PowerShell 7+ build.ps1 declares #Requires -Version 7
A static web server any Must serve web/ over HTTPS (see Hosting)
A reachable GlobalRanks instance Default: https://ranks.flappybird2026.com/

FFDec is auto-located from PATH or the default install dirs; otherwise pass -FFDec "C:\path\to\ffdec-cli.exe" to build.ps1.

Build

pwsh ./build.ps1
# or, with an explicit FFDec path:
pwsh ./build.ps1 -FFDec "C:\Program Files (x86)\FFDec\ffdec-cli.exe"

This re-imports assets/scripts into a copy of assets/NyanCat.swf and writes web/NyanCat.swf. FFDec recompiles the ActionScript, so the build also acts as the compile check — a non-zero exit means an AS3 error (see the output).

Configuration

Edit web/nyan-ranks.js if the backend differs:

  • API_BASE — GlobalRanks API root (default https://ranks.flappybird2026.com/api/v1).
  • GAME_SLUG — leaderboard key (default nyan-cat-lost-in-space; lowercase [a-z0-9_-]).

The bridge stores the per-user UUID and secret token in localStorage and sends them as X-User-UUID / X-Auth-Token. It refuses to use the auth token on a non-HTTPS origin (localhost excepted); leaderboard reads still work, but score submission and renames require HTTPS.

GlobalRanks username behaviour

Two server-side traits of GlobalRanks affect the in-game "Change Name" panel (the client surfaces both with specific messages — it cannot work around them):

  • Usernames are unique across the whole GlobalRanks instance, not per game. A name already taken by any user of any game registered against that deployment returns 409 → the panel shows "NAME ALREADY TAKEN". Auto-assigned Player_xxxxxx names never collide; pick a distinctive custom name.
  • A user profile only exists after the first score is submitted. Until then GET /users/{uuid} is 404: the panel shows "NAME SET AFTER 1ST SCORE", and a rename attempt shows "PLAY A ROUND FIRST". Play one round, then rename.

Hosting

  1. Run the build so web/NyanCat.swf exists.
  2. Serve the web/ directory with any static web server, over HTTPS (browsers and the bridge both require a secure context).
  3. The GlobalRanks server must send CORS headers permitting your page's origin (Access-Control-Allow-Origin, and the X-User-UUID / X-Auth-Token request headers). That is configured on the GlobalRanks deployment, not here.

Ruffle (CDN vs. self-hosted)

web/index.html loads Ruffle from the CDN pinned to an exact version (@ruffle-rs/ruffle@0.2.0). For production, self-hosting is recommended — it removes the third-party dependency entirely:

  1. Download the *-selfhosted.zip for a release from https://github.com/ruffle-rs/ruffle/releases.
  2. Extract it into web/ruffle/.
  3. In web/index.html, change the Ruffle <script src> to ./ruffle/ruffle.js (drop crossorigin).

If you keep the CDN, add a Subresource Integrity hash to the <script>: integrity="sha384-…" computed from that exact pinned artifact. Re-pin and recompute the hash when bumping Ruffle.

Security response headers (required for production)

Set these at the web server. A Content-Security-Policy meaningfully limits the blast radius of any XSS, but Ruffle's CSP needs care and must be validated in a browser — Ruffle compiles WebAssembly and spawns a blob worker, and index.html currently contains an inline <script>/<style>.

Working starting point (self-hosted Ruffle; everything same-origin):

Content-Security-Policy:
  default-src 'none';
  script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  connect-src 'self' https://ranks.flappybird2026.com;
  img-src 'self' data:;
  worker-src blob:;
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=63072000; includeSubDomains

Notes:

  • If you keep the CDN instead of self-hosting, add https://unpkg.com to both script-src and connect-src (Ruffle fetches its .wasm from there).
  • 'unsafe-inline' is required only because index.html has an inline bootstrap script and stylesheet. To drop it, move that JS/CSS into separate files under web/ (or apply CSP hashes/nonces) and tighten script-src /style-src to 'self'.
  • Strict-Transport-Security only once the site is reliably HTTPS.

Example — Caddy:

ranks-game.example.com {
    root * /srv/nyancat/web
    file_server
    header {
        Content-Security-Policy "default-src 'none'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://ranks.flappybird2026.com; img-src 'self' data:; worker-src blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "no-referrer"
        Strict-Transport-Security "max-age=63072000; includeSubDomains"
    }
}

Example — nginx:

location / {
    root /srv/nyancat/web;
    add_header Content-Security-Policy "default-src 'none'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://ranks.flappybird2026.com; img-src 'self' data:; worker-src blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "no-referrer" always;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
}

Development notes

  • Only assets/scripts/** is hand-edited; assets/NyanCat.swf is the pristine build input. Re-run build.ps1 after any script change.
  • Pre-commit gate: run /review and /security-audit and resolve findings before committing (see CLAUDE.md). Record changes in CHANGELOG.md.
  • See CHANGELOG.md "Security" for the tracked, deferred hardening items (SRI, CSP/header rollout) referenced above.