X-Men: Mutant Academy 2 PC

X-Men: Mutant Academy 2 PC brings X-Men: Mutant Academy 2 to PC.

README

X-Men: Mutant Academy 2 — PC port

A static recompilation of the PlayStation game X-Men: Mutant Academy 2 (SLUS-01382), built with RecompOne. The PS1 executable and its eighteen character overlays are translated to C# ahead of time and linked against RecompOne's runtime, so this is a native program rather than an emulator: the game's own code runs directly, and the hardware it expects — GPU, SPU, CD drive, controllers — is provided by the runtime.

It runs end to end at 60 fps, rasterising internally at 4x, with an optional 4x texture pack built from the game's own art and optional 16:9 output.

Download v1.1

Download the Windows x64 release, including the 4x texture pack. Unzip into a writable folder and supply your own USA disc (SLUS-01382): CUE/BIN with all 13 tracks, or CHD passed as XMenMA2.exe "D:\roms\game.chd". No disc image is included. See the bundled README.txt for installation and controls.

v1.1 adds 16:9 widescreen support and one final texture replacement for the splash screen.

Fight

16:9, internal 4x, texture pack on.

Title

The front end has no 3D in it, so it stays 4:3 whatever the setting.

What this repository is

The port itself: the entry point, the patches, the build configuration, and the tooling written for it. It deliberately does not contain the game.

Kept outside source control:

why
the disc image (*.bin, *.cue)the game
port/generated/recompiled game code, derived from the disc
port/assets/textures decoded out of the disc
port/packs/the upscaled pack built from those textures
tools/RecompOne/, tools/esrgan/third-party, vendored locally

For the release download, supply your own disc image; the executable and pack are already built. To build from source, run the recompiler and build the pack using the scripts below.

Status

Works. Boots to the title, plays through arcade mode, 60 fps. FMV, XA audio, memory cards, the character-select and options screens, and all eighteen characters load. The published build is a single self-contained XMenMA2.exe.

Since the first release: optional 16:9 that widens the view rather than stretching it, the splash and other full-screen art brought into the upscale pack, and F12 for a screenshot of exactly what is on screen.

Unverified. Nobody has confirmed the audio actually sounds right — the developer setup for this port has no audio output. Two-player input is wired but only port A has been driven. Endings, credits and practice mode have never been reached. Round timing has not been compared against hardware.

Known rough edges are listed in TO_DO.md, which is kept honest rather than tidy — including the things that were tried and did not work.

Widescreen

Off by default; Settings > Display > Widescreen (16:9) turns it on, and resizes the window to match, because a 16:9 frame left inside a 4:3 window gets boxed a second time and reads as a fault.

Cyclops vs. Rogue widescreen comparison

Wolverine vs. Phoenix widescreen comparison

Actual v1.1 captures: Cyclops versus Rogue in the volcanic arena, and Wolverine versus Phoenix in the lunar arena. The cyan outline marks the centered 4:3 field of view inside each 16:9 capture; scenery outside it is the additional widescreen view. These are boundary overlays, not separately rendered 4:3 frames. The original captures are available here and here.

Capture method and validation.

The game culls anything that projects outside its own screen, so widening the area the renderer draws into achieves nothing — the scenery is dropped before the GPU sees it. Instead the perspective projection is squeezed by a quarter, which puts a 16:9 field of view inside the 4:3 screen the game believes in; its own culling then keeps all of it, and the frame is stretched back out at presentation. The stages are one and a half to three screens wide, so there is plenty there to show.

Frames with no 3D in them — the movies, the front end, the VS card — are left at 4:3 and pillarboxed, never stretched.

The HUD is flat artwork at fixed screen positions, so it would come out a third wide. Nothing in the GPU stream separates it from the stage — same primitives, one ordering table, shared palettes — so the separation comes from the game's own code: a sprite table at 0x800E30D8 holding every 2D element it draws. Primitives assembled in there are squeezed to match, and come out the shape they were drawn.

Press F12 at any time for a screenshot of exactly what is on screen.

The texture pack

The interesting part, and the part that took the longest to get right.

Texture comparison

