SilentPatch for TOCA 2 Touring Cars

May 9, 2021

5 min. to read

TL;DR - if you are not interested in a brief explanation of one of the compatibility issues the game had, scroll down to the Download section for a download link.


In the spirit of the initial purpose of SilentPatches, this release revisits an old game for once - TOCA 2: Touring Cars (released as TOCA 2: Touring Car Challenge in North America) from 1998. The patch corrects several compatibility issues with modern computers, previously requiring a manual fix, and adds full widescreen support. Original issues have also been corrected, and I also added some quality of life improvements, bringing the game closer to what more modern racing games can offer.

While most of the fixes I’ve done for TOCA 2 are ordinary and not worth covering, the single most severe compatibility issue the game had is unique, and I think it’s worth explaining.

Fixing the way time flows

While TOCA 2 generally aged well (like most games from Codemasters), it has one severe bug when running it on modern machines – it locks up when loading races and quitting the game! Thankfully, people quickly figured out a fix in form of a small hex edit. That’s what the game’s PCGamingWiki page suggests at the time I publish this article:

Crash on loading

Unpatched

  1. Open TC2.exe with hex editor.
  2. Scroll down to offset 0x706ED (row 000706E0, column 0D) and change 7E to EB.
  3. Save the file.

EB is a hex code for the short jmp assembly opcode, that means this fix makes the game jump over some code. Turns out, this code is a simple delay loop which gets skipped when said hex edit patch is in use – in pseudocode, it looks like this:

void Wait(int duration)
{
  totalTimeElapsed = 0;
  QueryPerformanceCounter(&PerformanceCount);
  lastTime = PerformanceCount.LowPart;
  if ( duration > 0 ) // This is where the hex edit inserts a jump
  {
    do
    {
      QueryPerformanceCounter(&PerformanceCount);
      if ( PerformanceCount.LowPart <= lastTime )
        timePart = PerformanceCount.LowPart - lastTime - 1;
      else
        timePart = PerformanceCount.LowPart - lastTime;
      totalTimeElapsed += timePart;
      elapsed = MulDiv(totalTimeElapsed, 3276800, gTimeFrequency);
      lastTime = PerformanceCount.LowPart;
    }
    while ( elapsed < duration );
  }
}

At the first glance, I was quick to jump to an obvious conclusion – performance timers are 64-bit values and this code only operates on the bottom 32-bit (LowPart) of it! However, this isn’t the root cause of this issue. Notice how, for some unknown reason, this code counts time in steps (instead of operating on currentTime - startTime, would’ve been simpler!), and had this suspicious block of code:

if ( PerformanceCount.LowPart <= lastTime )
  timePart = PerformanceCount.LowPart - lastTime - 1;
else
  timePart = PerformanceCount.LowPart - lastTime;

In my opinion, this block of code was added to prevent the timer from “getting stuck” if the current time and “last time” are equal (hence the check), but if you take a close look at the code executed when those times are identical, it ends up subtracting two equal numbers, and thus reduces to:

timePart = X - X - 1;

which in turn times timePart = -1! This elapsed time then gets added to the total time elapsed, and thus the delay… counts time backward 🤦 I’m sure the original intent was to advance the time at least by 1 unit every time, and so it becomes clear it’s a case of a missing bracket – had the code been written as

if ( PerformanceCount.LowPart <= lastTime )
  timePart = PerformanceCount.LowPart - (lastTime - 1);
else
  timePart = PerformanceCount.LowPart - lastTime;

it would have worked as intended!

SilentPatch fixes this issue by rewriting the function not to use those partial increments, and to use the entire 64-bit value. This results in a much simpler code that will never break:

void Wait(int duration)
{
  if (duration > 0)
  {
    int64_t diffTime;
    LARGE_INTEGER startTime;
    QueryPerformanceCounter(&startTime);
    do
    {
      LARGE_INTEGER time;
      QueryPerformanceCounter(&time);
      diffTime = time.QuadPart - startTime.QuadPart;
      diffTime *= 3276800;
      diffTime /= gTimeFrequency;
    }
    while (diffTime < duration);
  }
}

Changelog and download

This SilentPatch fixes plenty more issues, though. The full changelog is as follows; fixes marked with can be configured/toggled via the INI file.

Acknowledgements

Fixes related to the widescreen support have been based on the work of AuToMaNiAk005 – his extremely useful widescreen/ultrawide tutorials saved me a lot of time researching the way TOCA 2 handles aspect ratios and lists resolutions.


The modification can be downloaded from Mods & Patches. Click here to head to the game’s page directly:

Download SilentPatch for TOCA 2 Touring Cars

After downloading, all you need to do is to extract the archive to the game’s directory, and that’s it! I highly recommend checking the INI files for a range of useful settings to change, e.g. rear view mirror resolution or FOV. Not sure how to proceed? Check the Setup Instructions.


For those interested, the full source code of the mod has been published on GitHub, so it can be freely used as a reference: See source on GitHub

Share