Skip to content
matthewcodergamerPublic

About

A web based xbox360 emulator

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,700 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Render360 — Xenia-Web

Release 75 · Experimental browser-native Xbox 360 emulator research project built around Xenia's PPC/HIR architecture, WebAssembly, sparse Xbox guest memory and WebGPU, with a separate PC Source/Portal WebAssembly bring-up path.

Render360 is not claiming Xbox 360 commercial-game playability yet. A title is only promoted when a real user-supplied game continuously executes, produces real GPU work, presents title-produced frames, and accepts working input without synthetic frame substitution.

This README is the current public project status. Historical percentages and old screenshots are not compatibility ratings.

Current status — October 9, 2026

XBOX 360 / XENIA-WEB TRACK
STFS / CON PACKAGE INPUT                    WORKING FOUNDATION
XISO / XGD / XDVDFS ISO INPUT              WORKING FOUNDATION
RETAIL XEX PREPARATION                     WORKING FOUNDATION
XBOX PE SECTION MAPPING                    WORKING FOUNDATION
XENIA PPC SCANNER / HIR FRONTEND           WORKING FOUNDATION
WASM32 XENIA BOOTSTRAP (-O2)               WORKING FOUNDATION
SPARSE 32-BIT XBOX GUEST MEMORY            WORKING FOUNDATION (page table)
PPC CONTEXT / NESTED GUEST CALLS           ACTIVE BRING-UP
PPC INSTRUCTION SEMANTICS (HIR EXECUTOR)   XENIA TEST SUITE 1398/1398; CI
XBOXKRNL / XAM STARTUP SERVICES            PORTED FROM XENIA; CI CRITICS
GUEST FILE SYSTEM (game:, d:, Cdrom0)      IMPLEMENTED; CI CRITICS
SAVE DATA (XamContent, save:)              IMPLEMENTED; KEPT PER SESSION
GUEST THREAD / TLS / KTHREAD / KPCR        IMPLEMENTED; PREEMPTIVE FAIR-SHARE SCHEDULER
THREADS THAT BLOCK AND RESUME              IMPLEMENTED (Asyncify fibers on the HIR executor)
NESTED-CALL TRANSLATION CACHE              IMPLEMENTED (about 8x faster startup)
GPU INTERRUPTS (vblank + CP INTERRUPT)     IMPLEMENTED (Xenia ExecuteInterrupt)
XENOS PM4 / RING FOUNDATION                BANJO-TOOIE DRIVES IT (WAIT_REG_MEM, fences, IBs)
VdSwap / XE_SWAP PATH                      CI-PROVEN FOUNDATION
XENOS SOFTWARE RENDERER (EDRAM, RESOLVES)  BANJO-TOOIE "LOADING..." SCREEN RENDERED; NOT REAL-TIME
CONTROLLER -> XINPUT                       WIRED (KEYBOARD, PAGE PAD, GAMEPAD)
XENOS SHADER -> SPIR-V -> WGSL / WEBGPU    CI-PROVEN FOUNDATION
REAL XBOX COMMERCIAL-TITLE FIRST FRAME     BANJO-TOOIE LOADING SCREEN (TITLE-RENDERED PIXELS)
XBOX COMMERCIAL GAMEPLAY                   NOT YET VERIFIED

PC SOURCE / PORTAL TRACK                   UNCHANGED SINCE SEPTEMBER 7
WORKERFS RETAIL CONTENT MOUNT              VERIFIED ON IPHONE
SOURCE ENGINE CALLMAIN ENTRY               REACHED
SOURCE FILESYSTEM MODULE LOAD              CURRENT BRING-UP BOUNDARY
PORTAL GAMEPLAY / RENDERED FRAME           NOT YET VERIFIED

Braid: where it stands

The last real-device Braid measurement (iPhone, V74) got past the V58 shared-epilog blocker, executed 38 HIR instructions from entry 0x8236EF38, made five kernel calls and then called xboxkrnl!HalReturnToFirmware (ordinal 0x28). That is the title deliberately giving up, not an emulator crash: Xenia implements the same export as process exit. It almost always means an earlier kernel call returned something the game did not accept.

