Gets Cyber Troopers Virtual-On (PC, 1997) running properly on a modern system: installs it straight from a disc image, fixes the crashes, the frame rate and the keyboard, adds XInput gamepad support for both players, reads the soundtrack from files instead of the disc, and puts two-player versus on the internet - a code to share, no port forwarding.
In a nutshell - the patch makes the game just work ™️
Quick start · Virus warnings · Patches · Internet play · Gamepad · Music · Resolution · Which build
Defender and other scanners flag the download sometimes, as a false positive. It is an unsigned program that edits another program, which is the sort of thing they warn about.
If you would rather not run it, vo_patch.py does everything the download
does - see Running from source. Each release is
built here on GitHub and the build log lists the file's checksum, if you
want to check yours matches.
Download vo_patch-*.exe from the
latest release.
It is unsigned, so SmartScreen calls it an unknown publisher on the first
run, and see Virus warnings if a scanner objects. On
Linux, see Running from source.
The sections are numbered in the order to work through them.
- INSTALL - put your
.cuesheet in Source, choose a folder in Install to, and press Install game. Then Rip soundtrack, unless you plan to keep a disc in the drive. Already have the game installed? Leave this alone and start at 2; pick yourv_on.exethere and the tracks go beside it. See Installing from a disc image and Music. - GAME FILE - installing fills this in for you. Otherwise browse to
your
v_on.exe; only the unmodified disc file is accepted, and if yours is refused see Which build. - ESSENTIAL PATCHES - applied whole, no tick boxes. See What the patches do.
- EXTRA PATCHES - starts ticked and is yours to change. Click the ⓘ beside a patch to read what it does. Then Apply patches.
- ADD-ONS - starts collapsed. Press Install on the row you want:
- Internet play - two-player versus over the internet. Both players need it. See Internet play.
- Resolution and windowing - installs cnc-ddraw. See Resolution and windowing.
Then play. Restore original puts the game back if you change your mind. Add-ons write files beside the game rather than editing it, so Apply and Restore leave them alone.
The patcher reads the image itself, so there is nothing to mount and no virtual drive to set up.
Put the .cue sheet in Source - the one beside the .bin files, not
the .bin itself - choose a folder in Install to, and press Install
game. About 95 MB.
The Manual box picks which readme.txt, von.hlp and von.cnt are
copied, not the language of the game: every pressing carries one v_on.exe
and it is English. von.hlp is 1997 WinHelp, which Windows has not opened
since WinHlp32 stopped shipping for Windows 10; the readme is plain text.
Or from a terminal:
python3 vo_patch.py --install VIRTUAL-ON.cue ~/games/VIRTUAL-ON
python3 vo_patch.py --install VIRTUAL-ON.cue ~/games/VIRTUAL-ON --language GERMANThe patcher needs a bin/cue pair; it does not read a drive directly, and
a plain ISO will not do because it drops the audio tracks. Image the disc
once:
-
Windows - ImgBurn, Read mode, with the output set to BIN/CUE rather than ISO.
-
Linux -
cdrdao, then its owntoc2cue:cdrdao read-cd --driver generic-mmc-raw --datafile VIRTUAL-ON.bin \ VIRTUAL-ON.toc /dev/sr0 toc2cue VIRTUAL-ON.toc VIRTUAL-ON.cue
Then put the .cue in Source. On Linux a drive can also go straight in
Source for the soundtrack, though not for the install - see
Music. On Windows the image is the only way in.
The game directory, and the chosen manual. Nothing else: directx\ is a
1997 redistributable and the rest is the installer's own furniture.
No v_on.ini is written - the game makes its own on first run, a good one
with Better defaults applied. The disc's v_on_a.ini and v_on_b.ini
are copied as they are.
For how the copy rules are read off the disc, see docs/NOTES.md.
Every Essential patch is applied, with no tick box. Without them the game does not start, crashes when you lose a round, runs at a third of the frame rate, or loses the keyboard after ALT+TAB.
Keeping the keyboard alive across ALT+TAB means the game reads it in the background, so keys pressed in another window still reach it while the game is running.
Essential fixes what is broken on modern systems; Extra is down to taste. Every patch's offsets and internals are in NOTES.md.
- Skip processor check - the game refuses to start on a modern CPU.
Removes the check, so
ProcessorCheck=Offinv_on.iniis not needed. - Fix frame rate (60 FPS) - three fixes for the same complaint:
- raises the multimedia timer resolution. Without it the game runs at about 70% speed on Windows 2000 and later (not needed on Wine).
- makes
Motion=inv_on.inistick. The game read it, then overwrote it. - relabels the Motion Type radios on F5, which only ever offered 1/3 and 1/2 speed. They now read 30 FPS and 60 FPS.
- Fix crash on round loss - the game crashes when you lose as Temjin, Viper II, Apharmd or Raiden.
- Fix keyboard input after ALT+TAB - alt-tabbing away, or opening an F-key dialog, kills the keyboard for the rest of the session.
-
XInput gamepad support - a modern controller for both players: twelve bindable actions, plus the arcade twin-stick scheme. See Gamepad.
-
No disc required - removes the disc check and plays the soundtrack from
music\trackNN.wavbeside the game. See Music. -
Disable menu bar (Extras menu on F11) - hides the menu bar and moves the Debug options to a new F11 dialog: No shot, SE, CD, Kill 1P, Kill 2P, Scorekeeping and Quit Game. Credits is new - it jumps to the credit roll from any match, so you can see it without finishing the game. Motion has moved to F5. With the gamepad patch in, F11 also sets each player's stick deadzone. Every other menu was already on a key:
Key Opens F1 Help F3 Pause F4 High / low resolution F5 Graphic Settings F6 Mode Settings F7 Device Settings F8 Sound Test F11 Extras, the new dialog -
Better defaults with no v_on.ini - what the game falls back on for any setting
v_on.inidoes not have, which on a first run is all of them: Sky on, all three Texture boxes on, Field Graphic Rich, Screen Large. -
Sound fixes - the built-in delay before each sound effect is removed, output goes from 22050 to 44100 Hz, and an enemy Fei-Yen gets back the hypermode sound a bug left silent.
-
Intro, loading and ending screens - four fixes to the screens either side of the fighting:
- the intro movie is fitted to the window instead of sitting in a corner
- "Now Loading . . ." is hidden
- the ending credits can be skipped - hold A, Select or Space for a second. Stock has no way past them
- the initials screen after them takes those buttons too, and the weapon trigger
Version and credit in the game sits under ABOUT rather than with the patches, and is on by default. It prints the patcher's version in the bottom right of the title screen and adds two lines under the title of the ending credits.
The credit lines rewrite scrstfcg.bin and scrstfmp.bin, backing both up;
Restore original puts them back. If either file is missing the whole box
is skipped, version included, and everything else still applies.
Open the collapsed ADD-ONS header and press Install on a row. The same button reads Remove once installed.
| Row | What it is |
|---|---|
| Internet play | two-player versus over the internet. Both players need it. See Internet play |
| Resolution and windowing | downloads and installs cnc-ddraw beside the game. See Resolution and windowing |
The soundtrack rip lives in INSTALL at the top of the window, beside the game copy. See Music.
Link mode is two-player versus over a network, but stock it never leaves the LAN: the game finds opponents by broadcasting, and no router forwards a broadcast. Hence the usual advice to run a VPN and pretend everyone is on one LAN.
Internet play, under ADD-ONS, replaces that layer with plain UDP. One player hosts and gets a short code, the other types it in - no port forwarding, no VPN. Direct IP is still there for LAN play.
-
Both players install the add-on. Select
v_on.exe, open ADD-ONS and press Install on the Internet play row. Nothing downloads - the DLL is inside the patcher - and the row then reads Remove. If it says an older netplay DLL is installed, press Install to update it.Or from a terminal:
python3 vo_patch.py --netplay path/to/game # install python3 vo_patch.py --netplay path/to/game --remove # put the stock one back
-
Both players need the same two gameplay patches, Fix frame rate and Fix crash on round loss. Each machine runs its own copy of the game on both players' inputs, so those two have to agree. Nothing else does - sound, video and controls are each machine's own business. A mismatch is refused with a note saying so, so there is nothing to check by hand.
-
The stock
dpctrl.dllis kept asdpctrl.dll.stock, so Remove puts you back on LAN-and-VPN play.
This is the default, and nobody forwards anything.
- Host: leave the connection on Matchcode, pick a Region - Europe, America or Asia, whichever is nearer the host; the line below the buttons says where each one is - then choose Host a game and press OK.
- The dialog shows a code like
EU-ABCDE, with a Copy button. Send it to the other player. - Guest: choose Join a game, type or paste the code in, and press OK.
The EU, US or JP in front is the server the code lives on, so the
code is all the guest needs; hyphens, spaces and case do not matter. The two
machines talk directly where the routers allow it and through the server
where they do not, so it works from anywhere with UDP.
| Region | Code | Server | Where |
|---|---|---|---|
| Europe | EU- |
segaonline.net |
Helsinki |
| America | US- |
us.segaonline.net |
New York |
| Asia | JP- |
jp.segaonline.net |
Tokyo |
All three listen on UDP 47625.
The guest keeps trying until you cancel, so there is no rush to press things at the same moment. Once a match is running, a player who quits or crashes is noticed within a few seconds.
Custom points both players at a server that is not one of ours. Both
enter the same address, and codes from it read XX-ABCDE.
For a LAN, or when you would rather not depend on the server. The host forwards UDP 47624, picks Host a game and reads out their address - the dialog shows the local one and looks up the public one on request. The guest types it in and needs nothing forwarded.
Force the relay: the match goes through the server instead of connecting directly, which is often steadier over a long distance.
-
Open
vo-net.inibesidev_on.exe- create it if it is not there. -
Add
relay=1under[net], using the heading already in the file if there is one:[net] relay=1
-
Connect as usual.
One side setting it is enough. Take the line out again for a nearby opponent - direct is faster when it works. Matchcode games only.
Create an empty file called vo-net.log beside v_on.exe and try again. The
DLL writes what it did into it, and that file is what to send with a bug
report.
XInput gamepad support rebuilds the F7 device list. The legacy joystick profiles are hidden and four remain, for both players - pad 1 drives 1P, pad 2 drives 2P.
| Profile | What it is |
|---|---|
| Gamepad (XInput) | twelve named actions, bound from the F7 screen |
| Twin-stick (XInput) | the arcade levers, nothing to bind |
| Keyboard (Simple) | every action on a bindable key |
| Keyboard (Real) | the game's own two-lever keyboard scheme, bindable |
Four buttons work on every profile, Start on the pause screen included:
| Button | Does |
|---|---|
| A | Accept - confirms menus, and skips the win and lose screens |
| Select | Camera, and skips those screens too |
| Start | Pause |
| D-pad | Moves, so it also drives menus |
The D-pad is not bindable: it is wired to the same four directions as the movement binds, which is what the menus read.
A also skips the intro movie, the same as Space. Start does not - the game ignores F3 while the movie plays.
The prompts follow the pad: the pause screen reads PRESS START TO
UNPAUSE, and the title and scoreboard screens read Press A Button. That
last one is artwork rather than text, so escrgame.bin is rewritten too -
see TEXT.md. Applying the patch also moves v_on.ini aside,
because binds saved by the unpatched game do not fit the new device list. See
What gets written.
Sticks, triggers, bumpers and face buttons are all in the bind list; the sticks are read as eight directions. Defaults on both sides: left stick moves, right stick turns, LT and RB fire left and right, RT fires both, LB dashes, A jumps, X guards. Default on the F7 page puts them back.
Each thumbstick is a lever, the way the cabinet worked, so nothing is bindable.
| Input | Does |
|---|---|
| Both sticks the same way | walk, strafe |
| Left down + right up, or the reverse | turn |
| Sticks apart | jump, and auto-face the opponent |
| Sticks together | crouch, which is the guard |
| LT, RT | left and right weapon; both at once is the centre weapon |
| LB, RB | the turbo buttons - dash in the direction you are moving |
The game's original all-keys profile. It shares the bind page with the
gamepad, but each sees only its own inputs - the pad's sixteen on one page,
the keyboard's letters, digits and named keys on the other - and each keeps
its own saved set in v_on.ini, so switching between them costs nothing.
Keeps its own bind page. Two keyboard players cannot share a key: if 2P wants one 1P already has, rebind 1P first. If 1P is on a pad, 2P can take 1P's keys, since nothing is using them. Default resets whichever side you are editing.
How far a stick has to move before it counts, 40% out of the box. Set it per player in the Stick Deadzone % [ XInput ] box of the F11 Extras dialog (with Disable menu bar installed); that box's Defaults button puts both back to 40.
Closing the dialog saves each to its own v_on.ini line, editable by hand:
1P Deadzone=25
2P Deadzone=40Two digits, 05 to 95 - lower is more sensitive, higher rides out a worn
stick's drift.
The BGM is Redbook CD audio, so unpatched it needs a disc or a virtual drive with the audio tracks - a data-only ISO plays nothing. No disc required reads it from WAV files beside the game. No drive, no extra DLL.
Same Source box as the install - one cue sheet holds the game and the soundtrack both. Press Rip soundtrack; the note under the buttons names the folder they go to. Closing the window mid-rip discards the part-written track.
On Linux a device node works in Source too, a cdemu one like a physical drive. Windows drives are not read at all, in the window or from a terminal; image the disc first, as in If you have the disc, not an image.
Or from a terminal:
python3 vo_patch.py --rip VIRTUAL-ON.cue /path/to/VIRTUAL-ON
python3 vo_patch.py --rip /dev/sr0 /path/to/VIRTUAL-ON # Linux
python3 vo_patch.py --rip # list drives (Linux)The directory is the one holding v_on.exe; music\ is created inside it.
About 320 MB: 26 tracks, roughly 30 minutes, uncompressed.
VIRTUAL-ON\
v_on.exe
music\
track02.wav ... track27.wav
Track 1 is the data track and has no file. The numbering must match the disc, because the game asks for tracks by number.
The tracks are read by the No disc required patch, so untick it and the
folder is ignored however full it is. With the patch on and music\ missing
or empty, the game reads the drive as before; with tracks there, they are
used, disc or no disc. Under Wine they play through mciwave - no
dosdevices entry, raw device link or cdemu instance.
The game asks for 640x480 exclusive fullscreen and leaves the rest to the display, which on a modern panel usually means a stretched picture. The 4:3 framebuffer is baked into the rasteriser, so no byte edit fixes it - it takes something between the game and the graphics driver.
cnc-ddraw replaces the DirectDraw the game renders through, adding windowed and borderless modes, correct aspect ratio and upscaling. Every patch here works with it.
Install under ADD-ONS downloads the current release and unpacks it beside
v_on.exe - ddraw.dll, ddraw.ini, cnc-ddraw config.exe and the
shaders. The same button then reads Remove, which deletes them again and
keeps ddraw.ini. From a terminal:
python3 vo_patch.py --ddraw path/to/gameIt comes straight from
the releases page, so it
is always current, and an existing ddraw.ini is kept - re-running to update
leaves your settings alone. Close the game first: Windows will not replace a
DLL that is loaded.
On Linux there is a second step. Set ddraw to native in winecfg for
that prefix, or run cnc-ddraw config.exe once. Without it Wine keeps using
its own DirectDraw and nothing changes.
A fresh ddraw.ini is cnc-ddraw's own file with a few settings changed:
fullscreen, windowed, maintas, noactivateapp, toggle_borderless,
devmode and game_handles_close are all true, giving a borderless window
at 4:3 that does not trap the cursor. Everything else, comments and per-game
sections included, is left as it comes. Change any of it with cnc-ddraw config.exe.
game_handles_close is the one that is not about the picture: without it,
closing the window loses your settings and records for that session. If you
already have a ddraw.ini, add the line by hand:
[ddraw]
game_handles_close=trueThe intro movie does not go through DirectDraw, so upscaling leaves it small and in the corner. Intro, loading and ending screens under Extra fits it to the window; without that, skip it with Space, Enter, Escape or pad A.
gamescope handles the scaling without a DLL, if you would rather:
gamescope -W 1920 -H 1080 -w 640 -h 480 -f -S integer -- %command%-S fit fills more of the screen without whole-number scaling.
The patcher works on one build and refuses everything else: every patch is a fixed file offset, and on another build it would write into unrelated code.
| Build | Size | MD5 | Patcher |
|---|---|---|---|
| English retail | 6,650,880 | a464b0ff32d5bab499f265e45658504e |
patches |
| USA OEM | 6,649,344 | 4c70f780a7f0d98d74be62304fb99021 |
installs, does not patch |
| Japanese rerelease | 6,621,696 | d19320bdc3381a48228990907910a391 |
installs, does not patch |
| Japanese original | not known | not known | not seen |
I have not been able to acquire the Japanese original, so there is no checksum for it. The patcher calls that build an unrecognised file and prints the size and MD5 it found. That is what the row needs.
The three retail pressings - USA, USA Alt and the European rerelease -
carry the same v_on.exe byte for byte, so any of them patches. Anything
else is named if it is one of the builds above and unrecognised if not,
with both checksums side by side either way - in GAME FILE for a file
you picked, in INSTALL for a disc image.
Installing and ripping the soundtrack work whichever build the disc holds.
Only patching needs the English retail build, and copying its v_on.exe
into another build's install is not a way round that.
The original is copied to v_on.exe.bak first, and nothing is written
unless every selected patch applied, so a failure leaves the game as it
was.
XInput gamepad support touches two more files. v_on.ini is moved to
v_on.ini.bak and the game writes a fresh one, because binds saved by the
unpatched game do not fit the new device list. escrgame.bin is rewritten
with the new title artwork, after a copy is kept as escrgame.bin.bak.
Restore original puts all three back, keeping whatever the patched game
wrote as v_on.ini.patched. Use it rather than copying a .bak over by
hand: escrgame.bin and v_on.exe have to match, and restoring one alone
draws the title prompt as scrambled letters.
Windows, with Python from python.org - Tk ships with it, nothing else needed:
py vo_patch.py
On Linux, Tk usually needs installing:
sudo apt install python3-tk # Debian, Ubuntu, Mint
sudo dnf install python3-tkinter # Fedora, RHEL
sudo pacman -S tk # Arch, EndeavourOS
python3 vo_patch.pyEverything the patcher does is also available without a window:
python3 vo_patch.py --install CUE DIR # the game, out of a disc image
python3 vo_patch.py --rip SOURCE DIR # soundtrack, from a cue sheet or drive
python3 vo_patch.py --ddraw DIR # fetch and install cnc-ddraw
python3 vo_patch.py --netplay DIR # install the UDP netplay DLL
python3 vo_patch.py --selfcheck # validate the patch tablesTo build the Windows binary yourself, pip install pyinstaller and run
pyinstaller vo_patch.spec. It builds as vo_patch-dev.exe - releases take
their version from the git tag, and a source tree has none.
To change the machine code the patches install, see asm/; asm/build.py
builds it into the hex strings in vo_patch.py. Never edit those by hand.
The netplay DLL is built the same way from net/: edit net/dpctrl.c
and run python3 net/build.py, which compiles it with mingw and bakes it back
into vo_patch.py. net/README.md covers the protocol and
the matchcode server.
python3 tools/check.py runs every check in the project - give it your game
folder and it runs the ones that need one.
docs/DEVELOPING.md covers the rest of the workflow.
LLMs are part of the toolchain here. The scope, the disc dumps, the testing and the debugging are human, and nothing ships without a thorough read of the code and a run on the real game. Offsets and byte sequences are verified against the original executable before anything is written, and the patcher refuses any file that is not the unmodified English retail build - but this is a hobby project poking at a nearly 30-year-old binary, so expect bugs.
Some of the byte edits come from the original VO_Patch 0.43 (2008) by
UE2A-GEL. Rights to the game belong to
SEGA. LICENSE (MIT) covers the patcher, its tools and its documentation -
not the game, not the bytes quoted from it, and not the letterforms traced
from its artwork.

