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 with4am
, 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