- Gadgets
- A
Games on industrial devices? Easy! We port emulators and Wolfenstein 3D to a TSD for $5.32 in practice
Disclaimer: the words used like "port", "hack" and "reverse" do not mean that the article is intended exclusively for geeks! I try to write so that it is understandable and interesting to absolutely everyone!
Surely many of my readers have heard the news that famous games have been ported to various platforms. At some point, I came up with the same idea, but I wanted to port games and emulators to rather exotic industrial devices that run on the Windows CE platform. How did I port Wolfenstein and the NES emulator to a brave but decommissioned warehouse worker and why? Read today's detailed article!
❯ How, why, and for what?
My long-time readers know that I am a hardcore enthusiast when it comes to reviving various retro devices. In addition to standard x86 computers, many of which can still perform useful tasks, I am very interested in computers on rather unusual architectures: early ARM chipsets, MIPS, and, of course, SH3.
My goal is to ensure that as few devices as possible end up being recycled as scrap metal if they can find interesting applications even today. After all, many devices that initially seem useless, like industrial data collection terminals or, for example, cash registers, are actually not, and they can find various cool applications. However, the scenario of installing Putty and turning a compact machine into a portable terminal, or turning a device into a weather clock is not as interesting as turning everything with processors into gaming consoles!
Consumer society has already forgotten that the first Android TV boxes literally turn into gaming consoles in an hour by installing emulators or RetroArch, smartphones can host websites as easily as single-board computers, and on PlayStation... you can install Linux. But it's not always that easy: sometimes the platform is so specialized that there are no emulators or ports of any games for it, and therefore you need to muster your willpower, which I will tell you about in today's material!
Right now, my dear reader, next to me lies nothing other than a decommissioned M3 Green data collection terminal. At first, it seems that the data collection terminal is a very specialized device and belongs in a warehouse/in a "magnet", but if you delve into the details, it turns out that it is a very decent portable computer:
-
Processor: ARMv5 Intel PXA272 at 624MHz + Wireless MMX. However, the processor has a weak point: no hardware division (ISA ARM feature) and no FPU (floating point unit coprocessor).
-
RAM: 128 megabytes of SDRAM. Seems like a little? Don't forget that Windows CE consumes only about 8-16 megabytes of memory for its needs. As a result, we have a whole 100 megabytes left for ourselves. For example, modern versions of Windows require ~1GB of RAM at least without considering the cache for I/O operations!
-
Display: built-in 3-inch matrix with a resolution of 240x320. Seems like a little... but it's the norm for a PDA! Of course, there is also a resistive touchscreen.
-
Communications: one of the strongest points of such a device is the presence of a hardware USB host (in the docking station), the ability to synchronize with a PC, and of course Wi-Fi!
-
Keyboard: well, it's obvious :) Even F-keys are present!
In short, the initial data is simply awesome! Considering that the device is armored, it can be considered a fairly portable device that can be used in somewhat more extreme conditions.
❯ NES Emulator
We start with the emulator of the well-known "Dendy". There are now many different open-source emulators, take any and port it. Among those that are easiest to port, we can highlight InfoNES, which has already been ported to Windows CE, but on many modern machines it works unstably and needs to be adapted to a specific device. Then I thought a bit and remembered that I saw in the SDK samples a fairly fast NES emulator ported to one of the Chinese phones, which I mentioned in one of my articles. The only nuance is that it does not have sound emulation, but it works quickly. I could not find out the origins of the emulator, there are no copyrights, nothing. It is possible that this emulator formed the basis of many early Chinese game consoles.
Initially, the emulator was developed for the MRP platform, which only simplified the task. In essence, all applications for Chinese phones are 4 functions: initialization, rendering, event processing, and exit. Of course, there are also event handlers, for example, by timer, but in general, the concept is extremely clear. The emulator was literally "hardcoded" to specific paths to the ROM file (cartridge image):
int32 mrc_init(void)
{
keyinit();
ROM_len=0;
nesloop=0;
rom_file=NULL;
rom_file=(unsigned char *)mrc_readFileFromMrp("nes.nes",&ROM_len,0);
if(rom_file!=NULL&&ROM_len>1024&&ROM_len<=43*1024)
{
startopcodetable();
setkey();
if(index==8)
{
Start();
}
else
{
drawTxt("设置按键: 右");
}
}
else
{
drawTxt("内存不足...");
mrc_exit();
}
return MR_SUCCESS;
}
Everything was complicated by the fact that most of the variables were global and there was no single state for the emulator, so the code had to be refactored. But first we need to at least run something! To do this, we minimally rewrite the ROM loading logic to stdio, taking into account that in WinCE the root of the file system starts with \ (not '/', as in Unix):
f = fopen("\rom.nes", "rb");
if(!f)
return 0;
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
ptr = (unsigned char*)malloc(len);
fread(ptr, 1, len, f);
ROM_len = len;
fclose(f);
rom_file = ptr;
Now the emulator loads the ROM, but we still don't have any screen output or input processing. The state of the gamepad buttons is represented by the global variable KEY, where in the original a large switch simply maps the phone key code to the NES gamepad hardware button code:
if(MR_KEY_PRESS == code)
{
switch(p0)
{
case MR_KEY_SOFTRIGHT:
mrc_exit();
break;
case MR_KEY_SELECT:
KEY[4]=1;
break;
case MR_KEY_SOFTLEFT:
KEY[5]=1;
break;
case MR_KEY_LEFT:
KEY[1]=1;
break;
case MR_KEY_RIGHT:
KEY[0]=1;
break;
case MR_KEY_UP:
KEY[3]=1;
break;
case MR_KEY_DOWN:
KEY[2]=1;
break;
case MR_KEY_1:
KEY[6]=1;
break;
case MR_KEY_2:
break;
case MR_KEY_3:
KEY[7]=1;
break;
}
}
This section is rewritten in such a way as to map each hardware button of the device to a virtual key code in "Windows" and then be able to reassign them to any others. For WinCE navigators, where there are almost no buttons, it is relevant to implement input from the touchscreen (it is not currently in the repository):
void ProcessInput()
{
KeyMapping mapping[] = {
{ VK_RETURN, 4 },
{ VK_LEFT, 1 },
{ VK_RIGHT, 0},
{ VK_UP, 3},
{ VK_DOWN, 2},
{ 1, 7 }
};
if(GetAsyncKeyState(VK_ESCAPE) & 0x8000)
exit(0);
for(int i = 0; i < sizeof(mapping) / sizeof(KeyMapping); i++)
KEY[mapping[i].KeyCode] = GetAsyncKeyState(mapping[i].VirtualKeyCode) & 0x8000 ? 1 : 0;
}
Now we have input processing... but there is still nothing on the screen! And this is where the most interesting part begins. The fact is that there is no fast graphics API in Windows CE. In Windows Mobile, there was GX, designed for 240x320 displays, which provided direct access to the device's framebuffer, as well as a special ExtScape call that allowed the same. But neither method is supported on modern WinCE devices. Microsoft suggested using DirectDraw, familiar to readers from games of the 90s, but it was implemented almost nowhere except for PDAs. Therefore, only the 2D GDI subsystem remains, which draws windows and almost all graphics in regular Windows — a slow, sluggish way that does not allow you to squeeze all the performance out of our device.
We start by creating a window. Everything is standard here:
hwnd = CreateWindowW(L"static", L"Emulator", WS_VISIBLE | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, 0, 0);
dc = GetDC(hwnd);
SHFullScreen(hwnd, SHFS_HIDETASKBAR | SHFS_HIDESTARTICON | SHFS_HIDESIPBUTTON);
In the emulator, the display content is represented by the LCDBUF variable, which contains an RGB565 image with a resolution of 240x240 (slightly truncated). Since Windows CE devices also usually use 16-bit color, it would be enough to simply copy them directly into the display framebuffer by scanlines and get an image but... due to GDI, the system only accepts the RGB5551 format, which is then converted back to RGB565, causing lags on weak devices.
First, we fill in the BITMAPINFO structure describing the format of the emulator's "output" image:
BITMAPINFO info;
memset(&info, 0, sizeof(info));
info.bmiHeader.biBitCount = 16;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biHeight = -240;
info.bmiHeader.biWidth = 240;
info.bmiHeader.biCompression = BI_RGB;
info.bmiHeader.biSize = sizeof(info);
Then, in the main loop, while the window is open, we call input processing, the next NES cycle, and finally, display everything using SetDIBitsToDevice:
while(IsWindow(hwnd))
{
ProcessInput();
NEStimer(2);
SetDIBitsToDevice(dc, 0, 0, info.bmiHeader.biWidth, -info.bmiHeader.biHeight, 0, 0, 0, -info.bmiHeader.biHeight, LCDBUF, &info, DIB_RGB_COLORS);
}
Result: the emulator works quite well on fast devices with processors 400+ MHz, both on 240x320 and 480x800. It remains only to add the "face": ROM selection window, button remapping dialog, cheats (editing the RAM console), and game time management. It is also highly desirable to implement an adequate timer with a limit of 60 FPS, but... none of the devices I tested could emulate the NES at FullSpeed without skipping frames. But as a proof of concept, we already have an NES emulator!
In the case of other emulators, it is usually necessary to detach the platform-dependent part with the "face", interface, configs, and other goodies. Emulators where the core is clearly separated from the "face" and where this very core can be extracted without any problems are advantageous for porting!
❯ Wolfenstein 3D
Next, I decided to port the well-known game Wolfenstein 3D. Among the "big" open-source games, it is relatively undemanding (requires ~640KB of RAM, so theoretically it can be ported to fat microcontrollers). In this case, there is no need to take the original code (it has x86 assembly inserts and completely unnecessary drivers for sound cards, hardware timer handlers, and other DOS game features), you can start with the modern WolfSDL port, which uses the SDL 1.2 library for graphics output and input processing.
SDL itself perfectly abstracts platform features and is not particularly difficult to port, and there was already a port for WinCE — taking into account the platform's features with graphics and buttons. SDL is easy to build, there were no problems with this — go to the VisualCE folder, and build the library in VS2005.
Next comes the most interesting part — porting the game itself! At first, the game refused to build because of the sound module, since there is no port of SDL_mixer (a plugin for SDL that acts as a software sound mixer) for Windows CE. The role of the mixer can be performed by Windows itself using the waveout module, but for the time being, the sound can be "thrown out" :) To do this, simply remove all calls to SDL_mixer functions, the game's sound subsystem is not tied to any structures or return values of the library.
if(DigiChannel[which] != -1) return DigiChannel[which];
int channel = Mix_GroupAvailable(1);
if(channel == -1) channel = Mix_GroupOldest(1);
if(channel == -1) // All sounds stopped in the meantime?
return Mix_GroupAvailable(1);
return channel;
Next, the game refused to build because Wolf4SDL used POSIX calls like stat and open/read/write/close. The calls themselves are easily wrapped in stdio analogs, and stat was only used to check for the existence of a file (used in the game's episode detection mechanism):
int read(FILE* f, void* buf, int len)
{
return fread(buf, len, 1, f);
}
After that, the question arose about handling input from the game. The game relied on the built-in button binding mechanism in the settings, but in the case of WinCE devices, there is not always even a DPAD, let alone a full keyboard. Therefore, I had to be creative and hardcode some buttons for a specific device:
switch(LastScan)
{
case SDLK_LEFT:
Keyboard[sc_LeftArrow] = 0;
break;
case SDLK_KP7:
Keyboard[sc_RightArrow] = 0;
break;
case SDLK_UP:
Keyboard[sc_UpArrow] = 0;
break;
case SDLK_DOWN:
Keyboard[sc_DownArrow] = 0;
break;
case SDLK_SPACE:
Keyboard[sc_Space] = 0;
break;
case SDLK_RETURN:
Keyboard[sc_Return] = 0;
Keyboard[sc_Enter] = 0;
break;
}
After fixing some minor bugs and addressing the peculiarities of paths in WinCE (there is no concept of "current directory"), the game finally launched on the emulator!
And with the button fix, it also runs on the TSD itself!
❯ Conclusion
That's the interesting material we have today! The source code can be found on my GitHub. Projects can also be ported to GPS navigators on Windows CE by adding a virtual keyboard (however, multitouch is not and will not be available. A solution could be connecting a Bluetooth HID keyboard), bringing new life to them as well!
Friends! If you are interested in the device from the article, you can buy it here for $5.32, with a full set (box, disk, power supply, docking station, and the device itself, sometimes there are revisions with GSM). These are decommissioned devices, but fully functional, even the batteries hold a charge well. The person has more than 50 of them and wanted to sponsor a giveaway, just in case any of the readers are also interested in such an interesting device as I am. In addition, we will raffle off two of these beauties with you soon.
I also have my own personal Telegram channel "Club of Fans of Baldness", where I publish posts about programming, reverse engineering, and just show the backstage of articles mixed with a little shitposting. If you're interested - subscribe, the contest conditions will also be published there. We will start the contest as soon as the video version of this article is released. In the meantime, you can watch my recent video about reviving a laptop on the 386:
By the way, if any of the readers have unnecessary devices (including those with flaws) or cheap Chinese fakes of iPhones/iPads/MacBooks and other branded devices being non-working, slow, or bricked and you wouldn't want to throw them away, but rather give them to good hands and see an article about them - write to me on Telegram or in the comments! I am also ready to buy them. Especially looking for a display donor for a Chinese replica of the iPhone 11 Pro Max: my striker, the display controller is heating up and there is no image :(
Write comment