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 * Hints Revisited * Hall of Fame * Credits * Job Titles * Secret in-game end message * Conclusion * 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 (aside from the hints.) 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" as it is the standard 'go to tool'. # Pictures Here is what we'll be looking at: * ![source_3a.png](source_3a.png) * ![source_3b.png](source_3b.png) # Pre-requisites There are few different cracked versions out there. We'll use these disks: * [gumball_fixed](ftp://ftp.apple.asimov.net/pub/apple_II/images/games/action/gumball-fixed.zip) * [Gumball (4am & san inc crack)](ftp://ftp.apple.asimov.net/pub/apple_II/images/games/) 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 ``` 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 going to use the nomenclature and call these source code fragments `GB.#._` -- i.e. GB.1.a, GB.2.b, etc. 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! ```asm ; ; ; ; ; ; -------------------------------- ; < < ; < "BLOCK2" < ; < < ; < TABLES AND BLOCK SHAPES < ; < < ; < ROBER ``` It is continued on: 4am, T03S7 Fix, T02S7 ```asm ; 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. ```asm 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~, ~four,~ nay, 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 | We'll continue to inspect sector $C * 4am, T03SC * Fix, T02SC ```asm 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. ```asm 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 ```asm d 007F0F0000000000 (1) @01 d 7001000F00000A28 (2) @15 : d 07787C0300660 (13) @F1 ``` ## Fragment GB.1.f Moving along, * 4am, T03SD * Fix, T02SD ```asm 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: * Mount disk * Press `F2` to re-boot * Wait for title screen * Press `F7` to enter the debugger * `bsave "gumball.fragment.1.1800",1800:1FFF` ## Dump Listing Here's a C program, [dump_list.c](dump_list.c), to dump the fragment: ```c // Includes #include // printf() #include // 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.1.1800"; if( nArg > 1 ) name = aArg[1]; FILE *in = fopen( name, "rb" ); printf( "Filename: %s\n", name ); 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! ```asm ; ; ; ; ; ; -------------------------------- ; | | ; | "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: ```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](dump_sym.c) to dump the symbol table: ```c // Includes #include // printf() #include // 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"; if( nArg > 1 ) name = aArg[1]; FILE *in = fopen( name, "rb" ); printf( "Filename: %s\n", name ); 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! ```asm __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|Fragment| |:-:|:-:| | 0 | 1 | | 7 | 2 | | E | 3 | | 6 | 4 | | D | 5 | | 5 | 6 | | C | 7 | | 4 | 8 | 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](dump_list.c) program: ```asm ; ; ; ; ; ; -------------------------------- ; | | ; | "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! # Conclusion Who knew that a WHOLE Track+ (20 sectors) of source was on disk! Now if only we could figure out where that `Z0DWARE` easter egg is ... # 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