Warn­ing! Your devicePixelRatio is not 1 and you have JavaScript dis­abled! (Or if this mes­sage is not in a red box, your brows­er doesn't even sup­port ba­sic CSS and you can prob­a­bly safe­ly ig­nore this warn­ing. You'll en­counter way more sub­op­ti­mal things in that case...) Since HTML is as brain­dead as it can get, pix­el graph­ics might look off with­out javashit workarounds. Try to get a non-HiDPI screen or en­able javashit for best re­sults.

Need for Speed 4: High Stakes for Pocket PC

Cre­at­ed: 1766518239 (2025-12-23T19:30:39Z), 3325 words, ~14 min­utes

Tags: , , , ,

This post is part of se­ries nfs: 1, 2, 3:HP (W, PS), 4:HS (W, PS, CE), 5:PU, MCO, 6:HP2

Try­ing NFS1 in 2024 for the first time might be crazy, try­ing the PS1 ver­sions of NFS3/4 af­ter it might be in­sane, but I'm strug­gling to find words to de­scribe what comes here. Psy­chot­ic, maybe? Any­way, one day I was ran­dom­ly brows­ing the in­ter­net, and I found out NFS4 had a Win­dows CE/Pock­et PC ver­sion! I knew I had to find it and try it...

BoxartJXL (JPEG JXL) JPEG (lossless compressed)

Technical shit#

I feel like this will be the biggest part of this post... Search­ing the in­ter­net doesn't give you a lot of in­fo about this game. The Wikipedia ar­ti­cle of this game doesn't men­tion the ex­is­tence of this ver­sion. NFS Wikia doesn't men­tion it ei­ther. Speedrun.com's mas­sive list of NFS ti­tles, which in­cludes things like the Pepe­ga edi­tion of MW/PS or U2 Ja­va ver­sion (which is a fuck­ing ROM-hack), miss­es it. There's a thread on led­dit with a few replies, but not much more. So based on this, the first hur­dle is find­ing the game at all—well, archive.org to the res­cue! In there, you find two cab files, if you have a Pock­et PC or an em­u­la­tor, you can run it...

Yeah, eas­i­er said than done when we're talk­ing about ob­scur­ish 20 years old tech­nol­o­gy. Em­u­la­tion Gen­er­al Wi­ki has a bare-bones page about Win­dows CE. Spoil­er alert: none of those em­u­la­tors worked for me. Some didn't even in­stall in wine, some lacked the cab in­staller from the Win­dows CE im­age (thanks?), some in­stalled the game, but on start it in­stant­ly crashed... Af­ter some search­ing I came across the CE Col­lec­tions project, which col­lect­ed a lot of Win­dows CE em­u­la­tors. Here I fi­nal­ly found a work­ing em­u­la­tor! The prob­lem is, they on­ly use git to store the source of the few helper ap­pli­ca­tions they wrote, all the em­u­la­tors and disk im­ages are on­ly avail­able un­der re­leas­es, where you can down­load (as of ver­sion 2.02) an 1.74 GiB 7Z file, which if you ex­tract yields you a 3.32 GiB ISO file. And ul­ti­mate­ly you on­ly need two files from this ISO im­age, WNT/DeviceEmulator.exe and ARMV4/510PPC.BIN, weight­ing around 21.4 MiB. Yuck! As al­ways, I mir­rored these two files to save you band­width. (Al­so, most of the im­ages in this project are ac­tu­al­ly for x86, use­less if you want to run an ARM pro­gram.)

So fi­nal­ly I have a work­ing em­u­la­tor, I in­stall the cab, I try to play it, and this is what I get:

First tryJXL PNG

Um, thanks? Well, the thing is, ear­ly Pock­et PC ver­sions didn't re­al­ly have a con­cept of ro­tat­ing the de­vice (some­where I read lat­er ver­sions had, but this game was def­i­nite­ly made be­fore it). Some fullscreen apps, like some games or nav­i­ga­tion apps could be used in land­scape mode, but that was solved by the apps draw­ing things 90 de­grees ro­tat­ed. Hence the re­sult above.

Pre­vi­ous­ly I men­tioned there are two cabs in the archive.org down­load, one called QVGA and one called VGA. QVGA is sup­posed to rep­re­sent the 320x240 res­o­lu­tion, while VGA is 640x480, but the on­ly dif­fer­ence I could tell be­tween the two ver­sions is the VGA ver­sion one doesn't in­stall a start menu icon. Both run in 320x240, or more ex­act­ly 240x320—if your res­o­lu­tion is small­er than this, the game sim­ply crash­es, if big­ger, it on­ly us­es the top left cor­ner. The VGA ver­sion comes with a hacked GX.DLL, I think it was sup­posed to up­scale the QVGA game to VGA, but it didn't work. I'm not sure if this is a lim­i­ta­tion of the em­u­la­tor or the pack­age is just plain bro­ken, but I have oth­er ways to up­scale the im­age, and I need them as even 640x480 is too small these days.

