Hacking [RELEASE] vc64_240p — real 240p for N64 Virtual Console on the Wii (no deflicker blur)

Kreycai

New Member
Newbie
Joined
Aug 14, 2026
Messages
2
Reaction score
3
Trophies
0
Age
25
XP
11
Country
Brazil
vc64_240p

Patches an N64 Virtual Console channel so it outputs 240p instead of 480i, running on Nintendo's own emulator. Optionally also removes the emulator's dark filter.

You keep everything the official emulator gives you — the per-game compatibility Nintendo did, native saves, suspend data — and you get the resolution the homebrew emulators give you. Until now you had to pick one.

Download: github.com/kreycai/vc64_240p/releases
Source and technical writeup: github.com/kreycai/vc64_240p




Why this needed patching at all

N64 VC on the Wii is locked to 480i, and it is not a settings problem: the emulator renders internally at 480 lines, so there is no 240p signal to force. NES, SNES and Genesis VC titles all output 240p when the console is set to 480i. N64 is the exception.

Every other route is closed:

  • vWii (Wii U) — cannot output 240p at all, hardware limitation
  • Not64 / Wii64 — do 240p, but heavier games struggle and several have documented issues. The standing community advice for N64 on Wii is still to use the VC releases
  • Swiss / Nintendont — only force 480p/576p; force-240p was requested and never implemented
  • Render 480 and downscale — possible, but it means rewriting the emulator's copy path rather than flipping fields in a struct (see the correction below)

So this patches the emulator binary instead.

How it works

Six changes in the emulator binary inside the WAD, 14 bytes per TV format:

  1. viTVmode *_INT -> *_DS — double-strike, i.e. 240p
  2. viHeight halved — the VI window
  3. efbHeight/xfbHeight left alone — the emulator keeps drawing 480 lines, so nothing is cropped or zoomed
  4. xFBmode left as DF — the double-field stride is what produces the 2:1 decimation, and therefore the correct geometry. Note this drops lines rather than filtering them
  5. vfilter -> progressive profile — deflicker off, sharpness preserved
  6. NOP the add that offsets the second VI field base by one line — without this you get 240p with severe flicker, because the VI alternates between the even and odd line sets every frame

Item 6 took the longest to find and is why a naive attempt at this looks broken.

Nothing is hardcoded to a game. Every target is located by structural pattern matching, so it works on emulator builds it has never seen. The writeup in the repo has the offsets and the hypotheses that were wrong first.

Requirements

  • A Wii common key (common-key.bin, 16 bytes). Not included — generate it with gzinject -a genkey.
    Heads up: genkey asks you to type 45e and press enter. If you pipe empty input it prints "successfully generated" and produces a wrong key — those three characters are the decryption IV. This is not documented anywhere.​
  • A Wii with cIOS 249 and Priiloader (or BootMii)
  • Console set to 480i. In 480p the emulator selects the progressive render mode, which is deliberately not patched
  • PAL is not supported. The PAL code path overwrites the heights at runtime, so patching that entry has no effect. It would also target 288p, not 240p

Install the output WAD with YAWM ModMii Edition and pick IOS249. Error -1017 means that IOS has no trucha patch — try another cIOS slot.

The output keeps the same channel id, so it replaces the original channel and keeps your saves.

What it is not

The GUI is not a ROM injector. It patches N64 VC WADs that already exist — retail channels, or injects somebody else built. To build a channel from a ROM, use FriishProduce and then run its output through this. The two fit together: no injector does 240p, and this does not inject.

The dark filter removal is not my discovery — the method is NoobletCheese / Maeson's, as implemented in FriishProduce. Here it is located structurally instead of by fixed offset. The 240p approach isn't my discovery either — u/BirdonWheels got there first and posted about it on r/crtgaming a few months ago, patching builds one at a time in radare2. What's mine is the released patcher and locating the targets structurally, so it works on builds nobody has analysed.

Confirmed working on real hardware

Five different emulator builds, all verified on a CRT:

  • Majora's Mask (USA, retail VC) — LZ77 content1
  • Majora's Mask (PT-BR inject) — same build
  • Ocarina of Time (PT-BR inject) — raw DOL, completely different offsets
  • F-Zero X (PT-BR inject) — different SDK revision
  • Spider-Man (inject on a Mario Party base) — fifth build

This is still a testing phase — please report what fails

Only those five have been verified in person. The N64 VC library plus injects is a much larger surface than one person with one CRT can cover, and emulator builds differ between channels — which is the whole reason targets are located structurally rather than by hardcoded offsets.

