23

I've been puzzled by this for a while now. The (very old) game Phantasie comes with three small TSRs that are run prior to running the main game executable. This is the content of the file PH.BAT, used to start the game:

@echo off
r32768.com
m1.com
m2.com
phantasi.exe

If you try running phantasi.exe directly, the game refuses to run, and you immediately go right back to the command prompt. These programs that run first are extremely tiny; M1.COM and M2.COM are both 44 bytes, and R32768.COM is 86 bytes. I did a quick and dirty disassembly with DEBUG.EXE. Below is the source code for each, along with my interpretations, which could be incorrect (I'm not an 8086 assembly expert).

M1.COM

    CLI
    SUB AX,AX
    MOV ES,AX
    ES:
    MOV AX,[0188]
    CMP AX,49A7
    JZ  lbl_011A
    ES:
    MOV AX,[018A]
    CMP AX,49A7
    JZ  lbl_011A
    STI
    INT 20
lbl_011A:
    ES:
    MOV WORD PTR [0188],0000
    ES:
    MOV [018A],CS
    STI
    MOV DX,84D8
    INT 27

This seems to disable interrupts, check if 0000:0188 = 49A7h or 0000:018A = 49A7h, and if neither one matches, it enables interrupts and quits back to DOS. Then, it stores 0 into 0000:0188, stores the current CS register value into 0000:018A, enables interrupts, and tells DOS it wants to terminate and stay resident, occupying memory through CS:84D8.

M2.COM

    CLI
    SUB AX,AX
    MOV ES,AX
    ES:
    MOV AX,[018C]
    CMP AX,49A8
    JZ  lbl_011A
    ES:
    MOV AX,[018E]
    CMP AX,49A8
    JZ  lbl_011A
    STI
    INT 20
lbl_011A:
    ES:
    MOV WORD PTR [018C],0000
    ES:
    MOV [018E],CS
    STI
    MOV DX,84D8
    INT 27

Basically the same as M1.COM, but with different addresses/values. It checks if 0000:018C = 49A8h or 0000:018E = 49A8h, stores 0 in 0000:018C, copies CS to 0000:018E, and TSRs, also asking for memory through CS:84D8.

R32768.COM

    CLI
    SUB AX,AX
    MOV ES,AX
    ES:
    MOV AX,[0180]
    CMP AX,49A6
    JNZ lbl_011A
    ES:
    MOV AX,[0182]
    CMP AX,49A6
    JNZ lbl_011A
    STI
    INT 20
lbl_011A:
    ES:
    MOV WORD PTR [0180],49A6
    ES:
    MOV WORD PTR [0182],49A6
    ES:
    MOV WORD PTR [0188],49A7
    ES:
    MOV WORD PTR [018A],49A7
    ES:
    MOV WORD PTR [018C],49A8
    ES:
    MOV WORD PTR [018E],49A8
    ES:
    MOV WORD PTR [0184],0000
    ES:
    MOV [0186],CS
    STI
    MOV DX,8000
    INT 27

Disables interrupts, then checks if 0000:0180 and 0000:0182 both equal 49A6. If they do, it enables interrupts and exits (contrary to the previous two programs, which quit if neither one matches the test values). Then it writes 16 bytes starting at the locations 0000:0180 (notice that 0000:0184-0187 are written out of order, and 0000:0186 gets a copy of CS rather than the previous constant). Once finished, it TSRs, and reserves memory up through CS:8000.

It seems like they're rewriting the interrupt vector table starting at around int 60h, but I can't really figure out what they're meant to accomplish.

7
  • Just a quick guess without having analyzed the assembler code, I used to write TSR's and I'm guessing that they might be memory managers specific for the game, perhaps to enable memory overlays, but again, just a guess. Jun 4, 2018 at 20:28
  • @BillHileman You could be onto something. The names M1, M2, and R32768 definitely make me think of memory. And there are two overlay files with the game (OV1.OVR and OV2.OVR). The main game EXE is 21 KB, and the overlays are 33 KB and 26 KB.
    – db2
    Jun 4, 2018 at 20:32
  • @BillHileman That was my thought - "r32768" made me thing "Reserve 32k of memory" long before I scrolled down to see "reserves memory up through CS:8000", which just so happens to be 32k. Jun 4, 2018 at 20:33
  • Looks quite like it. Int 60-67h are designated as user interrupts' thus it would make sense to use them for such an extension.
    – Raffzahn
    Jun 4, 2018 at 21:05
  • Yah, the three TSRs just reserve three blocks of memory, 8000h, 84D8h, and 84D8h bytes long. The three interrupt vectors at 61h. 62h, and 63h respectively are used to store pointers to start of these blocks of memory. Not sure why the game would be doing this, but it probably stems from a misunderstanding on how MS-DOS allocates memory.
    – user722
    Jun 4, 2018 at 22:06

2 Answers 2

21

As others have mentioned this does indeed look like a strange attempt to allocate memory for the game.

What do the programs do?

The TSRs (and presumably the game executable) use interrupt vectors as global storage to hold flags and pointers to the allocated memory. The first program, R32768, uses 49A6 in 00180 and 00182 (interrupt vector 60h) as its signature. If those values are present, it considers that it’s already resident, and exits; otherwise it sets flags up for M1 and M2 (49A7 in 00188 and 0018A (interrupt vector 62h), and 49A8 in 0018C and 0018E (interrupt vector 63h) respectively), stores its location in memory in 00184 (as a double word, interrupt vector 61h), and exits, staying resident with 32KiB allocated. M1 checks whether its signature is present (i.e., R32768 has left it there), and if it is, replaces it with its location in memory, and exits, staying resident with 34,008 bytes allocated. M2 does the same.

Then the main game executable can check whether R32768’s signature is present in 00180, and whether M1’s and M2’s signatures aren’t present in 00188 and 0018C; if everything’s OK, it knows it can use the memory pointed at by the double words stored in 00184, 00188, and 0018C for its own purposes. Note that there is no (easy) way of freeing the memory that’s been allocated in this fashion, so you need to reboot after running the game. There isn’t any error handling either, so if there isn’t quite enough memory available, R32768 can succeed, M1 and M2 can run and fail, leaving an interesting setup for the main executable — it will probably end up running in the memory it thinks M1 or M2 allocated for it, leading to corruption in relatively short order.

What should they do?

All this is completely unnecessary, in all versions of DOS. In DOS 1, only COM files were supported; these are allocated all the available memory at startup, and the amount of memory allocated to them is stored at offset 2 in the PSP (as the segment of the first paragraph following the last one allocated to the program). Thus COM programs never need to allocate conventional memory for themselves, and they can determine how much memory they have to play with (and react accordingly if there isn’t enough). In DOS 2 and later, EXE files are supported too, and their headers describe how much memory they need to start (“MINALLOC”) and how much they want (“MAXALLOC”, which is usually FFFF so that they are allocated the largest available block of memory). On top of that, programs running under DOS 2 and later can use function 0x48 of interrupt 0x21 to request more memory, if available. All the memory allocated using these different mechanisms is freed when the program exits.

Why are they doing this?

This part is speculation of course. A couple of details might help understand why the developers came up with this stratagem:

  • PHANTASI.EXE is compressed with LZEXE. Decompressing it yields a 34,561-byte executable with no relocations.
  • A number of files look like they could be overlays: MSTR1IBM.PAT, MSTR2IBM.PAT, OV1.OVR, OV2.OVR. The uncompressed main executable contains strings pertaining to overlay handling (“.ovr”, “Error %d loading overlay: %s$”; the $ at the end of the latter suggests a home-grown system rather than some library since that’s the DOS end-of-string marker).

My theory is that the developer realised that the game was going to be larger than 64KiB, which is the limit for COM files. Not realising that they didn’t need to care about memory allocation (apart from checking that enough memory had been allocated to the program), or perhaps that COM programs could natively manage more than 64KiB of memory, they designed a COM-based overlay system using the three TSRs to allocate space for the overlays (and perhaps other data). At some point during development they also realised that the game was too big (as shipped, it barely fits on a 360KiB floppy), and decided to compress the main executable; this required converting it to a MZ-format EXE, probably using COM2EXE (which accompanied LZEXE).

18
  • 2
    "All this is completely unnecessary" - right. and then there is always the chance it's part of some weired copy protection scheme. Remember the times :)
    – Raffzahn
    Jun 5, 2018 at 7:51
  • 4
    I'd say it's probably not related to copy-protection, as the game does manual-based checks, making you periodically look up some key word (and unceremoniously reboots the machine if you fail enough times). The MSX version of the game - which I didn't realize even existed - looks wildly different, so it's probably not a code-sharing thing. It's very plausible that the programmer was just being daft, which, being a programmer myself, I can say is often the first possibility you should explore. :D
    – db2
    Jun 5, 2018 at 11:18
  • 4
    @mnem that’s an interesting thought, and not-so-PC-compatibles were still around when this game was developed. However the features involved in memory management, process startup etc. are fundamental parts of DOS; everything in the last paragraph would be true on any DOS computer, PC-compatible or otherwise. Of course the developer might not have been aware of this ;-). Jun 6, 2018 at 5:06
  • 4
    @mnem indeed, some DOS computers had a different memory map, but their “non-standard” version of DOS was supposed to take care of the differences (for well-behaved DOS programs at least). Jun 6, 2018 at 8:58
  • 3
    Although a COM may use storage between itself and the end of memory, a compiled program must avoid using any storage the compiler is expecting to use for its own purposes. Perhaps Phantasie used a compiled language which--like basica/gwbasic, was incapable of using storage beyond 64K by itself, but did allow code with an explicit segment/offset address to set or examine the storage there (using something analogous to BASICA/GWBASIC's def seg and peek/poke). It would seem a bit weird for a program to be using that much storage that way, but not impossible.
    – supercat
    Jun 6, 2018 at 17:01
0

I've written similar stuff to this back in '86 on a Commodore-16.

The reason I did that was because I needed to interact with BASIC and the command line, and not screw up any machine code that was written with the in-built 1-pass assembler.

My guess is whoever wrote this needed to protect certain areas of memory while requiring the use of DOS. S/He was probably coding on a tight budget and lacked professional tools to achieve their goal, so they had to be ingenious with workarounds.

Makes complete sense to me - but obviously, nowadays I'd use proper tools.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .