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:
So this patches the emulator binary instead.
How it works
Six changes in the emulator binary inside the WAD, 14 bytes per TV format:
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
Install the output WAD with YAWM ModMii Edition and pick IOS249. Error
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:
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
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
MIT licensed. Source, technical documentation in English and Portuguese, and the offsets are all in the repo.
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:
viTVmode*_INT->*_DS— double-strike, i.e. 240pviHeighthalved — the VI windowefbHeight/xfbHeightleft alone — the emulator keeps drawing 480 lines, so nothing is cropped or zoomedxFBmodeleft 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 themvfilter-> progressive profile — deflicker off, sharpness preserved- NOP the
addthat 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 withgzinject -a genkey.
Heads up:genkeyasks you to type45eand 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,






