Monthly Archives: November 2019

SoftPC (and the quest for the XT memory map)

Before Macintosh was created, there was a computer architecture called IBM PC. With Intel 8086 processor, it was incompatible with the Motorola 68000 family used in Macintosh computers – and thus, no IBM PC software would work in Macintosh. But there were some really smart people, who created a special application called SoftPC in mid-80’s, which interpreted Intel’s x86 instructions on the fly, and emulated enough PC architecture and BIOS ROM to allow those programs to run on Macintosh. And now, SoftPC finally also works on M.A.C.E.

SoftPC 2.51 architecture

The version of SoftPC we got to run is 2.51, which happened to be compatible with the “Classic”-type 68000 Macintosh computers, one which M.A.C.E. aims to emulate in the Phase 1. That is, it works on 68000 processor, supports the 1-bit “classic” QuickDraw on 512×342 screen, and appears to be compatible with System 6 level Toolbox calls.

The IBM PC architecture which SoftPC 2.51 emulates is IBM XT with 640 kilobytes of RAM, and a CGA monochrome display. It also supports two serial ports (although M.A.C.E. serial port drivers are still unfinished to try them out), a printer port (no printing support for us yet), two hard drives (through disk images), and two physical floppy drives (which we don’t either emulate yet).

The XT memory map

The challenging part of getting SoftPC to work was ingenious way it emulates the PC memory map. As everybody knows, IBM PC has physically 20-bit addressing scheme (through 16-bit segment and offset register pairs, which give 4+16 bits of usable virtual address space, as there is overlap between segments). This 20 bits equals 2^20 = 1048576, or 1 megabyte of address space. What SoftPC does, is that it allocates this entire 1 megabyte area to be linearly accessible throughout the physical Macintosh address space, but it saves memory by allowing this area to have “holes” in it for the non-used areas of the PC memory map. This is basically the layout it creates:

  • 000000-09FFFF: RAM
  • 0A0000-0B7FFF: hole
  • 0B8000-0DDFFF: CGA video memory
  • 0DE000-0EFFFF: hole
  • 0F0000-0F4FFF: ROM BIOS #1
  • 0F5000-0FDFFF: hole
  • 0FE000-100000: ROM BIOS #2

The steps it uses to ensure this linear layout is created is quite unique, and a good challenge to test the memory manager implementation:

  1. For starters, the availability of large enough linear memory space is ensured by doing allocation of 1048576+32 bytes of memory using NewPtr. If this fails, it will know that there won’t be enough memory.
  2. The allocated space is shrunk down to the 640K+32 RAM size using SetPtrSize. (The 32 bytes extra is used for aligning the actual addresses inside allocations to 16-byte boundaries for apparently memory access speed optimization on later Macs).
  3. The three other blocks (CGA memory and two ROM areas) are allocated at correct locations using the following algorithm:
    1. The size of “hole” preceding the allocation is calculated. For example, having video memory at B8000 would leave 18000 bytes space between A0000 and B8000.
    2. Two pointers are allocated after each other using NewPtr: one for the hole (using calculated hole size), and the actual memory block (+32 bytes for alignment).
    3. Now this is where the magic happens: If the latter allocated block’s start would not be within alignment range of the required offset (in CGA case B8000) from start of RAM (the 640K block), the size of hole would be decremented with 16 bytes, the two blocks would be disposed, and allocation re-attempted at step 3-2.
    4. After repeating steps 3-2 through 3-3 enough so that required range is reached, the pointer to the allocated hole block is saved, and next one of the three blocks is allocated starting from step 3-1. (It should be noted, that because of a very minor bug in M.A.C.E. memory manager, the allocation size failed to decrease, and this loop would be terminated after 64 attempts, with incorrect block left allocated.)
  4. After all three blocks have been allocated at correct offsets, the holes preceding each of them would be disposed using DisposePtr. This part of process leaves space inside the emulated PC address space, which the Toolbox can use for any regular memory allocations – pointers and handles, even resources. It is not 100% certain, but it may be that SoftPC may optimize performance in such way, that writing to PC address space at these holes might actually even cause native memory to get corrupted, BUT for now we did not try to see if that might happen.

