QuickSilver cartridge for PCjr - A look at the BIOS patches it includes
hexdump -C QUIKSILV.ROM > quiksilv.hex
hexdump -C SYSTEM.ROM > system.hex
00000000 31 35 30 34 30 33 36 20 43 4f 50 52 2e 20 49 42 |1504036 COPR. IB|
00000010 4d 20 31 39 38 31 2c 31 39 38 33 49 01 57 01 6d |M 1981,1983I.W.m|
00000020 01 86 01 ba 01 20 4b 42 47 0a 47 0a bb 0a 84 0a |..... KBG.G.....|
00000030 45 52 52 4f 52 41 42 43 44 45 46 47 48 78 03 78 |ERRORABCDEFGHx.x|
00000040 02 ef f7 b0 00 e6 a0 fe c8 e6 10 e4 a0 fa b8 8f |................|
00000050 10 ba c0 00 b9 04 00 0a c4 ee 80 c4 20 e2 f8 b0 |............ ...|
...
$ diff -u system.hex quiksilv.hex
--- system.hex 2020-10-03 13:39:02.227589679 +0900
+++ quiksilv.hex 2020-10-03 13:38:47.920171210 +0900
@@ -139,7 +139,7 @@
000008a0 80 e6 f2 c6 06 84 00 00 bf 78 00 1e 07 b8 14 14 |.........x......|
000008b0 ab ab b8 01 01 ab ab e4 21 24 fe e6 21 1e b8 50 |........!$..!..P|
000008c0 00 8e d8 80 3e 18 00 00 1f 74 10 b2 02 e8 3c 11 |....>....t....<.|
-000008d0 b4 00 cd 16 80 fc 1c 75 f7 eb 05 b2 01 e8 2c 11 |.......u......,.|
+000008d0 b4 00 cd 16 80 fc 1c 75 f7 eb 05 eb 03 ee 2c 11 |.......u......,.|
000008e0 bd 3d 00 33 f6 2e 8b 56 00 b0 aa ee 1e ec 1f 3c |.=.3...V.......<|
000008f0 aa 75 06 89 94 08 00 46 46 45 45 83 fd 41 75 e5 |.u.....FFEE..Au.|
00000900 33 db ba fa 03 ec a8 f8 75 08 c7 87 00 00 f8 03 |3.......u.......|
@@ -183,7 +183,7 @@
00000b60 8b 1e 72 04 81 fb 34 12 8c c2 8e da 75 0b f3 ab |..r...4.....u...|
00000b70 8e d8 89 1e 72 04 8e da c3 81 fb 21 43 74 ef 88 |....r......!Ct..|
00000b80 05 8a 05 32 c4 74 03 e9 82 00 fe c4 8a c4 75 ef |...2.t........u.|
-00000b90 8b e9 b8 aa aa 8b d8 ba 55 55 f3 ab 4f 4f fd 8b |........UU..OO..|
+00000b90 eb dc b8 aa aa 8b d8 ba 55 55 f3 ab 4f 4f fd 8b |........UU..OO..|
00000ba0 f7 8b cd ad 33 c3 75 64 8b c2 ab e2 f6 8b cd fc |....3.ud........|
00000bb0 46 46 8b fe 8b da ba ff 00 ad 33 c3 75 4e 8b c2 |FF........3.uN..|
00000bc0 ab e2 f6 8b cd fd 4e 4e 8b fe 8b da f7 d2 0a d2 |......NN........|
@@ -340,11 +340,11 @@
00001530 ff 20 ff 54 55 56 57 58 59 5a 5b 5c 5d 68 69 6a |. .TUVWXYZ[\]hij|
00001540 6b 6c 6d 6e 6f 70 71 37 38 39 2d 34 35 36 2b 31 |klmnopq789-456+1|
00001550 32 33 30 2e 47 48 49 ff 4b ff 4d ff 4f 50 51 52 |230.GHI.K.M.OPQR|
-00001560 53 fb 50 53 51 52 56 57 1e 06 fc e8 1d fe 8a e0 |S.PSQRVW........|
-00001570 3c ff 75 1b bb 80 00 b9 48 00 e8 b8 ca 80 26 17 |<.u.....H.....&.|
-00001580 00 f0 80 26 18 00 0f 80 26 88 00 1f e9 bb 00 24 |...&....&......$|
-00001590 7f 0e 07 bf 5c 14 b9 08 00 f2 ae 8a c4 74 03 e9 |....\........t..|
-000015a0 98 00 81 ef 5d 14 2e 8a a5 64 14 a8 80 75 51 80 |....]....d...uQ.|
+00001560 53 fb 50 53 51 52 56 57 1e 06 fc e8 1d fe e4 60 |S.PSQRVW.......`|
+00001570 8a e0 3c ff 75 1b bb 80 00 b9 48 00 e8 b6 ca 80 |..<.u.....H.....|
+00001580 26 17 00 f0 80 26 18 00 0f 80 26 88 00 1f e9 b9 |&....&....&.....|
+00001590 00 24 7f 0e 07 bf 5c 14 b9 08 00 f2 ae 8a c4 75 |.$....\........u|
+000015a0 1f 90 81 ef 5d 14 2e 8a a5 64 14 a8 80 75 51 80 |....]....d...uQ.|
000015b0 fc 10 73 07 08 26 17 00 e9 8f 00 f6 06 17 00 04 |..s..&..........|
000015c0 75 78 3c 52 75 22 f6 06 17 00 08 75 6d f6 06 17 |ux<Ru".....um...|
000015d0 00 20 75 0d f6 06 17 00 03 74 0d b8 30 52 e9 0b |. u......t..0R..|
All the modifications appear in the lower 32k. So the cartridge only needs to care about CS6 (F0000-F7FFF)
and can pull BASE2 low to take over the BUS when one of the memory ranges it needs to replace is accessed. Speaking
of memory ranges, based on the above, here are the modified address ranges....
08C9 74 10 JE F15A_0 ; Continue if no error
08CB B2 02 MOV DL,2 ; 2 Short beeps (error)
08CD E8 1A0C R CALL ERR_BEEP
0BD0 ERR_WAIT:
0BD0 B4 00 MOV AH, 00
0BD2 CD 16 INT 16H
0BD4 B0 FC 1C CMP AH, 1CH
0BD7 75 F7 JNE ERR_WAIT
0BD9 EB 05 JMP SHORT F15C
08DB B2 01 F15A_0: MOV DL,1 ; 1 SHORT BEEP (NO ERRORS)
0BDD E8 1A0C R CALL ERR_BEEP
; -- Setup printer and rs232 base addresses if device attached
08E0 BD 003D R F15C: MOV BP,OFFSET F4
The jump equals at address 08C9 will jump to F15_A (address 08DB) when there is no error. There,
DL is set to 1 for one beep and ERR_BEEP is called.
08DB EB 03 F15A0_0: JMP SHORT F15C
0BDD EE 2C 11 ; Skipped non-sense (EE is OUT DX, al), 2C 11 is (SUB AL, 11H)
; -- Setup printer and rs232 base addresses if device attached
08E0 BD 003D R F15C: MOV BP,OFFSET F4
The "MOV DL, 1" which prepares the argument (number of beeps) for calling ERR_BEEP is simply replaced by
a jump to F15C (address 08E0). What follows is never executed, and it does not make much sense.
Why EE (OUT DX, AL)? Maybe this is an adjustment to make sure the BIOS checksum check (if there such a check)
still passes?
0B59 Start of the memory test proc here
... setup, warm-start test, etc. will jump to P1 (0879) on cold boot. ...
0B6E F3/ AB P12: REP STOSW ; Simple fill with 0 on warm-start
... some clean-up code not shown ...
0B78 C3 RET ; And Exit
0879 81 FB 4321 P1: CMD BX, 4321H ;
... does a short test
...
0B90 8B E9 MOV BP, CX ; Save word count
... Do tests using all 256 data patters (slow)
The memory test procedure starts at address 0B59. It does a few things
and jumps to P1 (address 0879) on cold boot (The cold boot memory test
is the one that takes time). At P1 a short test is performed first. The
code at address 0B90 prepares for the long test where all 256 patterns
are tested.
0B90 EB DC JMP SHORT P12 (0B6E)
So the cartridge replaces the MOV BP,CX at address 0B90 by a JMP. The jump takes place after the short test,
skipping the part where all 256 patterns are tested. The code at P12 does a fill with 0, as it would if this
were a warm-start, restores some registers and returns.
; ------ Keyboard Interrupt Routine
1651 KB_INT PROC FAR
1561 FB STI
1562 ... a bunch of register pushes ...
156A FC CLD
156B E8 138B R CALL DDS
156E 8A E0 MOV AH, AL
The 8A E0 in the original code gets replaced by E4 60. Guess what this is?
156E E4 60 IN AL, 60H
Exactly. But this adds two bytes at the beginning of what is almost identical code. In fact, most of
the routine is intact. This is obvious when showing both routines aligned like this:
Original code: 8a e0 3c ff 75 1b bb 80 00 b9 48 00 e8 b8 ca 80 26 17 00 f0 80 26 18 00 0f 80 26 88 00 1f e9 bb 00 24 7f 0e 07 bf 5c 14 b9 08 00 f2 ae 8a c4 74 03 e9 98 00
Patched code: e4 60 8a e0 3c ff 75 1b bb 80 00 b9 48 00 e8 b6 ca 80 26 17 00 f0 80 26 18 00 0f 80 26 88 00 1f e9 b9 00 24 7f 0e 07 bf 5c 14 b9 08 00 f2 ae 8a c4 75 1f 90
The part where "E8 B8 CA" becomes "E8 B6 CA" is simply a call with relative addressing getting fixed (the caller is two bytes later in memory). Same thing for the "E9 BB 00" which becomes "E9 B9 00" (a jmp target is adjusted).
159D 74 03 JE K17 ; Jump if match found
159F E9 163A R JMP K25 ; If no match, then no shift found
; ----- Shift key found
15A2 81 EF 145D R K17: SUB DI, OFFSET K6+1
...
...
163A K25:
...
So the last 5 bytes are a Jump if Equal and an unconditional jump occupying 5 bytes. The new
sequence must fit in 3 bytes, and it does. It has been replaced by a Jump if Not Equal and a Nop.
159F 75 1F JNE 0x21 (15B4) ; Jump if no match found (shift not found)
15A1 90 NOP ; Fallthrough if match found
....
15A2 81 EF 145D R K17: SUB DI, OFFSET K6+1
15B4 08 26 0017 R OR KB_FLAG, AH ; Turn on shift bit
15B8 E9 164A R JMP K26 ; Interrupt return
...
15C0 75 7B JNZ K25
...
163A K25: ; test for hold state, test for break key and stuff
...
164A K26:
... series of POPs, then IRET.
I don't fully understand this one. Changing the the JE, JMP pair for the inverse
logic (jump in NOT equal, or fall through otherwise) is fine, but I don't understand
why the jump target is 15B4. The original code jumped to K25. Maybe K25 is out of range for JNE, but then why not aim for 15C0 (using the 75 2B opcodes),
which is only 12 bytes further down and still within reach of JNE with its signed 8-bit operand? This is right in the middle of a subroutine, but conveniently
it is a JNZ K25 instruction. The CPU would always jump again from there to K25, JNE and JNZ being equivalents, the condition for jumping (ZF clear) still holding.
0000 1000 1101 1011 08DB
0000 1000 1101 1100 08DC
0000 1000 1101 1xxx
Range selected if [A14..3] is 0x08D8 (8 bytes). (08D8 - 08DF). 5 original bytes.
0000 1011 1001 0000 0B90
0000 1011 1001 0001 0B91
0000 1011 1001 000x
Range selected if [A14..1] is 0x0B90 (2 bytes). (0B90 - 0B91). No original bytes.
0001 0101 0110 1110 156E
0001 0101 1010 0001 15A1
0001 0101 xxxxxxxx
Range selected if [A14..8] is 0x1500 (256 bytes). (1500 - 15FF). 204 original bytes.