/* Pacman Instructional Emulator User interface for Windows Copyright (c) 1997-2003,2004 Alessandro Scotti http://www.ascotti.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "SDL.h" #include "emu.h" #include "arcade.h" #include "dib24.h" unsigned framesPerSecond = PacmanMachine::VideoFrequency; unsigned samplesPerSecond = 44100; // Valid values: 11025, 22050, 44100 unsigned bytesPerSample = 2; // Valid values: 1 or 2 unsigned samplesPerFrame = samplesPerSecond / framesPerSecond; unsigned sampleBytesPerFrame= bytesPerSample * samplesPerFrame; unsigned soundBuffers = 3; static SDL_Surface *screen = NULL; // Handle of main window static int windowWidth = PacmanMachine::ScreenWidth; // Width of client area static int windowHeight = PacmanMachine::ScreenHeight; // Height of client area bool emuIsActive = false; // Program active/inactive // Bitmap used for off-screen rendering //static Dib24 dCanvas( PacmanMachine::ScreenWidth, PacmanMachine::ScreenHeight ); // DIP switches static unsigned dipDifficulty = PacmanMachine::DipDifficulty_Normal; static unsigned dipGhostNames = PacmanMachine::DipGhostNames_Normal; static unsigned dipLivesPerGame = PacmanMachine::DipLives_3; static unsigned dipCabinet = PacmanMachine::DipCabinet_Upright; static unsigned dipBonus = PacmanMachine::DipBonus_10000; static char szHelp[] = "1 = one player\n" "2 = two players\n" "3 = insert coin in slot 1\n" "4 = insert coin in slot 2\n" "5 = add credit\n" "A = toggle rack advance switch\n" "F = activate speed hack\n" "S = deactivate speed hack\n" "T = toggle test switch\n" "Left/Up/Right/Down arrow keys = move joystick\n" "F2 = single/double window size\n" "F5/F6/F7 = save game snapshot\n" "F9 = restore F5 snapshot\n" "Escape = quit"; static char szAbout[] = "PIE - Pacman Instructional Emulator\n" "Copyright (c) 1997-2003,2004 Alessandro Scotti\n" "Ported to SDL/Unix by Guillaume Libersat\n" "\n" "http://www.ascotti.org/"; const char * SnapF5 = "snap.f5"; const char * SnapF6 = "snap.f6"; const char * SnapF7 = "snap.f7"; enum { GameNone = 0, GamePacmanMidway, GamePacmanNamco, GameMsPacman }; PacmanMachine * machine = NULL; // Arcade emulator int gameLoaded = GameNone; // What type of machine is currently loaded bool applicationInitialized = false; bool showError( const char * msg ) { printf("ERROR : %s\n", msg); return false; } bool loadFile( const char * name, unsigned char * buf, int len, bool showErrorMsg = false ) { char msg[200]; FILE * f = fopen( name, "rb" ); if( f == NULL ) { sprintf( msg, "Cannot open '%s'", name ); if( showErrorMsg ) showError( msg ); return false; } int n = fread( buf, len, 1, f ); fclose( f ); if( n != 1 ) { sprintf( msg, "Error reading from '%s'", name ); if( showErrorMsg ) showError( msg ); return false; } return true; } /* Sets the game DIP switches and updates the program menu accordingly. */ void setDipSwitches() { // HMENU hMenu = GetMenu( hWindow ); // CheckMenuItem( hMenu, IDM_NORMAL, menuCheck(dipDifficulty == PacmanMachine::DipDifficulty_Normal) ); // CheckMenuItem( hMenu, IDM_HARD, menuCheck(dipDifficulty == PacmanMachine::DipDifficulty_Hard) ); // CheckMenuItem( hMenu, IDM_LIFE_0, menuCheck(dipLivesPerGame == PacmanMachine::DipLives_1) ); // CheckMenuItem( hMenu, IDM_LIFE_1, menuCheck(dipLivesPerGame == PacmanMachine::DipLives_2) ); // CheckMenuItem( hMenu, IDM_LIFE_2, menuCheck(dipLivesPerGame == PacmanMachine::DipLives_3) ); // CheckMenuItem( hMenu, IDM_LIFE_3, menuCheck(dipLivesPerGame == PacmanMachine::DipLives_5) ); // CheckMenuItem( hMenu, IDM_GHOST_0, menuCheck(dipGhostNames == PacmanMachine::DipGhostNames_Normal) ); // CheckMenuItem( hMenu, IDM_GHOST_1, menuCheck(dipGhostNames == PacmanMachine::DipGhostNames_Alternate) ); // CheckMenuItem( hMenu, IDM_CABINET_UPRIGHT, menuCheck(dipCabinet == PacmanMachine::DipCabinet_Upright) ); // CheckMenuItem( hMenu, IDM_CABINET_COCKTAIL, menuCheck(dipCabinet == PacmanMachine::DipCabinet_Cocktail) ); // CheckMenuItem( hMenu, IDM_BONUS_10000, menuCheck(dipBonus == PacmanMachine::DipBonus_10000) ); // CheckMenuItem( hMenu, IDM_BONUS_15000, menuCheck(dipBonus == PacmanMachine::DipBonus_15000) ); // CheckMenuItem( hMenu, IDM_BONUS_20000, menuCheck(dipBonus == PacmanMachine::DipBonus_20000) ); // CheckMenuItem( hMenu, IDM_BONUS_NONE, menuCheck(dipBonus == PacmanMachine::DipBonus_None) ); unsigned dip = machine->getDipSwitches() & (PacmanMachine::DipPlay_Mask | PacmanMachine::DipMode_Mask); machine->setDipSwitches( dip | dipDifficulty | dipGhostNames | dipLivesPerGame | dipBonus | dipCabinet ); } /* Saves a snapshot of the game. */ void takeSnapshot( const char * filename ) { unsigned bufsize = 1 + machine->getSizeOfSnapshotBuffer(); unsigned char * buffer = new unsigned char [ bufsize ]; *buffer = (unsigned char)(gameLoaded); machine->takeSnapshot( buffer+1 ); FILE * f = fopen( filename, "wb" ); assert( f != NULL ); fwrite( buffer, 1, bufsize, f ); fclose( f ); delete buffer; } /* Restores a snapshot of the game. */ void emuRestoreSnapshot( const char * filename ) { PacmanMachine * newmachine = 0; FILE * f = fopen( filename, "rb" ); if( f != NULL ) { unsigned char game; fread( &game, 1, 1, f ); switch( game ) { case GamePacmanMidway: case GamePacmanNamco: newmachine = new PacmanMachine; break; case GameMsPacman: newmachine = new MsPacmanMachine; break; } if( newmachine != 0 ) { unsigned bufsize = newmachine->getSizeOfSnapshotBuffer(); unsigned char * buffer = new unsigned char [ bufsize ]; if( fread( buffer, 1, bufsize, f ) == bufsize ) { newmachine->restoreSnapshot( buffer ); dipDifficulty = newmachine->getDipSwitches() & PacmanMachine::DipDifficulty_Mask; dipGhostNames = newmachine->getDipSwitches() & PacmanMachine::DipGhostNames_Mask; dipLivesPerGame = newmachine->getDipSwitches() & PacmanMachine::DipLives_Mask; dipBonus = newmachine->getDipSwitches() & PacmanMachine::DipBonus_Mask; setDipSwitches(); delete machine; machine = newmachine; } else { delete newmachine; } fclose( f ); delete buffer; } } else { fprintf(stderr, "Unable to load snapshot (%s) !\n", filename); exit(1); } } /* Initializes the sound library and creates the DirectX sound objects used by the application. */ bool initSound( void ) { return false; } /* Activates/deactivates the program. Note: when the program is not active, emulation and sound playing are stopped. */ void emuActive( bool active ) { if( !applicationInitialized || (gameLoaded == GameNone) ) return; active = active ? true : false; // Normalize the boolean values! if( active != emuIsActive ) { emuIsActive = active; } } bool loadGameROM( int game ) { bool romLoaded = false; PacmanMachine * newmachine = 0; unsigned char buffer[ 0x6000 ]; unsigned char palette[0x20]; unsigned char color[0x100]; // Files common to all ROM sets if( (! loadFile( "82s126.4a", color, sizeof(color), true)) || (! loadFile( "82s123.7f", palette, sizeof(palette), true)) ) { return false; } // Basic game ROMs switch( game ) { case GamePacmanNamco: newmachine = new PacmanMachine; romLoaded = loadFile( "namcopac.6e", buffer, 0x1000 ) && loadFile( "namcopac.6f", buffer+0x1000, 0x1000 ) && loadFile( "namcopac.6h", buffer+0x2000, 0x1000 ) && loadFile( "namcopac.6j", buffer+0x3000, 0x1000 ) && loadFile( "pacman.5e", buffer+0x4000, 0x1000 ) && loadFile( "pacman.5f", buffer+0x5000, 0x1000 ); break; case GameMsPacman: newmachine = new MsPacmanMachine; romLoaded = loadFile( "pacman.6e", buffer, 0x1000 ) && loadFile( "pacman.6f", buffer+0x1000, 0x1000 ) && loadFile( "pacman.6h", buffer+0x2000, 0x1000 ) && loadFile( "pacman.6j", buffer+0x3000, 0x1000 ) && loadFile( "5e", buffer+0x4000, 0x1000 ) && loadFile( "5f", buffer+0x5000, 0x1000 ); break; case GamePacmanMidway: newmachine = new PacmanMachine; romLoaded = loadFile( "pacman.6e", buffer, 0x1000 ) && loadFile( "pacman.6f", buffer+0x1000, 0x1000 ) && loadFile( "pacman.6h", buffer+0x2000, 0x1000 ) && loadFile( "pacman.6j", buffer+0x3000, 0x1000 ) && loadFile( "pacman.5e", buffer+0x4000, 0x1000 ) && loadFile( "pacman.5f", buffer+0x5000, 0x1000 ); break; } // Set ROMs and, if necessary, load aux ROMs if( romLoaded ) { newmachine->setROM( buffer ); newmachine->setVideoROMs( buffer+0x4000, buffer+0x5000 ); newmachine->setColorROMs( palette, color ); // Load Ms. Pacman ROMs (must do it after the basic ROMs!) if( game == GameMsPacman ) { romLoaded = loadFile( "u5", buffer+0x0000, 0x0800 ) && loadFile( "u6", buffer+0x0800, 0x1000 ) && loadFile( "u7", buffer+0x1800, 0x1000 ); // Add extra (ROM) board to emulator reinterpret_cast(newmachine)->setAuxROM( buffer+0x0000, buffer+0x0800, buffer+0x1800 ); } } if( romLoaded ) { delete machine; machine = newmachine; machine->reset(); gameLoaded = game; } else { delete newmachine; } return romLoaded; } void loadGame( int game, int quiet ) { if( ! loadGameROM( game ) ) { if( ! quiet ) showError( "Game ROMs not found!" ); } emuActive( true ); } /* Initializes the game engine. */ bool emuInit( void ) { // Load game ROMs loadGame( GamePacmanMidway, false ); applicationInitialized = true; return true; } /* Runs the game engine for one frame. */ void emuOneFrame(Dib24 *dCanvas) { if( ! emuIsActive ) return; // Run the machine for a frame machine->run(); //bool drawVideo = (machine->getFrameCount() & 1) != 0 && dCanvas != NULL; bool drawVideo = dCanvas != NULL; // Draw the video image into the off-screen bitmap if( drawVideo ) { unsigned char video_buffer[ PacmanMachine::ScreenWidth * PacmanMachine::ScreenHeight ]; unsigned char * vbuf = video_buffer; const unsigned * palette = machine->getPalette(); machine->renderVideo( vbuf ); while ( SDL_MUSTLOCK(dCanvas->getHandle()) < 0 ) SDL_Delay(10); for( int y=0; ygetScanLine( y ); for( int x=0; xgetHandle()->format, color, (color>>8), color>>16); *dst++ = (unsigned char) sdl_color & 0xff; // Red *dst++ = (unsigned char) (sdl_color >> 8) & 0xff; // Green *dst++ = (unsigned char) (sdl_color >> 16) & 0xff; // Blue *dst++ = 0xff; // Alpha } } SDL_UnlockSurface(dCanvas->getHandle()); } } void setWindowSize( int sizeFactor ) { windowWidth = sizeFactor*PacmanMachine::ScreenWidth; windowHeight = sizeFactor*PacmanMachine::ScreenHeight; screen = SDL_SetVideoMode(windowWidth, windowHeight, 32, SDL_SWSURFACE); } /* * Events processing */ bool emuProcessEvents() { SDL_Event event; while ( SDL_PollEvent(&event) ) { switch ( event.type ) { case SDL_KEYDOWN: case SDL_KEYUP: PacmanMachine::InputDeviceMode mode = (event.type == SDL_KEYDOWN) ? PacmanMachine::DeviceOn : PacmanMachine::DeviceOff; switch ( event.key.keysym.sym ) { case SDLK_a: printf("Key 1 pressed\n"); machine->setDeviceMode( PacmanMachine::Key_OnePlayer, mode ); break; case SDLK_c: machine->setDeviceMode( PacmanMachine::CoinSlot_1, mode ); break; case SDLK_RIGHT: machine->setDeviceMode( PacmanMachine::Joy1_Right, mode ); machine->setDeviceMode( PacmanMachine::Joy2_Right, mode ); break; case SDLK_LEFT: machine->setDeviceMode( PacmanMachine::Joy1_Left, mode ); machine->setDeviceMode( PacmanMachine::Joy2_Left, mode ); break; case SDLK_UP: machine->setDeviceMode( PacmanMachine::Joy1_Up, mode ); machine->setDeviceMode( PacmanMachine::Joy2_Up, mode ); break; case SDLK_DOWN: machine->setDeviceMode( PacmanMachine::Joy1_Down, mode ); machine->setDeviceMode( PacmanMachine::Joy2_Down, mode ); break; case SDLK_SPACE: emuActive( ! emuIsActive ); break; case SDLK_F5: takeSnapshot( SnapF5 ); break; case SDLK_F6: emuRestoreSnapshot( SnapF5 ); break; case SDLK_F9: machine->setDeviceMode( PacmanMachine::Switch_RackAdvance, PacmanMachine::DeviceToggle ); break; case SDLK_ESCAPE: return false; break; } } } return true; } // LRESULT PASCAL WndProc( HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam ) // { // PAINTSTRUCT stPS; // HDC hDC; // static bool boSavedActiveFlag = false; // switch( wMsg ) { // // No need to erase the background // case WM_ERASEBKGND: // return true; // // Paint the window by just blitting the game video // case WM_PAINT: // hDC = BeginPaint( hWnd, &stPS ); // dCanvas.stretch( hDC, 0, 0, windowWidth, windowHeight ); // EndPaint( hWnd, &stPS ); // return true; // // Application is activating/deactivating // case WM_ACTIVATEAPP: // setActive( (bool)wParam ); // break; // case WM_ENTERSIZEMOVE: // case WM_ENTERMENULOOP: // boSavedActiveFlag = boIsActive; // setActive( false ); // break; // case WM_EXITSIZEMOVE: // case WM_EXITMENULOOP: // setActive( boSavedActiveFlag ); // break; // // Menu commands // case WM_COMMAND: // switch( LOWORD(wParam) ) { // case IDM_LOAD_PACMAN: // loadGame( GamePacmanMidway, false ); // break; // case IDM_LOAD_NAMCOPAC: // loadGame( GamePacmanNamco, false ); // break; // case IDM_LOAD_MSPACMAN: // loadGame( GameMsPacman, false ); // break; // case IDM_NEW: // machine->reset(); // break; // case IDM_SNAP_0: // restoreSnapshot( SnapF5 ); // break; // case IDM_SNAP_1: // restoreSnapshot( SnapF6 ); // break; // case IDM_SNAP_2: // restoreSnapshot( SnapF7 ); // break; // case IDM_EXIT: // PostMessage( hWnd, WM_CLOSE, 0, 0 ); // break; // case IDM_WINDOW_SIZE: // setWindowSize( (windowWidth == PacmanMachine::ScreenWidth) ? 2 : 1 ); // break; // case IDM_HELP_KEYS: // MessageBox( 0, szHelp, "Keys", MB_OK | MB_ICONINFORMATION ); // break; // case IDM_ABOUT: // MessageBox( 0, szAbout, szAppName, MB_OK | MB_ICONINFORMATION ); // break; // case IDM_LIFE_0: // dipLivesPerGame = PacmanMachine::DipLives_1; // setDipSwitches(); // break; // case IDM_LIFE_1: // dipLivesPerGame = PacmanMachine::DipLives_2; // setDipSwitches(); // break; // case IDM_LIFE_2: // dipLivesPerGame = PacmanMachine::DipLives_3; // setDipSwitches(); // break; // case IDM_LIFE_3: // dipLivesPerGame = PacmanMachine::DipLives_5; // setDipSwitches(); // break; // case IDM_HARD: // dipDifficulty = PacmanMachine::DipDifficulty_Hard; // setDipSwitches(); // break; // case IDM_NORMAL: // dipDifficulty = PacmanMachine::DipDifficulty_Normal; // setDipSwitches(); // break; // case IDM_GHOST_0: // dipGhostNames = PacmanMachine::DipGhostNames_Normal; // setDipSwitches(); // break; // case IDM_GHOST_1: // dipGhostNames = PacmanMachine::DipGhostNames_Alternate; // setDipSwitches(); // break; // case IDM_BONUS_10000: // dipBonus = PacmanMachine::DipBonus_10000; // setDipSwitches(); // break; // case IDM_BONUS_15000: // dipBonus = PacmanMachine::DipBonus_15000; // setDipSwitches(); // break; // case IDM_BONUS_20000: // dipBonus = PacmanMachine::DipBonus_20000; // setDipSwitches(); // break; // case IDM_BONUS_NONE: // dipBonus = PacmanMachine::DipBonus_None; // setDipSwitches(); // break; // case IDM_CABINET_UPRIGHT: // dipCabinet = PacmanMachine::DipCabinet_Upright; // setDipSwitches(); // break; // case IDM_CABINET_COCKTAIL: // dipCabinet = PacmanMachine::DipCabinet_Cocktail; // setDipSwitches(); // break; // } // break; // // Keyboard events // case WM_KEYDOWN: // case WM_KEYUP: // // Handle machine device events first... // { // PacmanMachine::InputDeviceMode mode = (wMsg == WM_KEYDOWN) ? PacmanMachine::DeviceOn : PacmanMachine::DeviceOff; // switch( wParam ) { // case '1': // machine->setDeviceMode( PacmanMachine::Key_OnePlayer, mode ); // break; // case '2': // machine->setDeviceMode( PacmanMachine::Key_TwoPlayers, mode ); // break; // case '3': // machine->setDeviceMode( PacmanMachine::CoinSlot_1, mode ); // break; // case '4': // machine->setDeviceMode( PacmanMachine::CoinSlot_2, mode ); // break; // case '5': // machine->setDeviceMode( PacmanMachine::Switch_AddCredit, mode ); // break; // case VK_LEFT: // machine->setDeviceMode( PacmanMachine::Joy1_Left, mode ); // machine->setDeviceMode( PacmanMachine::Joy2_Left, mode ); // break; // case VK_RIGHT: // machine->setDeviceMode( PacmanMachine::Joy1_Right, mode ); // machine->setDeviceMode( PacmanMachine::Joy2_Right, mode ); // break; // case VK_UP: // machine->setDeviceMode( PacmanMachine::Joy1_Up, mode ); // machine->setDeviceMode( PacmanMachine::Joy2_Up, mode ); // break; // case VK_DOWN: // machine->setDeviceMode( PacmanMachine::Joy1_Down, mode ); // machine->setDeviceMode( PacmanMachine::Joy2_Down, mode ); // break; // } // } // // ...then handle application events // if( wMsg == WM_KEYDOWN ) switch( wParam ) { // case 'A': // machine->setDeviceMode( PacmanMachine::Switch_RackAdvance, PacmanMachine::DeviceToggle ); // break; // case 'F': // machine->setSpeedHack( 1 ); // Fast pacman // break; // case 'S': // machine->setSpeedHack( 0 ); // Slow (normal) pacman // break; // case 'T': // machine->setDeviceMode( PacmanMachine::Switch_Test, PacmanMachine::DeviceToggle ); // break; // case ' ': // setActive( ! boIsActive ); // break; // case VK_ESCAPE: // PostMessage( hWnd, WM_CLOSE, 0, 0 ); // return 0; // case VK_F2: // setWindowSize( (windowWidth == PacmanMachine::ScreenWidth) ? 2 : 1 ); // break; // case VK_F4: // // Cheat: add one life // { // unsigned char * ram = const_cast(machine->getRAM()); // ram[0x4E14]++; // ram[0x4E15]++; // } // break; // case VK_F5: // takeSnapshot( SnapF5 ); // break; // case VK_F6: // takeSnapshot( SnapF6 ); // break; // case VK_F7: // takeSnapshot( SnapF7 ); // break; // case VK_F9: // restoreSnapshot( SnapF5 ); // break; // } // break; // // Destroy window // case WM_DESTROY: // PostQuitMessage( 0 ); // break; // } // return DefWindowProc( hWnd, wMsg, wParam, lParam ); // }