1

Sierra’s Macintosh Timebomb

 1 year ago
source link: https://www.benshoof.org/blog/sierras-macintosh-timebomb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Sierra’s Macintosh Timebomb

On Saturday, September 18 1993, Sierra's Macintosh games stopped working. Franchises froze as King's Quests, Space Quests, and even the Leisurely Suited Larries locked up. Practically every Mac SCI game had broken overnight. Sierra developed a patch within nine days, released it on their BBS, and announced it in their magazine. Anyone who wrote in received a free patch disk in the mail. The MCDATE patch was a Mac program that fixed almost any game you pointed it at. And yet here in the future, they're failing all over again. What went wrong?

King's Quest 6 gets stuck on its copyright message

The Queen Mum is a noted masteress of suspense, but not for this

Each Mac game seems to lock up differently but they're all doing the same thing: waiting way too long for a pause to complete. Specifically, the games hang when they set a delay on a Script object in seconds. SCI games are full of these short delays and now they bring everything to a halt. King's Quest VI can't even finish its opening copyright message because of a three second pause that never seems to end.

Timing is tricky in SCI, but for today we just need to know that Script delays in seconds are implemented by polling the system clock for changes. Scripts don't wait for three seconds, they wait for three clock changes. It doesn't matter what any of those times are as long as they're different than before. That's a pretty big clue. Just by looking at the scripts we can tell that the only way this scheme can get stuck is if the clock gets stuck, and scripts get their clock time from KGetTime.

The K in KGetTime stands for Kernel. Kernel functions live in Sierra's interpreter for game scripts to call. Sierra ported SCI games to Mac by first converting the SCI interpreter to a Mac program, and that meant rewriting a lot of code to do things the Mac way. Reading the clock is one of those low-level things that's so different between platforms that it needs to be rewritten for each. That makes KGetTime a great candidate for a Mac-only bug.

KGetTime has several modes to provide times in different formats. Our Mac problems occur when Script delays call KGetTime(1) so that's the one we'll focus on. KGetTime(1) returns the time of day in a 16-bit format: HHHH MMMM MMSS SSSS

If a game wants to display the current time then it calls KGetTime(1) and unpacks those individual values, but if it's only looking to see if time has changed then it just stores the result and compares it to the next. Unfortunately some pope crammed too many seconds in a day and 16 bits aren't enough to hold them. You need 17 bits for that, so KGetTime(1) compromises by limiting the hours to 12. If you need to know which half of the day it is then you use KGetTime(2), which compromises on the other end by limiting the resolution to every 2 seconds. Script delays only want to know when the seconds change so KGetTime(1) does the job. Although, that does mean all that bit packing is a waste of time, in like... a couple ways.

KGetTime turns out to be just a small wrapper function. SysTime is where the real work is done.

  ; Macintosh SysTime(1)

  2038 020c        move.l   Time, d0   ; seconds since January 1 1904
  80fc a8c0        divu.w   #$a8c0, d0 ; divide by 12 hours
  4840             swap     d0
  0280 0000 ffff   andi.l   #$ffff, d0 ; seconds since midnight or noon
  80fc 003c        divu.w   #60, d0
  7200             moveq    #0, d1
  3200             move.w   d0, d1
  4840             swap     d0         ; 0000 0000 00SS SSSS
  82fc 003c        divu.w   #60, d1
  3401             move.w   d1, d2
  4841             swap     d1
  ed49             lsl.w    #6, d1
  8041             or.q     d1, d0     ; 0000 MMMM MMSS SSSS
  123c 000c        move.b   #12 ,d1
  e36a             lsl.w    d1, d2
  8042             or.w     d2, d0     ; HHHH MMMM MMSS SSSS

Macintosh clock time is the number of seconds since midnight on January 1 1904. The operating system updates the Time global variable every second. SysTime(1) only cares about the current 12 hour period so it divides Time by 12 hours ($A8C0 seconds) and takes the remainder. From there it calculates the hours and minutes and remaining seconds and packages them up in 16 sweet bits. All of this sounds good, but we know there's a problem here that involves September 18 1993. $A8C0 is the only interesting constant, so let's inspect that second instruction.

The Motorola 68000 processor's DIVU instruction divides a 32-bit unsigned integer by a 16-bit unsigned integer and puts the quotient in the lower 16 bits and the remainder in the upper 16. If you only care about the remainder then you swap 'em and clear the upper bits, just like SysTime does. But DIVU can fail.

Overflow may be detected and set before the instruction completes. If the instruction detects an overflow, it sets the overflow condition code, and the operands are unaffected.

M68000 Family Programmer's Reference Manual

If Time gets too big then DIVU won't do anything but SysTime will proceed as if it had. SysTime will then cheerfully swap the bits from the Macintosh clock time, throw away the useful ones that change every second, and keep the lifers that only budge every 18 hours. At that point the rest of the code keeps producing the same result for quite a while and the clock is effectively stuck. Time gets too big when dividing by $A8C0 produces an answer that doesn't fit in 16 bits. Once that answer reaches $10000 the timebomb goes off. According to NumberWang, $A8C0 times $10000 is $A8C00000, or as its more colloquially known: the two billion eight hundred and thirty one million one hundred and fifty five thousand and two hundred seconds between January 1 1904 and September 18 1993.

What terrible luck! Look, the entire software development profession and several of its more lucrative subfields are based on every programmer cranking out a dozen overflows a day. That's a given. But the Sierra programmer tasked with Mac SysTime in 1990 was working with a 136 year format that still had another 50 good years on it. And yet dividing by 12 hours happened to light a fuse so short that it would only run out once the code was in everyone's homes and still on the shelves. Mac SCI was born with barely four years to live! That would drive you totally Batty.

Oh well, it's just a division overflow. There are plenty of ways to work around that when you only care about the remainder. Or hey, you could use Mac's Secs2Date function that does this work for you, and SysTime(3) already calls that. Patching binaries is hard, especially under pressure, but at least Sierra had options. I can't wait to see how they fixed this!

  ; Macintosh SysTime(1) - Patched

  2038 020c        move.l   Time, d0       ; seconds since January 1 1904
+ 0480 19bf cc00   subi.l   #$19bfcc00, d0 ; subtract 5,000 days
  80fc a8c0        divu.w   #$a8c0, d0     ; divide by 12 hours
  4840             swap     d0
  0280 0000 ffff   andi.l   #$ffff, d0     ; seconds since midnight or noon
  ....             ....

Oh so that's how we fixed Y2K. I sure hope we subtracted a bigger number! $19BFCC00 is 432,000,000 seconds which is 5,000 days which is 13.6 years which is almost a Guns N Roses song. Sierra didn't disarm the damn bomb! They just lashed on more fuse with a granny knot. I want to make fun of kicking the can down to May 28 2007, but that turned out to be a generous assessment of classic Mac's future. Here in our future, those 5,000 days blew by and these games are back to blowing up. But now they explode in emulators where you can't always change their clock to work around it. And changing your clock is an exercise in Kaczynskism that detonates your TLS connections and takes you off the grid.

Actually we probably should make fun of this because it gets worse. It would be one thing if this was a temporary fix. After all, patching a fleet of binaries in the wild means operating under a lot of constraints. But no, bumping the incept date was Sierra's permanent solution. Future games with newer interpreters shipped with this same code instead of simply checking for overflow. As a result, there are now more Mac games with this bug than when it was fixed.

Well, Sierra fudged it, but they weren't the only ones to try. Enrico Rolfi is a legendary SCI hacker of the Italian persuasion who reverse engineered a lot of resource formats before it was cool. His TraduSCI software has allowed fans to completely translate Sierra games to other languages since 2004. Astoundingly, he's still at it! Two months ago he released a new version that integrates with Google Translate. I'm terribly impressed, and I take comfort that someone else maintains their Bush-era Windows software and forgets their blog for years.

In 2002, Enrico resurrected Space Quest III and Space Quest IV to run on newer Macintoshes, and that meant taking on KGetTime. Enrico's patched versions still run today no matter what year it is. Success! But they don't fix SysTime, they skip it. Instead, KGetTime ignores the mode parameter from the game scripts and always returns the tick counter. The interpreter increments this every 1/60th of second, so while Script delays no longer hang, they now run 60 times too fast. I know some folks who would love this...

Sierra's MCDATE patch

Did someone draw the Sierra logo in Kid Pix?

So what have we learned today? We've learned why Sierra's Mac games froze on September 18 1993. We've learned how a shoddy fix froze them again on March 28 2007. And we've learned that they were never truly frozen; Mac users were just too impatient for a three day copyright message. But most importantly, we've learned that Y2K will still murder us all in our sleep.*

Here in the future, can we do any better? Pfft, maybe you can, the whole thing's a proper mess innit? Each game's interpreter is different and now they're patched or unpatched or post-patch or fan-patched. And patching these old programs yourself isn't simple; the code is in Mac resource forks and those forks are compressed. If you were really crazed, you could write a proper SysTime in m68k assembly with overflow checks and use ResEdit to patch that into Sierra's patcher. Seriously, if you can keep yours within 178 bytes you can replace the wonky SysTime at Data resource 128. You're on your own for applying that to post-patch games. That's a lot of work when we have ScummVM, which already goes out of its way to run Sierra's old Mac versions on all modern platforms. Classic Mac has sunk — everybody into the lifeboat!

Of course the real obstacle to fixing Sierra's Mac games is that nobody cares. And why would they? It's never been easier to run Sierra games, meanwhile the Mac versions are increasingly inaccessible and boring. They're just ports of the DOS versions. Aside from a few Mac gimmicks, the games themselves don't bring anything new to the table. Although, there is one exception...

Next time: Mo' Mac? Maybe!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK