If you'd like to support our preservation efforts (and this wasn't cheap), please consider donating or supporting us on Patreon. Thank you!
Notes:The Adventures of Captain Comic (DOS)
This page contains notes for the game The Adventures of Captain Comic (DOS).
Contents
Revisions
The Internet Archive has all five revisions under the item TheAdventuresOfCaptainComic:
It also has three other versions:
- swh11988 (file_id.diz labels it "Hacked/grafitti version 1")
- swh21988 (file_id.diz labels it "Hacked/grafitti version 2")
- R4a1sw1989 (file_id.diz labels it "Alternate version 1")
File Formats
Malvineous has documented the file formats here.
Executable Packing
In every revision, COMIC.EXE is packed with EXEPACK. Revision 1 uses an earlier version of EXEPACK that not all unpackers can handle. Try these ones:
R1swh21988 (a hacked version) doesn't have COMIC.EXE but rather MENU.EXE, and the EXEPACK layer appears to be wrapped in another layer that adds an advertisement for "Red Point".
Sound Effects
The format of a sound effect is an array of
struct {
uint16_t freq_divider;
uint16_t duration;
};
The 16-bit values are stored little-endian. Each freq_divider divides the Programmable Interval Timer base frequency of 1193182 Hz. For example, a freq_divider of 1c3f would result in an output frequency of 1193182 / 0x1c3f ≈ 165 Hz. The special freq_divider value 0028 indicates a rest. duration is in number of beats; each beat is about 55 ms (one tick of the 18.2 Hz IRQ 0 timer). The end of the sound is marked by a freq_divider of 0000.
Table of offsets to sound effects in the unpacked executable. Assumes that the unpacked executable has an EXE header of 512 bytes. Changed sounds are highlighted.
| R1sw1988 | R1swh11988 | R2sw1988 | R3sw1989 | R4a1sw1989 | R4sw1989 | R5sw1991 | |
|---|---|---|---|---|---|---|---|
| title | 2521 | 2521 | 25a2 | 25d2 | 27a2 | 27a2 | 29c4 |
| start | 2665 | 2665 | 2bc0 | 2bf2 | 2daa | 2d9e | |
| door | eda9 | eda9 | f309 | f339 | f4f9 | f4e9 | fc29 |
| player death | edd1 | edd1 | f331 | f361 | f521 | f511 | fc51 |
| teleport | edf9 | edf9 | f359 | f389 | f549 | f539 | fc79 |
| bonus | ee19 | ee19 | f379 | f3a9 | f569 | f559 | fc99 |
| materialize | ee25 | ee25 | f385 | f3b5 | f575 | f565 | fca5 |
| game over | ee85 | ee85 | f3e5 | f415 | f5d5 | f5c5 | fd05 |
| screen transition | eede | eede | f43e | f46f | f62f | f61f | fd3f |
| lost life | eefe | eefe | f45e | f48f | f64f | f63f | fd5f |
| shoot | ef4e | ef4e | f4ae | f4de | f69e | f68e | fdae |
| enemy death | ef5a | ef5a | f4ba | f4ea | f6aa | f69a | fdba |
| player damaged | ef71 | ef71 | f4d1 | f501 | f6c1 | f6b1 | fdd1 |
| item get | ef81 | ef81 | f4e1 | f511 | f6d1 | f6c1 | fde1 |
| extra life | ef95 | ef95 | f4f5 | f525 | f6e5 | f6d5 | fdf5 |
References for Code Changes
R3 Esc
| Revision 2 @ 76 | Revision 3 @ 76 |
|---|---|
cd16 int 0x16 ; get keystroke; al=ASCII code
3c6b cmp al, 0x6b ; 'k'
7407 je setup_keyboard
3c4b cmp al, 0x4b ; 'K'
7403 je setup_keyboard
e95001 jmp game_start
setup_keyboard:
|
cd16 int 0x16 ; get keystroke; al=ASCII code
3c6b cmp al, 0x6b ; 'k'
740e je setup_keyboard
3c4b cmp al, 0x4b ; 'K'
740a je setup_keyboard
3c1b cmp al, 0x1b ; Esc
7503 jne any_other_key
e95203 jmp terminate_program
any_other_key:
e95401 jmp game_start
setup_keyboard:
|
R3 Keyboard Setup Hack
| Revision 2 @ 83 | Revision 3 @ 8a |
|---|---|
setup_keyboard:
1e push ds
b8b70f mov ax, 0xfb7 ; segment containing keyboard-related strings
8ed8 mov ds, ax ; set ds to it
e8f200 call input_key_mapping ; eat the 'k' already released
b80200 mov ax, 2 ; video mode 2 (80x25 text)
cd10 int 0x10 ; set video mode (clear screen)
|
setup_keyboard:
1e push ds
b8b70f mov ax, 0xfb7 ; segment containing keyboard-related strings
8ed8 mov ds, ax ; set ds to it
b80200 mov ax, 2 ; video mode 2 (80x25 text)
cd10 int 0x10 ; set video mode (clear screen)
|
R3 Keyboard Buffer
Interrupts were disabled in two places:
| Revision 2 @ 115 | Revision 3 @ 1d1 |
|---|---|
; 0040:1a is pointer to beginning of the buffer of pending keystrokes
; 0040:1c is pointer to end
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl ; set end = beginning
|
; 0040:1a is pointer to beginning of the buffer of pending keystrokes
; 0040:1c is pointer to end
fa cli ; disable interrupts
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl ; set end = beginning
fb sti ; enable interrupts
|
| Revision 2 @ 2b4 | Revision 3 @ 2bf |
|---|---|
; 0040:1a is pointer to beginning of the buffer of pending keystrokes
; 0040:1c is pointer to end
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl ; set end = beginning
|
; 0040:1a is pointer to beginning of the buffer of pending keystrokes
; 0040:1c is pointer to end
fa cli ; disable interrupts
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl ; set end = beginning
fb sti ; enable interrupts
|
Interrupts were not disabled at four other instances of the same pattern:
| Revision 2 @ ff5 | Revision 3 @ 1004 |
|---|---|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
| Revision 2 @ 103f | Revision 3 @ 104e |
|---|---|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
| Revision 2 @ 110b | Revision 3 @ 111a |
|---|---|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
| Revision 2 @ 1228 | Revision 3 @ 1237 |
|---|---|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
|
R3 Key Mapping
| Revision 2 @ 17e | Revision 3 @ 174 |
|---|---|
input_key_mapping:
2ec6061f0000 mov byte cs:[last_key], 0 ; set in int9_handler
2e803e1f0000 cmp byte cs:[last_key], 0
74f8 je input_key_mapping ; loop until a key is released
2e8a1e1f00 mov bl, byte cs:[last_key]
32ff xor bh, bh ; bx = scancode
2e3a1e1900 cmp bl, byte cs:[scancode_jump]
74e4 je input_key_mapping
2e3a1e1b00 cmp bl, byte cs:[scancode_left]
74dd je input_key_mapping
2e3a1e1c00 cmp bl, byte cs:[scancode_right]
74d6 je input_key_mapping
2e3a1e1a00 cmp bl, byte cs:[scancode_fire]
74cf je input_key_mapping
2e3a1e1d00 cmp bl, byte cs:[scancode_open]
74c8 je input_key_mapping
4b dec bx ; scancode == 0x1 (Esc)?
74c5 je input_key_mapping
83fb51 cmp bx, 0x51 ; scancode > 0x52 (Ins)?
7fc0 jg input_key_mapping
d1e3 shl bx, 1
d1e3 shl bx, 1
d1e3 shl bx, 1
8d164a40 lea dx, word [key_names]
03d3 add dx, bx ; look up the key's name
b409 mov ah, 9
cd21 int 0x21 ; print string
2ea01f00 mov al, byte cs:[last_key] ; return scancode
c3 ret
|
input_key_mapping:
e84400 call wait_for_keypress
8ad8 mov bl, al
32ff xor bh, bh ; bx = scancode
8bf3 mov si, bx
2e3a1e1900 cmp bl, byte cs:[scancode_jump]
74f0 je input_key_mapping
2e3a1e1b00 cmp bl, byte cs:[scancode_left]
74e9 je input_key_mapping
2e3a1e1c00 cmp bl, byte cs:[scancode_right]
74e2 je input_key_mapping
2e3a1e1a00 cmp bl, byte cs:[scancode_fire]
74db je input_key_mapping
2e3a1e1d00 cmp bl, byte cs:[scancode_open]
74d4 je input_key_mapping
4b dec bx ; scancode == 0x1 (Esc)?
74d1 je input_key_mapping
83fb52 cmp bx, 0x52 ; scancode > 0x53 (Del)?
7fcc jg input_key_mapping
d1e3 shl bx, 1
d1e3 shl bx, 1
d1e3 shl bx, 1
8d164a40 lea dx, word [key_names]
03d3 add dx, bx ; look up the key's name
b409 mov ah, 9
cd21 int 0x21 ; print string
8bc6 mov ax, si ; return scancode
c3 ret
wait_for_keypress:
2ec6061f0000 mov byte cs:[last_key], 0 ; set in int9_handler
2e803e1f0000 cmp byte cs:[last_key], 0
74f8 je wait_for_keypress ; loop until a key is pressed
; clear BIOS keyboard buffer
33c0 xor ax, ax
8ec0 mov es, ax
2ea01f00 mov al, byte cs:[last_key] ; return scancode
fa cli
268a0e1a04 mov cl, byte es:[0x41a]
26880e1c04 mov byte es:[0x41c], cl
fb sti
c3 ret
|
R3 Key Press/Release
| Revision 2 @ 3f8 | Revision 3 @ 405 |
|---|---|
saved_int9_handler: ; pointer to the original handler for interrupt 9
00000000 dw 0x0000, 0x0000
int9_handler: ; IRQ 1, keyboard data ready, called for every keyboard event
50 push ax
53 push bx
51 push cx
52 push dx
e460 in al, 0x60 ; al = scancode
50 push ax ; save al
9c pushf
2eff1ef803 lcall cs:[saved_int9_handler] ; call original interrupt handler
58 pop ax ; restore al = scancode
ba0100 mov dx, 1 ; initially set dl = 1 to indicate key press
a880 test al, 0x80 ; check the high bit of the scancode
7408 jz continue ; if 0, it was indeed a key press
247f and al, 0x7f ; otherwise, it was a key release
2ea21f00 mov byte cs:[last_key], al ; clear high bit and store scancode
33d2 xor dx, dx ; set dl = 0 to indicate key release
continue:
|
saved_int9_handler: ; pointer to the original handler for interrupt 9
00000000 dw 0x0000, 0x0000
int9_handler: ; IRQ 1, keyboard data ready, called for every keyboard event
50 push ax
53 push bx
51 push cx
52 push dx
e460 in al, 0x60 ; al = scancode
50 push ax ; save al
9c pushf
2eff1e0504 lcall cs:[saved_int9_handler] ; call original interrupt handler
58 pop ax ; restore al = scancode
ba0100 mov dx, 1 ; initially set dl = 1 to indicate key press
a880 test al, 0x80 ; check the high bit of the scancode
7406 jz key_was_pressed ; if 0, it was indeed a key press
247f and al, 0x7f ; otherwise, it was a key release
33d2 xor dx, dx ; set dl = 0 to indicate key release
eb04 jmp continue ; do not store scancode
key_was_pressed:
2ea21f00 mov byte cs:[last_key], al ; clear high bit and store scancode
continue:
|