We just released a Feb. 5 '89 prototype of DuckTales for the NES!
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)

From The Cutting Room Floor
Revision as of 03:49, 18 November 2019 by Mouser (talk | contribs) (→‎References for Code Changes: Key press/release, key mapping.)
Jump to navigation Jump to search

This page contains notes for the game The Adventures of Captain Comic (DOS).


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: