apple2_gumball/README.md
2018-04-19 10:15:46 -07:00

21 KiB

Revised: April 19, 2018

Gumball Easter Eggs

Table of Contents

  • Introduction
  • Pre-requisites
  • In-game Hints
  • Long Lost Gumball Source Code! ** Fragment GB.1.a ** Fragment GB.1.b ** Fragment GB.1.c ** Fragment GB.1.d ** Fragment GB.1.e ** Fragment GB.1.f ** Dump Listing ** Fragment GB.1.All ** Fragment GB.2 ** Dump Symbols ** Fragment GB.2.All ** Fragment GB.3.a ** Fragmnet GB.3.b ** Fragment GB.3.All (TODO)
  • Hints Revisited
  • Hall of Fame ** Credits
  • Job Titles
  • Secret in-game end message
  • Appendix A: Sector Interleave

Introduction

I don't know what it is about Apple 2 games but I love using a sector editor viewing every track and sector for easter eggs. I guess it is the thrill of (re)discovering something that hasn't been seen in 20 - 40 years that has been seen only by the author.

Today we'll discover some easter eggs hidden in Br0derbund's "Gumball". A regular player will never see these since they are never shown. They are "remnants" left over from the build process. And the only way to view them is to either

  • view memory
  • view the disk contents

We'll be using a "cracked" copy -- specifically games that have been converted over to a standard Track/Sector format. We'll also be using a sector editor -- "Copy ][ Plus" is the standard 'go to too' that we'll use.

Pre-requisites

There are few different cracked versions out there.

We'll use these disks:

4am's version is the most "pristine" -- he only removes the copy protection scheme and keeps everything else in-tact. We even have his cracking notes to follow along and verify!

I'll also be using another version since it will be curious if other crackers kept everything or not.

In-game Hints

4am, T02SB

We have a strange 24 character string:

1B:_____ ________ __FIG YRJMYR<FF>

Instead of C's ASCIIZ we have ASCIIFF where FF is the end sentinel.

This is actually in-game hint #4 (using a subtituion chipher)

YOU RETIRE

4am has documented all of those in his notes:

  • "Gumball (4am & san inc crack).txt"

The remaining hints have a lookup table on T12/S4/@AA

    $FB2
    $FD4
    $FF6
    $1014

Note: The strings are not null terminated but $FF terminated.

Hint Location Memory
RBJRY JSYRR T12S4 @B2 $0FB2
VRJJRY ZIAR T12S4 @D4 $0FD4
ESRB T12SB @F6 $0FF6
FIG YRJMYR T12SB @14 $1014

For some reason the last hint is duplicated on T02SB which is located in memory @ $101B ?

And now for something completely different ...

Long Lost Gumball Source Code!

No, this isn't the entire source code -- only fragments of it.

Still let's see what treasure we can "mine" by "spelunking."

Fragment GB.1.a

I'm call this Source Code Fragment GB.1._x_

Since we have two disk images:

  • I'll prefix 4am's DSK image with 4am, and
  • I'll prefix Gumball Fixed DSK image with 'Fix'.

Here is the track/sector infor for the first fragment.

  • 4am, T3S0
  • Fix, T2S0

The first fragement is an assembler header block!

;
;
;
;
;
; --------------------------------
; <                              <
; <           "BLOCK2"           <
; <                              <
; <    TABLES AND BLOCK SHAPES   <
; <                              <
; <         ROBER

It is continued on:

4am, T03S7 Fix, T02S7

;                T COOK          <
; <                              <
; --------------------------------
;
;
;
;
;
;
; GUMBALL TITLE BLOCK
;
;
;
;
;
; GUMBALL       
d 280000000
d 0000000000000000
d 0000000000000000
d 0000000000000000
d 00000A2800000000
d 0

NOTE:

  • I'm leaving out the control characters,
  • It looks like $13 is used to represent a TAB ($09) char?

This is continued on either T03S4, T03SC, or T03SE. But which is one is correct?

Fragment GB.1.b

4am, T03S4 Fix, T02S4

Let's padd the front with space(s) to align the text.

   F7F837F8360FF81  (1) @00
d 000A28703F80E67F  (2) @11
d FC0FE0FFF83FFE7F  (3) @25
d F37F787F7F0FFE5F  (4) @39
d FF077F8360FF8100  (5) @4D
d 0A28703F80667FFC  (6) @61
...
d 7F0360FF81000A28

