Jump to content
  • entries
    42
  • comments
    8
  • views
    8,548

Beneath Apple Manor

Atari_Ace

219 views

I was reading Dungeon Hacks, by David L. Craddock, the other day and found a short reference to the Atari 8-bit in the chapter on the first Rogue-like, Beneath Apple Manor:

For
Beneath Apple Manor Special Edition
, I reverse engineered the 6502 runtime environment for the Galfo Integer BASIC compiler and ported it to 8086 machines (IBM PC). Bob Christiansen of Quality Software also ported it to the Atari. I did not write
Ali Baba
--Bob might have--but it was in integer BASIC too I think, so Quality used the Galfo compiler on it to port it between machines too. Bob must have given me a copy, which is why I had it.


So there's another virtual machine hiding inside this game, so I had to find it and figure out how it works.

The first step of course was to download the xex file from www.atarimania.com and hook up a my disassembler to the image with appropriate offsets. The image has a number of parts:
OFFSET: 0006 bf00->bf9fOFFSET: 00aa 02e2->02e3OFFSET: 00b0 0c34->803fOFFSET: 74c0 8100->a7ffOFFSET: 9bc4 0110->0132OFFSET: 9beb 02e0->02e1


Given this, my disassembler becomes:

use strict;use m6502;sub open_lst {  open my $fh, '<', 'bam.lst' or die;  $fh;}sub read_img {  my ($addr, $size) = @_;  read_img_core(    $addr, $size, '../bam.xex',    [0x0006 - 0xbf00, 0xbf00, 0xbf9f],    [0x00aa - 0x02e2, 0x02e2, 0x02e3],    [0x00b0 - 0x0c34, 0x0c34, 0x803f],    [0x74c0 - 0x8100, 0x8100, 0xa7ff],    [0x9bc4 - 0x0110, 0x0110, 0x0132],    [0x9beb - 0x02e0, 0x02e0, 0x02e1]);}assem(@ARGV);


The first chunk at $BF00 sets of a simple display list for the load. The load into $02E2 invokes that code before continuing the rest of the image. The next two chunks consist of the main portion of the code, with a small gap that isn't used by the program. The last two chunks load a small init routine and invoke it.

Let's look at the init routine:

​0110: A2 00             LDX #00112: BD 00 A0          LDA $A000,X0115: 9D 34 04          STA $0434,X0118: A9 00             LDA #0011A: 9D 00 A0          STA $A000,X011D: E8                INX011E: D0 F2             BNE $01120120: EE 14 01          INC $01140123: EE 17 01          INC $01170126: EE 1C 01          INC $011C0129: AD 1C 01          LDA $011C012C: C9 A8             CMP #$A8012E: D0 E0             BNE $01100130: 4C 34 04          JMP $0434


This piece of self-modifying code relocates the data at $A000 to $A7FF to $0434 to $0C33. So should add a relocation definition to the read_img routine: [0x74c0 - 0x8100 + 0xa000 - 0x434, 0x0434, 0x0c33].

Now let's look at $0434:

0434: A9 06             LDA #60436: 85 04             STA 40438: 85 05             STA 5043A: A2 7F             LDX #$7F043C: BD 80 04          LDA $0480,X043F: 95 80             STA $80,X0441: CA                DEX0442: E0 FF             CPX #$FF0444: D0 F6             BNE $043C0446: A9 20             LDA #<$80200448: 8D 30 02          STA SDLSTL044B: A9 80             LDA #>$8020044D: 8D 31 02          STA SDLSTL+1...047D: 4C AF 00          JMP $00AF


Another relocation routine, this time relocating $480 to $4FF to $80 to $FF, so we add one more relocation element to read_img: [0x74c0 - 0x8100 + 0xa000 - 0x434 + 0x480 - 0x80, 0x80, 0xff].

If we disassemble $80 to $FF we see a lot of data, but there is some code hiding in there. Labeling $B0 as IP, it looks like this:

​                ; pull IP-1 from stack009A: 68                PLA009B: 85 B0             STA IP009D: 68                PLA009E: 85 B1             STA IP+1                ; increment IP by 100A0: E6 B0             INC IP00A2: D0 0B             BNE $00AF00A4: E6 B1             INC IP+100A6: D0 07             BNE $00AF                ; increment IP by A00A8: 18                CLC00A9: 65 B0             ADC IP00AB: 85 B0             STA IP00AD: B0 F5             BCS $00A4                ; read byte code and jump00AF: AD 40 1D          LDA $1D40 ; start at 1d4000B2: 85 B5             STA $B500B4: 6C FA 63          JMP ($63FA)


This is a little virtual machine fetch loop, with page $63 containing the bytecode vectors (which point to code in pages 64 to 6F), and entry points here to increment the IP by 1 or A, or pull the IP-1 from the stack (implementing an RTS-like return). The machine even has the start address built in ($1D40). It patches the bytecode into the indirect jump address directly, so it only supports 128 even byte-codes. The Atari Pascal virtual machine I posted about recently supported 256, but at the cost of a more expensive fetch loop.

We can now write a bytecode disassembler by disassembling the codes, figuring out how long each is, and making a table of those lengths. My preliminary disassembler looks like this:

my $codeLen = [  0, 3, 3, 3, 3, 3, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, # 0x  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, # 2x  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, # 4x  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, # 6x  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 1, 0, 0, # 8x  0, 0, 0, 0, 0, 0, 3, 0,  0, 3, 0, 0, 0, 0, 0, 0, # ax  0, 0, 0, 0, 1, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, # cx  0, 0, 0, 0, 0, 0, 0, 0,  1, 1, 0, 2, 0, 0, 0, 0, # ex];sub basic_buf {  my ($buff, $addr, $size) = @_;  for (my $i = 0; $i + 1 < $size; ) {    my $val = unpack "C", substr($buff, $i, 1);    my $len = $codeLen->[$val >> 1];    if ($len >= 1) {      bytes_buf($len, substr($buff, $i, $len), $addr + $i, $len);      $i += $len;    }    else {      die sprintf "%02x", $val;    }  }}sub basic {  my ($buff, $addr, $size) = read_img(@_);  basic_buf($buff, $addr, $size);}sub main {  return if assem(@_);  if (0) {  }  else {    basic(@_);  }}main(@ARGV);


And my preliminary bytecode disassembly of the start address is:

1D40: F0                .BYTE $F01D41: 06 00 10          .BYTE $06,$00,$101D44: F0                .BYTE $F01D45: 0A 00 10          .BYTE $0A,$00,$101D48: F6 EA             .BYTE $F6,$EA1D4A: F6 12             .BYTE $F6,$121D4C: C8                .BYTE $C81D4D: 02 0A 71          .BYTE $02,$0A,$711D50: 9A                .BYTE $9A1D51: F0                .BYTE $F01D52: 08 12 10          .BYTE $08,$12,$101D55: F0                .BYTE $F01D56: 08 14 10          .BYTE $08,$14,$10...


For example, the most common bytecode here, F0, pushes 0 onto the stack.

Some other comments about the image:

  • $81F0-$9FFF is the data for an ANTIC mode E picture, the title screen, with the display list at $8126. So 1/5 of the image is for this picture.
  • $7C40-$8000 is the data for an ANTIC mode 2 (graphics 0) screen, with the display list at $7C20. The code modifies most of it to ANTIC mode 4 during the game for use as the main screen.
  • A 1k character set is located at $0C00-$0CFF. The game doesn't just modify the Atari set.
  • The game requires 48K to load, but likely runs in 32K. A little creativity (compressing the title screen and character set might be enough) could have squeezed it into 32K, but 48K was probably quite common when this hacked image was circulating.


I'll probably return to do more work on disassembling this, but I've established how the virtual machine works, so I'm happy enough for a few hours work.

P.S. I took a quick look at Ali Baba and didn't see the same virtual machine. I'll need to explore that more fully to determine if it has a similar architecture.



2 Comments


Recommended Comments

FYI, Dungeon Hacks, Expanded Edition is in the current StoryBundle (Spring Fired Up at storybundle.com). It includes Dungeon Hacks, One-Week Dungeons, Anything But Sports: The Making of FTL: Faster Than Light and Red to Black: The Making of Rogue Legacy. There are a number of other books in the bundle as well that are interesting.

  • Like 1

Share this comment


Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...