dethrace-labs / dethrace

Reverse engineering the 1997 game "Carmageddon"
https://twitter.com/dethrace_labs
GNU General Public License v3.0
667 stars 38 forks source link

Issue/fix: IRandomBetween() not working properly on Win32/x86 #355

Open PierreMarieBaty opened 8 months ago

PierreMarieBaty commented 8 months ago

When compiling on Win32/x86, I couldn't get the new game video, which should be 50% chance either GARAGE1.SMK or GARAGE2.SMK to play the first one.

After investigation it turned out the culprit was this function in src/DETHRACE/common/utility.c:

// IDA: int __usercall IRandomBetween@<EAX>(int pA@<EAX>, int pB@<EDX>)
int IRandomBetween(int pA, int pB) {
    int num;
    char s[32];

    num = rand();
#if RAND_MAX == 0x7fff
    //  If RAND_MAX == 0x7fff, then `num` can be seen as a fixed point number with 15 fractional and 17 integral bits
    return pA + ((num * (pB + 1 - pA)) >> 15);
#else
    //  If RAND_MAX != 0x7fff, then use floating numbers (alternative is using modulo)
    return pA + (int)((pB + 1 - pA) * (num / ((float)RAND_MAX + 1)));
#endif
}

I changed it to the following so that it falls back on the floating-point based code when sizeof(int) is 32 bit:

// IDA: int __usercall IRandomBetween@<EAX>(int pA@<EAX>, int pB@<EDX>)
int IRandomBetween(int pA, int pB) {
    int num;
    char s[32];

    num = rand();
#if (INT_MAX > 0x7fffffff) && (RAND_MAX == 0x7fff) // Pierre-Marie Baty -- looks like this hack doesn't work when sizeof(int) == 4 (e.g. on Windows)
    //  If RAND_MAX == 0x7fff, then `num` can be seen as a fixed point number with 15 fractional and 17 integral bits
    return pA + ((num * (pB + 1 - pA)) >> 15);
#else
    //  If RAND_MAX != 0x7fff, then use floating numbers (alternative is using modulo)
    return pA + (int)((pB + 1 - pA) * (num / ((float)RAND_MAX + 1)));
#endif
}

I haven't tested when compiling for Win32/x64 though, but considering Windows is the only well-known x64 platform where sizeof(int) is 4, it should be the same.

madebr commented 8 months ago

Looks like IRandomBetween is a bit more complicated. Does the following work for you on Windows?

int IRandomBetween(int pA, int pB) {
    long long int a = rand() * (pB + 1 - pA);
    a &= 0x7fffffffffffffff;
    return pA + (a  >> 15);
}
PierreMarieBaty commented 8 months ago

Seems to work!

I have no idea of the "randomness" of such a distribution though. While I was relying on the floating-point code, I found out that it had made the game much more interesting (esp. the pedestrians behaviour). I'll soon see what changes in this regard.

Message ID: @.***>