V75 therefore replaces the placeholder kernel surface with a port of Xenia's own xboxkrnl/xam behaviour (see below). Braid has not been re-measured on a device with V75 yet. The next real-device run is the next authoritative data point; the browser now names the stop reason and lists the kernel calls behind it, so one run tells us exactly what to fix next.

October 9: Banjo-Tooie draws its loading screen; loader threads now run

  • Software Xenos backend. Banjo-Tooie's first real frame is its "Loading..." screen, rendered from the title's own draws. The renderer models the 10 MiB EDRAM and runs the vertex and pixel shaders through Xenia's ShaderInterpreter. Resolves to guest memory go through Xenia's draw_util::GetResolveInfo, and tiled textures, DXT included, are sampled with filtering. It is a correctness reference, about 600 ns per pixel, not a real-time path. Real-time rendering needs the WebGPU backend.
  • Frames in the browser. During a run, the page shows the newest swapped frontbuffer, and the controller (keyboard, on-page pad, Gamepad API) is written into the guest's XInput state every slice.
  • Preemptive, fair-share guest threads. The fiber scheduler was cooperative. Banjo-Tooie's main thread presents "Loading..." frames without ever blocking, so the loader threads never ran and the loading screen never ended. Guest threads are now preempted at call boundaries after a 2M-instruction quantum. The browser's host time slices no longer reset that quantum. The next thread to run is the one that has run least, so a loader thread woken by its partner runs at once, as it would on its own hardware thread in Xenia. The loader threads now run and decompress, so loading progresses, but slowly. The HIR executor runs about 5M guest instructions per second, far below the console, so Banjo-Tooie is still on its loading screen after 9 billion instructions (26,567 frames). Loading does advance: between 1 and 9 billion instructions, file reads rose from 53 to 130, and the loader wrote about 60 MB in 32 KB blocks. The next performance milestone is executor speed.
  • Audio render-driver clients. XAudioRegisterRenderDriverClient callbacks are now called the way Xenia's AudioSystem worker calls them: one per 256-sample frame played at 48 kHz, with up to 64 frames queued. Banjo-Tooie has not registered a client yet at this point.
  • Runner diagnostics. tools/run-title.mjs gained these options:
    • --progress
    • --frame out.png
    • --render-from-minstr N (fast-forward without rasterizing)
    • --watch-kernel-arg V (log kernel calls by argument, thread or export)
    • A per-thread report of what each guest thread is waiting on.

October 8 (later): Banjo-Tooie reaches its frame loop

In the headless runner, the Banjo-Tooie XBLA package now runs past startup and into the game's render loop. In 3.3 billion guest instructions it presented 5,001 frames (VdSwap), and the GPU command processor executed 14,870 draw calls and 4,999 swaps, with vblank and command-processor interrupts delivered to the title. What those draws look like on screen is not verified yet: no pixels have been checked against Xenia, and it is far from real-time speed.

What got it there:

  • Guest threads that block and resume. Xenia gives every XThread its own host thread. Render360 now runs each guest thread as a fiber on the HIR executor. Each fiber has its own C stack, its own copy of the per-thread executor state, and a Binaryen Asyncify buffer. A wait on an object another thread must signal switches threads and retries; a real deadlock is still reported as a wait.
  • Translation cache. Nested guest calls used to re-scan and re-translate the callee on every call (over 80% of the time). The finalized HIR is now kept per function, invalidated when its code pages change. Startup runs about 8x faster.
  • GPU interrupts. The title's graphics interrupt callback runs at 60 Hz (vblank) and for every PM4 INTERRUPT packet, as Xenia's Processor::ExecuteInterrupt does.
  • Command processor fixes, ported from Xenia:
    • GPU addresses are physical (TranslatePhysical).
    • Indirect buffers of any size.
    • WAIT_REG_MEM stalls and resumes at the exact packet.
    • Scratch-register write-back (D3D fences) and COHER_STATUS_HOST coherency.
    • The full 0x5003-entry register file.
    • Xenia's fixed GPU register reads (interrupt status, display size).
  • XAM app messages. XMsgInProcessCall and related exports, through ports of Xenia's XMP (music player), XGI and XLiveBase apps.
  • Interior tail entries now start only at a HIR block head; the mid-block case caused the sign_extend stop.
  • The browser runs titles continuously. Execution is cut into ~25 ms host slices: the core unwinds to the page between slices, so the page stays responsive, the progress line shows frames presented, and Stop works. The swapped frontbuffer is captured from the fetch constant recorded at swap time. The image is still black: the command processor executes draws but has no Xenos renderer for EDRAM and resolves yet. That renderer is the next GPU milestone.

