Porting DOS games to modern platforms
Porting DOS games to modern platforms
Hello guys,
few years ago I made an experiment where I translated the disassembled code of a game (Alley Cat, Star Goose) into C++ or Javascript. It was just a proof of concept and a lot of things needed to be fixed by hand. I sporadically returned to this experiment to improve the translation process. Now it has became quite powerful and standalone and this is a result (Star goose in swift):
https://youtu.be/IaZiQrUdR9s
Anyone interested in this topic? Anyone who would like to contribute to this project? It is in C++ and there are still a lot of things to be improved. Currently I am finetuning the conversion tool to port the Xenon2 game and I am looking to extend the compatibility to Borland C applications.
Gabriel
few years ago I made an experiment where I translated the disassembled code of a game (Alley Cat, Star Goose) into C++ or Javascript. It was just a proof of concept and a lot of things needed to be fixed by hand. I sporadically returned to this experiment to improve the translation process. Now it has became quite powerful and standalone and this is a result (Star goose in swift):
https://youtu.be/IaZiQrUdR9s
Anyone interested in this topic? Anyone who would like to contribute to this project? It is in C++ and there are still a lot of things to be improved. Currently I am finetuning the conversion tool to port the Xenon2 game and I am looking to extend the compatibility to Borland C applications.
Gabriel
Porting DOS games to modern platforms
Hi, and welcome to the forums! I fixed the YT link for you.
Did I understand correctly that your tool can work with multiple different games?
I know of several successful projects that rebuilt the code of DOS games from disassembled binaries: OpenDUNE, OpenFodder, SDLPoP, Omnispeak to name a few. But I believe each of them required some meticulous work with the disassembled code and could not become a base for putting more games through the same process (save for the manual skills acquired by the respective authors that is).
You might also find this topic of interest:
https://www.dosgames.com/forum/viewtopic.php?t=27053
Did I understand correctly that your tool can work with multiple different games?
I know of several successful projects that rebuilt the code of DOS games from disassembled binaries: OpenDUNE, OpenFodder, SDLPoP, Omnispeak to name a few. But I believe each of them required some meticulous work with the disassembled code and could not become a base for putting more games through the same process (save for the manual skills acquired by the respective authors that is).
You might also find this topic of interest:
https://www.dosgames.com/forum/viewtopic.php?t=27053
Porting DOS games to modern platforms
Yes, exactly like that. The idea behind this project is to have a tool which can be used on any DOS application. But from my research (tested about 10 games), not every game can be ported easily. Currently it supports apps written in raw assembly and it highly depends on how the game was programmed - if the author uses some tricks for the program flow extensively (indirect calls) or some flags related operations, a lot od manual fixing is required. Star goose is very nicely written and whole application can be converted into working form almost automatically. Currently I am trying to finetune the process for a set of chosen games, write tutorial how to use these tools and then extend the support to Borland Pascal and Borland C applications.
Thank you for the links, porting commander keen was on my todo list
Thank you for the links, porting commander keen was on my todo list
Porting DOS games to modern platforms
That's some very nice project, please keep us updated and good luck!
Porting DOS games to modern platforms
Yes, but there are some requirements though, the game binary cannot be packed or protected. In those old days there were various tricks to protect the games from copying and the hacker groups either produced "vigrin" binary of the game, or they made resident app that cracks the game during execution. Some games used even more crazy methods to protect (e.g. Storm lord which decodes just the instruction that are being executed and then encodes them back). So if there are some guys who had experience in cracking of ancient games, I would appreciate their help with finding or producing unprotected binaries (storm lord, titus the fox, arkanoid2).
The tool produces C++ code from assembly, but it requires some light skills with IDA - sometimes the disassembler does not identify all parts of the code. Especially when the game jumps to some memory location through indirect call or indirect jump. So it takes few iterations:
- unpack executable (if applicable)
- extract data segment (or whole binary blob from exe)
- ida disassembly
- cicoparser conversion asm to c++
- create host application which loads the data segment and handles dos/bios/video/input/filesystem stuff (I usually recycle it from previous project and add missing parts)
- run the app, fix parts of code where cicoparcer left marks (it will stop execution when it hits the mark and you will need to fix it, if using visual studio with edit&continue feature, it is really quick task)
- implement missing dos/other calls
- identify missing parts of code during execution of the freshly made app (indirect calls)
- ida - mark missing parts as code and generate assembly again
- convert
- build & test
- repeat if necessary
But the best part of this is - you can alter the game in any way - you have everything in control. For example I ported CDMAN to wasm, so it can run in web browser. And to provide mouse/touch support, I made small extension which handles touches and calculates shortest distance for the pacman to reach the destination.
https://rawgit.valky.eu/gabonator/Proje ... index.html
and here is source code for those who would like to see more:
https://github.com/gabonator/Projects/t ... man/source
The tool produces C++ code from assembly, but it requires some light skills with IDA - sometimes the disassembler does not identify all parts of the code. Especially when the game jumps to some memory location through indirect call or indirect jump. So it takes few iterations:
- unpack executable (if applicable)
- extract data segment (or whole binary blob from exe)
- ida disassembly
- cicoparser conversion asm to c++
- create host application which loads the data segment and handles dos/bios/video/input/filesystem stuff (I usually recycle it from previous project and add missing parts)
- run the app, fix parts of code where cicoparcer left marks (it will stop execution when it hits the mark and you will need to fix it, if using visual studio with edit&continue feature, it is really quick task)
- implement missing dos/other calls
- identify missing parts of code during execution of the freshly made app (indirect calls)
- ida - mark missing parts as code and generate assembly again
- convert
- build & test
- repeat if necessary
But the best part of this is - you can alter the game in any way - you have everything in control. For example I ported CDMAN to wasm, so it can run in web browser. And to provide mouse/touch support, I made small extension which handles touches and calculates shortest distance for the pacman to reach the destination.
https://rawgit.valky.eu/gabonator/Proje ... index.html
and here is source code for those who would like to see more:
https://github.com/gabonator/Projects/t ... man/source
Porting DOS games to modern platforms
For Titus the Fox, you could try http://ttf.mine.nu/
Groeten van Frenkel
Visit us at the Official S&F Prod. Homepage
Visit us at the Official S&F Prod. Homepage
Porting DOS games to modern platforms
I have partially ported another game - this time I have used C++ to javascript converter instead of C++ compiled into WASM. Still has some issues, but playable:
Rick Dangerous 2
https://l.valky.eu/dosrick2
Rick Dangerous 2
https://l.valky.eu/dosrick2
Porting DOS games to modern platforms
If you haven't already, maybe it'd be a good idea to post your results at VOGONS.
Porting DOS games to modern platforms
Thank you for suggestion, I posted it there as well. Just for update, here is Xenon2 ported to javascript (just first level):
http://x.valky.eu/xenon2js
http://x.valky.eu/xenon2js
Porting DOS games to modern platforms
And the demo version of Titus the Fox.
Groeten van Frenkel
Visit us at the Official S&F Prod. Homepage
Visit us at the Official S&F Prod. Homepage
Porting DOS games to modern platforms
I have finally found some time to make a demonstration video how a game can be completely rewritten into C++ in less than 25 minutes: https://www.youtube.com/watch?v=4fAeUx8A-OE
Porting DOS games to modern platforms
Hey, that's very cool, thanks for sharing!
Previously you mentioned that some games lend themselves to this kind of reverse-engineering better than others, depending on the way they were coded. Can you give any more guidelines as to which games would have a higher probability of successful reverse-engineering with minimal user input? I guess that stuff like Doom or Daggerfall would not be easy (we don't need to reverse-engineer Doom, I'm just naming it as an example).
Previously you mentioned that some games lend themselves to this kind of reverse-engineering better than others, depending on the way they were coded. Can you give any more guidelines as to which games would have a higher probability of successful reverse-engineering with minimal user input? I guess that stuff like Doom or Daggerfall would not be easy (we don't need to reverse-engineer Doom, I'm just naming it as an example).
Porting DOS games to modern platforms
There is a major update on cicoparser, and here are some results:
Tunneler - network multiplayer: https://github.com/gabonator/Projects/b ... /readme.md
Bumpy - with level browser and replays: https://rawgit.valky.eu/gabonator/Proje ... bumpy.html
Star goose - https://rawgit.valky.eu/gabonator/Proje ... goose.html
And many other games which are just ported to C++ (arcade volleyball, prehistorik, arkanoid, captain comic, cdman, popcorn, rick dangerous 1, rick dangerous 2, tetris, xenon)
Dynablaster with network multiplayer is on the way...
If you are interested, check this folder: https://github.com/gabonator/Projects/t ... it/gamelib
for each game you will see a bunch of files:
- fetch.sh - to download a game
- cico.sh - to convert into C++, uses Capstone disassembler to do the hard work
- run.sh - compile and run the game
- game.patch - in some cases cicoparser is confused how to disassemble the code, so it places there some marks which needs to be fixed manually
Previously the conversion required IDA or Ghidra, but it is now a standalone tool with rich command line interface. It is starting to be easy and fun tool for disassembling real mode DOS applications. There is not documentation yet, but by looking into the gamelib folder you should get a pretty good grasp on how to use it
Tunneler - network multiplayer: https://github.com/gabonator/Projects/b ... /readme.md
Bumpy - with level browser and replays: https://rawgit.valky.eu/gabonator/Proje ... bumpy.html
Star goose - https://rawgit.valky.eu/gabonator/Proje ... goose.html
And many other games which are just ported to C++ (arcade volleyball, prehistorik, arkanoid, captain comic, cdman, popcorn, rick dangerous 1, rick dangerous 2, tetris, xenon)
Dynablaster with network multiplayer is on the way...
If you are interested, check this folder: https://github.com/gabonator/Projects/t ... it/gamelib
for each game you will see a bunch of files:
- fetch.sh - to download a game
- cico.sh - to convert into C++, uses Capstone disassembler to do the hard work
- run.sh - compile and run the game
- game.patch - in some cases cicoparser is confused how to disassemble the code, so it places there some marks which needs to be fixed manually
Previously the conversion required IDA or Ghidra, but it is now a standalone tool with rich command line interface. It is starting to be easy and fun tool for disassembling real mode DOS applications. There is not documentation yet, but by looking into the gamelib folder you should get a pretty good grasp on how to use it
Porting DOS games to modern platforms
Impressive, keep it up!
How does one compile Windows binaries?gabonator wrote: ↑ If you are interested, check this folder: https://github.com/gabonator/Projects/t ... it/gamelib
for each game you will see a bunch of files:
- fetch.sh - to download a game
- cico.sh - to convert into C++, uses Capstone disassembler to do the hard work
- run.sh - compile and run the game
- game.patch - in some cases cicoparser is confused how to disassemble the code, so it places there some marks which needs to be fixed manually
Porting DOS games to modern platforms
Building cicoparser:
- building on OSX or linux
- download source file: https://github.com/gabonator/Projects/b ... s/main.cpp
- brew install capstone or apt-get install capstone
- g++ -std=c++17 main.cpp -I/opt/homebrew/Cellar/capstone/5.0.1/include/ -L/opt/homebrew/Cellar/capstone/5.0.1/lib -lcapstone.5 -o cicodis
- building on windows:
- it has no dependencies besides of capstone, so with basic Visual Studio skills it should be fairly easy: https://github.com/capstone-engine/caps ... UILDING.md
- To build capstone you will need cmake: https://cmake.org/download/
Building individual games (Stargoose for example, https://github.com/gabonator/Projects/t ... elib/goose):
- install SDL2 framework (used by all games for creating windows, rendering framebuffer and keyboard input), check here: https://github.com/libsdl-org/SDL/releases or here: https://wiki.libsdl.org/SDL2/Installation
- download the game binaries:
- on linux or osx run: https://github.com/gabonator/Projects/b ... e/fetch.sh
- on windows: just find some place where you can download the game, goose.exe should have 53684 bytes, there should be also 6 resource files (bird1.x, bird2.x, blox.x, intro.x, newbird.x, podz1.x). Place all files into "dos" directory
- in goose.cpp fix the path to game resources: load("/Users/gabrielvalky/Documents/git/Projects/CicoJit/gamelib/goose/dos", "GOOSE.EXE", 53684);
- building and running the game
- linux/osx: run run.sh, it just calls g++ -std=c++17 cicorun/goose.cpp cicorun/main.cpp `pkg-config --libs --cflags sdl2` -o goose
- windows: create visual studio project and place all files there, add SDL2 dependency into your project (header and library files, set library/header search paths)
Converting game into c++:
./cicodis $PWD/dos/GOOSE.EXE -asm -coverage -tree \
-jumptable 1000:104e 1000:105a 35 callwords indirect \
-simplestack -ctx -recursive start,1000:05fc,1000:04b7,1000:05ce
There are two required parameters: input executable file (goose.exe) and a method (or comma separated methods) you want to disassemble (start,1000:05fc,1000:04b7,1000:05ce). Providing just start is sufficient, those 3 extra addresses are the interrupt handlers which are not called by the game anyway, but I included them to make the "coverage report" look better. Coverage report is a separate topic, I don't want to go much into details. Check it here: https://rawgit.valky.eu/gabonator/Proje ... erage.html
Extra switches used in this example:
- -asm generate assembly code before each C++ function
- -coverage generate extra comments for the coverage generator tool (the byte range for a specific method)
- -tree dump call tree (not yet used anywhere)
- -recursive dump all identified methods recursively, remove if you want to disassemble a single method
- -segofscomment show method address in comment
- -jumptable 1000:104e 1000:105a 35 callwords indirect replaces indirect call at 1000:104e with a switch with address table with 35 entries placed at 1000:105a
A single disassembled method will look like this:
- building on OSX or linux
- download source file: https://github.com/gabonator/Projects/b ... s/main.cpp
- brew install capstone or apt-get install capstone
- g++ -std=c++17 main.cpp -I/opt/homebrew/Cellar/capstone/5.0.1/include/ -L/opt/homebrew/Cellar/capstone/5.0.1/lib -lcapstone.5 -o cicodis
- building on windows:
- it has no dependencies besides of capstone, so with basic Visual Studio skills it should be fairly easy: https://github.com/capstone-engine/caps ... UILDING.md
- To build capstone you will need cmake: https://cmake.org/download/
Building individual games (Stargoose for example, https://github.com/gabonator/Projects/t ... elib/goose):
- install SDL2 framework (used by all games for creating windows, rendering framebuffer and keyboard input), check here: https://github.com/libsdl-org/SDL/releases or here: https://wiki.libsdl.org/SDL2/Installation
- download the game binaries:
- on linux or osx run: https://github.com/gabonator/Projects/b ... e/fetch.sh
- on windows: just find some place where you can download the game, goose.exe should have 53684 bytes, there should be also 6 resource files (bird1.x, bird2.x, blox.x, intro.x, newbird.x, podz1.x). Place all files into "dos" directory
- in goose.cpp fix the path to game resources: load("/Users/gabrielvalky/Documents/git/Projects/CicoJit/gamelib/goose/dos", "GOOSE.EXE", 53684);
- building and running the game
- linux/osx: run run.sh, it just calls g++ -std=c++17 cicorun/goose.cpp cicorun/main.cpp `pkg-config --libs --cflags sdl2` -o goose
- windows: create visual studio project and place all files there, add SDL2 dependency into your project (header and library files, set library/header search paths)
Converting game into c++:
./cicodis $PWD/dos/GOOSE.EXE -asm -coverage -tree \
-jumptable 1000:104e 1000:105a 35 callwords indirect \
-simplestack -ctx -recursive start,1000:05fc,1000:04b7,1000:05ce
There are two required parameters: input executable file (goose.exe) and a method (or comma separated methods) you want to disassemble (start,1000:05fc,1000:04b7,1000:05ce). Providing just start is sufficient, those 3 extra addresses are the interrupt handlers which are not called by the game anyway, but I included them to make the "coverage report" look better. Coverage report is a separate topic, I don't want to go much into details. Check it here: https://rawgit.valky.eu/gabonator/Proje ... erage.html
Extra switches used in this example:
- -asm generate assembly code before each C++ function
- -coverage generate extra comments for the coverage generator tool (the byte range for a specific method)
- -tree dump call tree (not yet used anywhere)
- -recursive dump all identified methods recursively, remove if you want to disassemble a single method
- -segofscomment show method address in comment
- -jumptable 1000:104e 1000:105a 35 callwords indirect replaces indirect call at 1000:104e with a switch with address table with 35 entries placed at 1000:105a
A single disassembled method will look like this:
Code: Select all
/* Assembly listing of 1000:33b3 sub_133b3()
sub_133b3 PROC
1000:33b3 50 push ax
1000:33b4 a1 f8 94 mov ax, word ptr [0x94f8]
1000:33b7 8e c0 mov es, ax
1000:33b9 bf 00 00 mov di, 0
1000:33bc ba ce 03 mov dx, 0x3ce
1000:33bf 58 pop ax
1000:33c0 8a e0 mov ah, al
1000:33c2 b0 00 mov al, 0
1000:33c4 ef out dx, ax
1000:33c5 b8 01 0f mov ax, 0xf01
1000:33c8 ef out dx, ax
1000:33c9 fc cld
1000:33ca b9 a0 0f mov cx, 0xfa0
1000:33cd f3 ab rep stosw word ptr es:[di], ax
1000:33cf b8 00 00 mov ax, 0
1000:33d2 ef out dx, ax
1000:33d3 fe c0 inc al
1000:33d5 ef out dx, ax
1000:33d6 c3 ret
sub_133b3 ENDP
*/
void sub_133b3()
{
// coverage: 0x35b3-0x35d7 method sub_133b3
push(ax);
ax = memoryAGet16(ds, 0x94f8);
es = ax;
di = 0x0000;
dx = 0x03ce;
ax = pop();
ah = al;
al = 0x00;
out(dx, ax);
ax = 0x0f01;
out(dx, ax);
flags.direction = false;
cx = 0x0fa0;
rep_stosw<MemAuto, DirAuto>();
ax = 0x0000;
out(dx, ax);
al++;
out(dx, ax);
}