Fix­ing the ro­ta­tion prob­lem is al­so more com­pli­cat­ed than it should be. You can set ro­ta­tion in the em­u­la­tor's set­tings—ex­cept it throws some in­ter­nal er­ror when you try to open it in wine, and you get a half-pop­u­lat­ed set­tings screen where noth­ing does any­thing. For­tu­nate­ly most of the set­tings can be set from the com­mand line, but try­ing to ro­tate the em­u­la­tor re­sults in this:

RotatePNG

The drawn im­age is dis­tort­ed, mouse clicks are not ro­tat­ed, so even get­ting to just launch­ing NFS in a has­sle, and if you do it, the em­u­la­tor just re­sets to the nor­mal non-ro­tat­ed mode. Thanks Mi­crosoft (or wine or what­ev­er). So what now? Some­what ran­dom­ly I fired up a Xephyr, and in a ter­mi­nal I typed xrandr --output default --rotate left and... wait a minute, it ac­tu­al­ly worked? Then I tried it with my Xephyr fork, of course it crashed. But af­ter I fixed the prob­lem in my patch, I could fi­nal­ly try this game!

So what do you need to run this fine game?

wine path_to/DeviceEmulator.exe path_to/510PPC.BIN /memsize 256 /sharedfolder path_to_extracted_cabs /defaultsave

Let's un­pack this shit. First, you need to give the im­age of the de­vice to em­u­late, 510PPC.BIN in our case. (You can try oth­er im­ages from the ISO, I tried a few ran­dom im­ages, they seemed to work as long as you stick to the im­ages end­ing in PC. SP, short for Smart Phone, means no touch in­put. Yeah, this was made in a time when mo­bile phones didn't have touch screens...) /memsize 256 is just some val­ue I've seen in some oth­er peo­ple's code, prob­a­bly overkill, but I didn't both­er too much about it, 256 MiB RAM is noth­ing these days. /sharedfolder lets you spec­i­fy a fold­er to be used as SD card, this is prob­a­bly the on­ly sane way of shar­ing files be­tween the em­u­lat­ed de­vice and the out­side world. /defaultsave is weird, it al­lows you to save the em­u­la­tor's state some­where un­der your wine's win­dows user's dir. Now, ac­cord­ing to the em­u­la­tor help, there's a /s switch too, which "Spec­i­fies the save-state file­name." Us­ing this op­tion en­ables you to save the em­u­la­tor state un­der the spec­i­fied file name, but there doesn't seem to be any way to load it back! And you need save state, be­cause by de­fault every­thing you do in­side the em­u­la­tor is ephemer­al, every changed set­ting and file and in­stalled ap­pli­ca­tion is lost if you close the em­u­la­tor oth­er­wise. There's a /flash op­tion which en­ables you to save the de­vice's flash mem­o­ry to a file, but just like with /s, I couldn't find a way to load it...

So long sto­ry short, af­ter start­ing the em­u­la­tor us­ing the above com­mand line, wait un­til this Win­dows-lite abom­i­na­tion boots, Start -> Pro­grams -> File Ex­plor­er, open the My Doc­u­ments drop­down and se­lect Stor­age Card, and nav­i­gate to your down­loaded CAB. As I not­ed ear­li­er there's no ap­par­ent dif­fer­ence be­tween the two ver­sions in the em­u­la­tor, I went with the QVGA ver­sion as at least it in­stalls a menu en­try for the game. So click on the CAB and in­stall it on the De­vice. You can in the­o­ry in­stall it on the Stor­age Card too, but this re­sult­ed in ran­dom er­rors for me. Af­ter done, ok it (in the up­per right cor­ner, if you haven't used Pock­et PC be­fore), then you can go back to Pro­grams -> Games. Now I rec­om­mend you to close the em­u­la­tor at this point to save its state, sav­ing while the game in run­ning re­sult­ed in no au­dio for me. Be­sides, you need xran­dr to ro­tate the screen in the cor­rect ori­en­ta­tion:

xrandr --output default --rotate right

Right, not left ro­ta­tion you need. If you do this, you can en­joy the in­tro "video", which is hon­est­ly just a still pic­ture with some ef­fect ap­plied on it.