October 8: Banjo-Tooie report, and the CPU checked against Xenia's tests

A device run of Banjo-Tooie (XBLA) stopped with "Unreachable code should not be executed": a WebAssembly trap inside the core while it ran the title entry. The trap left the cached core with a dangling execution context, so every later launch failed with define-function-failed. Now:

  • A crash no longer poisons the next launch. A trapped core is thrown away and a fresh one is loaded on the next Play, and the executor state is reset at every title handoff.
  • Crashes say what happened: the "Game Stopped" sheet reports the assertion text or allocation size, and which guest function was being translated or run. Diagnostics adds the last guest and kernel calls.
  • Xenia's Release assert behaviour: the core is built with NDEBUG as Xenia ships. Xenia's code assumes asserts are compiled out, and in WebAssembly a live assert is a fatal trap.
  • Global constructors run (_initialize). Xenia's dynamically built tables, such as the vsldoi permute table, were all zeros before.
  • The interpreter is checked against Xenia's own PPC instruction tests (src/xenia/cpu/ppc/testing, run by test-xenia-ppc-instruction-suite.mjs in CI). 630 passed before and 1398 of 1398 pass now. The missing pieces were carry arithmetic (adde, addze, subfe…), high multiplies and divides, select/min/max, traps, mftb, and essentially all of VMX/VMX128. The VMX work covers permutes, packs/unpacks including the D3D vertex formats, saturating arithmetic, converts, estimates, dot products and whole-vector shifts, transcribed from Xenia's x64 sequences (hir_vector_semantics.h). It also fixes an upstream Xenia constant-folding bug in mulhdu on non-MSVC builds.

The Banjo-Tooie trap needs a new device run to confirm. If it still stops, the sheet now names the cause.

What changed in V75

  • Kernel services ported from Xenia (src/xenia_web_bootstrap/kernel_xboxkrnl_services.cpp): XConfig settings, module handles and sections, system time, IRQL/spinlocks, events, semaphores, mutants, timers, single/multiple waits, thread creation with Xenia-shaped KTHREAD/KPCR/TLS, pool and physical memory, virtual-memory queries, Rtl strings/critical sections/SLists, NT status mapping, video-mode and display helpers, XAM user/sign-in/locale/input, XAudio render-driver clients and XMA contexts. Export tables are generated from Xenia's xboxkrnl_table.inc/xam_table.inc (tools/generate-kernel-export-tables.py).
  • More of Xenia's startup surface: SHA-1/SHA-256/HMAC/RC4 crypto (checked against Node's crypto), NtProtectVirtualMemory, kernel stacks, KeSetCurrentStackPointers, profile settings with Xenia's default profile, locale, launch data, offline XNet/WSA startup, the XMA context register API, and real notification listeners that receive Xenia's startup notifications (sign-in changed, UI, input devices). Braid polls XNotifyGetNext during startup.
  • Save data: XamContentCreate/Open/Close/Delete, enumeration, device queries and the headless device selector, backed by writable in-memory devices mounted at the title's root (for example save:). Saves last for the session; storing them in browser storage is still to do.
  • XBLA license: a per-game setting (Trial by default, as in Xenia, or Full for a title you own) feeds XamContentGetLicenseMask.
  • Kernel variables (KeTimeStampBundle, XboxHardwareInfo, ExLoadedImageName, VdGpuClockInMHz, ...) use Xenia's layout; anything unmapped gets Xenia's D###BEEF placeholder instead of zero.
  • Guest file system: game:/d: resolve to \Device\Cdrom0 over the user's ISO, package or folder, with NtCreateFile/NtReadFile/NtQueryDirectoryFile and friends. Large files are read on demand; a read the browser cannot satisfy synchronously stops at a named host-I/O boundary and is retried.
  • Named stop reasons: title exit (HalReturnToFirmware, XamLoaderTerminateTitle, bug checks), blocking waits and unimplemented exports are reported by name in the "Game Stopped" sheet; Diagnostics shows the last stop with the recent kernel calls as #n Name(args) -> result.
  • CPU runtime speed: the bootstrap is built at -O2, guest memory uses a flat two-level page table with host pointers, SSA values are indexed by Xenia's value ordinals, and per-call text tracing is off unless requested. Guest loop benchmarks improved roughly 1.5–1.75×.
  • UI: fixed the iPhone profile sheet opening half off-screen, the laptop header margin, the truncated status line and clipped settings values.
  • Release lanes (runtime, title runtime, content bridge, package core, service worker) all agree on V75; the fastlane republishes the browser bootstrap as V75 on main.

