I added the valid Croc passwords to the wiki (based on blind faith for now, I will get round to checking at some point soon) with a credit from this forum post. Happy to change it once its on twitter or the blog of course. Curiously 2-1 and 5-5 match the psx codes but 3-1 and 4-1 do not. Croc: Legend of the Gobbos/Hidden content - Sega Retro EDIT - all four passwords work on the US version (I only check they load the level they're supposed to, not for any other effects)
Thought I'd have a look into this - the ST quote comes from Fergus McGovern, top dawg of Probe who is infamous for having his head stuck in a dozen titles. I honestly don't know if he's correct, however I am pretty sure the two ports of Bubble Bobble and Rainbow Islands are entirely separate products which Probe were forced to jam together. Bubble Bobble was ported by Probe, but Rainbow Islands was ported by Graftgold - both games show a credits screen if you wait long enough on their titles. Graftgold were responsible for porting Rainbow Islands to the Amiga and Atari ST in 1988/89 - there are even some shared staff, however I don't think this is ST code running on a Saturn. I think it was written in C... because if you have a look at the CD-ROM, "2.BIN" contains hoards of uncompiled C code: Code (Text): void Refresh(void) { ScreenModeInfo *Scr = SCREEN(USBuffer); if (NewLook==0) { memcpy(Scr->Address,(WorkMem->Address)+(MasterY *320),320*224); } else { if (CurrentPlayer->PlayerIsland == 4 || CurrentPlayer->PlayerIsland == 6) Refresh2(); else Refresh3(); // Refresh1(); } } UBYTE NRemap[]= /* player2 remap table for newlook graphics only */ { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x5f,0x5e,0x5d,0x5c,0x5b,0x5a,0x59,0x58,0x57,0x56,0x55,0x54,0x53,0x52,0x51,0x50, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf }; /* ---------------------------------------------------------------------------- SimplePlotPlayer Standard plotter adapted to remap player --------------------------------------------------------------------------- */ void SimplePlotPlayer(int xat,int yat,Memory *Data,int Frame) { ScreenModeInfo *Scr = SCREEN(USBuffer); struct SpriteBPPS *Base; struct InfoBPPS *SPInfo; /* the next line is a three fold data integrity check : */ if (*((int*) Data->Address) != 0x53505042) return; Base = (SpriteBPPS *) Data->Address; SPInfo = Base->InfoBPPS + ((Frame >= Base->Sprites) ? 0 : Frame); { char *SpDat = Data->Address + SPInfo->SpriteAt; /* read from here */ char *Addr = Scr->Address; int LineSize = Scr->LineSize; int XLimit = Scr->XSize; int YLimit = Scr->YSize; int XPos = xat + Scr->XOrigin - SPInfo->XOfst; int YPos = yat + Scr->YOrigin - SPInfo->YOfst; int Lines = SPInfo->YSize; /* XSize is not that important */ Addr += YPos * LineSize; /* assume byte per pixel screen */ while((Lines--) && (YPos < YLimit)) { int xp = XPos; int Strips = *SpDat++; while(Strips--) { int Skip = *SpDat++; int Plot = *SpDat++; char *DtOfs = SpDat; SpDat += Plot; xp += Skip; /* clipping : */ if (YPos >= 0) { if (((xp + Plot) > 0) && (xp < XLimit)) { int Draw = Plot; int Over,atx = xp; if (atx < 0) { Draw += atx; DtOfs -= atx; atx = 0; } Over = (atx + Draw) - XLimit; if (Over > 0) { Draw -= Over; } while(Draw-- > 0) { Addr[atx++] = NRemap[(*DtOfs++)]; } } } xp += Plot; } YPos++; Addr += LineSize; } } } /* ---------------------------------------------------------------------------- ReverseXPlotPlayer reverse x plotter --------------------------------------------------------------------------- */ void ReverseXPlotPlayer(int xat,int yat,Memory *Data,int Frame) { ScreenModeInfo *Scr = SCREEN(USBuffer); struct SpriteBPPS *Base; struct InfoBPPS *SPInfo; char *SpDat; char *Addr = Scr->Address; int LineSize = Scr->LineSize; int XLimit = Scr->XSize; int YLimit = Scr->YSize; int XPos; int YPos; int Lines; int xp; int Strips; int Skip; int Plot; char *DtOfs; int Off; /* the next line is a three fold data integrity check : */ if (*((int*) Data->Address) != 0x53505042) return; Base = (SpriteBPPS *) Data->Address; SPInfo = Base->InfoBPPS + ((Frame >= Base->Sprites) ? 0 : Frame); SpDat = Data->Address + SPInfo->SpriteAt; /* read from here */ XPos = xat + Scr->XOrigin - SPInfo->XOfst + SPInfo->XSize -1; YPos = yat + Scr->YOrigin - SPInfo->YOfst; Lines = SPInfo->YSize; /* XSize is not that important */ Addr += YPos * LineSize; /* assume byte per pixel screen */ while((Lines--) && (YPos < YLimit)) { xp = XPos; Strips = *SpDat++; while(Strips--) { Skip = *SpDat++; Plot = *SpDat++; DtOfs = SpDat; /* pointer into strip */ SpDat += Plot; xp -= Skip; /* clipping : */ if (YPos >= 0) /* don't plot if off the top of screen */ { /* right clipping */ if (xp >= XLimit) /* off the rhs */ { Off = xp-XLimit+1;/* by 'Off' Pixels */ if (Off >= Plot) /* don't plot strip at all! */ { xp -= Plot; continue; /* go to Next Strip */ } /* can plot some of strip */ Plot -= Off; xp -= Off; DtOfs += Off; } /* left clipping */ if (xp>=0) /* only do if on screen */ { if (Plot>xp+1) /* can't plot full strip */ Plot = xp+1; /* so truncate */ while(Plot--) { Addr[xp--] = NRemap[(*DtOfs++)]; } } } } YPos++; Addr += LineSize; } } if (!Err) Err=DoFileLoad(MainMem,"ending2.ggi",&LoadedSprites, NULL,MEM_WORD_32_ALIGNED,"gfx:loaded",FL_GRAPHICS); if (!Err) { InitGGI(LoadedSprites); /* process ggi for endian ... */ BuildSprites(LoadedSprites,FramesInMAIN); } LoadedIsland = (-1); break; } case GF_MAPS: { break; /* Maps already in PC data */ GetSpriteBlocks(); /* Allocate sprite blocks and form linked list */ SetPlayers(); SetPlayerDevices(); /* Select default control methods in Players */ PrintFront(TopLegend); if (MaxCredits == 0) GState = GS_START_DEMO; else GState = GS_TITLE_2; break; } case GS_EXITMENU: { SpriteClear(); DState = 0; ClearGameScreen(SC_BLACK); if (AnalogInput.DigitalGatedButtons & AKeyMenuUp) { if (ExitOption > 0) { ExitOption--; SoundRequest(SFXMenuUD,4); } } else if (AnalogInput.DigitalGatedButtons & AKeyMenuDown) { if (ExitOption < (MAX_EXIT_OPTIONS - 1)) { ExitOption++; SoundRequest(SFXMenuUD,4); } } else if (AnalogInput.DigitalGatedButtons & (AKeyMenuSelect)) { if (ExitOption == EXIT_YES) { Err = &UserExit; } else { ExitOption = EXIT_NO; GState = GS_TITLE_2; SoundRequest(SFXMenuButton,3); } } *ExitCursorPos = ExitOption*16 + 0x58; ClearGameScreen(SC_UNPAUSE); PrintFront(ExitMenu); PrintFront(ExitCursor); break; } case GS_TITLE_2: { /* this is where end of game loops back to !!! */ PrintFront(PrintCredit); SpriteClear(); SystemFlags &= ~Quit; SystemFlags &= ~Paused; /* UN - PAUSE */ GameFlags &= ~SuspendSprites; // SoundReset(); HintFlag=0; Playback=0; if (CurrentPlayer->PlayerNumber != 1) { PLAYER *a0 = CurrentPlayer; CurrentPlayer = BenchPlayer; BenchPlayer = a0; } GState = GS_TITLE_3; break; } case GS_TITLE_3: { ActiveOption=0; Players=0; Cheats=0; CheatNumber=(-1); KevCheatFrame=0; KevCheatFrame2=0; *(CheatTen) = Term; PlayerStencil.PlayerMaximum = 0x00999999; Money(); if (Credit) GState = GS_MONEY_IN; else GState = GS_TITLE_LOOP; break; } case GS_TITLE_LOOP: { int i; KeyIndex = 0; /* Reset cheat input */ for (i=0; i<8; i++) { PrintCheat[4+i]='.'; } SpriteClear(); ClearGameScreen(SC_BLACK); InitiateSprite(R_InitLogo); InitiateSprite(R_InitTaito0); InitiateSprite(R_InitHeartList); PrintFront(TitleScreen); PrintFront(PrintCredit); if (Money()) GState = GS_MONEY_IN; else GState = GS_TITLE_LOOP_1; break; } case GS_TITLE_LOOP_1: { ChangePalette(P_TITLES,16); Timer=3*Second; if (Money()) GState = GS_MONEY_IN; else GState = GS_TITLE_LOOP_2; break; } case GS_TITLE_LOOP_2: /* loop on Timer static logo */ { if (Money()) GState = GS_MONEY_IN; else if (Timer>0) { CheatCodes(); } else { Timer = 7*Second; GState = GS_TITLE_LOOP_3; #if TEST_DEMO GState = GS_START_DEMO; #endif } break; } case GS_TITLE_LOOP_3: /* loop on Timer while cycling logo */ { if (Money()) GState = GS_MONEY_IN; else if (Timer>0) { CycleLogo(); CheatCodes(); } else { ChangePalette(P_BLACK,16); GState = GS_TITLE_LOOP_FADE; } break; } case GS_TITLE_LOOP_FADE: { if (Money()) GState /* AMP.C -------------------------------------------------------------------*/ #include <stdlib.h> #include <string.h> #include "GLibrary.h" /* Graftgold library header */ #include "amp.h" #include "game.h" #include "fruit.h" #include "resident.xxh" #include "text.h" #include "sound.h" #include "pal.h" #include "plot.h" #include "support.h" /* for definition of 'Second' */ extern UWORD _RandomNumber(); extern SWORD _SinCos(); extern SWORD Sine14; extern SWORD Cosine14; extern UWORD PlotTimer; /* NEW 22/3/96 KH */ /* purge objects further than this from visible screen (in pixels) */ #define PURGE_RANGE (7*8) /* In pixels */ #define Limit(p1,p2,p3) {if (p1<(p2)) p1=(p2);else if (p1>(p3)) p1=(p3);} /* -------------------------------------------------------------------------- RandNum -------------------------------------------------------------------------- */ UDWORD RandNum(void) { return(Rndm()); /* use GLIB machine independent random number routine */ } void FrameUpdate(void); UWORD AutoAnimate(SPRITE *Current); PLAYER *AddStatistic(UWORD d0); SPRITE *ActivateAMP(AMPDATA *a0); SPRITE *ActivateAMPRES(UWORD Res); static void JumpOption(void); static void StepOption(void); static void SearchOption(void); UWORD OriginUnder(SWORD d0, SWORD d1, UWORD **Pa0, UWORD **Pa1); void IdentifyCellLeft(SWORD d0); void IdentifyCellRight(SWORD d0); UWORD CellCheck(SWORD d4,SWORD d5,SWORD d6,UWORD **pa0,UWORD **pca0); static void UnplotCollision(void); static void PlotCollision(void); static void PreparePlayer(PLAYER *a0,UWORD d0,UWORD d1); static void Stop(void); static void WalkCheckX(void); static void WalkCheckY(void); static void JumpUpCheckX(void); static void JumpDownCheckX(void); static void FallCheckX(void); static void FallCheckY(void); static void RideCheckX(void); static void RideCheckY(void); static void CrashCheck(void); void ActivateSpecial(void); static void IdentifyCell( SWORD d0); static void AddDiamond(SWORD d0,UWORD d6,UWORD d7); void MDelay(void); void MLoop(void); void MEndLoop(void); void MPolarSpeed(void); void MPolarTurn(void); void MPolarFace(void); void MPolarAccelerate(void); void MMoveUpdate(void); void MRandomMask(void); void MGotoDependingOn(void); void MMouseDirect(void); void MJoystickDirect(void); void MAnimate(void); void MFrame(void); void MPlot(void); void MAVector(void); void MMVector(void); void MLVector(void); void MFollow(void); void MBoundaryX(void); void MBoundaryY(void); void MDie(void); void MMasterUpdate(void); void MMapRelative(void); void MPriority(void); void MModifyField(void); void MActivate(void); void MCollision(void); void MSetFlag(void); void MClearFlag(void); void MPassThrough(void); void MCollisionWith(void); void MRelativeUpdate(void); void MCheckFlag(void); void MPushScroll(void); void MDeclareBlock(void); void MScore(void); void MTransform(void); void MSetField(void); void MSeaRise(void); void MBaseFace(void); void MDrownCheck(void); void MManualMode(void); void MPlayerMove(void); void MPlayerFace(void); void MAlternatePlot(void); void MPlayerFire(void); void MSeaFall(void); void MReleaseBlock(void); void MGoalCheck(void); void MFosterParent(void); void MCountRainbows(void); void MStarFire(void); void MForceScroll(void); void MClearParentFlag(void); void MRelativeBoundaryX(void); void MRelativeBoundaryY(void); void MQuickSetSpeed(void); void MCheckExternalFlag(void); void MGetRainbowBlock(void); void MFreeRainbowBlock(void); void MBuildCell(void); void MDeleteRainbowCells(void); void MCheckPlayerFlag(void); void MHideCells(void); void MPurgeRainbow(void); void MPlayerRestart(void); void MSetTimer(void); void MSetExternalFlag(void); void MClearExternalFlag(void); void MGetParentPosition(void); void MAllocateRainbowBlock(void); void MPrintPosition(void); void MAllocateFruitBlock(void); void MGetFruitBlock(void); void MFreeFruitBlock(void); void MDeallocateFruitBlock(void); void MDeleteFruitCells(void); void MFruitSearch(void); void MBuildSprite16(void); void MGetFruitNumber(void); void MGetScoreNumber(void); void MGravity(void); void MFruitFrame(void); void MFruitKill(void); void MSetGoalIn(void); void MFireGoalIn(void); void MFallCheck(void); void MMapLocate(void); void MGetChestBlock(void); void MFruitTrajectory(void); void MForceScoreNumber(void); void MRoundX(void); void MBigBossCheck(void); void MCheckRound(void); void MCheckScroll(void); void MAcknowledgeSpecial(void); void MPlayerFeatures(void); void MScaleSpeed(void); void MCrashOption(void); void MQuickModifySpeed(void); void MTransferSprite(void); void MSnapPositions(void); void MClearBusy(void); void MSetUsed(void); void MBranchSet(void); void MGetHiddenNumber(void); void MSetPlayerFlag(void); void MRangeField(void); void MFourFruit(void); void MPalette(void); void MBigFruitTrajectory(void); void MCancelOptions(void); void MStarControl(void); void MStarRelease(void); void MBlanketCollision(void); void MRainbowActivate(void); void MGetPrintPosition(void); void MStatistic(void); void MSpinFrames(void); void MPowerBar(void); void MProduce(void); void MStretch(void); void MFaceSpeedX(void); void MSetParentFlag(void); void MRelativeBounceX(void); void MRelativeBounceY(void); void MHitPlayer(void); void MLocatePlayerX(void); void MMeanieSpeedX(void); void MAcknowledgeBigSpecial(void); void MAdvanceToSeven(void); void MAdvanceToEight(void); void MDropTurnCheck(void); void MDropCheck(void); void MBlockCheck(void); void MGetBigScoreNumber(void); void MKillUpdate(void); void MKill(void); void MFirstIsland(void); void MPositionIsland(void); void MShowGem(void); void MQuickModifyPosition(void); void MHitBigBoss(void); void MOpenSilverDoor(void); void MMeanieSmallFace(void); void MMeanieLargeFace(void); void MMeanieSmallFrame(void); void MMeanieLargeFrame(void); void MMeanieSmallBase(void); void MMeanieLargeBase(void); void MHitBossOnce(void); void MRainbowRide(void); void MSlopeFrame(void); void MLocatePlayerY(void); void MMeanieSpeedY(void); void MLocatePlayerPolar(void); void MMeanieSpeedPolar(void); void MHurryAngry(void); void MPurgeCheck(void); extern void MRainbowTouch(void); void MPlayerAboveBy(void); void MRelativeStopX(void); void MLandSearchUp(void); void MWebAlign(void); extern void MRainbowBounce(void); void MSideBounce(void); void MGenerate(void); void MSelectPolarX(void); void MReadyToFire(void); void MRainbowReverse(void); void MPolarVeer(void); void MBeeMove(void); void MFireOption(void); void MShootRainbow(void); void MPurgeCheckOption(void); void MHitSolid(void); void MGroundCheck(void); void MSpeedXFace(void); void MStorePlayerY(void); void MLevelWithPlayerY(void); void MTopBounce(void); void MFanFire(void); void MReverseSpeedX(void); void MReverseSpeedY(void); void MCheckFruitSlots(void); void MRainbowContact(void); void MSelectiveFruitKill(void); void MSideHit(void); void MSound(void); void MMultiSFX(void); void MExtendPoints(void); void MTune(void); void MTheme(void); void MFastTheme(void); void MBigBossTheme(void); void MDisplayHearts(void); void MHurrySound(void); void MCompletedSound(void); void MShowPower(void); void MRemoveLife(void); void MCameraShake(void); void MPanHurrySound(void); void MEndCompletedSound(void); UWORD GlobalCollision=0; /* Global class of collision */ SPRITE SpriteReady; SWORD IDRainbowY=0; SWORD IDRainbowCell=0; SWORD IDRainbowOffset=0; /* ------------------------------------------------------------------------- ; ; Master co-ordination blocks, link sprite control blocks to cell cluster ; DefineBlock must be first to initialise cell offset, followed by all ; rainbow blocks, then all fruit blocks. ; ;------------------------------------------------------------------------- */ RAINBOW RainbowBlock12={0,0,0,0, 221}; RAINBOW RainbowBlock11={&RainbowBlock12,0,0,0,201}; RAINBOW RainbowBlock10={&RainbowBlock11,0,0,0,181}; RAINBOW RainbowBlock9={&RainbowBlock10,0,0,0,161}; RAINBOW RainbowBlock8={&RainbowBlock9,0,0,0,141 }; RAINBOW RainbowBlock7={&RainbowBlock8,0,0,0,121 }; RAINBOW RainbowBlock6={&RainbowBlock7,0,0,0,101 }; RAINBOW RainbowBlock5={&RainbowBlock6,0,0,0,81 }; RAINBOW RainbowBlock4={&RainbowBlock5,0,0,0,61 }; RAINBOW RainbowBlock3={&RainbowBlock4,0,0,0,41 }; RAINBOW RainbowBlock2={&RainbowBlock3,0,0,0,21 }; RAINBOW RainbowBlock1={&RainbowBlock2,0,0,0,1 }; FRUIT FruitBlock22 = {0,0,0,0,241 +84,NULL,0}; FRUIT FruitBlock21 = {&FruitBlock22,0,0,0,241 +80,NULL,0}; FRUIT FruitBlock20 = {&FruitBlock21,0,0,0,241 +76,NULL,0}; FRUIT FruitBlock19 = {&FruitBlock20,0,0,0,241 +72,NULL,0}; FRUIT FruitBlock18 = {&FruitBlock19,0,0,0,241 +68,NULL,0}; FRUIT FruitBlock17 = {&FruitBlock18,0,0,0,241 +64,NULL,0}; FRUIT FruitBlock16 = {&FruitBlock17,0,0,0,241 +60,NULL,0}; FRUIT FruitBlock15 = {&FruitBlock16,0,0,0,241 +56,NULL,0}; FRUIT FruitBlock14 = {&FruitBlock15,0,0,0,241 +52,NULL,0}; FRUIT FruitBlock13 = {&FruitBlock14,0,0,0,241 +48,NULL,0}; FRUIT FruitBlock12 = {&FruitBlock13,0,0,0,241 +44,NULL,0}; FRUIT FruitBlock11 = {&FruitBlock12,0,0,0,241 +40,NULL,0}; FRUIT FruitBlock10 = {&FruitBlock11,0,0,0,241 +36,NULL,0}; FRUIT FruitBlock9 = {&FruitBlock10,0,0,0,241 +32,NULL,0}; FRUIT FruitBlock8 = {&FruitBlock9 ,0,0,0,241 +28,NULL,0}; FRUIT FruitBlock7 = {&FruitBlock8 ,0,0,0,241 +24,NULL,0}; FRUIT FruitBlock6 = {&FruitBlock7 ,0,0,0,241 +20,NULL,0}; FRUIT FruitBlock5 = {&FruitBlock6 ,0,0,0,241 +16,NULL,0}; FRUIT FruitBlock4 = {&FruitBlock5 ,0,0,0,241 +12,NULL,0}; FRUIT FruitBlock3 = {&FruitBlock4 ,0,0,0,241 + 8,NULL,0}; FRUIT FruitBlock2 = {&FruitBlock3 ,0,0,0,241 + 4,NULL,0}; FRUIT FruitBlock1 = {&FruitBlock2 ,0,0,0,241 + 0,NULL,0}; #define DefineRainbow(p1) \ {0,0,NULL,0xa8,0 ,0,p1}, \ {0,0,NULL,0xa8,1 ,0,p1}, \ {0,0,NULL,0x2c,2 ,0,p1}, \ {0,0,NULL,0xac,3 ,0,p1}, \ {0,0,NULL,0xac,4 ,0,p1}, \ {0,0,NULL,0xa8,5 ,0,p1}, \ {0,0,NULL,0x2a,6 ,0,p1}, \ {0,0,NULL,0xa0,7 ,0,p1}, \ {0,0,NULL,0xa0,8 ,0,p1}, \ {0,0,NULL,0xa4,9 ,0,p1}, \ {0,0,NULL,0x2e,10,0,p1}, \ {0,0,NULL,0xac,11,0,p1}, \ {0,0,NULL,0xab,12,0,p1}, \ {0,0,NULL,0xa0,13,0,p1}, \ {0,0,NULL,0xa4,14,0,p1}, \ {0,0,NULL,0xaf,15,0,p1}, \ {0,0,NULL,0xab,16,0,p1}, \ {0,0,NULL,0xa0,17,0,p1}, \ {0,0,NULL,0xa4,18,0,p1}, \ {0,0,NULL,0xaf,19,0,p1}, //#define DefineFruit(p1) \ // {0,0,0,0x80,0,0,p1}, \ // {0,0,0,0x80,1,0,p1}, \ // {0,0,0,0x80,2,0,p1}, \ // {0,0,0,0x80,3,0,p1}, #define RainbowCells (&CellBlocks[1]) CELL CellBlocks[]= { {0,0,0,0,0,0,0}, /* Dummy zero entry */ /* RainbowCells */ DefineRainbow(&RainbowBlock1) DefineRainbow(&RainbowBlock2 ) DefineRainbow(&RainbowBlock3 ) DefineRainbow(&RainbowBlock4 ) DefineRainbow(&RainbowBlock5 ) DefineRainbow(&RainbowBlock6 ) DefineRainbow(&RainbowBlock7 ) DefineRainbow(&RainbowBlock8 ) DefineRainbow(&RainbowBlock9 ) DefineRainbow(&RainbowBlock10) DefineRainbow(&RainbowBlock11) DefineRainbow(&RainbowBlock12) }; RAINBOW ActiveRainbows={&ActiveRainbows,&ActiveRainbows}; UWORD InactiveRainbowCount=12; RAINBOW InactiveRainbows={&RainbowBlock1}; UWORD InactiveFruitCount=22; FRUIT ActiveFruit={&ActiveFruit,&ActiveFruit}; FRUIT InactiveFruit ={&FruitBlock1}; /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* ^^^^THIS SECTION DUMMIES LOADED RESIDENT.DAT ^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ extern Memory *Resident; extern AMPDATA **ResAMPs; /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* this bit builds REFS for AMPs CSizes and CBlocks */ #include "amptable.h" /* this bit builds DEFS for AMPs CSizes and CBlocks */ #define XXHDEF #include "amptable.h" /* ;--------------------------------------------------------------------------- */ /* ; Imports */ /* XRef Print,Charac,PrintOne,Print_X,Print_Y */ /* XRef PlotMethods */ /* XRef ExternalFlags */ /* XRef SystemFlags,GameFlags,GameFlags2,GameFlags3 */ /* XRef CharacterBuffer,ScoreMaintain,CharacterReal */ /* XRef MasterX,MasterY,PlayerNumber,Cheats */ /* */ /* XRef ColourBarReload,ReadySync,ColourReload */ /* XRef ScoreRefresh,SessionEnded,DoorTaken */ /* XRef SuspendSprites,HurryMessage,GoalAchieved */ /* XRef RedStar,YellowStar,ClockTick,GraftgoldStar */ /* XRef GraftgoldKey,BonusInhibit */ /* */ /* XRef JoystickLR,JoystickUD,JoystickFire,JoystickGate */ /* XRef _ScrollRequest,DisplayStatic,JoystickJump */ /* XRef SeaLevel,ClipSea,NormalPalette */ /* XRef CharacterCutoff,AMPWalkFast */ /* XRef AMPStand,AMPWalk,AMPJumpUp,AMPJumpDown,AMPFall */ /* XRef RoundStages,BarrelTop,BuildFrontCharacter */ /* XRef BuildTop,MasterTop,MasterBottom,BuildCharacter */ /* XRef CellBlocks,ActiveRainbows,InactiveRainbows */ /* XRef InactiveRainbowCount */ /* XRef ActiveFruit,InactiveFruit,InactiveFruitCount */ /* XRef FruitCell00,FruitMeanie,SecretY */ /* XRef InitHidden */ /* XRef HiddenValues,ScoreValues */ /* XRef InitLeftStar,InitRightStar,InitCompleted,Init1Up */ /* XRef InitWing1,InitWing2,InitAngelPerm,InitAngelTemp */ /* XRef InitHundredGrand,InitBigCompleted */ /* XRef InitGem,InitGemFrame,InitGemX,InitSmallLifeX */ /* XRef InitClear,InitClearX,InitLargeLife,InitSmallLife */ /* XRef ActiveOption,ForceFruit,CountOption */ /* XRef InitLightning,InitDrugs,InitCup,InitClock */ /* XRef InitFlashing,InitBigMoney */ /* XRef InitFourFruit,InitFourDisplace,InitFourX */ /* XRef InitRedStar,InitRedCourse */ /* XRef InitBalloonControl */ /* XRef InitYellowStar,InitYellowCourse */ /* XRef InitGraftgoldStar,InitGraftgoldCourse */ /* XRef InitWave1,InitWave2,KillCount,KillAccumulate */ /* XRef RoundTime,Players,BigBossPower,BigScoreValues */ /* XRef CompletedCount */ /* */ /* XRef ScreenOne,ScreenTwo */ /* */ /* XRef InitMiniX,InitMiniY,InitMiniFrame */ /* XRef InitNumberX,InitNumberY,InitNumberFrame */ /* XRef InitBigGem,InitGemDisplace,InitSilverDoor */ /* XRef InitFlashingGem,InitHeart */ /* */ /* XRef Mouse,ControlMethod1,ControlMethod2 */ /* */ /* ; Exports */ /* XDef ScreenBase,ScreenSeen,ScreenUnseen,ScreenSaved */ /* XDef ActiveSprites,InactiveSprites,PreservedSprites */ /* XDef PreviousSprite,PendingSprites */ /* If PlotRestore=Yes */ /* XDef RestoreSeen,RestoreUnseen,RestoreStack */ /* EndC */ /* XDef RainbowSeen_1,RainbowUnseen_1 */ /* XDef RainbowSeen_2,RainbowUnseen_2 */ /* XDef RainbowOffset */ /* XDef ShadowPalette,GemPalette,LegendPalette */ /* XDef PlotPriority */ /* XDef CollisionX,CollisionY,CollisionIDList */ /* */ /* XDef GetSpriteBlocks,PlayerOne,PlayerTwo */ /* XDef SetScreenAddr,ScreenAddresses */ /* XDef SetSpriteReady,SpriteReady */ /* XDef RestoreBack,PrimeAll */ /* XDef ClearScreen,ClearGameScreen,CopyScreen */ /* XDef InitiateSprite,InitiateBlock,HexPrint */ /* XDef SpriteClear,SpriteHandler,Feedback */ /* XDef AutoAnimate,PlotSchedule */ /* XDef PlotCollision,UnplotCollision,ReadCollision */ /* XDef FadeIn,FadeOut,FadeRun,FadeSpeed,SetColours */ /* XDef FadeColours,ToColours,FromColours */ /* XDef PreparePlayer,CurrentPlayer,SetPlayers */ /* XDef LargeFrames,LargeFrameFree */ /* XDef FirstLargeLeft,FirstLargeRight,FirstLarge */ /* XDef SmallFrames,SmallFrameFree */ /* XDef FirstSmallLeft,FirstSmallRight,FirstSmall */ /* XDef PlayerStop */ /* XDef WalkCheckX,WalkSpeeds,FallCheckY,OriginUnder */ /* XDef RideCheckX,IdentifyCell,IDRainbowY */ /* XDef RainbowY,IdentifyCellLeft,IdentifyCellRight */ /* XDef CrashCheck,DisplayLives,DisplayGems */ /* XDef ExecuteAnim,ActivateAMP */ /* XDef PlayerStencil,Statistics,BenchPlayer,JumpUpSpeed */ /* XDef ContinuePlayer,ReverseWord,GlobalCollision */ /* XDef DemoPlayer */ /* XDef NextIsland */ /* */ /* ;--------------------------------------------------------------------------- */ /* ; */ /* ; Globals */ /* ; */ /* ;--------------------------------------------------------------------------- */ /* */ /* Section Text */ /* */ SWORD SpriteOldX=0; /* ScreenBase dc.l 0 ; Address of screen to access */ /* ScreenSeen dc.l 0 ; Address of viewed screen */ /* ScreenUnseen dc.l 0 ; Address of unseen screen */ UBYTE *ScreenSaved=0; /* dc.l 0 ; Address of saved background */ SPRITE *ActiveSprites; /* dc.l 0 ; Active sprite link list head */ SPRITE *PendingSprites; /* dc.l 0 ; Newly initiated sprites */ SPRITE *InactiveSprites; /* dc.l 0 ; Inactive sprite link list head */ SPRITE *PreservedSprites; /* dc.l 0 ; Temporarily suspended list */ SPRITE *PreviousSprite; /* dc.l 0 ; Deleted sprite previous entry */ SWORD ActiveSpriteCount; STATSDATA Statistics[]= { {0,3}, /* RubyRing - Shoes */ {0,3}, /* CrystalRing - Red jars */ {0,3}, /* AmethystRing - Yellow jars */ {0,77}, /* Necklace - Rounds */ {0,33}, /* Special - Games */ {0,30}, /* Pentagram - Completions */ {0,2}, /* HolyComet - Crosses */ {0,3}, /* RainbowCross - Any lamps */ {0,2}, /* ThunderCross - Any rings */ {0,27}, /* RainbowDrug - Rounds */ {0,10}, /* MagicalCape - Player deaths */ {0,15}, /* HolyCup - Any jars */ {0,6}, /* PeacockFeather - Hurry messages */ {0,6}, /* CopperRod - Copper crowns */ {0,8}, /* SilverRod - Silver crowns */ {0,10}, /* GoldenRod - Gold crowns */ {0,2}, /* Balloon - Any rods */ {0,8}, /* BookOfWings - Shoes */ {0,3}, /* Clock - Tiaras */ {0,120}, /* BlueTiara - Enemies crushed */ {0,20}, /* GreenTiara - Enemies killed by fairy */ {0,30}, /* RedTiara - Enemies with star */ {0,20}, /* Bell - Rounds */ {0,2}, /* StarRod - Capes */ {0,20}, /* RedLamp - Red stars */ {0,10}, /* YellowLamp - Yellow stars */ {0,5}, /* BlueLamp - Holy cups */ {0,7}, /* RedWand - Red diamonds */ {0,7}, /* OrangeWand - Orange diamonds */ {0,7}, /* YellowWand - Yellow diamonds */ {0,7}, /* GreenWand - Green diamonds */ {0,7}, /* BlueWand - Blue diamonds */ {0,7}, /* IndigoWand - Indigo diamonds */ {0,7}, /* PurpleWand - Purple diamonds */ {0,2}, /* RedHolyWater - Red wands */ {0,2}, /* OrangeHolyWat - Orange wands */ {0,2}, /* YellowHolyWat - Yellow wands */ {0,2}, /* GreenHolyWater - Green wands */ {0,2}, /* BlueHolyWater - Blue wands */ {0,2}, /* IndigoHolyWat - Indigo wands */ {0,2}, /* PurpleHolyWat - Purple wands */ {0,2} /* Key - Feathers */ }; PLAYER *CurrentPlayer=NULL; PLAYER *BenchPlayer=NULL; UDWORD BCDScore=0; SPRITE *PlotPriority[16]; SWORD Feedback=0; /* AMP communication data */ #define MaximumSprites 100 /* System maximum sprites */ #define CollisionGridX (CharacterX*2) /* Map size for collision matrix */ #define CollisionGridY (CharacterY*2) UDWORD BonusLives[]= { 0x00010000, /* First at 100,000 */ 0x00100000, /* Second at 1,000,000 */ 0xffffffff /* High values! No more. */ }; PLAYER PlayerStencil= { 0, /* .Score dc.l $00000000 */ 0, /* .NextLife dc.w 0 ; Index into bonus lives table */ 3, /* .Lives dc.w 3 */ 0, /* .High dc.l 0 */ 0x00999999, /* .Maximum dc.l $00999999 */ 0, /* .Enhancements dc.w 0 */ 0, /* .Permanent dc.w 0 */ 0, /* .Island dc.w 0 ; 0-9 */ 0, /* .Round dc.w 0 ; 0-3 */ 0, /* .Stage dc.w 0 ; 0-3 */ 0,0, /* .BusyUsed dc.b 0,0 */ 0, /* .Diamonds dc.w 0 ; Small diamonds collected */ 0, /* .BigDiamonds dc.w 0 */ 0, /* .Mirrors dc.w 0 */ 0, /* .Secrets dc.w 0 */ 0, /* .NextSpecial dc.w 0 ; Next special to release */ 0,80, /* .HiddenFruit dc.b 0,80 ; Hidden fruit image number */ 0,80, /* .NormalKills dc.b 0,80 ; Enemy kills image number */ 0,8, /* .SpecialIndex dc.b 0,8 ; Next feature */ 0,3, /* .Feature dc.b 0,3 ; Enemy kill count */ 1, /* .Number dc.w 1 ; Player number, 1 or 2 */ 0, /* .Difficulty dc.w 0 ; multiples of 4, max. 28 */ 0, /* .Control dc.w 0 ; Control method */ 0 }; /* */ PLAYER PlayerOne,PlayerTwo; /* ;--------------------------------------------------------------------------- */ /* ; */ /* ; Collision system data area. */ /* ; SpriteClear is responsible for clearing the area between CollisionStart */ /* ; and CollisionEnd. X overspill areas are present as rainbows can project */ /* ; off the side of the screen. Some Y overspill also present to cover */ /* ; any mishaps. */ /* ; */ /* ;--------------------------------------------------------------------------- */ SPRITE *CollisionIDList[32]; typedef struct COLLISION { UDWORD Start[64*2]; /* X overspill left */ UDWORD X[CollisionGridX]; /* Collision grid size, to 4 pixel */ UDWORD XOSR[64*2]; /* X overspill right */ UDWORD YOST[32*2]; /* Y overspill top */ UDWORD Y[CollisionGridY]; /* resolution. */ UDWORD YOSB[32*2]; /* Y overspill bottom */ } COLLISION; /* CollisionEnd */ COLLISION CollTable; #define CollisionX (&CollTable.X[0]) #define CollisionY (&CollTable.Y[0]) /* ;--------------------------------------------------------------------------- */ UBYTE Diamond [130]; /*************************************************************************** InitAngles constructs x+y secant tables *****************************************************************************/ void InitAngles(void) { SWORD XPos,YPos; UBYTE Angle ; UBYTE Last ; UWORD Value; UWORD Entry; UWORD Count; for (Value = 0; Value < 130 ; Value ++) Diamond [Value] = 255; for (Angle= 0; Angle < 128; Angle ++ ) { _SinCos (Angle) ; if (Sine14<0) YPos = 0-Sine14; else YPos = Sine14; if (Cosine14<0) XPos = 0-Cosine14; else XPos = Cosine14; Value = (XPos + YPos ) ; Value = 64 + ( Cosine14 / (Value >> 6 )) ; Diamond [Value] = Angle; } /*** fill in gaps ***/ Last = 128; Diamond [0] = 128; Diamond [128] = 0 ; for (Value = 0; Value < 130 ; Value ++) { Angle= Diamond [Value]; if (Angle == 255) { Count = 0; for (Entry = Value; (Entry < 130) && (Diamond [Entry] == 255); Entry++) { Angle = Diamond [Entry]; Diamond [Entry] = Last; Count+=1; } Last= Diamond [Entry]; /* get next value to back copy */ Count = Count >> 1; if (Count >0) { Entry -= Count; for (Value= Entry ; Count > 0 ; Value ++) { Count -= 1; Diamond [Value] = Last; } } } Last = Diamond [Value]; } } /*************************************************************************** GetAngle(SWORD X,SWORD Y) looks up angles *****************************************************************************/ UWORD GetAngle(SWORD X,SWORD Y) { SWORD XPos; SWORD YPos; SDWORD Value; UWORD Angle; if (X<0) XPos = 0-X; else XPos = X; if (Y<0) YPos = 0-Y; else YPos = Y; Value = (XPos +YPos); if (Value>0) { Value = (( X << 6) / Value)+64; Angle = (Diamond [Value]); if (Y<0) Angle = 0-Angle; } else { Angle = 0 ; /*for x y zero case */ } return (Angle); } /*************************************************************************** GetDistance(SWORD X,SWORD Y) calcs distance using |X| +|Y| halfing lowest factor *****************************************************************************/ UDWORD GetDistance(SWORD X,SWORD Y) { if (X<0) /* make each positive */ X = 0-X; if (Y<0) Y = 0-Y; if (X>Y) /* half smallest component */ Y>>=1; else X>>=1; return (X+Y); } /*-------------------------------------------------------------------------- SinCos ; ; Calculate Sine and Cosine of binary angle ; ; Expects d0 angle ; ; Returns d0 sine {14 bp} ; d1 cosine {14 bp} ;KH NOW defined as a macro - assumes d0,d1 defined as SWORDS ;--------------------------------------------------------------------------*/ #define SinCos(angle) _SinCos(angle); d0=Sine14; d1=Cosine14; /* ------------------------------------------------------------------------- AngleDistance ; ; Angle and Distance Calculator ; ; Expects d0,d1 W Vector ; ; Returns d0 W Binary angle ; d1 W Distance ; ;KH NOW defined as a macro - assumes d0,d1 defined ;------------------------------------------------------------------------- */ #define AngleDistance(x,y) d1=GetDistance(x,y); d0=GetAngle(x,y); /* ------------------------------------------------------------------------ */ /* --------------------------------------------------------------------------- ContinuePlayer --------------------------------------------------------------------------- */ void ContinuePlayer(void) { CurrentPlayer->PlayerPerm |= Continued; PreparePlayer(CurrentPlayer,sizeof(CONTINUEPLAYER),CurrentPlayer->PlayerNumber); /* 12=number of bytes to copy*/ GameFlags |= ScoreRefresh; ScoreMaintain(); } /* --------------------------------------------------------------------------- SetPlayers --------------------------------------------------------------------------- */ void SetPlayers(void) { BenchPlayer= &PlayerTwo; if (Players == 1) { PlayerTwo.PlayerLives = (-1); } else { PreparePlayer(&PlayerTwo,sizeof(PLAYER),2); /* a0,d0,d1 */ // PlayerTwo.PlayerControl = ControlMethod2; PlayerTwo.PlayerScoreX = 29*8; GameFlags |= ScoreRefresh; ScoreMaintain(); } PreparePlayer(&PlayerOne,sizeof(PLAYER),1); /* a0,d0,d1 */ // PlayerOne.PlayerControl = ControlMethod1; PlayerOne.PlayerScoreX = 1*8; GameFlags |= ScoreRefresh; ScoreMaintain(); } /* --------------------------------------------------------------------------- DemoPlayer --------------------------------------------------------------------------- */ void DemoPlayer(void) { PreparePlayer(&PlayerOne,sizeof(PLAYER),1); /* a0,d0,d1 */ PlayerOne.PlayerScoreX = 1*8; GameFlags |= ScoreRefresh; ScoreMaintain(); } /* --------------------------------------------------------------------------- PreparePlayer --------------------------------------------------------------------------- */ void PreparePlayer(PLAYER *a0,UWORD d0,UWORD d1) { PLAYER *a1; UWORD PlayerControl; CurrentPlayer = a0; a1= &PlayerStencil; PlayerControl = a0->PlayerControl; /* Preserve this */ memcpy(a0,a1,d0); /* copy d0 bytes from stencil */ a0->PlayerNumber = d1; a0->PlayerBusy = d1-1; a0->PlayerUsed = d1+1; a0->PlayerControl = PlayerControl; /* Restore it */ AddStatistic(_Stats33rd); } /* ------------------------------------------------------------------------- GetSpriteBlocks ; ; Allocate all required sprite blocks ; and build them into a linked list with InactiveSprites as header ;------------------------------------------------------------------------- */ void GetSpriteBlocks() { SysError *Err=NULL; int i; SPRITE *Current; if((Err=MemoryClaim(MainMem,MaximumSprites*sizeof(SPRITE), &SpBlocks,NULL,MEM_FIXED_ADDRESS,"SP:INACTIVE")) == NULL) { Current=(SPRITE *)SpBlocks->Address; InactiveSprites=NULL; for (i=0 ; i<MaximumSprites ;i++) { Current->SpriteLink = InactiveSprites; Current->SpriteID = 0; InactiveSprites = Current++; } } } /* ------------------------------------------------------------------------ SetSpriteReady ------------------------------------------------------------------------ */ void SetSpriteReady(void) { SpriteReady.SpriteFlags = 0; /* Required */ SpriteReady.SpriteLife = -0x8000; /* Required */ SpriteReady.SpritePlot = _NullPlot;/* Safety */ SpriteReady.SpritePriority = 0; /* Safety */ SpriteReady.SpriteOffset = 0, /* Required */ SpriteReady.SpriteParent = 0; /* Required */ SpriteReady.SpriteBase2 = 0; /* Required */ SpriteReady.SpriteDisplace = 0; /* Safety */ SpriteReady.SpriteAnimate = (-1); /* Required */ SpriteReady.SpriteARC = 0; /* Required */ SpriteReady.SpriteMRepeat = 0; /* Required */ SpriteReady.SpriteMPC = 0; /* Required */ SpriteReady.SpriteAIRQ = 0x7fff; /* Required */ SpriteReady.SpriteMIRQ = 0x7fff; /* Required */ SpriteReady.SpriteLVector = 0; /* Required */ SpriteReady.SpriteCID = (-1); /* Required */ SpriteReady.SpriteCMask = 0x0000; /* Safety */ SpriteReady.SpriteCResult = 0; /* Required */ SpriteReady.SpriteCSize = 0; /* Safety */ SpriteReady.SpriteClass = 0; /* Safety */ } /* ;--------------------------------------------------------------------------- */ /* ;-------------------------------------------------------------------------- HexPrint ; ; Convert a hex value 0-99 into two ASCII characters and store them for ; printing, with zero suppression on the top digit. ; ; Requirements: d0 - word value to convert ; d1 - Zero suppression pad character in ASCII or control code ; a0 - location to store result at ;xx Returns: a0 - unchanged ; ; Note that the result is stored as bytes and so a0 may be passed as an ; odd address. ; ;--------------------------------------------------------------------------- */ void HexPrint(UBYTE *a0,UWORD d0,UWORD d1) { UWORD Temp; Temp=d0/10; if (Temp==0) Temp=d1; /* zero supress */ else Temp|=0x30; *a0++ = Temp; /* tens */ *a0 = (d0%10)|0x30; /* units */ } /* ;------------------------------------------------------------------------- SpriteClear ; ; Kill all allocated or pending sprites by moving them to the inactive list, ; clear the collision matrix, and the collision ID list, ; deallocate all active rainbow and fruit blocks, ; clear all 'Declared' blocks. ; ;-------------------------------------------------------------------------- */ void SpriteClear(void) { int i; SPRITE *a1; GameFlags &= ~SuspendSprites; memset(&CollTable,0,sizeof(COLLISION)); /* clear entire collision table */ for (i=0;i<32;i++) CollisionIDList[i]=0; /* clear ID List */ InactiveRainbowCount=12; InactiveFruitCount=22; /* Fruit: */ { FRUIT *a0,*a1,*a2,*a3; while (1) { a1=ActiveFruit.FruitFLink; a0= (&ActiveFruit); if (a1 == a0) break; a2=a1->FruitFLink; a3=a1->FruitBLink; a3->FruitFLink = a2; a2->FruitBLink = a3; a0= (&InactiveFruit); a1->FruitFLink=a0->FruitFLink; a0->FruitFLink=a1; } } /* Rainbow: */ { RAINBOW *a0,*a1,*a2,*a3; while (1) { a1=ActiveRainbows.RainbowFLink; a0= (&ActiveRainbows); if (a1 == a0) break; a2=a1->RainbowFLink; a3=a1->RainbowBLink; a3->RainbowFLink = a2; a2->RainbowBLink = a3; a0= (&InactiveRainbows); a1->RainbowFLink=a0->RainbowFLink; a0->RainbowFLink=a1; } } /* .Control */ for (i=0;i<NumberOfCBlocks;i++) ControlBlocks[i]=0; /* .Sprite */ ActiveSpriteCount = 0; while ((a1 = ActiveSprites)) { ActiveSprites = a1->SpriteLink; a1->SpriteLink = InactiveSprites; InactiveSprites = a1; a1->SpriteID=0; } /* .Pending */ while ((a1 = PendingSprites)) { PendingSprites = a1->SpriteLink; a1->SpriteLink = InactiveSprites; InactiveSprites = a1; a1->SpriteID=0; } } AMPDATA d2; /* NB Error value returned from MRoutines */ SPRITE *a2; AMPDATA *ad; AMPDATA *a3; /* ;--------------------------------------------------------------------------- SpriteHandler ; ; Main manoeuvre-program-driven object handler. Updates all active objects ; and queues them according to their display priority. The PlotSchedule ; routine will plot all objects once they are 'sorted' into priority ; sequence. ; ;--------------------------------------------------------------------------- */ void SpriteHandler(void) { AMPDATA *Next; AMPDATA Com; UDWORD d0; AMPDATA d3; AMPDATA d4; AMPDATA d5; /*AMPDATA d6; Removed 19/4/96 - AB, optimised out */ void (*a4)(); memset(PlotPriority,0,16*sizeof(SPRITE *)); /* zeroise PlotPriority array */ PreviousSprite = (SPRITE *)&ActiveSprites; a2 = ActiveSprites; while (a2 != NULL) { if (((GameFlags & SuspendSprites) != 0) && ((a2->SpriteFlags & SuspendExempt) == 0)) { /* If suspend on and not exempt */ goto Plot; /* then plot only, no action */ } UnplotCollision(); /* .Collision */ if (a2->SpriteCResult & a2->SpriteCMask) { a2->SpriteMPC = a2->SpriteCVector; a2->SpriteMRepeat = 0; goto Instruct; } if (a2->SpriteLife >= 0) { a2->SpriteLife--; if ((a2->SpriteLife < 0) && (a2->SpriteLVector != 0)) { a2->SpriteMPC = a2->SpriteLVector; a2->SpriteMRepeat = 0; goto Instruct; } } if (a2->SpriteARC >= a2->SpriteAIRQ) { a2->SpriteMPC = a2->SpriteAVector; a2->SpriteMRepeat = 0; } Instruct: /* Manoeuvre program */ if (a2->SpriteMRepeat) goto More; Next=a2->SpriteMPC; /* NextInstruct: */ Com = *Next; if (Com < 0) { a2->SpriteMSavePC = Next; a2->SpriteMRepeat = 1; goto More; } if ((Com & (1<<13))) Com = 1 + ((Com & 0x03ff) & RandNum()); a2->SpriteMRepeat = Com; More: a3 = a2->SpriteMSavePC; d3 = *(a3+0); /* d4 = *(a3+1); d5 = *(a3+2); d6 = *(a3+3); */ a4 = AMPList[ ((d3 & 0x03fc)>>2) ]; /* address of routine */ if (a2->SpriteMRepeat & (1<<14)) /* Forever? */ goto Remaining; if (--a2->SpriteMRepeat) goto Remaining; if (d3 < 0) { a3 = a2->SpriteMPC; a2->SpriteMPC += 1+(d3 & 0x0003); } else { a2->SpriteMPC ++; /* NB ++ will advance by 2 bytes! */ } Remaining: /* =================================================================== */ d2=0; switch ((d3 & 0x03fc)>>2) { case _Delay: /* MDelay */ { break; } case _Goto: /* Mgoto */ { d4 = *(a3+1); a2->SpriteMPC = a3 + (d4/2); /* NB d4 is byte offset /2 for AMPDATA offset */ a2->SpriteMSavePC = a2->SpriteMPC; break; } case _Activate: /* MActivate */ { d4 = *(a3+1); ActivateAMP(a3 + (d4/2)); break; } case _Animate: /* MAnimate */ { UBYTE cd3; d4 = *(a3+1); d5 = *(a3+2); cd3 = d3 & 0x0003; if (cd3 <2 ) a2->SpriteAnimate = d4; /* Stop/start auto animation */ else { if (cd3 == 3) { a2->SpriteBase = d5; a2->SpriteBase2= 0; } a2->SpriteList = ((UBYTE *)a3) + d4; /* new list addr */ a2->SpriteOffset=0; a2->SpriteAnimate=0; /* force read */ } break; } case _AVector: /* MAVector */ { d4 = *(a3+1); d5 = *(a3+2); a2->SpriteAVector = a3+(d5>>1); a2->SpriteAIRQ = d4; break; } case _MVector: /* MMVector */ { d4 = *(a3+1); d5 = *(a3+2); a2->SpriteMVector = a3+(d5>>1); a2->SpriteMIRQ = d4; break; } case _LVector: /* MLVector */ { d4 = *(a3+1); if (d4) a2->SpriteLVector = a3+(d4>>1); else a2->SpriteLVector = 0; break; } case _CVector: /* MCVector */ { d4 = *(a3+1); d5 = *(a3+2); if (d5) { /* CVECTOR ON */ a2->SpriteCVector = a3 + (d5>>1); a2->SpriteCMask = d4; } else { /* 0 if CVECTOR OFF */ a2->SpriteCVector = NULL; a2->SpriteCMask = 0; } break; } case _Transform: /* MTransform */ { d4 = *(a3+1); InitiateBlock(a2,a3+(d4>>1),sizeof(SPRITE)); break; } case _GDOMarker: /* GDOMarker */ { break; } default: { ad = a3+1; a4(); /* Execute instruction */ break; } } /* =================================================================== */ if (a2->SpriteMIRQ == d2) /* Check return code */ { a2->SpriteMPC = a2->SpriteMVector; a2->SpriteMRepeat = 0; goto Instruct; } if (!(d3 & (1<<14))) /* Check delay bit */ goto Instruct; /* Unset, do next instruction */ if (a2->SpriteID) /* Deleted if ID set to zero */ goto Animate; /* Delete: */ PreviousSprite->SpriteLink = a2->SpriteLink; /* unlink from active */ a2->SpriteLink = InactiveSprites; InactiveSprites = a2; /* and link into inactive */ ActiveSpriteCount--; a2=PreviousSprite; goto Next; Animate: a2->SpriteARC = AutoAnimate(a2); /* Return code for next time */ PlotCollision(); Plot: d0 = (a2->SpritePriority & 0x003c) >>2; /* convert to index */ a2->SpritePlotLink = PlotPriority[d0]; PlotPriority[d0] = a2; Next: PreviousSprite = a2; a2 = a2->SpriteLink; } } /**************************************************************************** PlotSchedule Plot all objects queued into the sixteen priority link lists. Any objects of the same priority will be plotted in last in, first out sequence. Each object has its own designated plot method which are listed in the PlotMethods table. ****************************************************************************/ void PlotSchedule(void) { int i; PlotTimer++; /* used by flashing objects (see malternateplot) */ InitPlotList(); for (i=0;i<16;i++) /* for each plot priority (up to 16 used!) */ { a2=PlotPriority[i]; while (a2) { switch (a2->SpritePlot) { case _FullPlot16: case _FullPlot32: case _AlignedPlot16: case _AlignedPlot32: case _TwoFullPlot32: case _TwoAlignedPlot32: case _FourFullPlot32: { FullPlot(); break; } case _CutPlot16: case _CutPlot32: case _TwoCutPlot32: { CutPlot(); break; } case _VLinePlot16: case _VLinePlot32: { VLinePlot(); break; } case _PlayerPlot32: { PlayerPlot(); break; } case _RainbowPlot: /* Ready for semi-trans on PSX */ { RainbowPlot(); break; } } a2=a2->SpritePlotLink; } } PlotFrontDrop(); FinishPlotList(); } /* ;--------------------------------------------------------------------------- ExecuteAnim ; ; As MAnimate but using a loaded resident animation list as the source ; ;----------------------------------------------------------------------- */ void ExecuteAnim(UWORD d0) { a2->SpriteList = (UBYTE *)(*(ResAMPs+d0)); /* new list addr */ a2->SpriteOffset=0; a2->SpriteAnimate=0; /* force read */ } /* ;--------------------------------------------------------------------------- ActivateAMP ; ; Single sprite activate from an AMP instruction. Faster and more readable ; than using ExecuteAnim on an Activate instruction. ; ; Requirements: a0 - pointer to initiation block ; Expects: a2 - current sprite block pointer ; Returns: d2 - return code of 12 if failed, else unaffected ; a1 - Pointer to new child sprite, or 0 if failed ; ;-------------------------------------------------------------------------- */ SPRITE *ActivateAMP(AMPDATA *a0) { SPRITE *a1; if (!(a1=InactiveSprites)) { d2=12; return(a1); /* ; No sprites available */ } InactiveSprites = a1->SpriteLink; /* remove first from inactive list */ memcpy(a1,&SpriteReady,sizeof(SPRITE)); a1->SpriteLink = a2->SpriteLink; a2->SpriteLink = a1; /* insert into active list */ a0=InitiateBlock(a1, a0, sizeof(SPRITE)); ActiveSpriteCount++; a1->SpriteParent= a2; if (!a1->SpriteMPC) /* ; MPC set */ a1->SpriteMPC = a0; /* ; no, assume AMP follows */ return(a1); } /* ;--------------------------------------------------------------------------- ActivateAMPRES ; ; Single sprite activate from an AMP instruction. Faster and more readable ; than using ExecuteAnim on an Activate instruction. ; ; Requirements: d0 - index of initiation block ; Expects: a2 - current sprite block pointer ; Returns: d2 - return code of 12 if failed, else unaffected ; a1 - Pointer to new child sprite, or 0 if failed ; ;-------------------------------------------------------------------------- */ SPRITE *ActivateAMPRES(UWORD d0) { return(ActivateAMP(*(ResAMPs+d0))); } /* -------------------------------------------------------------------------- AutoAnimate ; ; Object auto animation system. Objects with a +ve animation count have ; it decremented. A zero result causes the next frame in the animation ; list to be fetched and added to the base frame to produce the actual ; animation frame. ; Rainbows also use this system but instead of the base and displacement ; producing a frame number, the displacement is used to displace into the ; colour list of the base frame for 'rotating rainbow effects'. ; ;--------------------------------------------------------------------------- */ UWORD AutoAnimate(SPRITE *Current) { UWORD Ret=0; /* Return code assumed zero */ UBYTE *a0; SWORD d0; UBYTE d1; if (Current->SpriteAnimate >= 0) { if (Current->SpriteAnimate > 0) { Current->SpriteAnimate--; return(Ret); } Ret = 4; /* Return code at least 4 */ a0 = Current->SpriteList; d0 = Current->SpriteOffset; while (Ret < 16) /* Stop interpretting on disaster */ { d1 = *(a0+d0); /* d1=frame or command */ if (!(d1 & 0x80)) { Current->SpriteDisplace = (d1); /* extract frame offset */ Current->SpriteFrame = Current->SpriteBase + Current->SpriteBase2 + Current->SpriteDisplace; Current->SpriteAnimate = *(a0+d0+1); /* extract frame count */ Current->SpriteOffset = d0+2; return(Ret); } d0 +=2; switch (d1) { case EndList: { /* Restart list, rc=8 */ d0 = 0; Ret = 8; break; } case NewList: { /* Switch list, rc=12 */ a0 += d0 + *((SWORD *)(a0+d0)); Current->SpriteList = a0; d0 = 0; Ret = 12; break; } case EndAuto: { /* Auto-animation off, rc=16 */ Current->SpriteAnimate = (-1); Ret = 16; break; } case ALeft: { a2->SpriteDepth |= XFLIP; break; } case ARight: { a2->SpriteDepth &= ~XFLIP; break; } case AUp: { a2->SpriteDepth &= ~YFLIP; /* Changed AB 8/3/96 */ break; } case ADown: { a2->SpriteDepth |= YFLIP; /* Changed AB 8/3/96 */ break; } } } } return(Ret); } /* ----------------------------------------------------------------------- UnplotCollision ; ; Collision matrix maintaining routines. ; ;------------------------------------------------------------------------- */ void UnplotCollision(void) { SWORD d0,d2; SDWORD d1; UWORD *a0; UDWORD *a1; if ((d0=a2->SpriteCID) >=0 ) /* -ve = Read only, don't need to unplot as entry was not written */ { d1 = (-1); d1 &= ~(1<<d0); /* bclr.l d0,d1 */ a0 = (UWORD *)a2->SpriteCSize; a1 = CollisionX; d0 = a2->SpriteMasterX.W.H; d0 += *a0++; d0 &= 0xfffc; d2 = *a0++; a1 += (d0>>2); /* nb d0 is byte offset */ do { *a1++ &= d1; /* check this !!!! and.l d1,(a1)+ */ } while (d2--); a1 = CollisionY; d0 = a2->SpriteMasterY.W.H; d0 += *a0++; d0 &= 0xfffc; d2 = *a0++; a1 += (d0>>2); /* nb d0 is byte offset */ do { *a1++ &= d1; /* check this !!!! and.l d1,(a1)+ */ } while (d2--); } } /* ----------------------------------------------------------------------- PlotCollision ; ; Collision matrix maintaining routines. ; ;------------------------------------------------------------------------- */ void PlotCollision(void) { SWORD d0,d2; SDWORD d1; SWORD *a0; UDWORD *a1; d1 = 0; a2->SpriteCResult = d1; if ((d0 = a2->SpriteCID) >= 0) /* -ve = Read only, don't plot */ { d1 |= (1<<d0); a0 = a2->SpriteCSize; a1 = CollisionX; d0 = a2->SpriteMasterX.W.H; d0 += *a0++; d0 &= 0xfffc; d2 = *a0++; a1 += (d0>>2); do { *a1++ |= d1; } while (d2--); a1 = CollisionY; d0 = a2->SpriteMasterY.W.H; d0 += *a0++; d0 &= 0xfffc; d2 = *a0++; a1 += (d0>>2); do { *a1++ |= d1; } while (d2--); } } /* ----------------------------------------------------------------------- ReadCollision ; ; Collision matrix maintaining routines. ; ;------------------------------------------------------------------------- */ void ReadCollision(void) { SWORD d0,d7; SDWORD d1,d2; SWORD *a0; UDWORD *a1; if (!(d0 = a2->SpriteCMask)) { a2->SpriteCResult = d0; return; } /* Class specified, so CVector ready for collsion */ d1 = 0; d2 = 0; if ((a0 = a2->SpriteCSize)) /* Safety, no Collision if spritecsize 0 */ { a1 = CollisionX; d0 = a2->SpriteMasterX.W.H; d0 += *a0++; d0 &= 0xfffc; d7 = *a0++; a1 += (d0>>2); do { d1 |= *a1++; } while (d7--); a1 = CollisionY; d0 = a2->SpriteMasterY.W.H; d0 += *a0++; d0 &= 0xfffc; d7 = *a0++; a1 += (d0>>2); do { d2 |= *a1++; } while (d7--); d1 &= d2; if ((d0=a2->SpriteCID) >= 0) d1 &= ~(1<<d0); if (d1) { d7 = 31; do { if (d1 & (1<<d7)) { a2->SpriteCResult |= CollisionIDList[d7]->SpriteClass; } } while (d7--); } } a2->SpriteCResult |= GlobalCollision; } /* ;----------------------------------------------------------------------- ReadAllCollisions ; ; ;---------------------------------------------------------------------- */ void ReadAllCollisions(void) { int i; for (i=0;i<16;i++) /* for each plot priority (9 of 16 used!) */ { a2=PlotPriority[i]; while (a2) { ReadCollision(); a2=a2->SpritePlotLink; } } } /*--------------------------------------------------------------------------- InitiateSprite ; ; Initiate Sprite block, sets all defaults and necessary details first, ; then sets all specified fields ; Requirements: PrimeData - PrimeList data address ;xx Returns : pointer to data following prime data ; Returns : pointer to new sprite (NULL if cannot initiate) ; ;---------------------------------------------------------------------------*/ SPRITE *InitiateSprite(int ResSpriteNum) { SPRITE *New; AMPDATA *Ret; AMPDATA *PrimeData; PrimeData = *(ResAMPs+ResSpriteNum); New = InactiveSprites; /* remember where one is */ if (New != NULL) /* if any available? */ { InactiveSprites = InactiveSprites->SpriteLink; /* unlink it */ memcpy(New,&SpriteReady,sizeof(SPRITE)); /* copy in defaults */ New->SpriteLink = ActiveSprites; ActiveSprites = New; /* link into active list */ ActiveSpriteCount++; Ret=InitiateBlock(New,PrimeData,sizeof(SPRITE)); if (New->SpriteMPC == NULL) /* if MPC not set */ { New->SpriteMPC = Ret; /* assume data follows */ } } return(New); } /*--------------------------------------------------------------------------- InitiateBlock ; ; Initialise Block with specific data. ; Requirements: a0 - PrimeList data address ; a1 - Target block ; a2 - Parent Block or NULL ; d0 - End of block offset for end condition ; PrimeListEnd must match d0 passed ; Returns : a0 - Next word after initiation block ; a1 - Target block ; Note: Optimised by Dominic! ; ;--------------------------------------------------------------------------- */ AMPDATA *InitiateBlock(SPRITE *New,AMPDATA *PrimeData,int Size) { AMPDATA Data; UBYTE *Target,*Source; UDWORD LDat; while (1) { Data = *PrimeData++; /* read next field/offset */ Target = (UBYTE *)(New); if (Data & 0x8000) { /* 4 bytes of data (field/offset was inverted */ Data = ~Data; Target += Data; LDat = *PrimeData++; LDat <<= 16; LDat += (UWORD)(*PrimeData++); *(UDWORD *)(Target) = LDat; /* transfer 4 bytes from PrimeData */ } else { if (Data == Size) /* terminator = size of block */ return(PrimeData); else if (Data > Size) /* terminator > size of block */ break; Target += Data; *(UWORD *)(Target) = *(UWORD *)(PrimeData); /* transfer 2 bytes from PrimeData */ PrimeData ++; } } /* PUT INHERIT CODE HERE !!!! */ while (1) { Data = *PrimeData++; /* read next field/offset */ Target = (UBYTE *)(New); Source = (UBYTE *)(a2); if (Data & 0x8000) { /* 4 bytes of data (field/offset was inverted */ Data = ~Data; Target += Data; Source += Data; LDat = *PrimeData++; LDat <<= 16; LDat += (UWORD)(*PrimeData++); *(UDWORD *)(Target) = LDat + *(UDWORD *)(Source); /* transfer 4 bytes from Parent */ } else { if (Data >= Size) /* terminator > size of block */ break; Target += Data; Source += Data; *(UWORD *)(Target) = (*PrimeData++) + *(UWORD *)(Source); /* transfer 2 bytes from Parent */ } } return(PrimeData); } /* */ /* */ /* ;-------------------------------------------------------------------------- */ /* ; */ /* ; Alien Manoeuvre Program primitives. */ /* ; Available : a2 - Current sprite control block, */ /* ; a3 - Current MPC, for relative jumps or accesses, */ /* ; a5 - Global */ /* ; d2 - Zero value at start of routine, see below, */ /* ; d3 - Manoeuvre opcode word, */ /* ; d4 - First word parameter, */ /* ; d5 - Second word parameter, */ /* ; d6 - Third word parameter, */ /* ; Returns : d2 - Return code word, 0=Ok */ /* ; 4=warning */ /* ; 8=caution */ /* ; 12=error */ /* ; 16=disaster */ /* ; d3 - untouched */ /* ; Scratch : d0,d1,d7,a0,a1 */ /* ; d4-d6 and a3 may also be scratched */ /* ; d3 should not be corrupted */ /* ; A non-zero return-code (d2) may cause an immediate jump to the MVector */ /* ; if it is waiting for that value. */ /* ; */ /* ;--------------------------------------------------------------------------- */ /* --------------------------------------------------------------------------- MLoop --------------------------------------------------------------------------- */ void MLoop(void) { AMPDATA *a0; AMPDATA d1; AMPDATA d4; d4= *ad; if (!(d4 & (1<<13))) { goto Fixed; } d4 &= 0x03ff; if (!d4) { goto Feedback; } d4 &= RandNum(); Fixed: if (!d4) goto Skip; a2->SpriteMLoop = d4; a2->SpriteMHead = a2->SpriteMPC; return; Feedback: d4=Feedback; if (d4) goto Fixed; Skip: a0 = a2->SpriteMPC; /* If Feedback = 0 */ Loop: /* then search for */ while ((d1 = *a0++)>=0) ; /* the EndLoop */ d1 &= NotDelayFlag; if (d1 == (AMPDATA)((_EndLoop<<2)|Command)) goto Found; a0 += (d1 & 0x0003); /* add length of amp parameters */ goto Loop; Found: a2->SpriteMPC = a0; } It might be Graftgold's old ST code being ported to C, but I'm pretty sure it's not ST emulation. But what about Bubble Bobble? That's a bit harder to know for sure - I think the entire game lives in a file called "3.BIN" - everything else on the disc is either Rainbow Islands or the menus. What I do know is... it doesn't look like the ST port. But it's also not a perfect arcade conversion: The ST conversion is credited to Software Creations for Firebird - I don't think there's any relation at all other than "they're both ports of Bubble Bobble". But maybe Probe were developing a version of Bubble Bobble independently that got put on a shelf for a few years, idk. I suspect the Saturn version was ported from the PlayStation though don't quote me on that. tl;dr it's all a bit weird
Could be that they used the ST code as a reference for in-game logic rather than a direct port which wouldn't make alot of sense given how different the architectures are.
I added screenshots to Saturn Bomberman Fight!!/Hidden content. Here's a Mednafen cheat to unlock the bonus stages without needing a special controller: Code (Text): [18b088997c7ad5c514eab30b580971cf] Saturn Bomberman Fight!! (Japan) R A 1 B 0 0605dd90 01 BONUS_STAGES Don't have it enabled at bootup, or the Saturn BIOS won't load.
If you're still looking for cute mascot platformers to throw your disassembler at @Bo102010, here's a good mystery. Cheat sites list a whole bunch of codes for Rayman, but when we tested them all, we could only get one to work. Maybe it's another Croc where the Saturn has a cut down set of codes?
It's possible... From what I recall, accurate emulation of the arcade game wasn't even possible until the protection MCU was decapped some time in the mid-2000s, due to the game logic running directly through it. Prior to that, Emulated Bubble Bobble played wrong... Yeah, I know, we're talking ports here rather than emulation, but the fact that game behaviour back then would've been entirely down to eyeballing rather than reverse-engineering, maybe they went back to the work of a team that had already done the lion's share. *does quick google search* - Here we go: When it comes to Graftgold's ports of Rainbow Islands, Taito gave them every piece of material they ever needed for a 1:1 recreation when they were making the ST/Amiga ports. There were articles in magazines at the time, about the unprecedented partnership between Taito and Ocean that had never really happened with licensees in the past, where they were giving them design docs and original graphics discs. A lot of Ocean's later Taito ports are *very* accurate to the originals because of this.
According to this, Taito gave them the graphics and design docs but not the source code! Their versions have the secret islands because one of the devs was good enough at the arcade game to actually unlock them (and the mapping was done by watching video of the guy playing and pausing it to sketch the maps). I love the UK 8 bit Micro scene, it was just the wild west, you can't imagine it these days. They did a hell of a job given those circumstances! Rainbow Islands | The Beginning (uridiumauthor.blogspot.com) EDIT - I misread and got it backwards, the 8 bit versions don't have the secret islands, they were only discovered by playing the arcade game during development and they weren't able to get them in!
While you're on a cheat code hunt, it might be good for you to go through this: Getting Bean in Virtua Striker games. https://forums.sonicretro.org/index.php?threads/bean-the-dynamite-in-virtua-striker-2.36524/ There's an idea of what you actually need (50 wins), but might be good to check Arcade, Dreamcast etc versions and see if Bean still exists in them all. It'd also be great if you can find any way to hack a save file so this can occur without people having to wing 50 damn games as one team :P
Interesting, an easter egg cameo appearance that only triggers after the machine has been used for a long while isn't unheard of. As you say though, it'd have to be a hack, no one in their right mind is winning 50 games in a row for real. Super model is now a really solid emulator, which makes it a bit more plausible to investigate. Here's a cheat table for 'always 99 goals' for someone to grind through if they particularly want to (untested) Supermodel Forum • View topic - Model 3 cheat database! (supermodel3.com) EDIT - ended up doing it myself in cheat engine rather than artmoney which I've never used before. I'll leave it to win a bunch of 1 minute game tournaments for a bit and see what happens :-) EDIT 2 - should have seen this coming, when you win a tournament it's supposed to do a bunch of replays of the 'winning goal' of each match over the credits. Except I have no winning goal so it crashes :-D. Oh well.
If we're tackling Model 3 mysteries that one would be nice. Virtua Striker 2 is annoying because there are a bazillion revisions: Virtua Striker 2 Virtua Striker 2 Version '98 Virtua Striker 2 Version '99 Virtua Striker 2 Ver. 2000 Virtua Striker 2 Ver. 2000.1 and there are genuine differences between them all. So if it's a case of having them all win 50 games then managing to get one of the secret teams to make a substitution... yeah. This is one where you really need to hack the game - if Bean is a subtitute... well teams usually have more than one sub. Ideally we'd want to have a list (and screenshots?) of the full team... which tbh we'd need anyway even if they weren't fun secret characters. Case in point: FC Sega is made up of Sega developers. So there could be real people in there that you essentially never see. And then we have the revisions that remove and/or replace teams - do these older teams still exist? Mind you, could be worse. Fighters Megamix infamously unlocks the AM2 palm tree after 84 hours of play* - were this an arcade game, it's not inconceivable that a new player would arrive on a machine that had already exceeded this play time. Thus the palm tree wouldn't be "hidden" (and at popular venues, it might be rare to see the game in a pre-84 hour state). *fun fact: I never confirmed this. Because even with emulation, simulating 84 hours is... unpleasant.
Indeed, I was gonna try it once on vf2 (and even that is US step 2, there's a Japan step 1.5 which might be a better choice) and be done with it! But the crash thing makes it a pain to automate so probably not. I'll leave it to future reverse engineers instead.
Hoo boy, this was a rabbit hole. The button handling code in Rayman is actually insane. Most games store button presses in a 16-bit field of flags have something like a buffer of recent inputs that they compare to a list of valid bit patterns. Rayman uses a 16-bit field of flags for button presses, but it turns around and maps each button's pattern to a numerical value: 00 is up, 01 is down, etc. Then it uses the mapping for your first button press to decide which code to track, and from there says how many buttons need to be held for each part of the code. I'll write up the technical details eventually... Anyway, here is the set of codes that do something legible: 5 lives: B, X, B, Z, LT+RT+Down+B 99 Lives: Up, X+B+Z, LT, RT, LT, RT, Up+Y 10 continues: Left, A+C, RT+LT+Z, X+Z+Up 20 lives: A, Right+B, Down+Left+RT, Y+C+Z Full health: RT, Down, Left, Up, Right, B+C, LT+RT You have to put the multi-button ones in as shown. Like you have to hold LT, then RT, then Down, then B. There are three others that the game recognizes: Unknown 1: LT, X, A, X, LT, Down Unknown 2: Right, A+Z+X, Y+B+RT, LT+X+C, Left+A+B+C, Right Unknown 3: C, Right+A, Z+Left+C, X I don't know what they do! Are there any Rayman experts who can try them and see? My actual guess is that they don't do anything. You can set a break point for 0601304e to see if you've entered them correctly, since you may not be able to tell otherwise.
Crikey, that's lightning fast work, well done! Pinging @BSonirachi and @Chimes who expressed interest in this back in 2022(!). This is basically new knowledge, although there's stuff like this list, where the codes sort of resemble the 5 understood correct ones (and have the correct descriptions) but there's mistakes in all of them which explains why they never worked for anyone. Rayman Cheats, codes, hints, tips, tricks (Sega Saturn) (consoledatabase.com) Does anyone know whether there's a Rayman discord or fansite or something we can toss these over to? Bet unknown 2 does something cool will all those buttons.
@RibShark yo peep this one There indeed is. There's the RayWiki and the Rayman Pirate Community, and both of them have discords. I'll hand some of the info to the RPC
Actually did some proper digging. He's not. Amiga/ST Bubble Bobble was programmed by David Broadhurst, who just so happened to also work on the Saturn/PS1 versions. https://www.lemonamiga.com/interviews/david_broadhurst/ According to this interview, the Amiga/ST version was made by eyeing up an arcade cabinet, whereas Taito provided some actual documentation for the Saturn/PS1 versions. So I guess any quirks in the Saturn version should be entirely separate to the older computer ports.
A real 8-bit home computers dream team, Bubble Bobble by Software Creations and Rainbow Islands by Graftgold. Neat.
Some good Rayman stuff in @Bo102010 's twitter at the moment, the three unknown codes appear to be disabled but had functionality in a prototype. The proto even came with a paper guide to the codes (which have different button combos compared to release). I'll hold fire on adding to the wiki until it all shakes out! Bo (Low Context Burning Rangers) on X
Ah, um, yes, okay. Rayman (prototype; 1995-07-20) was dumped back in 2015 and actually came with a piece of paper with codes on it. And that image is included in the RAR mirrored on Hidden Palace. Oop. I'm surprised this hasn't been covered in more obvious detail. Granted it's a port of an then-already finished Jaguar game, but the series is pretty popular online, and all the versions of Rayman differ slightly.
Ooooh ooh I understand now - it's because the details aren't obvious. I spent about five minutes with the prototype - there's a lot of differences in the initial presentation, but when you get into game... it's incredibly similar to the final. Which in turn will be incredibly similar to the Jaguar version. And while I appreciate Rayman 1, I've always found it a bit of a "slow" game compared to... well pretty much any Rayman release since. I'm not sure I want to be sitting down with all the different versions to see how close this Saturn prototype is to any of them, especially if the bigger differences turn out to be in the latter half. One point of note though: this prototype is missing the initial FMV when you start a game. It's instead a slideshow... with a Japanese voiceover. It may be worth checking, if you haven't already, that the GameFAQs codes don't work in the final Japanese version, if it turns out that one was finished first.
I confirmed a few things by comparing the prototype code to to the final version code: The functions that implement the cheats appear in the same order in both versions The three extra ones in the prototype correspond to the three that jump to the same place in the final The button presses are easier to execute in the prototype because they're all single-button, and correspondingly, the code in the prototype is much simpler The functions to implement the missing three codes in the final are just totally gone; they're not present-but-unused In any case, we know some new AR codes for Rayman now.