Now we're going to "cheat" -- that is: "Work smarter not harder"

Searching for FC 0F E0 FF the raw bytes on line 3 can be found at:

  • 4am, T7S5 @4C
  • Fix, T5S1 @4C

From this we can deduce three, no four, no five, things:

  • We know the first digit should be 7,
  • That rules out Sectors $4 and $E, leaving Sector $C,
  • We must verify our assumptions,
  • We can start to see the sector interleaving table,
  • Even if we weren't able to find the raw hex bytes on disk we could have assumed that Sector $E followed from Sector $7 by inspection and noticing the run of zeroes.

Fragment GB.1.c

Making a table of what we have so far:

Sector Mem
0 $1800
7 $1900
: :
C $?000
4 $?100

4am, T03SC Fix, T02SC

       F7F7F3F603C   (1) @00
d FF07307E7F01FF07   (2) @0D
d 70FF01000A28707F   (3) @21
d 000000781F607F70   (4) @35
d FF7F7F7F3FF0FC7F   (5) @49
...
d 7FF773FB87E7F077  (13) @E9
d 7                 (14) @FD

Without visually inspecting these HGR bytes it would look like T03S4 is the contination due to each row being 16 characters.

Why?

Since S3S7 ends with 1 character that means the next row will only have 15 characters. At first glance T03S4 would fit the bill.

However, we must always verify our assumptions.

That's why we did the Hex Search earlier and found the actual answer was T03SC and not our expected T03S4.

An exercise for the reader: "Rip" the sprites from the startup demo and re-construct the actuall assembly source.

Fragment GB.1.d

Sector $E looks like the continuation of S0.

   000000000000000   (1) @00
d 0000000000000000   (2) @11
d 0000000000000000   (3) @25
d 000A280000000000   (4) @39
d 0000000000000000   (5) @4D
d 0000000000000000   (6) @61
d 0000000000000000   (7) @75
d 0A28000000000000   (8) @89
d 0000000000000000   (9) @9D
d 0000000000000000  (10) @B1
d 0000000000000000  (11) @C5
d 2800007E0300000F  (12) @D9
d 4007007003007C00  (13) @ED
Sector Mem
0 $1800
7 $1900
E $1A00
: :
C $?000
4 $?100

Fragment GB.1.e

From the above analysis we have enough to put together a sector interleaving table.

Logical Physical
0 0
1 7
2 E
3 6
4 D
5 5
6 C
7 4
8 B
9 3
A A
B 2
C 9
D 1
E 8
F F

Let's inspect Sector $6

4am, T03S6 Fix, T02S6

d 007F0F0000000000   (1) @01
d 7001000F00000A28   (2) @15
:
d 07787C0300660      (13) @F1

Fragment GB.1.f

4am, T03SD Fix, T02SD

               730   (1) @00
d 7E00000A2800CF7F   (2) @05
:
d 0A2860FF7         (13) @F5

Fragment GB.1.All

Here is the the complete sector and memory list for Fragment 1:

Sector Mem
0 $1800
7 $1900
E $1A00
6 $1B00
D $1C00
5 $1D00
C $1E00
4 $1F00

A complete half of track's worth of data is source code!?

The simplest way to save this non-linear data into a linear format is too boot the game up and inspect memory at the title sequence.

From AppleWin:

  • bsave "gumball.fragment.1.1800",1800:1FFF

Dump Listing

Here's a C program, dump_list.c, to dump the fragment:

// Includes
    #include <stdio.h>  // printf()
    #include <stdlib.h> // malloc()

// Util

    size_t FileSize( FILE *file )
    {
        if( !file )
            return 0;

        // Yes, this is insecure
        // and we should use stat()
        // but that isn't available under Windows
        fseek( file, 0, SEEK_END );
        size_t size = ftell( file );
        fseek( file, 0, SEEK_SET );

        return size;
    }

