Shadow Warrior and its addons Wanton Destruction and Twin Dragon on MiSTer FPGA as a hybrid port.
The game is JFShadowWarrior, Jonathon Fowler's port of 3D Realms' Shadow Warrior (1997) on his port of Ken Silverman's Build engine, with the engine's classic software renderer. It runs on the MiSTer's ARM CPU. The jfSW FPGA core provides native 15kHz video (CRT, VGA and HDMI) at 320x200, 640x200, 320x240 or 640x240, 44.1kHz audio, an FM chip for the AdLib music of the shareware version, and keyboard, mouse and gamepad input. The two halves talk through shared DDR3 memory.
Beta. Expect rough edges and please report problems in the issues.
Disclaimer: AI is being used to speed up development of this project.
- danik_hybrid_ports, the launcher (
Scripts/danik_hybrid_ports.sh): run it once from the MiSTer'sScriptsmenu. It starts the game whenever its menu entry is loaded, keeps running after a reboot, and serves all our hybrid ports; its screen picks which ports and games to have, and can keep them up to date throughupdate_all. It replacesdanik_hybrid_cores, the launcher before it, and moves ports installed the old way (games/jfSW/) into the new folders by itself. Without it the core says "Game not running". - Game data, which is not included:
SW.GRPof Shadow Warrior 1.2, from the original CD or from Shadow Warrior Classic Complete / Classic Redux (GOG, Steam), or of the shareware version. The addons Wanton Destruction (WT.GRP) and Twin Dragon (TD.GRP, which needs the full game'sSW.GRPas well) are optional.
With update_all (Downloader 2.4 or newer, which update_all keeps up to date):
- Copy
downloader_danik_hybrid_ports.inito the root of your SD card (/media/fat). - Run
update_all. That installs the launcher, danik_hybrid_ports, inScripts. - Run danik_hybrid_ports from the
Scriptsmenu, tick the games you want and press X: Apply. They are installed, and update_all keeps them up to date.
By hand:
-
Download the newest
jfSW_YYYYMMDD.zipfrom releases and extract it to the root of your SD card (/media/fat). That gives you:_Other/dnk-Shadow Warrior.mgl,_Other/dnk-SW Wanton Destruction.mgland_Other/dnk-SW Twin Dragon.mgl, the games' entries in theOthermenu_Other/dnk/dnk-jfSW_YYYYMMDD.rbf, the FPGA coregames/dnk/jfSW/, the game binary and its launch scriptScripts/danik_hybrid_ports.sh, the launcher (from Hybrid_MiSTer)downloader_danik_hybrid_ports.ini, with whichupdate_allkeeps the ports up to date from then on
-
Copy everything in the game's folder (the one with
SW.GRP) to/media/fat/games/dnk/jfSW/, with its folders as they are, and leave nothing out (files the port does not use do no harm). The port finds in it:SW.GRP- the music, which is the audio of the game's CD:
Track02.oggtoTrack14.ogg, there or in a foldermusic. Without them the full game has no music, only its sound effects (see Music). The shareware version needs none: its music is in itsSW.GRP. - optional:
WT.GRPandTD.GRP, there or in a folderaddons.
The whole folder of Shadow Warrior Classic Redux (the one with
SW.GRP,addonsandclassic) can be copied as it is: the addons and the music (classic/MUSIC) are found in it. -
Run danik_hybrid_ports from the
Scriptsmenu, if you have not done so before (see Requirements). -
Load the game from the
Othermenu, e.g. dnk-Shadow Warrior.
The game data can go on a USB drive instead, into the same folder there (/media/usb0/games/dnk/jfSW/): the game looks for it there first, and then keeps its settings and saved games there as well. Everything else stays on the SD card.
The game files are known by their contents, whatever their names. The menu entry of Shadow Warrior starts the shareware when the full game is not there. When a game's files are missing, the core says which files to copy where. Each game keeps its own OSD settings and button mappings. The core file itself (_Other/dnk/dnk-jfSW_*.rbf, or from Recent) lists the games that are there to pick from; quitting a game then returns to the list. The game's log is in /media/fat/logs/dnk/jfSW/jfsw.log. Settings (sw.cfg) and saved games (game0.sav to game9.sav) are in /media/fat/games/dnk/jfSW/ (on the USB drive, if the game data is there).
| Option | |
|---|---|
| Aspect ratio, Scale, Scandoubler Fx, Stereo Mix | as in other cores |
| FPS Counter | shows the pictures per second in the top left corner |
| HDMI Only | Yes if no CRT is connected: resolutions of 640x400 and more are then progressive instead of interlaced, and the analog output is switched off while one is shown, so that a 15kHz screen can never get such a signal. This game has no such resolution, so nothing changes. Not there with direct_video=1 |
| CRT Options | for a 15kHz screen: Horizontal Size, Horizontal Pos and Vertical Pos fit the picture to it. The pixels stay as they are; HDMI is not affected. The size is for the game's picture: the core's own, with its messages, keeps its width. Not there with forced_scandoubler=1. HUD (Bottom): in the 240-line resolutions, how many lines the status bar (or the mini bar) sits above the bottom edge of the screen, 0 to 24 (10 by default), so that a CRT's overscan does not hide it; greyed out at 200 lines |
| Resolution | what the game renders and the core puts out, always at 15kHz: 320x200 (default, as the original), 640x200, 320x240 or 640x240. 640 across is twice the detail in the same place: every pixel becomes two. 240 lines are square pixels at 320 across: the view fills all of them, from the top edge to the bottom one, with the status bar drawn over it at the HUD (Bottom) margin and the weapon on top of the bar; the menus and pictures keep their 200 lines in the middle. The artwork is never stretched. Applies at once in the game and its menu |
| L. Stick Sensitivity, R. Stick Sensitivity | how fast the gamepad's left stick (walk, strafe) and right stick (turn, look) move the player, 25% to 300%. At 100% a full tilt walks and turns about as fast as the keys do when running. They apply at once and take the place of the axis scale in the game's own menu |
| Limit Vertical Look | how far the view tilts up and down. The Build engine does not turn the camera, it shifts the picture, which distorts it the further you look up or down. On (default) leaves half of the game's own range in both directions, Off is the whole range. It holds for the keys, the mouse and the gamepad alike |
| Cheats | God Mode: nothing hurts you while it is on. No Clipping: you walk through walls while it is on. Give All: every weapon with full ammo, armor, the inventory items and all keys, once each time it is selected. Next Level: the level counts as completed, as at its exit, and the next one starts (not on the last level) |
- Keyboard and mouse:
| Key | Action |
|---|---|
| W, S (or up, down) | Walk forward, back |
| A, D | Sidestep left, right |
| Mouse (or left, right) | Turn. Moving the mouse up and down looks up and down |
| Left mouse button (or right Ctrl) | Fire |
| E (or right mouse button) | Open, use |
| Space | Jump |
| Left Ctrl | Crouch |
| Shift | Run (Caps Lock: always run) |
| 1 - 0 | Weapons |
| R, Q (or mouse wheel) | Next, previous weapon |
F (or Enter), [, ] |
Use the inventory item, previous and next item |
| Page Up, Page Down, Home, End | Look and aim up and down |
| Tab | Map |
| Esc | Menu |
This is a modern scheme, the default of this build; with the arrows and the right Ctrl the game can be played on a keyboard alone. Options in the game's menu changes keys, mouse and gamepad assignments, and has the keys of the original game (Apply Classic Defaults: arrows, Ctrl fires, Space opens, A and Z jump and crouch). They are saved in sw.cfg, so a sw.cfg from an earlier release keeps its keys until Apply Modern Defaults is chosen there.
- Gamepad (default mapping):
| Button | Action |
|---|---|
| Left stick | Walk and strafe |
| Right stick | Turn, look up and down |
| D-pad | Walk and turn |
| R (RB / R1) | Fire |
| B (Xbox A / PlayStation Cross) | Open, use |
| A (Xbox B / PlayStation Circle) | Jump |
| L (LB / L1) | Crouch |
| Y (Xbox X / PlayStation Square) | Next weapon |
| X (Xbox Y / PlayStation Triangle) | Use the inventory item |
| Select | Map |
| Start | Menu |
In the game's menus the buttons that confirm and go back are the ones of your MiSTer menu, whatever they do in the game: its Menu OK / Menu Back if you set them in Define joystick buttons, else its A / B. A controller never set up in MiSTer confirms and goes back as on its console: Cross / Circle on PlayStation, A / B on Xbox and on Nintendo.
Change the buttons in the OSD under Define jfSW buttons. Menu OK and Menu Back there are only needed for a spare button without a game function. While the OSD is open, the game gets no input.
Naming a saved game without a keyboard: up/down changes the last letter, right adds a letter, left removes one, Menu OK confirms.
The full game plays its music from the CD. This build plays the same tracks from Ogg Vorbis files named Track02.ogg to Track14.ogg (track 1 of the CD is the data). Classic Redux comes with them; from an original CD, rip the audio tracks and encode them.
The shareware version has no CD: its music is MIDI, written for the sound cards of 1997. This build plays it as an AdLib or Sound Blaster card did, on an FM chip: a YM3812 (OPL2) in the FPGA core, JTOPL. The game runs the AdLib driver of the original (the Apogee Sound System's, with the game's own instruments from swtimbr.tmb) and sends the core the chip's register writes, each with the audio sample it is due at; the chip's sound is made in the FPGA and mixed with the game's. The game's Music Volume sets its level. It needs the core of the same release: with an older .rbf the shareware version stays without music.
Shadow Warrior moves 40 times per second. A 15kHz picture a TV accepts has about 60 fields per second: the core shows 59.64. This build draws exactly one frame per field, with the game's clock taken from the field counter of the core instead of the system clock, and puts everything that moves between its last two positions for the time of that field, as the game does on any monitor that is faster than 40Hz.
The game holds the full rate of one frame per field at 320x200 and 640x200, in the levels it was measured in.
Everything builds in Docker on a Linux PC.
git clone --recursive https://github.com/ItsDanik/jfSW_MiSTer.git
cd jfSW_MiSTer
./build.sh # ARM game binary -> build/mister/jfsw
./core/build_core.sh # FPGA core (Quartus Lite 17.0.2) -> core/output_files/jfSW.rbf
./package.sh # release zip -> dist/jfSW_YYYYMMDD.zipThe toolchain image (Debian bullseye, glibc 2.31 to match the MiSTer) is built from hybrid/toolchain/ on first use. The first build also downloads and builds SDL2, SDL2_mixer and SDL2_net, and libogg and libvorbis for the music.
| Path | |
|---|---|
hybrid/ |
submodule: ItsDanik/Hybrid_MiSTer, what all our hybrid ports share: the FPGA host module, the ARM side library, SDL2 drivers, the launcher (danik_hybrid_ports.sh), toolchain and conventions. See its README. |
core/ |
FPGA core, based on Template_MiSTer: jfSW.sv (OSD and button names) around hybrid/rtl/hybrid_host.sv |
jfsw/ |
submodule: ItsDanik/jfsw branch mister, JFShadowWarrior with the MiSTer changes (CMakeLists.txt, src/mister_port.c and a few #ifdef MISTER_HYBRID). Two of its own submodules have the rest: jfbuild/, the Build engine (ItsDanik/jfbuild branch mister), with video, clock and joystick in src/sdlayer2.c, and jfaudiolib/, the sound library (ItsDanik/jfaudiolib branch mister), with the MIDI driver for the core's FM chip in src/driver_misterfm.c |
package/ |
files shipped in the release next to the binary (danik_hybrid_launch.sh, README), and games.txt: the games and their menu entries |
releases/ |
release packages |
./build.sh hostbuilds the same game for the PC. It runs againsthybrid/tools/fakecore, a stand-in for the FPGA core that takes scripted input and saves screenshots and audio:gcc -O2 -o build/host/fakecore hybrid/tools/fakecore.c build/host/fakecore /tmp/shm script.txt audio.raw & cd <data folder> && MISTER_HYBRID_SHM=/tmp/shm <repository>/build/host/jfsw
MISTER=root@<address> tools/test_on_mister.sh SWtries the build on a MiSTer next to what is installed there, without installing it: from/tmp, with the installed game's folder mirrored there (what the game writes stays in/tmp) and its own OSD settings.WT,TDfor the addons,listandnodatafor the core started by itself and without data,logfor the game's log,stopto remove all of it again.touch /tmp/jfsw_nolaunchon the MiSTer keeps the core loaded without starting the game, so you can start a development binary by hand (setMISTER_HYBRID_CORE=jfSW, andMISTER_HYBRID_SETfor a game's set name, e.g.jfSW_WT)./tmp/danik_hybrid_ports.logshows what the launcher daemon did.
If you enjoy this project, you can support my work on Patreon.
- JFShadowWarrior, JFBuild, JFAudioLib and JFMACT by Jonathon Fowler and contributors.
- Shadow Warrior by 3D Realms Entertainment, who released its source code in 2005.
- Build engine by Ken Silverman.
- Apogee Sound System by James R. Dose: its AdLib MIDI driver, in the form EDuke32 keeps it in, is the one that plays the music on the FM chip.
- JTOPL by Jose Tejada (jotego): the FM chip in the core.
- libogg and libvorbis by the Xiph.Org Foundation.
- SDL by Sam Lantinga and contributors.
- MiSTer by Sorgelig and the MiSTer-devel contributors: the framework and Template_MiSTer.
- MiSTer Frontier by MiSTer Organize: thank you for the inspiration. Hybrid cores on the MiSTer, and the way their game is launched (a daemon that watches the loaded core and runs a script from its games folder), come from MiSTer Frontier. Our launcher is a separate implementation and does not need MiSTer Frontier installed.
This project and its maintainers are in no way associated with or endorsed by 3D Realms, Devolver Digital or any other holder of rights in Shadow Warrior. It does not include any game data.
The top-level scripts, tools and documentation are licensed under the GPL-3.0. The components keep their own licenses: the shared framework in hybrid/ is GPL-3.0 except where its files say otherwise, JFShadowWarrior is GPL-2.0-or-later (jfsw/GPL.TXT) and the Build engine in it is under Ken Silverman's Build license (jfsw/jfbuild/buildlic.txt: free of charge, no commercial use), libogg and libvorbis are BSD, SDL and the SDL drivers in hybrid/sdl2 are zlib, the FPGA core and the MiSTer framework are GPL-2.0 (core/LICENSE, with the core's own sources GPL-2.0-or-later), JTOPL in it is GPL-3.0 (hybrid/rtl/jtopl/LICENSE).