Fairly long post incoming…
God bless Georg Dahn…
So, RayCarrot was kind enough to provide me with install directories of all the versions he had collected, and I spent a few days
plumbing support for them into the TSR.

In the course of this work, I have come to the conclusion that the earliest and rarest versions, v1.00 and v1.10, are
EVIL!
Both of them have way more DRM than the subsequent versions. Firstly, there are additional checks on the CD at initialization. That's fair enough since, at least they're upfront. It makes sure that the
"copyright",
"abstract", and
"bibliographic doc" filenames are all "RAYMAN", which makes sense. It then checks that the CD has exactly one data track, and exactly 25 audio tracks, and exactly the right total number of sectors (obviously different between the two versions). I am somewhat bemused by the fact that they knew the right number of sectors when they were still writing the code – I guess they compiled it with a dummy number, calculated the number of sectors and then wrote that back into the code and recompiled.
Next come the sneakier checks, which happen while the game is running. As per the newer versions, on loading a map with
num_world>=2 and
num_level>=7, the game checks the file-sizes of INTRO.DAT and CONCLU.DAT on the CD, and crashes without warning if they are not as expected. But in these early versions, there's more… There are three points in the code where the game checks if the CD's volume label is also "RAYMAN". It checks this at initialization, when loading the world map, and when loading a level. If the volume label is wrong, the game goes into Limited Mode
without warning, even if the check fails at initialization! (Note that the other initialization-time checks put up a message giving you a choice between changing the disc, playing in Limited Mode, or exiting.) When the game is in Limited Mode, it crashes after finishing a level.
There are a couple of other interesting points about this volume label check. Firstly, the code looks as if it's going to check for the label "RAYMAN
VOL", but then at the last moment replaces the 'V' with a null byte, truncating the string to "RAYMAN". I guess they were planning on using a longer volume label at some point – God knows why, or why it changed… Secondly, all the newer versions also check the volume label, but they don't act on the result. This is presumably because later versions can be included in bundles, so the volume label can legitimately be "RAYGOLD", or "RAYFAN", etc.
Now so far, all this seems
fairly reasonable. But this next bit is truly shocking…
Each WLD file contains two bytes near the beginning known as
BiosCheckSum and VideoBiosCheckSum. In most versions of the game, they are unused. But in v1.00 and v1.10, they
do exactly what they say on the tin. When the game finishes loading a WLD file, it checks whether or not it is running from the CD. If it is, everything is fine, but if the game is running from your hard drive (which it surely is if you've installed it…), it proceeds to calculate the checksum of your BIOS ROM (i.e. memory in the address range 0xF0000–0x100000). If it doesn't match what's in the WLD file, then the game goes into Limited Mode without warning.
Just think about that for a second. If your BIOS doesn't match the one used by the developers, the
game will not work properly.
Then, when loading a level, it calculates the checksum of the graphics card ROM (assumed to be in the address range 0xC0000-0xD0000). Again, if this doesn't match the
VideoBiosCheckSum, the game goes into Limited Mode without warning. Essentially, those two checks mean: if you want to run the game from the hard drive, you have to have the same motherboard and graphics card as the game's developers!
It gets even worse though. When you're saving the game, it first does that same check on the
BiosCheckSum, and if it fails, it
hangs the machine! Then it creates/updates the save file, then it checks the
VideoBiosCheckSum. Again, if it fails, it hangs the bloody machine! Now, again, these checks are only done if the game is running from the hard drive, but still, that's pretty awful. Now I see why these versions are so rare, since they must have been basically broken for a large chunk of the user-base…
Apart from that, there's another interesting thing I noticed about these two versions. Version 1.00 is the only one that doesn't check if it's running on OS/2 (as opposed to real DOS) on startup. Version 1.10, on the other hand, skips all the CD checks (including the extra ones mentioned here) if it detects that it's running on OS/2. I don't think there are any other Rayman versions that skip the checks specifically if running on OS/2, although the newer versions do skip the bit that hangs Dosbox (since it presumably would hang the OS/2 DOS box too):
PluMGMK wrote: Sat Aug 21, 2021 6:49 pm
As I recall, the game keeps polling the drive until it reports its status as fully open, or something like that, which I guess never happens in Dosbox…
However, even if running on OS/2, v1.10 still carries out those evil BIOS checksum checks if it's running from the hard drive. I'm not too familiar with OS/2's DOS box, but it seems unlikely to me that it would even allow access to the real hardware ROM, so those checks must be pretty much guaranteed to fail in that case…
––––––––––––––––
On a separate note:
dr_st wrote: Sat Aug 21, 2021 7:29 pm
PluMGMK wrote: Sat Aug 21, 2021 6:49 pm
I'm gonna have to look into that #GP with the Intro/Conclu files – it sounds intriguing!
What happens is this. A routine calls an inner routine. The inner routine is identical in both versions (US 1.21 and FR 1.21). The calling routine is different and the one in US 1.21 is corrupt and does not set up the registers properly before the call. Unfortunately, the
bad routine is also 15 bytes shorter, so there is no way to simply replace the opcodes to make it do what it has to.
So, some good news: I've managed to figure out how this
probably happened, and reproduce it with a minimal example. Here is
CRASHPMW.C, a short program I wrote to demonstrate the issue:
Code: Select all
#include <io.h>
#include <share.h>
#include <stdio.h>
#include <fcntl.h>
#ifndef CRASH
#include <dos.h>
#endif
#define BYTES_TO_READ 20
#define FILE_TO_OPEN "C:\\AUTOEXEC.BAT"
int main() {
char bytes[BYTES_TO_READ+1];
short hdl;
unsigned int bytes_read;
hdl = sopen(FILE_TO_OPEN, O_RDWR, SH_DENYNO);
if (hdl < 0) {
perror(FILE_TO_OPEN);
return 1;
}
_dos_read(hdl, bytes, BYTES_TO_READ, &bytes_read);
bytes[bytes_read] = 0;
printf("Read the following %u bytes from %s:\n%s",
bytes_read,
FILE_TO_OPEN,
bytes);
return 0;
}
Thanks to the
#ifndef, you can use the Open Watcom distribution to compile a working version like so:
Code: Select all
wcl386 -3r crashpmw.c
pmwlite crashpmw.exe
Or to compile a version that crashes with a General Protection fault, like so:
Code: Select all
wcl386 -3r -DCRASH crashpmw.c
pmwlite crashpmw.exe
Here's the corresponding disassembly listing (from IDA) for the working version:
Code: Select all
; int __fastcall main()
main proc near ; CODE XREF: sub_EA60+2F↓p
buf = byte ptr -28h
bytes = dword ptr -10h
argc = dword ptr 4
argv = dword ptr 8
envp = dword ptr 0Ch
push 60
call sub_E090
push ebx
push ecx
push edx
sub esp, 1Ch
push 40h
push 2
push offset aCAutoexecBat ; "C:\\AUTOEXEC.BAT"
call sopen
add esp, 0Ch
test ax, ax
jge short loc_E047
mov eax, offset aCAutoexecBat ; "C:\\AUTOEXEC.BAT"
call perror
mov eax, 1
jmp short loc_E080
; ---------------------------------------------------------------------------
loc_E047: ; CODE XREF: main+24↑j
lea ebx, [esp+28h+bytes]
push ebx ; bytes
cwde ; handle
mov edx, 20 ; count
mov ecx, ds ; buf
lea ebx, [esp+2Ch+buf] ; buf
call _dos_read ; Correct signature
mov eax, [esp+28h+bytes]
mov [esp+eax+28h+buf], 0
mov eax, esp
push eax
push offset aCAutoexecBat ; "C:\\AUTOEXEC.BAT"
push [esp+30h+bytes]
push offset aReadTheFollowi ; "Read the following %u bytes from %s:\n%"...
call printf
add esp, 10h
xor eax, eax
loc_E080: ; CODE XREF: main+35↑j
add esp, 1Ch
pop edx
pop ecx
pop ebx
retn
main endp
And for the crashing version:
Code: Select all
; int __fastcall main()
main proc near ; CODE XREF: sub_EA60+2F↓p
var_24 = byte ptr -24h
bytes = dword ptr -10h
var_C = dword ptr -0Ch
push 60
call sub_E08C
push ebx
push ecx
push edx
sub esp, 1Ch
push 40h
push 2
push offset aCAutoexecBat ; "C:\\AUTOEXEC.BAT"
call sopen
add esp, 0Ch
test ax, ax
jge short loc_E047
mov eax, offset aCAutoexecBat ; "C:\\AUTOEXEC.BAT"
call perror
mov eax, 1
jmp short loc_E07B
; ---------------------------------------------------------------------------
loc_E047: ; CODE XREF: main+24↑j
cwde ; handle
lea ecx, [esp+28h+bytes] ; bytes
mov ebx, 20 ; count
mov edx, esp ; buf
call _dos_read ; Incorrect signature
mov eax, [esp+28h+bytes]
mov byte ptr [esp+eax+0], 0
mov eax, esp
push eax
push offset aCAutoexecBat ; "C:\\AUTOEXEC.BAT"
push [esp+30h+bytes]
push offset aReadTheFollowi ; "Read the following %u bytes from %s:\n%"...
call printf
add esp, 10h
xor eax, eax
loc_E07B: ; CODE XREF: main+35↑j
add esp, 1Ch
pop edx
pop ecx
pop ebx
retn
main endp
So, what's the story? The function signature for
_dos_read is:
Code: Select all
_WCRTLINK extern unsigned _dos_read( int __handle, void _DOSFAR *__buf, unsigned __count, unsigned *__bytes );
When defined correctly, as it is in
<dos.h>, the
_DOSFAR macro indicates that this is a far pointer, with segment and offset to be passed separately. So, when using the
fastcall convention (as Rayman does), Watcom passes around the arguments as follows:
__handle is in
EAX
__buf (segment:offset) is in
CX:EBX
__count is in
EDX
__bytes is pushed on the stack
But, if
<dos.h> is omitted, then there is no prototype for
_dos_read, and, not knowing that one of them is a far pointer, the compiler just assumes the four arguments go here:
__handle is in
EAX
__buf (offset only) is in
EDX
__count is in
EBX
__bytes is in
ECX
Of course, the compiler generates a warning if it can't find a prototype for the function, but compiler warnings can (
but shouldn't!) be ignored. Evidently, this is exactly what happened at Ubi Soft during the development of Rayman v1.21 US: somebody accidentally removed a
#include <dos.h> somewhere, and ignored the resulting compiler warning!
I suspect that the omission of INTRO.DAT and CONCLU.DAT was a direct result of this, and not to save space on the Rayman Gold / Forever discs. Whereas, for v1.21 FR, the error is fixed, so in that case the omission of those files genuinely was to save space on the Rayman Collector disc.
Well, that really was a long post!

Thanks for reading, and I hope you found it interesting!