The tool refuses to write rather than write something broken: if it cannot find a target, it stops and says so. The failure mode you should expect is "it told me it could not patch this WAD", not a channel that bricks. Have Priiloader anyway.

If a WAD does not work for you, please say which one — and whether the tool refused, or it patched and the TV stayed at 480i. Those two are different bugs. A report that names the game is worth far more than a report that something worked. That is the only way this gets past five titles.

One bug worth knowing about, already fixed

Until today the patch was written into the render mode struct of a single TV format, chosen in the interface. The emulator carries NTSC, PAL, MPAL and EURGB60 side by side and picks one at runtime from the console's video setting, not from the WAD. If your console used a different format than the one selected, the patch landed in a struct nothing reads — and the tool still reported success. Silent failure, the worst kind.

It now patches every interlaced format at once. Writing into a format the console never selects is inert, so this is strictly safer than guessing. If you tried an earlier build and got no 240p, try again with v1.0.

Why there are no before/after photos

A still photo cannot show this, and I tried. At any exposure long enough to capture a full frame, a camera integrates both interlaced fields, so 480i photographs as a complete progressive image. And the actual artefact of 480i is the flicker, which is temporal — it exists between fields, not within one.

The difference is obvious on a CRT in person and invisible in a JPEG. A short video would show it; a screenshot never will. If someone with capture gear wants to record a comparison, that would be genuinely useful.

Correction

My claim that the GX display copy cannot downscale vertically was too broad, and I've corrected it above and in the README. What will not go below 1.0 is GXSetDispCopyYScale — but the interlaced copy modes do achieve a 2:1 vertical reduction, which is how Swiss does it.

The real difference between the two approaches is the one raised below: Swiss uses the vertical filter to do a linear 2:1 downsample, so lines are averages of their neighbours — softer, no aliasing. Here the DF stride makes the VI read every other line, which is plain dropping — sharper, with the aliasing that comes with it.

I prefer the unfiltered look on a CRT, but that's a preference, not a technical argument. What actually drove the choice is that Swiss controls its own GX setup, while this patches a closed binary: changing the copy path means finding and rewriting the emulator's copy configuration, whereas the VI-side change is 14 bytes and locatable structurally on any build.


Credits

  • NoobletCheese / Maeson — dark filter removal method
  • CatmanFan — FriishProduce, for injection and as a reference
  • KrimtonZ — gzinject, for WAD handling and the key generation

MIT licensed. Source, technical documentation in English and Portuguese, and the offsets are all in the repo.
 
Last edited by Kreycai,
  • Render 480 and downscale — the GX display copy cannot downscale vertically, only upscale
This is somewhat incorrect. The interlaced copy modes can do what you're looking for, and were used by Swiss in the past.

  1. viTVmode *_INT -> *_DS — double-strike, i.e. 240p
  2. viHeight halved — the VI window
  3. efbHeight/xfbHeight left alone — the emulator keeps drawing 480 lines, so nothing is cropped or zoomed
  4. xFBmode left as DF — the double-field stride is what produces the 2:1 decimation, and therefore the correct geometry
  5. vfilter -> progressive profile — deflicker off, sharpness preserved
  6. NOP the add that offsets the second VI field base by one line — without this you get 240p with severe flicker, because the VI alternates between the even and odd line sets every frame
This is essentially the method currently used by Swiss, but without using the vertical filter to do 2:1 linear downsampling.
 
You're right on both counts, and I'll correct the README.

On the GX point: I overstated it. GXSetDispCopyYScale only takes values >= 1.0,
which is where I got "cannot downscale vertically" from — but the interlaced
copy modes do achieve 2:1 vertical reduction, and I should have said that
instead of making a blanket claim.

On the method: yes, it lands close to what Swiss does, and the difference you
identified is the one that matters. I let the DF stride do the decimation, so
the VI reads alternate lines — no vertical filter, sharper, with the aliasing
that comes with it. Swiss doing a filtered linear 2:1 downsample is the more
correct approach signal-wise. On a CRT I happen to prefer the unfiltered look,
but that's a preference, not a justification, and the README should say so.

Where my situation differs from Swiss: Swiss controls its own GX setup. I'm
patching a shipped binary I don't have source for, so changing the copy path
means finding and rewriting the emulator's own copy configuration rather than
just flipping fields in a render mode struct. That's why I went for the VI-side
change — it's 14 bytes and locatable structurally across builds.

Do you think the copy-filter route is realistically patchable in that context?
If the emulator's copy setup is reachable, a filtered 2:1 would be worth doing
as an option alongside this.
 

Site & Scene News

Popular threads in this forum