The issue with Memory Manager in step 3-4 was, that as the DisposePtr call on the two blocks actually creates two adjacent “free” blocks, the allocation of new block using NewPtr right after that with only 16 bytes smaller size, would actually not return a physical block with smaller size. This was, because the block allocation routine checks if the space between allocated and next block would be less than minimum allowed block size (for creating new “free” block between allocation and next block), it would actually merge that small bit of space back to the physical allocation. The bug in Memory Manager was, that the when allocating block in free space, it was supposed to always merge the next “free” block after the allocation, and slice the new “free” space from that. A real Mac merges only one extra “free” block after allocation, but in M.A.C.E. for simplicity we fixed this by merging all free space in one run, thus at the same time reducing clutter of having many adjacent free blocks.

With this fix in Memory Manager, SoftPC is now finally able to boot up. Thanks to the Standard File Package implemented earlier this autumn, we could not only configure the hard disk image (C:/), but also set up a “shared” network disk (E:/) through a folder inside emulated Mac file system. This allows easy access of files (and thus DOS applications) from the M.A.C.E. file system, including some games. The shared disk access routines, however, surfaced luckily a couple File Manager bugs, related to the ioNamePtr handling in _GetCatInfo and _GetFileInfo calls, which possibly also improve compatibility of other applications too.


Below are some screenshots of the configuration dialogs of SoftPC (click for larger image):

And here is DIR command output right after booting up:

Also, there was GWBASIC included with the DOS installation, so we can run GWBASIC compatible BASIC programs:

And also some games work nicely. The Monochrome CGA emulation has interesting approach of emulating the 320×200 resolution with 1.5x scaling as 480×300 pixel bitmap display. The four CGA colors are also mapped into distinct vertical stripe patterns, which might also have been used on some old monochrome CGA displays:

Of the two above games, SOPWITH appears to be quite responsive and playable, but ALLEYCAT controls are very laggy. Also Sierra adventure games seem to run:

The Black Cauldron (pictured above) seems to work just fine, although for some reason the function keys were not working, while they did work in GWBASIC earlier (opening of doors with F6, for example). Also, none of the games produced any sound although the SoftPC boot beep works, so it might be that SoftPC may lack actual PC speaker emulation in this particular version (beyond the beep).

Other issue that we also fixed while working on this was a bug in Scrap Manager, which caused the clipboard to misbehave when working in “on-disk” mode (after _UnloadScrap was called). This allows Copy-Paste operations to work nicely in SoftPC, and it also improves clipboard functionality in other applications.

Only remaining issue with SoftPC is that some routine, possibly in SANE and most likely related to converting PC date & time, is causing divide overflow error to popup at random times, which appears to be related to the actual date & time. This needs to be investigated still at later point.

Full list of changes since last post

2019-11-27 00:34:43 +0200 • Fix a Scrap Manager bugs, fixes SoftPC copy+paste 
2019-11-26 23:06:55 +0200 • Fix ioDirFlag to be 4 which appears to be correct 
2019-11-26 23:06:27 +0200 • Don't crash on invalid zone in Handle validation  
2019-11-26 04:01:31 +0200 • Fix register-based 68k->native UPP calls' D0 nuke 
2019-11-26 03:57:20 +0200 • Don't overwrite *ioNamePtr if not indexing files  
2019-11-22 03:41:15 +0200 • Add FREMS selector to SANE's FP68K dispatcher     
2019-11-22 03:40:22 +0200 • Force merge of free blocks at end of BlockFindSize
2019-11-19 22:59:13 +0200 • Fix bug where everything left of a list got erased
2019-11-19 22:55:27 +0200 • Redraw modified cell in LSetCell                  
2019-11-19 01:42:03 +0200 • Add support 23 new native 68k exception handlers  

Bug fixing & stabilization