It's about 1.3 sec­onds long. Prob­a­bly the short­est in­tro of any game I've ever saw. The mu­sic in the main menu stut­ters a lot, I was a bit afraid I'll have to lis­ten to the game's au­dio like this, but for­tu­nate­ly ingame it works cor­rect­ly. It al­so works if you re­turn to the main menu af­ter a race, it's on­ly bro­ken the first time af­ter start­up. ¯\_(ツ)_/¯

(Note al­so how it says NFS Be­ta. Ap­par­ent­ly the ver­sion up­loaded to archive.org is not the fi­nal ver­sion, but I couldn't find any oth­er ver­sion of this game on­line, so every­thing in this ar­ti­cle is based on this ran­dom be­ta ver­sion. Re­as­sur­ing, right?)

The game#

This is go­ing to be short­er than usu­al. As you can see in the main menu this game has both Ca­reer Mode and Sin­gle Ar­cade mode, like the Win­dows ver­sion of the game, but I on­ly tried the lat­ter. Both modes re­quire cre­at­ing a user pro­file and you can't even can­cel the menu, like in Win­dows NFS5. Ar­cade has three modes, Sin­gle Race, Knock Out, and Tour­na­ment, even though I on­ly tried one mode here too (sin­gle race). You can se­lect from most­ly the same cars as the Win­dows ver­sion, but no tun­ing or col­or change or any­thing. Back to NFS1! (Ac­tu­al­ly worse, if you try to dis­able mu­sic, the game crash­es, and there's no vol­ume slid­er or car show­case.) On the tracks front, you can se­lect from a sub­set of the PS ver­sion: Dol­phin-Cove, Snowy-Ridge, Kin­di­ak-Park, Route-Adonf, Celtic-Ru­ins. Spaces have been re­placed by hy­phens in track names. Dol­phin-Cove has route adonf's (for some rea­son in low­er­case let­ters) min­imap in the menu. And while the min­imaps in the main menu were tak­en from the Win­dows ver­sion of the game, I didn't find any sim­i­lar­i­ty be­tween this Pock­et PC edi­tion's and the Win­dows/PlayStation edi­tion's tracks. (The in-game min­imaps, which are based on the ac­tu­al track's geom­e­try, al­so dif­fer a lit­tle...)

Al­so, do you no­tice any­thing un­usu­al in the fol­low­ing screen­shot, on the archive.org page, which in turn was tak­en from Ama­zog?

Amazog trackJXL (JPEG JXL) JPEG (lossless compressed)

Track name is "Costal". I sup­pose it's a ty­po of Coastal, a track from NFS1! All while dis­play­ing the mi­ni-map of Celtic Ru­ins.

And while I didn't do videos like this be­fore, here's a short video of me mess­ing around in the menus. Why? Be­cause the ac­tu­al game­play sec­tion will be pret­ty short.

(Be­fore you ask, the cur­sor looks nor­mal if you play in Xe­phyr. If you ro­tate a screen in nor­mal X, it will prob­a­bly look like in the video. The rea­son it looks like is be­cause I record­ed the video from in­side Xe­phyr (so I get the un­scaled ver­sion), but in this case I had to ro­tate the cap­tured video in obs, and it al­so ro­tat­ed the cur­sor...)

So, what about the races? The con­trols are weird. First, you can open the pause menu with F1. To ac­cel­er­ate press the left ar­row, to break the right. At first I didn't un­der­stand the log­ic be­hind this weird set­up, but when I went to set­tings and flipped the ori­en­ta­tion of the screen, the con­trols al­so flipped! Makes sense, if you ro­tate the PDA, the D-pad al­so ro­tates...

Next ques­tion is, how do you steer? Well, by push­ing your sty­lus on the screen and drag­ging it left/right, which in the em­u­la­tor trans­lates to mouse click and drag­ging. I tried to get used to this weird con­trol scheme, but af­ter a while I gave up. I was think­ing maybe I should go back to writ­ing in­putor scripts again, but af­ter play­ing a bit with this game I just couldn't both­er it. I don't think I'm los­ing any­thing by skip­ping this game.

It's ba­si­cal­ly un­playable. The steer­ing is way too sen­si­tive, even though I was play­ing with 6X zoom (which means small­er mouse move­ments) and I bare­ly moved the mouse most of the time. And the "walls" are un­for­giv­ing, it's like they're coat­ed in su­per glue, as soon as you hit one, it takes a lot of work to es­cape them. I wrote walls in quotes, be­cause as you can see from the video, "in­vis­i­ble walls all over the place" from NFS1 are back! And look at how the op­po­nents take off at the start, it's like rac­ing with a bi­cy­cle against a sports car. If you want to know why I didn't try the oth­er modes, here's the an­swer.

First I thought this is just the usu­al in­sane east­ern dif­fi­cul­ty, or the be­ta ver­sion is fucked up some­how. But then I looked at the above video. The race starts at around 0:06, ends at 3:33, and the in game clock says 4:29.80. Com­bine this with the car pre­view in the pre­vi­ous video, where the cars spin about 120 ro­ta­tions per sec­ond, makes me want to be­lieve some parts of the game de­pend on the CPU speed, and even with hav­ing to em­u­late ARM on X86 us­ing a shit­ty Mi­crosoft em­u­la­tor, it's still too fast on a mod­ern com­put­er.

Every track has its own mu­sic, like in NFS2/3, but they're on­ly about a minute long, and set to loop in­def­i­nite­ly. One would say they some­how had to fit the game in­to a few mebibytes, but this game us­es MOD files to store the mu­sic. They could have eas­i­ly added a few more pat­terns and loops to make the mu­sic less repet­i­tive with­out blow­ing up the file size.

Scrn0PNGScrn1PNGScrn2PNGScrn3PNGScrn4PNGScrn5PNGScrn6PNGScrn7PNGScrn8PNG

Technical shit, take two#

I hon­est­ly want­ed to just throw the game away, but my last ob­ser­va­tion about em­u­la­tion speed kept bug­ging me. Maybe if I can slow down the em­u­la­tion, it will work bet­ter? My first at­tempt was to use cpulimit (which ac­tu­al­ly comes from LimitCPU these days), but it didn't help much. All it does is send­ing SIGSTOP and SIGCONT to the process con­tin­u­ous­ly to lim­it the CPU us­age, by de­fault with a pe­ri­od of 0.1 s, which is way too large. It just makes the game stut­ter, not run slow­er. I edit­ed the source code to de­crease this pe­ri­od (for some rea­son there's no com­mand line switch for it), but as soon as I de­creased it enough for the stut­ter to stop, cpulimit al­so stopped lim­it­ing the CPU. From a quick look at the code it us­es nanosleep, which means it is re­strict­ed by the pre­ci­sion of ker­nel's task sched­uler and such doesn't work well for small sleeps.

