Standalone GitHub Pages site for opensaga. This repository owns the landing
page, shared design, progress explorer, Saga player, nudat archive explorer, and nusave save editor.
CI and deployment builds always use the latest main of each source repository
and record the exact revisions in build-info.json.
| Route | Application | Input |
|---|---|---|
/ |
Landing page and project FAQ | Saga's committed matching metrics |
/progress/ |
Interactive Saga decompilation statistics | vendor/saga/matching.json |
/play/ |
Experimental Saga WebAssembly player | Bazel build and your own Android OBB |
/nudat/ |
nudat archive explorer | Rust library built to WASM and your local DAT/OBB |
/nusave/ |
nusave save editor | Rust library built to WASM and your local PC/Android save |
nudat uses one connected explorer with a folder sidebar, back/forward/up navigation and breadcrumbs beside archive search. File sizes and compression stay in the file list; preview and file download share the right pane. Click anywhere on a file row or use the up/down keys to browse previews. It searches all archive paths, previews text with line numbers inline and in an expanded dialog, previews browser-supported images, audio, DDS and ETC1 textures, verifies archives, and downloads individual files or ZIPs of a folder, search results, or a selection. An included synthetic example makes the complete workflow available without original game assets. Long operations can be cancelled; an invalid replacement archive preserves the open archive. A Web Worker reads small Blob ranges through the library's generic reader API. Archives remain on your computer; large archives are never loaded in full into WASM memory. Text previews are limited to 1 MiB and 10,000 displayed lines; media previews are limited to 64 MiB and depend on browser codec support. Audio never autoplays and stops when leaving its preview. Downloading an individual entry is limited to the library's 512 MiB read limit; ZIP exports accumulate decoded output in browser memory.
nusave opens Windows PC and Android game saves and Android SuperOptions. Its summary and searchable tree use the library's schema and interpreted values. Sections contain levels, characters, areas, and missions, with minikits nested under their level. Selecting an item shows its controls; draft edits are retained when moving between branches. Edit values directly with numeric inputs, toggles, dropdowns, text fields, and named flag checkboxes. The library provides control types, limits, and choices; invalid edits preserve the current save. Create new Android saves/options, reset edits, and download saves with repaired derived fields or preserve their exact stored bytes. The application works in a Web Worker with a 16 MiB input limit. Unknown bytes and the detected platform layout are preserved; new saves use Android defaults rather than converting PC files.
Texture previews decode on the archive worker, without GPU compression extensions.
Supported formats are DDS BC1–BC5 (DXT1–DXT5, ATI1/2 and equivalent DX10 formats),
16/24/32-bit RGB(A), and ETC1 in DDS, PKM, or KTX1 containers. This includes
Saga's .etc1 and .android_etc1_tex DDS files and their nonstandard pixel-format
header size. Previews show the base mip of the first face/layer, with a 16-megapixel
limit; unsupported formats (including BC6/7, ETC2, and volume textures) show an
error instead of a misleading image. The parser follows the
Microsoft DDS documentation
and Khronos ETC1 specification.
The example archive contains generated DDS/ETC1 samples. Decoder tests cover
colors, alpha, subblock orientation, row padding, container variants, and corrupt data.
Requirements: Node.js 24 + npm, Python 3.12+, Rust with
wasm32-unknown-unknown, wasm-pack 0.15.0, and Bazelisk (Saga pins Bazel 9.2.0).
Bazel downloads its Emscripten and C++ toolchains.
git clone --recurse-submodules https://github.com/opensagadev/opensaga.dev.git
cd opensaga.dev
npm ci
rustup target add wasm32-unknown-unknown
cargo install wasm-pack --version 0.15.0 --locked
npm run build
npm test
npm run serveOpen http://127.0.0.1:8000. dist/ is the complete deployable artifact.
No original game assets or reference binaries are required to build the site.
For a quick layout preview, use npm run build:preview, then npm run serve;
that preview deliberately omits application binaries. To use a working Saga
checkout locally: python3 scripts/build.py --saga ../saga.
site/shared/ owns the shell, navigation, theme, typography, forms, buttons,
tables, dialogs, and responsive layouts. Tailwind scans all templates and JS
under site/; the build emits one stylesheet used by every application.
The theme uses neutral surfaces, thin rules, and simple document layouts. Violet
links and selection, gold folders, and the unchanged logo supply the color.
Matching graphs retain the red-to-green score scale. It follows
the system light/dark preference. Navigation, panels, buttons, inputs, tables,
and focus states all use these shared tokens. There are no page-local stylesheets. site/apps.json supplies navigation and
application titles; "wide": true gives an application the shared wide workspace.
For mixed widths, "sectioned": true lets the content use separate site-container
and site-container site-container-wide sections, as nudat does for its introduction
and explorer. Workspaces use up to 1440px with outer gutters. Save fields and
desktop archive lists flow with the page; section navigation and the archive
preview stay alongside them while scrolling. The progress map has a near-screen-height
viewport with a separate source filter sidebar. Shared tinted navigation areas,
white content surfaces, and purple primary actions distinguish the tools without
row-by-row borders. nusave groups labelled controls into a responsive form and
keeps technical descriptions in expandable details. Existing /play/ and /progress/ links, including ?obb=,
retain their behavior.
Add a manifest entry and a site/apps/<id>/index.html content fragment. The
builder wraps it in site/shared/layout.html and copies its JavaScript files.
Use ordinary semantic HTML and the shared site-section, site-title,
site-subtitle, site-intro, site-actions, and field-label classes. Add
reusable UI components to site/shared/theme.css. The migrated player and
progress explorer retain their complete HTML templates and receive the same
shared header, head, and footer.
crates/nudat-web and crates/nusave-web are thin WASM adapters. Save parsing,
checksums, schema, summaries, and validated edits live in the nusave library;
filesystem operations live in its CLI. Archive parsing, compression and
validation belong to nudat. Its library is portable by default; filesystem
operations and terminal dependencies belong to the separate CLI crate.
The library handles Read, Seek, and Write sources, while the native CLI
owns files, directories, temporary staging, and thread scheduling.
The progress explorer keeps its summary at the reading width and its map in a wider workspace around 90% of the desktop viewport height. Scroll over the map or use its zoom buttons to zoom; drag to pan. Labels are on by default, including source-unit headings in the function-size view, and can be toggled off. Source files can be searched in the sidebar. Matching graphs retain the red-to-green score scale.
Regenerate the synthetic nudat example with
python3 scripts/create_example.py --nudat /path/to/nudat. The checked-in
archive contains only generated demonstration files.
Every production deployment also builds the staging branch, if it exists,
into https://opensaga.dev/staging/. Pushing to staging requests a new
deployment from main. Unlike production, staging builds its own scripts and
uses its pinned submodules as-is, so it can point at unmerged Saga, nudat or
nusave commits. Staging pages carry a banner and a noindex tag. A failed
staging build is reported as a warning and does not block production.
To stage a change, reset staging to the branch under test, pin the
submodules it needs and push:
git checkout -B staging my-feature
git -C vendor/saga fetch origin my-saga-branch
git -C vendor/saga checkout FETCH_HEAD
git commit -am "Stage my-feature with my-saga-branch"
git push --force origin stagingProduction and pull request checks always build the latest main of Saga,
nudat and nusave: pages.yml runs git submodule update --remote before
building and never commits the result. The commits recorded for the
submodules only serve as a starting checkout and as the pins for staging, so
they never need bumping for production. To build against the same sources
locally:
git submodule update --init --remote vendor/saga vendor/nudat vendor/nusave
git submodule foreach 'git submodule update --init --recursive'
cargo update -p nudat -p nusave
npm run build
npm testEach deployment includes build-info.json with the exact source revisions.
Because every build refreshes all applications, a site-only push never rolls
back an application, and a pull request sees upstream breakage before merge.
.github/workflows/pages.yml builds all WASM applications, compiles shared
styles, assembles all pages, and validates the result on main pushes and pull
requests. Main pushes/manual dispatches upload dist/ as a Pages artifact;
pull requests only build and test. This repository publishes https://opensaga.dev/
using GitHub Actions. The repository Actions variable PAGES_DEPLOY_ENABLED is
set to true; setting it to false pauses publishing while retaining build checks.
Saga, nudat, and nusave each have a notify-pages.yml workflow that dispatches this
repository's pages.yml on pushes to main (and can be run manually).
The Actions secret OPENSAGA_PAGES_TOKEN is configured in all three source
repositories. It uses a fine-grained personal access token restricted to
opensagadev/opensaga.dev with Actions: read and write. When rotating it,
update the secret in all three repositories and approve the token for the
organization if required. The source repositories' automatic GITHUB_TOKEN
cannot dispatch workflows in another repository. If the dedicated secret is
missing, the notification step skips with a warning.
To rebuild manually with the latest application sources:
gh workflow run pages.yml --repo opensagadev/opensaga.dev --ref mainGitHub Pages is configured on this repository with GitHub Actions as its
source and opensaga.dev as its custom domain. The Saga migration removes
its old Pages deployment; source repositories only notify this repository.
The apex domain uses GitHub Pages IP addresses; www redirects to the apex
domain over HTTPS. CNAME in the build artifact alone does not configure
an Actions deployment; the repository's Pages settings own the domain.
Saga's threaded player has a /play/-scoped isolation service worker; other
applications use normal browser isolation. Remote OBB sources must permit
CORS. For local assets, use the player's file selector.
The existing opensaga branding was migrated from Saga. Stud proportions refer to James Jessiman's LDraw 6141, CC BY 4.0. Discord and GitHub icons come from Simple Icons 16.31.0 (CC0). D3 (ISC) and fflate (MIT) are bundled locally from their pinned npm packages. Copyrighted game assets are not included.