Since the previous update, focus has been mostly on improving the compatibility and stability of existing test applications through debugging, bug fixes and finishing some partially implemented features. This has so far had mostly effect on Excel, THINK Pascal and Fokker Triplane Flight Simulator:

Microsoft Excel 3.0 progress

Seems that Excel was complaining the out of memory error only because it could not locate the ‘PACK’ 6 resource (IntlUtils package). Adding a ‘PACK’ wrapper resource to System file, in similar fashion to the one which was added earlier for ResEdit as ‘PACK’ ID = 0, allowed this check to pass. After that a couple of new IntlUtils selectors were needed, with a couple file manager tweaks, and now Excel is able to start up successfully (albeit with a alert about not finding Excel, which can be passed).

It seems that a lot of tools and features are already functional, including basic worksheet creation, cell selection/modification, shape drawing, basic formatting etc. However, the most important feature, text input to allow entering data to worksheet cells, still requires TEDispatch to be implemented.

THINK Pascal 4.0 progress

One application which surfaced a lot of bugs and issues was THINK Pascal 4.0, here’s a rough summary of the issues found & ‘fixed’:

  • Reallocation of resources was not working properly in ChangedResource. Resource Manager did not save & restore active resource entry when measuring space available between current and next resource, which caused the new resource allocation in growth case to end up in wrong resource entry in the map (the next one was reallocated instead of current).
  • Removing name was attempted even if no name existed, causing resource names to get corrupted
  • RmveResource was not offsetting the types properly when removing the last instance of a given type.
  • The internal routine RGetResourceCount was trashing the resource entry pointer in resource manager’s stack state, causing some resources to be skipped altogether in iteration.
  • SetHandleSize was allowing locked blocks to move during the operation, which was explicitly told in Inside Mac documentation would not happen. This bug caused sometimes locked memory to move, with dereferenced pointers becoming invalid, causing crash during pascal code compilation.
  • A very rough approximation of MoveHHi was added. It works well enough to allow THINK Pascal to move resources out of the way to not prevent heap fragmentation from growing size of locked handles – but still causes sometimes fragmentation enough to surface this issue. However, this is for now enough to get the projects compile roughtly 2 to 3 times in one session before memory fragmentation kicks in and crashes the compiler.
  • The resource copy phase of THINK Pascal surfaced a bug in CountResources/GetIndResource, where CurMap was used instead of TopMap (CurMap should only be used when operating on 1-deep versions of the calls, Count1Resources/Get1IndResource). With this fix, the resource file copy phase succeeds.
  • Using “Transfer” command in THINK Pascal can be used to launch another app – in most cases, the application that was just compiled. However, the application zone teardown was – and still is – largely unfinished, having a number of issues. One that was fixed, was that resource manager attempts to release and purge all font caches with allocations in the application zone, which would be invalid upon launching the new program.
  • GetScrap trap had a minor issue, in which the case of hDest being nil it would crash, instead of just returning the scrap size as it was supposed to. This fixes the clipboard to (almost) work in THINK Pascal.

Now even a bit more complex programs can be compiled using THINK Pascal, for example one of Toni’s old games from as early as 1998. However, there are still minor glitches in various places:

  • Sometimes clipboard operation does not work on the first attempt.
  • Replacing existing application with “Build application” & replace dialog does not yet work because _Delete trap is still unfinished.
  • Sometimes text editing has very tiny minor selection glitches (when doing “Search again” in source code, where previous hit was selected and visible in area which was scrolled only little up, the old selection is left on when highlighting the new one).
  • Because resource map compaction is (still) unfinished, each time resources are updated and/or written in resource files, the file size grows indefinitely (by size of modified resource).
  • Compiling only works a couple times (2-3) on large projects, before heap fragmentation crashes the compiler.
  • The “Go” option does not yet work, because when running applications inside the THINK Pascal IDE, it does not actually launch a separate applications process for it. Instead, it loads the application “on top” of the pascal IDE, and in order to do this, it wraps a lot of its handlers to toolbox routines, and to do this, it manipulates the trap tables directly. This however assumes the toolbox trap table is located at 0xC00 on mac plus (0x75)-type machine, while it is currently located at 0xE00 (later machines). Because some currently used low memory globals overlap this area in non-compatible ways (for example, cursor device), they need to be adjusted a bit to behave differently on “Classic” type environment (like on real Mac Plus), and other way on later (slot-based / color-QD) environment.