Same frame, same script, pack off and on, at 1:1 output pixels.

Textures are read from the disc, not captured from a play session. DATA/WAD.WAD is a PWF archive holding 1,891 TIM images; every one of them exists whether or not anyone has visited the screen that draws it, which is what makes coverage a property of the disc rather than of how thoroughly you played.

Extracted textures

Two things had to be solved to make that usable.

A texture is what the game uploaded, not the page it landed in. The resolver used to identify a texture by a fixed 256-texel window around the tile being drawn. This game packs several images into the same VRAM rows, so that window is a slab of unrelated neighbours, all decoded with one depth and one palette — most of every "texture" was garbage. VramTracker now records the rectangle of each image the CPU DMAs into VRAM, and a tile is identified by the upload that contains it. An image load is the only event in the GPU command set that says these pixels are one picture.

A palette changes on its way into VRAM. Every entry gets bit 15 set, and the magenta colour key 0x7C1F becomes 0x0000. The first rule decides the lookup key; the second decides whether a sprite has transparency or a magenta blob. Verified against every palette the running game was observed to upload — 102 of 102 exact.

tools/vramhash.py reimplements the runtime's lookup hash so a texture decoded offline carries the same key the game computes at draw time, and tools/verify_textures.py checks every image is a correct decode before it ships.

Building the pack
cd port
python tools/disc.py extract disc                                     # ISO 9660 out of the disc image
python tools/extract_textures.py --wad disc/DATA/WAD.WAD --out assets/disc-textures
python tools/verify_textures.py --dir assets/disc-textures/textures --out assets/report
python tools/upscale_textures.py --dump assets --out packs/xmenma2-4x --only tiles

Full-screen pictures are collected from a run rather than from the disc, since they are keyed by the pixels the game uploads: play with XMENMA2_DUMP=images, then upscale_textures.py --only images puts them in the same pack.

Anything without a replacement falls back to the original, so a partial pack is always safe, and deleting a file from the pack simply restores that texture.

Tools

toolwhat it does
disc.pyreads the ISO 9660 filesystem out of a MODE2/2352 disc image
wad.pyreads DATA/WAD.WAD, the game's asset archive
tim.pyfinds and decodes TIM images, and models the palette upload
vramhash.pyreproduces the runtime's texture lookup hash
extract_textures.pydisc to PNGs, named by the key the runtime looks them up by
merge_textures.pycombines disc textures with textures observed at run time
verify_textures.pyrejects bad decodes, flags what is not a picture
restore_alpha.pyputs correct transparency back on a texture upscaled elsewhere
upscale_textures.pybuilds the 4x pack
wide_check.pytells the end of an arena apart from a renderer clipping it
closure.py, fixmaps.py, mipsdis.pyrecompilation support

Diagnostics live in port/patches/: Diag.cs (logging, watchdog, crash capture), Capture.cs (headless screenshots and scripted input), TextureDump.cs, and Harness.cs (RAM and scratchpad snapshots, pokes, watches, and a write probe that names the game code writing a range of memory).

The splash screen

Splash

Actual v1.1 capture with the bundled splash replacement enabled.

Not a texture. It and 87 other full-screen images — the whole concept-art gallery — are 512x480 8bpp pictures decoded straight into the framebuffer, so they never pass through the texture path at all. Format: 18-byte header, 768-byte BGR palette, pixels at 784, rows bottom-up. port/assets/fullscreen/ holds them once extracted.

They are upscaled now, by the same 4x pipeline as the textures. They do not arrive as pictures — the game uploads one row at a time, 480 separate transfers for a splash — so the rows are reassembled, hashed by their pixels, and a matching image in the pack is written over that part of video memory at full internal resolution. Nothing changes when the pack has no match.

Only the splash is in the pack so far. The other 87 need visiting once with XMENMA2_DUMP=images to collect them, and then the same upscale.

Legal

The source repository excludes the disc, generated game code, and texture pack. X-Men: Mutant Academy 2 is © 2001 Activision and Marvel Characters, Inc. The screenshots are of the game running. You need your own copy of the disc for any of this to do anything.