My sec­ond idea was to try to lim­it the fre­quen­cy of my CPU. Eas­i­er said than done on to­day's CPUs. I have Ryzen CPUs in my new­er ma­chines, they use the amd-pstate-epp dri­ver, which in a nut­shell means Lin­ux just lets the CPU's firmware do what­ev­er the fuck it wants with the fre­quen­cy. You have the usu­al cpufreq hi­er­ar­chy un­der /sys/devices/system/cpu/cpufreq/policy*, but all you can re­al­ly set here is an energy_performance_preference val­ue, which is a 4 val­ued bull­shit con­trol for re­tards (performance, balance_performance, balance_power or power), every­thing else here is read on­ly or sim­ply ig­nored if you set it. Com­mon ad­vice around the in­ter­net (for ex­am­ple here) is to dis­able the AMD P-State dri­ver com­plete­ly us­ing the amd_pstate=disable ker­nel com­mand line pa­ra­me­ter, to fall back to ACPI meth­ods, but it caused me to be on­ly be able to se­lect be­tween 3 dif­fer­ent fre­quen­cies. And of course you need to re­boot your com­put­er to get in­to/out of that mode. Af­ter read­ing a lot of mis­in­for­ma­tion (oof, am I be­com­ing CNN?) about this AMD P-State dri­ver on the in­ter­net in var­i­ous bugzil­las and fo­rums, I said fuck this shit and checked the source code... Long sto­ry short, you have to check the /sys/devices/system/cpu/amd_pstate/status file. If it says active (which I think is the de­fault for newish ker­nels), it means EPP con­trols your CPU's fre­quen­cy, and you can't do much about it. You should change it to passive (or maybe guided, but I test­ed it with passive) to give the con­trol back to the ker­nel. Well, some­what. In this case it's still the CPU's firmware which con­trols the fre­quen­cy, but the ker­nel can pro­vide it with a de­sired fre­quen­cy (in re­al­i­ty a per­for­mance val­ue, which is more-or-less a scaled ver­sion of fre­quen­cy), and the CPU will try to stay close to it. And fail mis­er­ably. Here's what I did: picked a ran­dom core X (don't pick core 0, on Lin­ux in­ter­rupts can on­ly be ser­vice by the first core), set it to the min­i­mal fre­quen­cy my CPU sup­port­ed (550 MHz in my case), pinned the Pock­et PC em­u­la­tor to core X and ran the game. It im­me­di­ate­ly jumped to about 1.5 GHz, even though it cor­rect­ly fell back to 550 MHz while idle. Sigh. This still doesn't work.