// Main

    int main()
    {
        const char *name = "gumball.fragment.1.1800";
        FILE       *in   = fopen( name, "rb" );

        if( in )
        {
            size_t size = FileSize( in );

//printf( "Size: %lld\n", size );

            if( size )
            {
                char *buffer = (char*) malloc( size );

                int  i;
                int  n = (int) size;
                char c;

                size_t read = fread( buffer, 1, size, in );

                if( read != size )
                    printf( "ERROR: Only read %lld / %lld bytes\n", read, size );

                for( i = 0; i < n; i++ )
                {
                    c = buffer[ i ] & 0x7F;

                    if( c == 0x0D )
                    {
                        printf( "\n" );

                        c = buffer[ ++i ];
                        if( (c == 0x13) || (c == 0x0B) )
                            printf( "\t" );
                        else
                            printf( " " );
                    }
                    else
                    if( c < 0x20 )
                        ;
                    else
                        printf( "%c", c );
                }

                free( buffer );
            }

            fclose( in );
        }
        else
            printf( "ERROR: Couldn't open: '%s'\n", name );

        return 0;
    }

And finally, our moment of truth!

;
 ;
 ;
 ;
 ;
 ; --------------------------------
 ; |                              |
 ; |           "BLOCK2"           |
 ; |                              |
 ; |    TABLES AND BLOCK SHAPES   |
 ; |                              |
 ; |         ROBERT COOK          |
 ; |                              |
 ; --------------------------------
 ;
 ;
 ;
 ;
 ;
 ;
 ; GUMBALL TITLE BLOCK
 ;
 ;
 ;
 ;
 ;
 GUMB    :
	d 28000000
	d 0000000000000000
	d 0000000000000000
	d 0000000000000000
	d 00000A2800000000
	d 0000000000000000
	d 0000000000000000
	d 0000000000000000
	d 000A280000000000
	d 0000000000000000
	d 0000000000000000
	d 0000000000000000
	d 0A28000000000000
	d 0000000000000000
	d 0000000000000000
	d 000000000000000A
	d 2800007E0300000F
	d 4007007003007C00
	d 007F0F0000000000
	d 7001000F00000A28
	d 00607F1F00601F60
	d 0F807C0F007F0370
	d 7F7F00401F00007C
	d 03603F00000A2800
	d 387E3F00203E301E
	d 007E3F607F07787F
	d 7F03707F00006407
	d 70BF80000A28008E
	d 7E6700303E301E80
	d 737F307E87FC7F7F
	d 07787C0300660730
	d 7E00000A2800CF7F
	d 6700703FF83F40F3
	d 7FB87E0FCC7F7F87
	d 787C07007E87307E
	d 00000A2840FF7F7F
	d 00F83FF83FE07F7F
	d 797F1F4C7F7F0F78
	d 7F0F007E87707F80
	d 000A28407F7F7F00
	d 983FF83FE0797FFD
	d 7F1FFC7F7F0F787F
	d 1F007287707F8000
	d 0A2860FF7F3F0098
	d 3F703FE0797F7F7F
	d 3F7C7F7F0F707F3F
	d 007287707F01000A
	d 28607F871F00183F
	d 707F607C7F7F7F3F
	d 789FFE0F407F7F00
	d F30770FF01000A28
	d 70FF810000781F60
	d 7F607C7F7F7F3F40
	d 3FFC87607F7F80FF
	d 0770FF01000A2870
	d FF800000781F607F
	d 70FF7F7F7F3F603C
	d FF07307E7F01FF07
	d 70FF01000A28707F
	d 000000781F607F70
	d FF7F7F7F3FF0FC7F
	d 83187F7F81FF8360
	d 7F01000A28703F80
	d 7007780FE0FF70FF
	d 7E7F773FB07E7F81
	d 9C7F7F03FF83607F
	d 01000A28703F807C
	d 3F780FE0FF707F7E
	d 7F773FB87E7F077C
	d 7F7F837F8360FF81
	d 000A28703F80E67F
	d FC0FE0FFF83FFE7F
	d F37F787F7F0FFE5F
	d FF077F8360FF8100
	d 0A28703F80667FFC
	d 0F407FF83F7C7F63
	d 7F785F7F0FFF1F7F
	d 877F0360FF81000A
	d 28707F007E7FFC0F
	d 407FF83F787F617F
	d 781F7C1FFF0FFF0F
	d 7F0360FF81000A28

Fragment GB.2

On Track $08 we we find more snippets of assembly source:

Fragment GB.2a, T08S4

    +,ANDPIC  V,BALLINDHW,BALLINDLZ,BMINDH  ],BMIND

This looks like a symbol table.

If we boot the game and again inspect memory at the title sequence we see that this is loaded into memory at $8400. We are interested in the snippet from $849a onwards.

The format of the symbol table is this in C:

struct
{
    uint8_t  name[8];
    uint16_t address;
}

The first two letters at $849A and $849B got over-written with zeroes. We can over-write these 2 end bytes of graphics data with underscores (_) $5F:

  • 849A:5F
  • 849B:5F
  • bsave "gumball.fragment.2.849a",849a:87FF

Dump Symobls

We can modify our dump_list converting it to dump_sym.c to dump the symbol table:

// Includes
    #include <stdio.h>  // printf()
    #include <stdlib.h> // malloc()

// Util

    size_t FileSize( FILE *file )
    {
        if( !file )
            return 0;

        // Yes, this is insecure
        // and we should use stat()
        // but that isn't available under Windows
        fseek( file, 0, SEEK_END );
        size_t size = ftell( file );
        fseek( file, 0, SEEK_SET );

        return size;
    }

// Main

    int main( const int nArg, const char *aArg[] )
    {
        const char *name = "gumball.fragment.2.849a";
        FILE       *in   = fopen( name, "rb" );

        if( nArg > 1 )
            name = aArg[1];       

        if( in )
        {
            size_t size = FileSize( in );

//printf( "Size: %lld\n", size );

            if( size )
            {
                char *buffer = (char*) malloc( size );

                int  src;
                int  len = (int) size;

                size_t read = fread( buffer, 1, size, in );

                char name[ 9 ];

                if( read != size )
                    printf( "ERROR: Only read %lld / %lld bytes\n", read, size );

    
                for( src = 0; src < len; )
                {
                    int dst;
                    for( dst = 0; dst < 8; dst++ )
                        name[ dst ] = buffer[ src + dst ];
                    name[ 8 ] = 0;

                    printf(
                        "%s EQU $%02X%02X\n"
                        , name
                        , (buffer[ src + 9 ] & 0xFF)
                        , (buffer[ src + 8 ] & 0xFF)
                     );

                    src += 10;
                }

                free( buffer );
            }

            fclose( in );
        }
        else
            printf( "ERROR: Couldn't open: '%s'\n", name );

        return 0;
    }

Fragment GB.2.All

Lo and behold!

__OB     EQU $084C
DROP1    EQU $087B
DROP2    EQU $0892
BALLDOOR EQU $08CF
RASEPOLE EQU $08E3
MAKECRS1 EQU $0900
SHOWGUMB EQU $0922
COPYRITE EQU $0948
PRELIM   EQU $0978
MAKECRS2 EQU $09A8
SHOWROB  EQU $09CE
SHOWDOUG EQU $09F4
GUMBDR   EQU $0A30
CROSSDR  EQU $0A6B
PLOT     EQU $0AB2
POLEUP   EQU $0AFC
OPENBALL EQU $0B1B
BALLDRP1 EQU $0B5D
CHUNKPLT EQU $0BA5
BALLSET  EQU $0BC5
BALLDRP2 EQU $0BEC
BBPLOT   EQU $0C3D
EEPLOT   EQU $0C63
ANPLOT   EQU $0C89
PRPLOT   EQU $0CBD
BBUNPLOT EQU $0CE3
EEUNPLOT EQU $0D07
ANUNPLOT EQU $0D2D
PRUNPLOT EQU $0D58
ROTKNOB  EQU $0D7E
PAUSE    EQU $0DD5
LPAUSE   EQU $0E15
SCRFLIP  EQU $0E1F
SCRCLR   EQU $0E33
SCRTRANS EQU $0E4C
TRANSOUT EQU $0E6E
TRANSIN  EQU $0E90
BLOCK1   EQU $0EB2
BLOCK2   EQU $0EE6
BLOCK3   EQU $0F52
B3LOOP   EQU $0F54
MAIN     EQU $0F9D
STARTUP  EQU $0FD3
YLO      EQU $6000
YHI      EQU $60C8
KNOBIND  EQU $6190
KNOB1    EQU $619A
KNOB2    EQU $62AE
KNOB3    EQU $63C2
KNOB4    EQU $64D6
KNOB5    EQU $65EA
BB       EQU $66FE
BRODB    EQU $68C5
EE       EQU $68C6
ELTE     EQU $6A2D
PRE      EQU $6A2E
PRES     EQU $6B0D
NEWY1    EQU $6B0E
NEWY2    EQU $6B6B
NEWY3    EQU $6BCB
NEWY4    EQU $6C0B
AND      EQU $6C2B
ANDPIC   EQU $6C56
BALLINDH EQU $6C57
BALLINDL EQU $6C5A
BMINDH   EQU $6C5D
BMINDL   EQU $6C60
BALL1    EQU $6C63
BALLM    EQU $6C99
OBALLIND EQU $6CCF
OBALL1   EQU $6CD9
OBALL2   EQU $6D60
OBALL3   EQU $6DE7
OBALL4   EQU $6E6E
OBALL5   EQU $6EF5
PATH1    EQU $6F7C
PATH2    EQU $6F9A
GUMCHUNK EQU $6FDF
BALL2    EQU $7033
BALL3    EQU $7069
WHTMASK  EQU $709F
BYTET    EQU $70D5
BLOCKPOS EQU $71D9
DOTS1    EQU $730A
DOTS2    EQU $7311
GUMB     EQU $7318
GUMBR    EQU $006B

