I’ve recently rediscovered an old game called “The Games: Winter Challenge”, a Winter Olympics sports game developed by MindSpan and published by Accolade in 1991 for DOS and Sega Genesis.
I had the DOS version of the game when growing up, so when I was randomly reminded of its existence, I was driven by a mix of nostaliga and curiosity to dig it up again.
Having grown up to become a computer scientist, I was not as much insterested in replaying it (though hearing the iconic music again was fun), but much more how it worked under the hood.
I had spent hours as a kid playing especially the ski jumping event, trying to reach the elusive mark of 100 meters, without success, and was determined to find out not only whether it was possible to achieve, but also what the theoretical optimum would be.
Conveniently, the game features a replay system that allows you to save and rewatch past attempts, which opens up great opportunities for creating a TAS and manufacture a perfect replay file, and push the game to its limts.
My initial plan of attack was simple: Find a copy of the game, crack it open in Ghidra, disassemble it to find out how the ski jumping works, and optimize based on the discovered mechanics.
As it turned out, each step of this plan was way more involved than anticipated, and created more questions along the way that demanded answers, opening up a rabbit hole of early 90s video game development intricasies.
This write-up will take you along on this ride of discovery, learning about how DOS-based programs worked, how video video game developers worked around the hardware limitations, how early copy protection worked, and how GOG sells you a broken version of the game (as of March 2025).
Taking stock – version chaos and copy protection circumvention
The game has had multiple releases, including the original release in 1991, two bundle releases with its successor “Summer Challenge” in 1992 (Europe) and 1996 (US), and a GOG release of the bundle in 2020, based on DOS emulation through DOSBox.
While the original floppy disks from my childhood are likely buried somewhere, they are of limited use today for lack of a floppy drive to read them with, so I searched the internet for the game.
Acquiring these different versions wasn’t too difficult thanks to the Internet Archive hosting various versions of the original media, and of course purchasing it from GOG.
The original game used a code wheel for copy protection.
Code wheels were a typical copy protection of the time: They are a physical set of disks sliding against each other, which you got together with the floppy disks containing the game.
At startup, the game asks you to turn them in a specific configuration, which reveals a code you need to enter into the game for it to work.
For those who are not old like me and have never seen a code wheel before, there is an interactive online version of this game’s code wheel available.
The original 1991 release as expected asks for this code when you try to play any discipline, and boots you out if you answer incorrectly twice.
Side investigation 1: how is the code wheel check implemented?
The GOG version does not ask you for the code and lets you play without it, so presumably they have removed the copy protection from it instead of distributing the code wheel.
Where it get very insteresting is that multiple people are complaining in the discussion of this game on GOG that the game is “improperly cracked” and doesn’t work correctly as a result.
The descriptions of some of the behaviors, like that you can’t land a ski jump beyond a certain distance, or that you always crash in the last lap of speed skating, actually resonated with my recollection of playing the game as a kid, which means either we had a poorly cracked version back then as well, or that this is not related to copy protection at all and the game is just buggy.
Side investigation 2: Are there hidden copy protection measures which affect gameplay?
The 1996 US release actually comes with a separate crack, presumably officially sanctioned: Next to the main WINTER.EXE
, it has a WINTER.COM
, only 879 bytes in size, which when executed removes the code wheel check from the game, otherwise the game still asks for it.
But the version confusion doesn’t end there.
While searching for different versions, I also found other versions of the game, often in the form of online playable images loaded in DOSBox in the browser.
None of these needed a code wheel, and while some were based on the 1996 US release, others used entirely different cracks, created by different release groups of the early 90s.
And to complete the mess, the original game actually offers an option to install the game to disk instead of playing it from floppy, including its very own set of mysteries.
The installation does not work like you might expect, just copying files from floppy to disk; instead, it actually creates a whole new WINTER.EXE
executable each time.
During installation, you can choose different options, including which graphics modes to support, and a “fast loading” mode which according to the manual makes the game load faster at the cost of additional hard drive space, and each combination of the options creates a different executable for you.
Side investigaton 3: How are these different versions of the executable being created, and how do they differ?
So taking stock and comparing all different acquired versions, there are a lot of distinct binaries:
- The original floppy version of the game
- Six different versions of the game when installed to hard disk, for each combination of “fast loading” and either or both of the EGA and VGA graphics modes
- The GOG version of the game, which is based on the installed VGA+EGA fast-load version with individual bytes modified
- A cracked binary of unknown origin, which is based on the installed VGA fast-load version with individual bytes modified
Furthermore, there are three different stand-alone cracks:
- The official
WINTER.COM
crack (879 bytes) which was provided with the 1996 US release alongside an unmodified floppy version - A
WG.COM
crack (366 bytes) by release group “The Humble Guys” from October 17, 1991, within days after the game’s release - A
WINTER.COM
crack (291 bytes) by release group “Razor1911” from October 17, 1991, the same day(!) as the other crack
Side investigation 4: How do the individual cracks work, and do they use different mechanisms?
Cracking the binary open – obfuscation and memory constraints
So to start somewhere, I loaded up the floppy version in Ghidra, and was immediately underwhelmed. It only managed to analyze a tiny fraction of the inital code, with most of it remaining binary blobs.
Opening the same file in IDA revealed much of the same picture, but IDA also provided accompanying warnings: It thinks the binary may be packed, and there are lots of unused bytes beyond the end of the code.
I figured that the binary must be packed or obfuscated in some way, and the tiny bit of initial code is the routine to unpack the rest of the binary.
So I began reverse-engineering the unpacking routine, and discovered a suspicious string in the binary, nestled between the assembly: *FAB*.
As it turns out, “FAB” stands for Fabrice Bellard, who next to being the original developer of widely used programs such as FFmpeg and QEMU, is also the creator of an executable compression utility called LZEXE, developed in 1990.
Luckily, the inner workings of LZEXE are widely documented and understood.
I won’t go into the details of how the compression works here, there are great existing write-ups by Scott Smitelli and Sam Russell if you want to dig deeper.
We just want to unpack the binary to get to the good stuff, and there are plenty existing unpacking utilities available, including UNLZEXE by Mitugu Kurizono from the same era.
The packing and unpacking is its own arms race microcosm, with protectors to prevent the unpacking, and more sophisticated unpackers to do it anyway, but luckily no additional unpacking protections were employed for this game.
The resulting unpacked binary has two surprises right off the bat: Firstly it is only 168kB in size, much smaller than the original executable despite extraction presumably making it grow in size, and secondly the result of unpacking it is identical across all different versions of the game.
This gives us a hint for how the game is structured: It contains a chunk of business logic, which is what we have unpacked and is the same across versions, and then it contains some resrouces, like sprites and sounds, which are included into the executable file and loaded out of it at runtime.
This assumption is supported by the fact that the extracted binary actually still works properly as long as it is placed beside the original WINTER.EXE
binary to load the assets out of.
But it also is somewhat surprising for the two cracked versions of the binary, I would have expected those to contain modified business logic in order to facilitate skipping the code wheel check.
The answer to that mystery becomes apparent quickly after opening the new extracted binary in a disassembler.
By looking around some, we find suspicious interrupt calls to int 3fh
.
Side bar: Interrupts
Interrupts are the main way DOS programs used to communicate with the operating system, analogous to today’s syscalls.
Whenever a program wants to interact with something outside it’s own code, it would call an interrupt and ask DOS to perform that task for it, handing back control to the operating system temporarily, and resuming when it is complete.
Anything from printing text to the screen, reading and writing files from disk, to allocating heap memory, is done through the main interrupt DOS provides,int 21h
.
Which action is requested and any arguments are determined by the value of the CPU registers when the interrupt is called.
Other interrupts exist likeint 33h
for mouse interactions, but notablyint 3fh
is not one of the DOS-provided interrupts.
Under the hood, the routing of interrupts is handled by an interrupt vector table, which contain for each interrupt the address of the routine that is executed from when the interrupt is called.
Programs can modify this table (using an interrupt) to add their own custom interrupts, andint 3fh
is likely used-defined this way.
IDA provides a helpful comment to these, that this interrupt is typically used for calling an “Overlay manager”.
Side bar: Overlays
Overlaying is a technique for loading additional pieces of code at runtime, where multiple such pieces, called overlays, can be swapped out in the same place in memory.
This was useful in programs of the time to save on RAM usage: DOS only allowed a maximum of 640kB of memory to be used by a program (aka Conventional memory), and large applications might themselves already be too big to fit all their code into that limit, not even considering any data.
Overlays are used to circumvent this limitation: By breaking the program code up into multiple overlays, the program only needs to load whatever overlay is needed for the current operation into memory.
Other overlays are loaded from disk as they are needed, replacing the previous overlay, allowing the program to have complex functionality with a small memory footprint.
Loading and managing overlays was the responsibility of an overlay manager, a library which kept track of which overlays are needed when and loaded and unloaded them accordingly.
As it turns out, the game was written in C and compiled with the Microsoft C compiler version 6, as hinted by an embedded string MS Run-Time Library - Copyright (c) 1990, Microsoft Corp
in the binary.
Perusing the compiler’s manual, the linker of that compiler did natively support overlays and would install its own overlay manager as int 3fh
by default, so this was my first suspicion for how this structure was created.
Overall, this was not good news.
It means that the unpacked binary is in fact not all the business logic that exists, and there are more pieces of code, presumably in the resources packaged with the executable.
Disassemblers don’t understand these overlays, can’t detect them or automatically disassemble them, so the work to understand the business logic will be more manual than planned.
In order to progress further, we need to find and extract all these overlays, to get a complete picture of the game’s code.
By finding where the interrupt 3fh is installed at the start of the program, we can identfy the overlay manager routine which is called each time an overlay is needed.
Based on documentation for how Microsoft’s overlay manager worked, each interrupt call is followed by 3 bytes, one byte for the index number of the overlay that is needed, and two for the 16-bit address within that overlay.
Calling the interrupt then works like a function call: The overlay is loaded, the function at the given address is invoked, and afterwards the control flow returns directly after the interrupt call.
In fact the interrupts are literal replacements for function calls: a the 5 bytes typically needed for a far call instruction (1 byte opcode, 2 bytes address offset, 2 bytes address segment) are replaced by the Linker with the 5 bytes for the interrupt (2 bytes opcode, 1 byte overlay index, 2 bytes address offset) where needed.
This is where the good news ended though.
According to the documentation, each overlay should be appended to the main program, including its own MZ header, but this is not what we find in our binary.
Worse still, when using DOSBox’ debugger to step through an invocation of the interrupt, the code that was loaded is nowhere to be found in the binary file.
Also, unlike typical overlays, they are not actually occupying the same space in memory, instead new memory is dynamically allocated for each overlay, and deallocated after use.
That is useful because it allows multiple overlays to be loaded at the same time, but also means this game is not actually using the overlay mechanism from Microsoft C, instead it uses what appears to be a bespoke overlay management implementation.
Statically reverse-engineering the overlay manager routine turned out to be a very time-consuming endeavor, but luckily there were still some hints that can help us take some shortcuts.
The DOS emulator DOSBox-X is a fork of DOSBox, and has additional useful debugging features, including logging of all file IO, and all int 21h
interrupts.
Watching those while the game starts up reveals that the game is seeking through the binary to specific locations, which happen to be directly after the bytes of the main program, and then reading many chunks of 22 bytes each.
...
4201235 DEBUG FILES:Seeking to 82944 bytes from position type (0) in WINTER.EXE
4201290 DEBUG FILES:Reading 2 bytes from WINTER.EXE
4201353 DEBUG FILES:Seeking to 82495 bytes from position type (0) in WINTER.EXE
4201408 DEBUG FILES:Reading 2 bytes from WINTER.EXE
4201475 DEBUG FILES:Seeking to 82497 bytes from position type (0) in WINTER.EXE
4201530 DEBUG FILES:Reading 2 bytes from WINTER.EXE
4204681 DEBUG FILES:Seeking to 82499 bytes from position type (0) in WINTER.EXE
4204735 DEBUG FILES:Reading 22 bytes from WINTER.EXE
4204855 DEBUG FILES:Reading 22 bytes from WINTER.EXE
4204975 DEBUG FILES:Reading 22 bytes from WINTER.EXE
4205095 DEBUG FILES:Reading 22 bytes from WINTER.EXE
4205215 DEBUG FILES:Reading 22 bytes from WINTER.EXE
4205335 DEBUG FILES:Reading 22 bytes from WINTER.EXE
4205455 DEBUG FILES:Reading 22 bytes from WINTER.EXE
...
These are likely the start of the resources, and when checking the binary at that location we find that the secion begins with two bytes spelling out MB
, similar to how the executables themselves start with an MZ
magic number.
Looking for this magic number in the disassembly brings us directly to the routine which parses out the structure of the embedded resources.
seg000:6D83 sub ax, ax ; sets ax to 0
seg000:6D85 push ax ; push argument 3 for fseek: 0 = seek relative to start of file
seg000:6D86 push winter_exe_overlay_start_index_hi ; push argument 2 for fseek: the offset to seek to
seg000:6D8A push winter_exe_overlay_start_index_lo ; it's a 4 byte value and pushed in two halves
seg000:6D8E push winter_exe_file_handle ; push argument 1 for fseek: the file handle of WINTER.EXE which was opened earlier
seg000:6D92 call fseek ; seek to the start of the resource section in the WINTER.EXE file
seg000:6D97 add sp, 8 ; clear the arguments for fseek from the stack again
seg000:6D9A push cs ; the function read_2_bytes_from_winter_exe is a far function, but we're making in near call, so we need to push the segment onto the stack manually
seg000:6D9B call near ptr read_2_bytes_from_winter_exe ; read the next two bytes from the file
seg000:6D9E cmp ax, 424Dh ; check if if contains the "MB" magic number
seg000:6DA1 jz short mb_marker_found ; jump if found
seg000:6DA3 push winter_exe_file_handle ; if not found, close file and return
seg000:6DA7 call fclose
seg000:6DAC add sp, 2
seg000:6DAF mov winter_exe_file_handle, 0
seg000:6DB5 jmp short done
seg000:6DB5 ; ---------------------------------------------------------------------------
seg000:6DB8 mb_marker_found:
seg000:6DB8 sub ax, ax ; sets ax to 0
seg000:6DBA push ax ; push argument 3 for fseek: 0 = seek relative to start of file
seg000:6DBB mov ax, winter_exe_overlay_start_index_lo ; load overlay start index and add 2 to it
seg000:6DBE mov dx, winter_exe_overlay_start_index_hi
seg000:6DC2 add ax, 2
seg000:6DC5 adc dx, 0
seg000:6DC8 push dx ; push argument 2 for fseek: the offset to seek to
seg000:6DC9 push ax
seg000:6DCA push winter_exe_file_handle ; push argument 1 for fseek: the file handle of WINTER.EXE which was opened earlier
seg000:6DCE call fseek ; seek to the next two bytes after the MB marker
seg000:6DD3 add sp, 8 ; clear the arguments for fseek from the stack again
seg000:6DD6 push cs
seg000:6DD7 call near ptr read_2_bytes_from_winter_exe ; read the next two bytes from the file
seg000:6DDA mov resource_chunk_count, ax ; next two bytes indicate the number of resources
seg000:6DDD done:
....
Sidebar: 16-bit architecture and segments
This program, and all DOS programs at the time, were built for a 16-bit architecture, compared to the 64-bit architecture today’s computers are using.
What that means is that all registers in the CPU can hold only 16-bit values, including any pointers.
Since 16-bit registers can only have 2^16 = 65536 different values, it can only address 64kB of memory.
This was too little, even back then, so in order to be able to address more memory, pointers typically consisted of 2 parts, a segment and an offset.Segments are chunks of memory, at most 64kB in size, which were typically assigned different roles: there are typically one or multiple code segments holding the program code, a data segment holding the work memory for any data the program stores, and a stack segment to hold the values which are put on the stack.
Those segments can be considered independent parts of the memory, and to interact with something from another segment, you would need a far pointer, consisting of both a segment and offset within that segment, whereas for referencing something within a segment a near pointer using only the offset is sufficient.Under the hood, the memory is still one linear chunk, and the resulting memory address of a far pointer is simply
segment * 16 + offset
.
That means segments can technically overlap with different segment-offset pairs pointing to the same physical address, but conventionally they were chosen to be distinct blocks.
The resources are all tabulated in a simple header structure, in entries of 22 bytes each.
Each entry contains two 4-byte numbers, indicating the length of the data and the offset in the file where they are located.
The remaining bytes contains a 0-terminated string spelling out the name of the resource (any byte after the 0 terminator is garbage).
However, this name is obfuscated by adding 0x60
to all bytes, so they don’t show up in any strings analysis of the binary.
By de-obfuscating the names, we can learn that these additional binary blobs contain both the assets like images, meshes, music and SFX files, and the code overlays in the form of pairs of files with extensions COD
and REL
.
4D 42 ; "MB" = magic number
F2 00 ; 242 = number of resources
4A 5C 00 00 10 57 01 00 B4 A9 B4 AC A5 8E AD B3 A8 00 (81 9F A2 01) ; Resource TITLE.MSH start 15710 end 1b3
25 Comments
cinntaile
I speedran through the whole article but this was a nice reverse engineering deep dive!
dlachausse
My favorite copy protection scheme was where you needed to enter some text from the printed manual that came with the game. The disks were easy to copy but the manuals required significant effort.
I also just really miss printed game manuals.
Mountain_Skies
Apparently 'The Terminator 2029' had such a trick in it. One of my friends in college was obsessed with the game and was frustrated about not being able to complete one of the levels due to a target being inaccessible. Eventually someone told him it was an intentional flaw introduced into copies that were pirated. Not sure if he ever bought the game so he could finish it.
paulryanrogers
Amazing that GOG was so lazy that they didn't check to ensure their DRM removal was complete, before offering it for sale. Hopefully this will motivate them to do a proper fix.
Reason077
"The “Razor1911” crack (1991)
Finally, we get to the only crack that actually works properly. Congratulations to Razor1911 for being the only ones not fooled by the game’s trickery."
No surprise here! I was never all that deep in the Warez scene, but every nerdy kid in the early 1990s knew that Razor 1911 were the most l33t game crackers around. It was kind of a mark of quality on any game. If Razor 1911 released it you knew that not only was it cracked competently, it was probably a good game too!
Ayesh
Not a DOS game, but one of the early Prince of Persia (circa 2007) had an evil DRM trick: after a few hours into the game, there is a pressure pad activated door that does not work on cracked versions. So if you are in a cracked versions, and if the crack is not good enough, you will spend a lot of time frustrated unable to go past that door.
It is possible that the crack itself broke the game, but I want to believe it's some genius evil idea someone from Ubisoft came up with.
bitmasher9
I have a core memory of playing a cracked copy of an elder scrolls game that was unwinnable, and spending two hours playing with console commands in the game to get past the broken section. If I recall correctly, key dialogues were broken preventing story advancement.
Sorry for stealing your game, I was young.
codesnik
I remember playing old french game "Metal Mutant", which on a level three or four asked something in french (it was probably asking for a code from manual) and if you answered wrong, it wouldn't exit the game, but it'd just silently disable all projectiles, making game unwinnable. I as a kid spent hours wandering around, thinking that I missed some clue. And game didn't have any saves, so after banging my head for a couple hours, I'd exit game frustrated, and in a month or two I had to start from scratch if I wanted to try to complete that level again.
2mlWQbCK
Chris Crawford wrote in his On Game Design about a trick like this, that he implemented in Patton Strikes Back. Plus some other tricks. He claims that he never found a cracked version that had fixed the secondary checks. The result was a crash just before winning the game.
This looks like an older version of the same text that he later edited into a chapter of the book (does not have the claim about only finding failed cracks):
https://www.erasmatazz.com/library/the-journal-of-computer/j…
fnord77
I remember an old Apple ][ game that someone had copied from somewhere and it got passed around by us jr high school students.
It was some sort of "Defender" style game. Apparently cracking ("Cracked by the Nibbler") caused some obstacles to become invisible. You could play the game for a bit but you pretty quickly crashed into one of these.
Wish I could remember the name of the game. I would have liked to played a legit copy
candl
Not DOS, but I remember playing a copy of Settlers III and was surprised when iron smelters produced pigs instead of iron.
watusername
I always find official cracks* like this to be amusing and worrying at the same time. Worrying because it could mean that the current owners don't even have access to the source code anymore, and it's sad to see the source of those games lost to time.
Tangentially, this phenomenon isn't limited to retro DOS games: Rockstar was caught shipping a pirated version of Midnight Club 2 [0], and Sinking Ship [1] is another example of this in the indie scene.
[0]: https://news.ycombinator.com/item?id=37394665
[1]: https://news.ycombinator.com/item?id=26311522
* Legally they aren't cracks because they are fully authorized distributions of the games
eej71
If you enjoy stuff like this – do read up on 4am's incredible efforts to preserve Apple ][ software. Just amazing.
https://paleotronic.com/2024/01/28/confessions-of-a-disk-cra…
tsunoo
[dead]
mschuster91
> As it turns out, “FAB” stands for Fabrice Bellard, who next to being the original developer of widely used programs such as FFmpeg and QEMU, is also the creator of an executable compression utility called LZEXE, developed in 1990.
Is there anything where you don't find Fabrice Bellard along the way if you just dig deep enough?
ferguess_k
Kudos to the original author who took the time to dive into it. I highly admire people who can dive into some technical topics and have the patience to figure everything else. They are the kind of people I look up to.
BTW whoever fascinated by the copy protection techniques of legacy systems should also check out this book: "Tome of Copy Protection", from ID (yeah the original Idea from the Deep).
flowrange
This article actually solves one of the great mysteries of my life: how to beat that game.
I still remember, back in the mid 90s, playing it with my brother and some friends. We spent so much time trying to beat the default bobsleigh time, land a 100+ meter jump in the ski jumping event, or survive that dreaded third lap in skating. But no matter what, we just couldn't pull it off.
Years later, I even gave it another shot under Dosbox, thinking, "Alright, I was just a clueless kid back then. Now it's my time to shine." Nope. Still couldn't do it.
Turns out we obviously had a cracked copy. But honestly, trying to actually buy a game when you’re a 12yo in mid-90s France (obviously without any Internet connection) wasn’t exactly easy.
tgsovlerkhgsel
The downside of these systems is that the behavior of the cracked game is often simply attributed to the game, contributing to the perception that the game is buggy (or just bad/not fun).
While they are somewhat effective at making pirates miserable, I have my doubts on whether they are actually good at driving sales. Keeping pirates from enjoying the game isn't a victory for the developer, generating sales is…
the_clarence
I couldn't play sim city because of that, the game would always throw insane natural disasters at me until I lost, I thought that was a very interesting way to mess with copies
hiccuphippo
For a modern example I had a bad time trying to play Celeste using the family sharing feature in Steam. The game would slow down the jumping making it impossible to advance. I don't know why it would deem it as an illegal copy, I just deleted it and never tried the game again.
g-b-r
God I thought I was an idiot, that game seemed so hard!!!
I'm so glad to have read this xD
(back then I didn't even know what piracy was, it was just a game that someone gave me)
skocznymroczny
Interesting, I remember the speed skating issue being a problem in the copy I had back in the 1990s, but I don't remember the issues in other games like downhill and such.
People usually find these gameplay based copy protections amusing as in "hehe stupid pirates let them play a broken game", but I have bad memories of them because I often had them trigger when playing legit copies of the game. All it took was having CD emulation software installed (not even running) and some games would already flag you as a pirate.
fipar
Not mentioned in the article is Sid Meyer's Pirates! (the exclamation mark was part of the name, though I do get excited when talking about the game so I'd add it myself if it weren't).
This was one of the 2 (!) games I had as original at that time(the other being Sub Battle Simulator), and it had a beautiful map and book. The book would include some details that were asked before the first fencing fight, like "When did ship X leave port Y?" and if you got the answer wrong, as best as I could try (and I did intentionally try to beat that part after giving the wrong answer) you'd always lose it and not be able to start your career.
p0w3n3d
Marvelous!
stands up and claps
Excellent!
Applause
brbcompiling
I wonder what kind of cool stuff you'd discover if you dug into the code of other classic DOS games from the 90s? Anyone ever try reverse engineering their childhood favorites?