My next idea was to use QEMU, es­pe­cial­ly since I read it has an -icount op­tion com­bined with align=on can slow down the em­u­la­tion, ex­act­ly what I need! Un­for­tu­nate­ly it on­ly works with the sys­tem em­u­la­tor, so I had to get a Win­dows VM in­side QEMU. I'll spare you the de­tails (I used an old WinXP im­age I had ly­ing around from Nep­tools CI, but OpenSSH 10 dropped DSA sup­port, so I had to down­grade OpenSSH (USE=legacy-ciphers emerge -a1 -j1 '<openssh-10') just to copy the em­u­la­tor on­to the VM...), since af­ter I was done and I could fi­nal­ly check the em­u­la­tor, it turned out QEMU's TCG back­end is way too slow, even with­out us­ing -icount. (KVM is fast but it us­es hard­ware sup­port, and thus doesn't sup­port -icount) The em­u­la­tor took min­utes to boot Win­dows CE and NFS was run­ning at around 1 FPS. Über fail.

Des­per­ate­ly I took my old note­book, 4th gen­er­a­tion In­tel i7, hop­ing it's old enough to have a sen­si­ble cpufreq sup­port. It does, I set scaling_governor to userspace, the fre­quen­cy to the min­i­mum (800 MHz), it seemed to work. I check it in htop, it seems to work. Great! I start wine pinned to the core, and... fre­quen­cies are all over the place again. Aargh! But af­ter some htop watch­ing, I re­al­ized all cores seem to change fre­quen­cy in rough­ly the same way. It seemed like this CPU can on­ly run all the cores at the same fre­quen­cy on­ly (prob­a­bly htop just can't read atom­i­cal­ly all of the core's fre­quen­cies, thus end­ing up dis­play­ing dif­fer­ent fre­quen­cies for dif­fer­ent cores some­times), and while Lin­ux al­lows me to set cpufreq poli­cies per thread(!), in the end the CPU will be set to the max­i­mum of the re­quest­ed fre­quen­cies. So next try, set all cores to userspace and 800 MHz, and it seemed to work! Al­so it was a bit too slow, but some progress fi­nal­ly!

Af­ter this I de­cid­ed to go back to my Ryzen note­book, and see if set­ting every core helps, and it does. Looks like while these new­er Ryzens do al­low set­ting the fre­quen­cy per core, it's still not com­plete­ly in­de­pen­dent. Af­ter some tri­al and er­ror, I fig­ured out the race's clock runs at re­al time around 1.2 GHz, so it's prob­a­bly the in­tend­ed speed of the game. For the record, here's the fi­nal set of com­mands I used, all ex­e­cut­ed as root:

echo passive > /sys/devices/system/cpu/amd_pstate/status
cd /sys/devices/system/cpu/cpufreq/
echo userspace | tee */scaling_governor
echo 1250000 | tee */scaling_setspeed

(Note how I write 1.25 GHz to get 1.2 GHz. As I men­tioned ear­li­er, the ker­nel can on­ly se­lect one from the dis­crete list of per­for­mance val­ues the CPU sup­ports, so you can't set it to ar­bi­trary fre­quen­cies. And when con­vert­ing the fre­quen­cy to per­for­mance val­ue, the ker­nel rounds down, so in prac­tice the ac­tu­al fre­quen­cy will be usu­al­ly slight­ly low­er than what you spec­i­fy.)

Then run wine pinned to a core: taskset -c 15 wine. To re­store it, just echo active in­to status above.

I didn't make videos here, most­ly be­cause the re­sults are pret­ty much the same. The car be­came a tiny lit­tle bit more con­trol­lable, ac­cel­er­a­tion is a lit­tle less crazy... but still, all I achieved was fin­ish­ing just un­der 4 min­utes in­stead of 4:30. I still took twice as long to fin­ish than the AI cars on the eas­i­est set­ting. Rac­ing these no en­gine sounds cars with a physics en­gine that bare­ly re­sem­bles re­al­i­ty is not easy. Es­pe­cial­ly when the cars start to slide, the on­ly thing you can do is break­ing to stop it, which for some rea­son works even if you have a right an­gle be­tween the di­rec­tion of your tires and your mo­tion vec­tor. Oh and you have ze­ro feed­back it hap­pens, it usu­al­ly takes me at least a sec­ond to no­tice this sit­u­a­tion, but then it's al­ready too late.

This post is part of se­ries nfs: 1, 2, 3:HP (W, PS), 4:HS (W, PS, CE), 5:PU, MCO, 6:HP2