4am, Track $8

Sector Mem
D $8400
5 $8500
C $8600
4 $8700

Fragment GB.2.a, T08S4 Fragment GB.2.b, T08S5 Fragment GB.2.c, T08SC Fragment GB.2.d, T08SD

Fragment GB.3a

There is 3rd fragment on disk!

  • 4am, T13S0
  • Fix, T11S0
;
;
;
;
;   -----------------------------
; <                              <
; <           "BLOCK3"           <
; <                              <
; <    TABLES AND BLOCK SHAPES   <
; <                              <
; <         ROBE

Fragment GB.3.b

It is continued on 4am, T13S7

                RT COOK          <
; <                              <
;   -----------------------------

Using our sector interleave table:

|:Sector:| Order | | 0 |0 | | 7 |1 | | E |2 | | 6 |3 | | D |4 |
| 5 |5 | | C |6 | | 4 |7 |

I still need to track down where this is loaded into memory.

To save this we have three choices:

  • Play the game, and inspect memory where this is loaded which is time consuming,
  • Manually save this "piece-meal" from Copy ][+ which is still time consuming, or
  • We make a "temp copy" of the game and and over-write the previous fragment 1 with fragment 3.

Let's do the latter:

  • Read: T13S0, Write: T3S0
  • Read: T13S4, Write: T3S4
  • Read: T13S5, Write: T3S5
  • Read: T13S6, Write: T3S6
  • Read: T13S7, Write: T3S7
  • Read: T13SC, Write: T3SC
  • Read: T13SD, Write: T3SD
  • Read: T13SE, Write: T3SE

Back in AppleWin

  • bsave "Gumball.fragment.3.1800",1800:1FFF

Fragment GB.3.All

Using our dump_list.c program:

;
 ;
 ;
 ;
 ;
 ; --------------------------------
 ; |                              |
 ; |           "BLOCK3"           |
 ; |                              |
 ; |    TABLES AND BLOCK SHAPES   |
 ; |                              |
 ; |         ROBERT COOK          |
 ; |                              |
 ; --------------------------------
 ;
 ;
 [ $5800
 ;
 ;
 ;
 ;
 ;
 ; CROWN PICTURE
 ;
 ;
 ;
 ;
 ;
 CROWNS  :
	d 624E624D624C644B
	d 644C644D644E664D
	d 664C664B664A684A
	d 684B684C6A4C6A4B
	d 6A4A6A496C496C4A
	d 6C4B6E4B6E4A6E49
	d 7049704A704B724B
	d 724A72497449744A
	d 744B764B764A7649
	d 7849784A784B784C
	d 7A4C7A4B7A4A7C4A
	d 7C4B7C4C7C4D7E4E
	d 7E4D7E4C7E4B804C
	d 804D804E824F824E
	d 844E824D824C844D
	d 824B844C864C864B
	d 844B844A864A8649
	d 8449824982488448
	d 8447824782468446
	d 8445824580458044
	d 8244824380438042
	d 8242824180417E41
	d 7E408040803F7E3F
	d 7E3E803E803D7E3D
	d 7C3D7C3C7C3B7E3C
	d 7E3B7E3A7E398038
	d 8039803A823A8239
	d 8238823784378438
	d 8439863986388637
	d 8636883688378838
	d 8A388A378A368C36
	d 8C378C388E388E37
	d 8E36903690379038
	d 9239923892379236
	d 943794389439963A
	d 9639963896379838
	d 9839983A9A399A3A
	d 9C3B9A3B9C3C9C3D
	d 9A3C9A3D983D983E
	d 9A3E9A3F983F9840
	d 9A409A4198419641
	d 9642984298439643
	d 9644984498459645
	d 9445944696469647
	d 9447944896489649
	d 94499249924A944A
	d 924B924C944B964B
	d 944C964C944D944E
	d 964F964E964D984C
	d 984D984E9A4E9A4D
	d 9A4C9A4B9C4A9C4B
	d 9C4C9C4D9E4C9E4B
	d 9E4AA049A04AA04B
	d A04CA24BA24AA249
	d A449A44AA44BA64B
	d A64AA649A849A84A
	d A84BAA4BAA4AAA49
	d AC49AC4AAC4BAE4C
	d AE4BAE4AAE49B04A
	d B04BB04CB24AB24B
	d B24CB24DB44EB44D
	d B44CB44BB64EB64D
	d B84FB84EB64CB84D
	d BA4EBA4DB84CBA4C
	d BC4CBC4BBA4BB84B
	d BA4ABC4ABC49BA49
	d B849B848BA48BA47
	d B847B846BA46BA45
	d B845B645B644B844
	d B843B643B642B842
	d B841B641B441B440
	d B640B63FB43FB43E
	d B63EB63DB43DB23D
	d B23CB43CB43BB23B
	d B23AB43AB439B239
	d B238B039B237B236
	d B036B037B038AE38
	d AE37AE36AC36AC37
	d AC38AA38AA37AA36
	d A836A837A838A638
	d A637A636A436A437
	d A4

Hints revisted

4am ,T0DS3 has this message:

    PRESS CTRL-Z DURING THE CARTOONS

Hall of Fame

T11S2 has the High Score headers

----------------------------------------

     ALL TIME CHAMPSIONS OF GUMBALL     

    #    INITIALS    SCORE     LEVEL
   ---  ----------  -------   -------

Credits

The end of the credits is:

4am, T11S3, @ 00

. STAUB
JIM KASSENBROCK    U.C.B.C.


AND ALL OF THE AMAZING PEOPLE AT

              BR0DERBUND

The first half of the credits is the following sector:

4am, T11S4, @BE

        >>>>>>>> CREDITS <<<<<<<<

THE FOLLOWING PEOPLE HAD SOMETH

4am, T11SB @00

THING TO DO  WITH 

Job Titles

T11SC

WORKER
FOREMAN
SUPERVISOR
MANAGER
VICE PRESIDEN

Secret in-game end message

We see another hint:

4am, T12S7 @87

              DOUBLE HELIX

Here we see the congratulations screen T12S7 @DD ...

AHA!  YOU MADE IT!
EITHER YOU AR

... continued on T12SE ...

E AN EXCELLENT GAME-PLAYER
OR (GAH!) PROGRAM-BREAKER!
YOU ARE CERTAINLY ONE OF THE PEOPLE
THAT WILL EVER SEE THIS SCREEN.

THIS IS NOT THE END, THOUGH.

IN ANOTHER BR0DERBUND PRODUCT
TYPE 'Z0DWARE' FOR MORE PUZZLES.

HAVE FUN!  BYE!!
________________

(I've displayed the spaces with underscores for the last line.)

... and concluded on T12S6

      R.A.C.

I discovered this back in the mid 80's. I never documented since I figured that if I had used a sector editor to see it, surely someone else would have.

Who knew that it was never publically announced.

DOH!

Oh well, it's been 20 years of fun knowing about these hidden treasures!

Appendix A: Sector Interleave

Here is a translation table to map logical sectors to physical sectors and vice versa.

NOTE: Copy ][+ reads physical sectors.

Logical Physical
0 0
1 7
2 E
3 6
4 D
5 5
6 C
7 4
8 B
9 3
A A
B 2
C 9
D 1
E 8
F F

Revisions

  • Sept 24, 2017
  • April 18, 2018
  • April 19, 2018