Running a title on a laptop

tools/run-title.mjs runs the same browser runtime headlessly in Node, straight from your own files, and prints why it stopped:

node tools/run-title.mjs <game.iso | package (LIVE/PIRS/CON) | default.xex | extracted folder>
     [--bootstrap build/xenia-ppc-bootstrap/xenia_ppc_bootstrap.wasm]
     [--trace 64] [--json report.json] [--budget N] [--verbose] [--trace-calls]

The report names the stop (for example title-requested-exit via HalReturnToFirmware), the next kernel export that needs implementing, the recent kernel calls with arguments and results, and the title's DbgPrint output. Nothing is uploaded or copied. On an iPhone the same information is under Game → Diagnostics after a stop.

The next CPU milestone: threads that wait

Every title that gets past its first seconds creates worker threads and blocks on events those threads signal. Today a blocking wait ends execution on both CPU tiers: the native HIR interpreter runs the main thread to its first blocker, and the generated-WASM tier can only yield between blocks of its top-level function, not from inside nested calls where waits actually happen. The browser has no native stack switching on Safari, so the plan is to make yields travel through guest calls in Render360's own WASM emitter (each call site becomes a resumable block and each guest thread keeps a stack of continuations), with Asyncify-style fibers for the interpreter as the fallback. Until that lands, the named "waiting in ..." stop is expected once a title blocks on another thread.

iPhone 11 and GTA IV / GTA V, honestly

Braid (XBLA, 2D) is the right first target, followed by other XBLA and lighter retail titles. GTA IV and GTA V are not realistic in Safari on an iPhone 11: they need most of the Xbox 360's 512 MiB of RAM plus Render360's own overhead inside a Safari tab limited to roughly 1–1.5 GB, three 3.2 GHz PowerPC hardware threads with heavy VMX128 vector code (WebAssembly has no native JIT, only generated Wasm), and a GPU pipeline that even desktop Xenia struggles to run at speed. Growable WebAssembly memory keeps Render360 from reserving that memory up front, but it cannot make it appear.

Browser execution architecture

Xbox 360 / Xenia-Web

lawfully obtained Xbox 360 package / ISO
        ↓
STFS or XDVDFS reader
        ↓
default.xex
        ↓
XEX2 preparation / AES / LZX
        ↓
Xbox PE mapping
        ↓
sparse 32-bit guest memory
        ↓
Xenia PPC scanner + HIR frontend
        ↓
Render360 Wasm / HIR execution path
        ↓
xboxkrnl + XAM HLE imports
        ↓
Xenos ring / PM4
        ↓
shader translation + EDRAM
        ↓
WebGPU
        ↓
real VdSwap-derived browser frame

PC Source / Portal

user-supplied Portal retail files
        ↓
Portal package adapter
        ↓
dedicated Source WebAssembly worker
        ↓
WORKERFS game-content mount
        ↓
Emscripten Source engine
        ↓
Source dynamic modules (.so / Wasm side modules)
        ↓
filesystem + engine subsystem initialization
        ↓
Source renderer / audio / input
        ↓
real Portal frame and gameplay

The PC Source route is independent of Xbox XEX/PPC emulation. A Portal Source failure must therefore remain in Portal/Source diagnostics instead of falling through to an unrelated Xenia CPU blocker.

GPU foundation

The Xbox GPU work already includes title ring and CP_RB_WPTR tracking, PM4 decoding and persistent Xenos state, VdSwap / XE_SWAP presentation semantics, mapped-frontbuffer validation, Xenos shader capture, upstream Xenia Xenos -> SPIR-V translation, Naga SPIR-V -> WGSL conversion, WebGPU shader validation, a 10 MiB browser WebGPU eDRAM mirror, async pipeline caching and browser streaming foundations.