Fokker Triplane Flight Simulator

It seems that the only issue causing Fokker Triplane to freeze upon starting new game was actually the lack of low-level interrupt handler emulation. The game attempted to override system VBL handler by adding its own VBL interrupt handler in the Lvl1DT low memory area containing the list of VIA’s Level 1 interrupt handler table, and as it was never called, caused the lock-up. The VBL, and 1-second timer, have now proper handlers in this low memory area, which is checked upon triggering the emulated VBL interrupt. This also includes the familiar performance optimization, where unpatched handlers skip the MixedMode interface with direct method call. It seems that with this change, the game is now fully playable with sound effects (although not thoroughly tested).

Full list of changes since last post

2019-11-16 04:08:45 +0200 • Lowlevel VIA interrupt handler for Fokker Triplane
2019-11-13 03:50:11 +0200 • Tweak SndNewChannel to not crash ResEdit          
2019-11-09 09:03:38 +0200 • Dummy OpenDeny/OpenRDeny (=>paramErr for local FS)
2019-11-09 07:51:53 +0200 • Workaround for mapping wideOpen Rgn in DrawPicture
2019-11-09 07:49:02 +0200 • Move File Manager commands to separate module     
2019-11-09 07:47:37 +0200 • Move SoundDriver test code to separate module     
2019-11-09 02:29:58 +0200 • Add dummy BitMapRgn trap for aWorm                
2019-11-06 05:40:10 +0200 • Respect port clipRgn in CopyBits                  
2019-11-06 04:31:42 +0200 • Fix edge cases in UnpackBits                      
2019-11-04 21:32:35 +0200 • If GetScrap argument hDest = nil, return only size
2019-11-04 11:31:38 +0200 • Clear font caches when closing res file w/ fonts  
2019-11-04 04:16:41 +0200 • Add IUMagString & improve Transliterate for Excel 
2019-11-04 03:09:38 +0200 • Fix Count…/GetIndResource to begin at TopMap    
2019-11-04 02:15:21 +0200 • Add memLockedErr enum missing from previous commit
2019-11-04 02:14:56 +0200 • (HACK) MoveHHi added - works, but not as in specs!
2019-11-04 02:12:08 +0200 • Fix SetHandleSize for locked blocks (don't move)  
2019-11-04 02:06:36 +0200 • Fix tail check in the zone check                  
2019-11-04 02:04:53 +0200 • Fix name allocation in AddResource                
2019-11-04 02:03:14 +0200 • StdFile: No "New folder" button in old dialogs    
2019-11-03 05:53:37 +0200 • More verbose heapcheck & optional free space debug
2019-11-02 18:13:37 +0200 • Fix possible TextEdit calibration memory move bug 
2019-11-02 17:40:30 +0200 • Fix resource map validation for maps with no names
2019-11-01 17:55:35 +0200 • Add dummy cstr2dec and pstr2dec selectors in Pack7
2019-11-01 17:54:15 +0200 • Don't trash ResourceEntry ptr in RGetResourceCount
2019-11-01 02:30:02 +0200 • Dummy IUMagWString & Transliterate(Text) for Excel
2019-11-01 00:43:41 +0200 • Add 'PACK' 3,4,5,6&7 mapping (Excel now starts up)
2019-10-31 21:33:21 +0200 • Resource fork compact placeholder code            
2019-10-31 01:01:48 +0200 • Fix RmveResource offset bug at last item of a type
2019-10-30 21:56:35 +0200 • Don't try to remove name if entry doesn't have one
2019-10-30 21:55:55 +0200 • Add map checks to Resource Manager calls for debug
2019-10-30 04:54:54 +0200 • Fix reallocation of resource in ChangedResource