Those systems are not the measured Braid blocker because the last Braid real-device run had not reached GPU initialization. They are also separate from Portal's Source rendering path.

Storage and browser constraints

Large Xbox ISO files must not be copied wholesale into Wasm RAM. Render360 uses bounded file/Blob reads and browser streaming infrastructure so multi-gigabyte media can remain outside the emulated 512 MiB Xbox address space.

Portal similarly uses browser-backed retail content rather than treating the whole installation as one giant Wasm-memory allocation. Browser storage quota is not the same thing as physical iPhone free storage, and Safari does not expose a trustworthy webpage API for exact remaining device flash capacity.

The current September 7 main baseline deliberately predates the later experimental Delete All Games & Copies / aggressive storage-cleanup sequence. Storage changes should be reintroduced only when they can be isolated from emulator and Source startup behavior.

What “playable” will mean

An Xbox commercial title will only be marked playable after a real user-supplied copy demonstrates sustained PPC execution, required kernel/XAM services, runnable guest scheduling, continuous PM4/ring consumption, real title shader/resource handling, repeated title-produced frames and usable controls without synthetic substitution.

For Portal/PC Source, playable means the real Source engine initializes from the user's retail content, loads the required modules and maps, continuously renders genuine game frames, accepts controls and advances through gameplay without a synthetic replacement renderer.

Near-term engineering order — September 26

XBOX / BRAID
1. run Braid on the real device with V75 (XBLA License: Full) and read the named stop + kernel calls
2. implement or correct the kernel service that report points at (Xenia semantics)
3. make guest threads able to block and resume (yield through calls in the WASM emitter)
4. deliver the graphics interrupt (vblank / swap) to the title's callback
5. reach Xenos ring initialization, PM4 traffic, VdSwap and the first genuine title frame
6. persist save packages in browser storage; move emulation into a Web Worker

PORTAL
1. determine whether libfilesystem_stdio.so returns, rejects, traps or hangs
2. reach Source filesystem/engine initialization
3. reach the first genuine Portal-rendered frame

Important files

  • src/xenia_web_bootstrap/kernel_xboxkrnl_services.cpp — Xenia-ported xboxkrnl/XAM services, guest file system, terminal and wait boundaries.
  • src/xenia_web_bootstrap/kernel_runtime_foundation.cpp — guest threads, TLS, handles and virtual memory.
  • src/xenia_web_bootstrap/kernel_import_probe.cpp — imported thunk / HLE boundary and named kernel call trace.
  • src/xenia_web_bootstrap/kernel_export_ordinals.h, render360-kernel-export-names.mjs — generated from Xenia's export tables.
  • render360-guest-vfs.mjs — registers the user's ISO/package/folder as the guest disc.
  • tools/run-title.mjs — headless title runner for laptop iteration.
  • render360-title-controller.mjs — extracted XEX title handoff, main-thread context, kernel variables and boundary telemetry.
  • render360-browser-ppc-session.mjs, render360-browser-thread-scheduler.mjs — generated-Wasm CPU session and cooperative guest scheduler.
  • src/xenia_web_bootstrap/sparse_guest_memory.cpp — authoritative sparse Xbox virtual memory (two-level page table).
  • src/xenia_web_bootstrap/hir_correctness_executor.cpp — base HIR compatibility executor.
  • src/xenia_web_bootstrap/probe_backend.cpp — nested-call resolver and shared-epilog helper bridge.
  • src/xenia_web_bootstrap/title_gpu_runtime.cpp — title ring/MMIO/VdSwap runtime.
  • src/xenia_web_bootstrap/xenos_gpu_foundation.cpp — Xenos PM4/resource/EDRAM state.
  • render360-webgpu-runtime.mjs — browser WebGPU/eDRAM foundation.
  • recompiled/pc/portal/source-wasm/ — Portal Source worker, package adapter and build.
  • docs/PROJECT_LAYOUT.md — repository rules; ROADMAP.md — broader milestones.

Legal / project scope

Render360 does not include commercial Xbox 360 games, copyrighted title assets, keys or firmware. Use only content you are legally permitted to use. Xenia-derived portions remain subject to upstream Xenia licensing terms; see LICENSE_XENIA.txt.

About

A web based xbox360 emulator

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages