/** ** Supermodel ** A Sega Model 3 Arcade Emulator. ** Copyright 2011 Bart Trzynadlowski ** ** This file is part of Supermodel. ** ** Supermodel 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 3 of the License, or (at your option) ** any later version. ** ** Supermodel 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 Supermodel. If not, see . **/ /* * Main.cpp * * Main program driver for the SDL port. * * Compile-Time Options * -------------------- * - SUPERMODEL_SOUND: Enables experimental sound code. The 68K core (Turbo68K) * only works on x86 (32-bit) systems, so this cannot be enabled on 64-bit * builds. * - SUPERMODEL_WIN32: Define this if compiling on Windows. * - SUPERMODEL_OSX: Define this if compiling on Mac OS X. * * TO-DO List * ---------- * - A lot of this code is actually OS-independent! Should it be moved into the * root of the source tree? Might not be worth it; eventually, OS-dependent * UIs will be introduced. */ #include #include #include #include #include #include "Pkgs/glew.h" #ifdef SUPERMODEL_OSX #include #else #include #endif #include "Supermodel.h" #include "SDLInputSystem.h" #ifdef SUPERMODEL_WIN32 #include "DirectInputSystem.h" #endif CLogger *GetLogger(); void SetLogger(CLogger *logger); /****************************************************************************** Display Management ******************************************************************************/ /* * Position and size of rectangular region within OpenGL display to render to */ unsigned xOffset, yOffset; // offset of renderer output within OpenGL viewport unsigned xRes, yRes; // renderer output resolution (can be smaller than GL viewport) /* * CreateGLScreen(): * * Creates an OpenGL display surface of the requested size. xOffset and yOffset * are used to return a display surface offset (for OpenGL viewport commands) * because the actual drawing area may need to be adjusted to preserve the * Model 3 aspect ratio. The new resolution will be passed back as well. */ static BOOL CreateGLScreen(const char *caption, unsigned *xOffsetPtr, unsigned *yOffsetPtr, unsigned *xResPtr, unsigned *yResPtr, BOOL keepAspectRatio, BOOL fullScreen) { const SDL_VideoInfo *VideoInfo; GLenum err; float model3Ratio, ratio; float xRes, yRes; // Initialize video subsystem if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) return ErrorLog("Unable to initialize SDL video subsystem: %s\n", SDL_GetError()); // Important GL attributes SDL_GL_SetAttribute(SDL_GL_RED_SIZE,5); // need at least RGB555 for Model 3 textures SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,5); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1); // Set video mode if (SDL_SetVideoMode(*xResPtr,*yResPtr,0,SDL_OPENGL|(fullScreen?SDL_FULLSCREEN|SDL_HWSURFACE:0)) == NULL) { ErrorLog("Unable to create an OpenGL display: %s\n", SDL_GetError()); return FAIL; } VideoInfo = SDL_GetVideoInfo(); // what resolution did we actually get? // If required, fix the aspect ratio of the resolution that the user passed to match Model 3 ratio xRes = (float) *xResPtr; yRes = (float) *yResPtr; if (keepAspectRatio) { model3Ratio = 496.0f/384.0f; ratio = xRes/yRes; if (yRes < (xRes/model3Ratio)) xRes = yRes*model3Ratio; if (xRes < (yRes*model3Ratio)) yRes = xRes/model3Ratio; } // Center the visible area *xOffsetPtr = (*xResPtr - (unsigned) xRes)/2; *yOffsetPtr = (*yResPtr - (unsigned) yRes)/2; // If the desired resolution is smaller than what we got, re-center again if (*xResPtr < VideoInfo->current_w) *xOffsetPtr += (VideoInfo->current_w - *xResPtr)/2; if (*yResPtr < VideoInfo->current_h) *yOffsetPtr += (VideoInfo->current_h - *yResPtr)/2; // Create window caption SDL_WM_SetCaption(caption,NULL); // Initialize GLEW, allowing us to use features beyond OpenGL 1.2 err = glewInit(); if (GLEW_OK != err) { ErrorLog("OpenGL initialization failed: %s\n", glewGetErrorString(err)); return FAIL; } // OpenGL initialization glViewport(0,0,*xResPtr,*yResPtr); glClearColor(0.0,0.0,0.0,0.0); glClearDepth(1.0); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glDisable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(90.0,(GLfloat)xRes/(GLfloat)yRes,0.1,1e5); glMatrixMode(GL_MODELVIEW); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // clear at least once to ensure black border // Write back resolution parameters *xResPtr = (unsigned) xRes; *yResPtr = (unsigned) yRes; return 0; } /****************************************************************************** Configuration ******************************************************************************/ #define CONFIG_FILE_PATH "Config/Supermodel.ini" #define CONFIG_FILE_COMMENT ";\n" \ "; Supermodel Configuration File\n" \ ";\n" // Create and configure inputs static CInputs *CreateInputs(CInputSystem *InputSystem, BOOL configure) { // Create and initialize inputs CInputs* Inputs = new CInputs(InputSystem); if (!Inputs->Initialize()) { ErrorLog("Unable to initalize inputs.\n"); return NULL; } // Open and parse configuration file CINIFile INI; INI.Open(CONFIG_FILE_PATH); // doesn't matter if it exists or not, will get overwritten INI.Parse(); Inputs->ReadFromINIFile(&INI, "Global"); // If the user wants to configure the inputs, do that now if (configure) { // Open an SDL window unsigned xOffset, yOffset, xRes=496, yRes=384; if (OKAY != CreateGLScreen("Supermodel - Configuring Inputs...",&xOffset,&yOffset,&xRes,&yRes,FALSE,FALSE)) { ErrorLog("Unable to start SDL to configure inputs.\n"); return NULL; } // Configure the inputs if (Inputs->ConfigureInputs(NULL, xOffset, yOffset, xRes, yRes)) { // Write input configuration and input system settings to config file Inputs->WriteToINIFile(&INI, "Global"); if (OKAY != INI.Write(CONFIG_FILE_COMMENT)) ErrorLog("Unable to save configuration to %s.", CONFIG_FILE_PATH); else printf("Configuration successfully saved to %s.\n", CONFIG_FILE_PATH); } else puts("Configuration aborted..."); puts(""); } return Inputs; } /****************************************************************************** Save States and NVRAM Save states and NVRAM use the same basic format. Header block name: "Supermodel Save State" or "Supermodel NVRAM State" Data: Save state file version (4-byte integer), ROM set ID (up to 9 bytes, including terminating \0). Different subsystems output their own blocks. ******************************************************************************/ #define STATE_FILE_VERSION 0 // save state file version static unsigned saveSlot = 0; // save state slot # static void SaveState(CModel3 *Model3) { CBlockFile SaveState; char filePath[24]; int fileVersion = STATE_FILE_VERSION; sprintf(filePath, "Saves/%s.st%d", Model3->GetGameInfo()->id, saveSlot); if (OKAY != SaveState.Create(filePath, "Supermodel Save State", "Supermodel Version " SUPERMODEL_VERSION)) { ErrorLog("Unable to save state to %s.", filePath); return; } // Write file format version and ROM set ID to header block SaveState.Write(&fileVersion, sizeof(fileVersion)); SaveState.Write(Model3->GetGameInfo()->id, strlen(Model3->GetGameInfo()->id)+1); // Save state Model3->SaveState(&SaveState); SaveState.Close(); printf("Saved state to %s.\n", filePath); DebugLog("Saved state to %s.\n", filePath); } static void LoadState(CModel3 *Model3) { CBlockFile SaveState; char filePath[24]; int fileVersion; // Generate file path sprintf(filePath, "Saves/%s.st%d", Model3->GetGameInfo()->id, saveSlot); // Open and check to make sure format is correct if (OKAY != SaveState.Load(filePath)) { ErrorLog("Unable to load state from %s.", filePath); return; } if (OKAY != SaveState.FindBlock("Supermodel Save State")) { ErrorLog("%s does not appear to be a valid save state file.", filePath); return; } SaveState.Read(&fileVersion, sizeof(fileVersion)); if (fileVersion != STATE_FILE_VERSION) { ErrorLog("Format of %s is incompatible with this version of Supermodel.", filePath); return; } // Load Model3->LoadState(&SaveState); SaveState.Close(); printf("Loaded state from %s.\n", filePath); DebugLog("Loaded state from %s.\n", filePath); } static void SaveNVRAM(CModel3 *Model3) { CBlockFile NVRAM; char filePath[24]; int fileVersion = STATE_FILE_VERSION; sprintf(filePath, "NVRAM/%s.nv", Model3->GetGameInfo()->id); if (OKAY != NVRAM.Create(filePath, "Supermodel NVRAM State", "Supermodel Version " SUPERMODEL_VERSION)) { ErrorLog("Unable to save NVRAM to %s. Make sure directory exists!", filePath); return; } // Write file format version and ROM set ID to header block NVRAM.Write(&fileVersion, sizeof(fileVersion)); NVRAM.Write(Model3->GetGameInfo()->id, strlen(Model3->GetGameInfo()->id)+1); // Save NVRAM Model3->SaveNVRAM(&NVRAM); NVRAM.Close(); DebugLog("Saved NVRAM to %s.\n", filePath); } static void LoadNVRAM(CModel3 *Model3) { CBlockFile NVRAM; char filePath[24]; int fileVersion; // Generate file path sprintf(filePath, "NVRAM/%s.nv", Model3->GetGameInfo()->id); // Open and check to make sure format is correct if (OKAY != NVRAM.Load(filePath)) { //ErrorLog("Unable to restore NVRAM from %s.", filePath); return; } if (OKAY != NVRAM.FindBlock("Supermodel NVRAM State")) { ErrorLog("%s does not appear to be a valid NVRAM file.", filePath); return; } NVRAM.Read(&fileVersion, sizeof(fileVersion)); if (fileVersion != STATE_FILE_VERSION) { ErrorLog("Format of %s is incompatible with this version of Supermodel.", filePath); return; } // Load Model3->LoadNVRAM(&NVRAM); NVRAM.Close(); DebugLog("Loaded NVRAM from %s.\n", filePath); } /****************************************************************************** Main Program Driver All configuration management is done prior to calling Supermodel(). ******************************************************************************/ #ifdef SUPERMODEL_DEBUGGER int Supermodel(const char *zipFile, CModel3 *Model3, CInputs *Inputs, Debugger::CDebugger *Debugger, unsigned ppcFrequency, BOOL multiThreaded, unsigned xResParam, unsigned yResParam, BOOL keepAspectRatio, BOOL fullScreen, BOOL noThrottle, BOOL showFPS, const char *vsFile, const char *fsFile) { #else int Supermodel(const char *zipFile, CInputs *Inputs, unsigned ppcFrequency, BOOL multiThreaded, unsigned xResParam, unsigned yResParam, BOOL keepAspectRatio, BOOL fullScreen, BOOL noThrottle, BOOL showFPS, const char *vsFile, const char *fsFile) { CModel3 *Model3 = new CModel3(); #endif // SUPERMODEL_DEBUGGER char titleStr[128], titleFPSStr[128]; CRender2D *Render2D = new CRender2D(); CRender3D *Render3D = new CRender3D(); unsigned prevFPSTicks, currentFPSTicks, currentTicks, targetTicks, startTicks; unsigned fpsFramesElapsed, framesElapsed; BOOL showCursor = FALSE; // show cursor in full screen mode? BOOL quit = 0; BOOL paused = 0; // Info log user options InfoLog("PowerPC frequency: %d Hz", ppcFrequency); InfoLog("Resolution: %dx%d (%s)", xResParam, yResParam, fullScreen?"full screen":"windowed"); InfoLog("Frame rate limiting: %s", noThrottle?"Disabled":"Enabled"); // Initialize and load ROMs Model3->Init(ppcFrequency, multiThreaded); if (OKAY != Model3->LoadROMSet(Model3GameList, zipFile)) return 1; // Load NVRAM LoadNVRAM(Model3); // Start up SDL and open a GL window xRes = xResParam; yRes = yResParam; sprintf(titleStr, "Supermodel - %s", Model3->GetGameInfo()->title); if (OKAY != CreateGLScreen(titleStr,&xOffset,&yOffset,&xRes,&yRes,keepAspectRatio,fullScreen)) return 1; // Initialize audio system if (OKAY != OpenAudio()) return 1; // Hide mouse if fullscreen Inputs->GetInputSystem()->SetMouseVisibility(!fullScreen); // Attach the inputs to the emulator Model3->AttachInputs(Inputs); // Initialize the renderer if (OKAY != Render2D->Init(xOffset, yOffset, xRes, yRes)) goto QuitError; if (OKAY != Render3D->Init(xOffset, yOffset, xRes, yRes, vsFile, fsFile)) goto QuitError; Model3->AttachRenderers(Render2D,Render3D); // Reset emulator Model3->Reset(); #ifdef SUPERMODEL_DEBUGGER // If debugger was supplied, set it as logger and attach it to system CLogger *oldLogger = GetLogger(); if (Debugger != NULL) { SetLogger(Debugger); Debugger->Attach(); } #endif // SUPERMODEL_DEBUGGER // Emulate! fpsFramesElapsed = 0; framesElapsed = 0; prevFPSTicks = SDL_GetTicks(); startTicks = prevFPSTicks; while (!quit) { // Check if paused if (!paused) { // If not, run one frame Model3->RunFrame(); // Swap the buffers SDL_GL_SwapBuffers(); } // Poll the inputs if (!Inputs->Poll(Model3->GetGameInfo(), xOffset, yOffset, xRes, yRes)) quit = 1; #ifdef SUPERMODEL_DEBUGGER if (Debugger != NULL) { Debugger->Poll(); // Check if debugger requests exit or pause if (Debugger->CheckExit()) quit = 1; else if (Debugger->CheckPause()) paused = 1; else { #endif // SUPERMODEL_DEBUGGER // Check UI controls if (Inputs->uiExit->Pressed()) { // Quit emulator quit = 1; } else if (Inputs->uiReset->Pressed()) { // Reset emulator Model3->Reset(); #ifdef SUPERMODEL_DEBUGGER // If debugger was supplied, reset it too if (Debugger != NULL) Debugger->Reset(); #endif // SUPERMODEL_DEBUGGER printf("Model 3 reset.\n"); } else if (Inputs->uiPause->Pressed()) { // Toggle emulator paused flag paused = !paused; } else if (Inputs->uiSaveState->Pressed()) { // Save game state SaveState(Model3); } else if (Inputs->uiChangeSlot->Pressed()) { // Change save slot ++saveSlot; saveSlot %= 10; // clamp to [0,9] printf("Save slot: %d\n", saveSlot); } else if (Inputs->uiLoadState->Pressed()) { // Load game state LoadState(Model3); #ifdef SUPERMODEL_DEBUGGER // If debugger was supplied, reset it after loading state if (Debugger != NULL) Debugger->Reset(); #endif // SUPERMODEL_DEBUGGER } else if (Inputs->uiDumpInpState->Pressed()) { // Dump input states Inputs->DumpState(Model3->GetGameInfo()); } else if (Inputs->uiToggleCursor->Pressed() && fullScreen) { // Toggle cursor in full screen mode showCursor = !showCursor; Inputs->GetInputSystem()->SetMouseVisibility(!!showCursor); } else if (Inputs->uiClearNVRAM->Pressed()) { // Clear NVRAM Model3->ClearNVRAM(); printf("NVRAM cleared.\n"); } else if (Inputs->uiToggleFrLimit->Pressed()) { // Toggle frame limiting noThrottle = !noThrottle; printf("Frame limiting: %s\n", noThrottle?"Off":"On"); /* SCSP_MidiIn(0xA0); SCSP_MidiIn(0x00); SCSP_MidiIn(0x01); // stop? SCSP_MidiIn(0xA0); SCSP_MidiIn(0x11); SCSP_MidiIn(0x2E); SCSP_MidiIn(0xA1); SCSP_MidiIn(0x70); SCSP_MidiIn(0x03); SCSP_MidiIn(0xAF);SCSP_MidiIn(0x10);SCSP_MidiIn(0x01); */ SCSP_MidiIn(0xAF);SCSP_MidiIn(0x10);SCSP_MidiIn(0x00); //static int snd=0xF; //SCSP_MidiIn(0xA0);SCSP_MidiIn(0x11);SCSP_MidiIn(snd++); //Sound codes: // A0 11 xx (0F=time extend, 11=jumbo left right) // AF 10 xx (music -- 01 seems to work) } #ifdef SUPERMODEL_DEBUGGER else if (Inputs->uiEnterDebugger->Pressed()) { // Break execution and enter debugger Debugger->ForceBreak(true); } } } #endif // SUPERMODEL_DEBUGGER // FPS and frame rate currentFPSTicks = SDL_GetTicks(); currentTicks = currentFPSTicks; // FPS if (showFPS) { ++fpsFramesElapsed; if((currentFPSTicks-prevFPSTicks) >= 1000) // update FPS every 1 second (each tick is 1 ms) { sprintf(titleFPSStr, "%s - %1.1f FPS", titleStr, (float)fpsFramesElapsed*(float)(currentFPSTicks-prevFPSTicks)/1000.0f); SDL_WM_SetCaption(titleFPSStr,NULL); prevFPSTicks = currentFPSTicks; // reset tick count fpsFramesElapsed = 0; // reset frame count } } // Frame limiting/paused if (paused || !noThrottle) { ++framesElapsed; targetTicks = startTicks + (unsigned) ((float)framesElapsed * 1000.0f/60.0f); if (currentTicks <= targetTicks) // add a delay until we reach the next (target) frame time SDL_Delay(targetTicks-currentTicks); else // begin a new frame { framesElapsed = 0; startTicks = currentTicks; } } } #ifdef SUPERMODEL_DEBUGGER // If debugger was supplied, detach it from system and restore old logger if (Debugger != NULL) { Debugger->Detach(); SetLogger(oldLogger); } #endif // SUPERMODEL_DEBUGGER // Save NVRAM SaveNVRAM(Model3); // Close audio CloseAudio(); // Shut down #ifndef SUPERMODEL_DEBUGGER delete Model3; #endif // SUPERMODEL_DEBUGGER delete Render2D; delete Render3D; // Dump PowerPC registers #ifdef DEBUG for (int i = 0; i < 32; i += 4) printf("R%d=%08X\tR%d=%08X\tR%d=%08X\tR%d=%08X\n", i + 0, ppc_get_gpr(i + 0), i + 1, ppc_get_gpr(i + 1), i + 2, ppc_get_gpr(i + 2), i + 3, ppc_get_gpr(i + 3)); printf("PC =%08X\n", ppc_get_pc()); printf("LR =%08X\n", ppc_get_lr()); /* printf("DBAT0U=%08X\tIBAT0U=%08X\n", ppc_read_spr(SPR603E_DBAT0U), ppc_read_spr(SPR603E_IBAT0U)); printf("DBAT0L=%08X\tIBAT0L=%08X\n", ppc_read_spr(SPR603E_DBAT0L), ppc_read_spr(SPR603E_IBAT0L)); printf("DBAT1U=%08X\tIBAT1U=%08X\n", ppc_read_spr(SPR603E_DBAT1U), ppc_read_spr(SPR603E_IBAT1U)); printf("DBAT1L=%08X\tIBAT1L=%08X\n", ppc_read_spr(SPR603E_DBAT1L), ppc_read_spr(SPR603E_IBAT1L)); printf("DBAT2U=%08X\tIBAT2U=%08X\n", ppc_read_spr(SPR603E_DBAT2U), ppc_read_spr(SPR603E_IBAT2U)); printf("DBAT2L=%08X\tIBAT2L=%08X\n", ppc_read_spr(SPR603E_DBAT2L), ppc_read_spr(SPR603E_IBAT2L)); printf("DBAT3U=%08X\tIBAT3U=%08X\n", ppc_read_spr(SPR603E_DBAT3U), ppc_read_spr(SPR603E_IBAT3U)); printf("DBAT3L=%08X\tIBAT3L=%08X\n", ppc_read_spr(SPR603E_DBAT3L), ppc_read_spr(SPR603E_IBAT3L)); for (int i = 0; i < 10; i++) printf("SR%d =%08X\n", i, ppc_read_sr(i)); for (int i = 10; i < 16; i++) printf("SR%d=%08X\n", i, ppc_read_sr(i)); printf("SDR1=%08X\n", ppc_read_spr(SPR603E_SDR1)); */ #endif return 0; // Quit with an error QuitError: #ifndef SUPERMODEL_DEBUGGER delete Model3; #endif // SUPERMODEL_DEBUGGER delete Render2D; delete Render3D; return 1; } /****************************************************************************** Error and Debug Logging ******************************************************************************/ static CLogger *s_logger = NULL; /* * Returns the current logger. */ CLogger *GetLogger() { return s_logger; } /* * Sets the current logger. */ void SetLogger(CLogger *logger) { s_logger = logger; } /* * DebugLog(fmt, ...): * * Logs a debug message with the logger. * * Parameters: * fmt Format string (same as printf()). * ... Variable number of arguments as required by format string. */ void DebugLog(const char *fmt, ...) { if (s_logger == NULL) return; va_list vl; va_start(vl, fmt); s_logger->DebugLog(fmt, vl); va_end(vl); } /* * InfoLog(fmt, ...); * * Logs an info message with the logger. * * Parameters: * fmt Format string (same as printf()). * ... Variable number of arguments as required by format string. */ void InfoLog(const char *fmt, ...) { if (s_logger == NULL) return; va_list vl; va_start(vl, fmt); s_logger->InfoLog(fmt, vl); va_end(vl); } /* * ErrorLog(fmt, ...): * * Logs an error message with the logger. * * Parameters: * fmt Format string (same as printf()). * ... Variable number of arguments as required by format string. * * Returns: * Always returns FAIL. */ BOOL ErrorLog(const char *fmt, ...) { if (s_logger == NULL) return FAIL; va_list vl; va_start(vl, fmt); s_logger->ErrorLog(fmt, vl); va_end(vl); return FAIL; } #define DEBUG_LOG_FILE "debug.log" #define ERROR_LOG_FILE "error.log" /* * Default logger that logs to debug and error log files. */ class CFileLogger : public CLogger { private: const char *m_debugLogFile; const char *m_errorLogFile; public: CFileLogger(const char *debugLogFile, const char *errorLogFile) : m_debugLogFile(debugLogFile), m_errorLogFile(errorLogFile) { // } /* * DebugLog(fmt, ...): * * Prints to debug log. The file is opened and closed each time so that its * contents are preserved even if the program crashes. */ void DebugLog(const char *fmt, va_list vl) { #ifdef DEBUG char string[1024]; FILE *fp; fp = fopen(m_debugLogFile, "ab"); if (NULL != fp) { vsprintf(string, fmt, vl); fprintf(fp, string); fclose(fp); } #endif // DEBUG } /* * InfoLog(fmt, ...); * * Prints information to the error log file but does not print to stderr. This * is useful for logging non-error information. */ void InfoLog(const char *fmt, va_list vl) { char string[4096]; FILE *fp; vsprintf(string, fmt, vl); fp = fopen(m_errorLogFile, "ab"); if (NULL != fp) { fprintf(fp, "%s\n", string); fclose(fp); } CLogger::DebugLog("Info: "); CLogger::DebugLog(string); CLogger::DebugLog("\n"); } /* * ErrorLog(fmt, ...): * * Prints an error to stderr and the error log file. */ void ErrorLog(const char *fmt, va_list vl) { char string[4096]; FILE *fp; vsprintf(string, fmt, vl); fprintf(stderr, "Error: %s\n", string); fp = fopen(m_errorLogFile, "ab"); if (NULL != fp) { fprintf(fp, "%s\n", string); fclose(fp); } CLogger::DebugLog("Error: "); CLogger::DebugLog(string); CLogger::DebugLog("\n"); } void ClearLogs() { #ifdef DEBUG ClearLog(DEBUG_LOG_FILE, "Supermodel v"SUPERMODEL_VERSION" Debug Log"); #endif // DEBUG ClearLog(ERROR_LOG_FILE, "Supermodel v"SUPERMODEL_VERSION" Error Log"); } // Clear log file void ClearLog(const char *file, const char *title) { FILE *fp = fopen(file, "w"); if (NULL != fp) { unsigned i; fprintf(fp, "%s\n", title); for (i = 0; i < strlen(title); i++) fputc('-', fp); fprintf(fp, "\n\n"); fclose(fp); } } }; /****************************************************************************** Diagnostic Commands ******************************************************************************/ // Disassemble instructions from CROM static int DisassembleCROM(const char *zipFile, UINT32 addr, unsigned n) { const struct GameInfo *Game; UINT8 *crom; struct ROMMap Map[] = { { "CROM", NULL }, { "CROMxx", NULL }, { NULL, NULL } }; char mnem[16], oprs[48]; UINT32 op; // Do we have a valid CROM address? if (addr < 0xFF800000) return ErrorLog("Valid CROM address range is FF800000-FFFFFFFF."); // Allocate memory and set ROM region crom = new(std::nothrow) UINT8[0x8800000]; if (NULL == crom) return ErrorLog("Insufficient memory to load CROM (need %d MB).", (0x8800000/8)); Map[0].ptr = crom; Map[1].ptr = &crom[0x800000]; // Load ROM set Game = LoadROMSetFromZIPFile(Map, Model3GameList, zipFile, FALSE); if (NULL == Game) return ErrorLog("Failed to load ROM set."); // Mirror CROM if necessary if (Game->cromSize < 0x800000) // high part of fixed CROM region contains CROM0 CopyRegion(crom, 0, 0x800000-0x200000, &crom[0x800000], 0x800000); // Disassemble! addr -= 0xFF800000; while ((n > 0) && ((addr+4) <= 0x800000)) { op = (crom[addr+0]<<24) | (crom[addr+1]<<16) | (crom[addr+2]<<8) | crom[addr+3]; printf("%08X: ", addr+0xFF800000); if (DisassemblePowerPC(op, addr+0xFF800000, mnem, oprs, 1)) { if (mnem[0] != '\0') // invalid form printf("%08X %s*\t%s\n", op, mnem, oprs); else printf("%08X ?\n", op); } else printf("%08X %s\t%s\n", op, mnem, oprs); addr += 4; --n; } delete [] crom; return OKAY; } /* * PrintGLInfo(): * * Queries and prints OpenGL information. A full list of extensions can * optionally be printed. */ static void PrintGLInfo(BOOL printExtensions) { const GLubyte *str; char *strLocal; GLint value; unsigned xOffset, yOffset, xRes=496, yRes=384; if (OKAY != CreateGLScreen("Supermodel - Querying OpenGL Information...",&xOffset,&yOffset,&xRes,&yRes,FALSE,FALSE)) { ErrorLog("Unable to query OpenGL.\n"); return; } puts("OpenGL information:\n"); str = glGetString(GL_VENDOR); printf(" Vendor: %s\n", str); str = glGetString(GL_RENDERER); printf(" Renderer: %s\n", str); str = glGetString(GL_VERSION); printf(" Version: %s\n", str); str = glGetString(GL_SHADING_LANGUAGE_VERSION); printf(" Shading Language Version: %s\n", str); glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &value); printf("Maximum Vertex Array Size: %d vertices\n", value); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value); printf(" Maximum Texture Size: %d texels\n", value); glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &value); printf("Maximum Vertex Attributes: %d\n", value); glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &value); printf(" Maximum Vertex Uniforms: %d\n", value); if (printExtensions) { str = glGetString(GL_EXTENSIONS); strLocal = (char *) malloc((strlen((char *) str)+1)*sizeof(char)); if (NULL == strLocal) printf(" Supported Extensions: %s\n", str); else { strcpy(strLocal, (char *) str); printf(" Supported Extensions: %s\n", (strLocal = strtok(strLocal, " \t\n"))); while ((strLocal = strtok(NULL, " \t\n")) != NULL) printf(" %s\n", strLocal); } } printf("\n"); } /****************************************************************************** Entry Point and Command Line Procesing ******************************************************************************/ // Print Supermodel title and copyright information static void Title(void) { puts("Supermodel: A Sega Model 3 Arcade Emulator (Version "SUPERMODEL_VERSION")"); puts("Copyright (C) 2011 by Bart Trzynadlowski"); puts(""); } // Print usage information static void Help(void) { puts("Usage: Supermodel [options]"); puts("ROM set must be a valid ZIP file containing a single game."); puts(""); puts("General Options:"); puts(" -?, -h Print this help text"); puts(" -print-games List supported games"); puts(""); puts("Emulation Options:"); puts(" -ppc-frequency= Set PowerPC frequency in MHz [Default: 25]"); puts(" -multi-threaded Enable multi-threading"); #ifdef SUPERMODEL_DEBUGGER puts(" -disable-debugger Completely disable debugger functionality"); puts(" -enter-debugger Enter debugger at start of emulation"); #endif // SUPERMODEL_DEBUGGER puts(""); puts("Video Options:"); puts(" -res=, Resolution"); puts(" -fullscreen Full screen mode"); puts(" -no-throttle Disable 60 Hz frame rate limit"); puts(" -show-fps Display frame rate in window title bar"); #ifdef DEBUG // ordinary users do not need to know about these, but they are always available puts(" -vert-shader= Load 3D vertex shader from external file"); puts(" -frag-shader= Load 3D fragment shader from external file"); #endif puts(""); puts("Input Options:"); puts(" -input-system= Set input system [Default: SDL]"); puts(" -print-inputs Prints current input configuration"); puts(" -config-inputs Configure inputs for keyboards, mice and joysticks"); puts(""); puts("Diagnostic Options:"); #ifdef DEBUG puts(" -dis=[,n] Disassemble PowerPC code from CROM"); #endif puts(" -print-gl-info Print extensive OpenGL information\n"); } // Print game list static void PrintGameList(void) { int i, j; puts("Supported games:"); puts(""); puts(" ROM Set Title"); puts(" ------- -----"); for (i = 0; Model3GameList[i].title != NULL; i++) { printf(" %s", Model3GameList[i].id); for (j = strlen(Model3GameList[i].id); j < 8; j++) // pad for alignment (no game ID is more than 8 letters) printf(" "); printf(" %s\n", Model3GameList[i].title); } } /* * main(argc, argv): * * Program entry point. */ int main(int argc, char **argv) { int i, ret; int cmd=0, fileIdx=0, cmdMultiThreaded=0, cmdFullScreen=0, cmdNoThrottle=0, cmdShowFPS=0, cmdPrintInputs=0, cmdConfigInputs=0, cmdPrintGames=0, cmdDis=0, cmdPrintGLInfo=0; #ifdef SUPERMODEL_DEBUGGER int cmdDisableDebugger = 0, cmdEnterDebugger=0; #endif // SUPERMODEL_DEBUGGER unsigned n, xRes=496, yRes=384, ppcFrequency=25000000; char *vsFile = NULL, *fsFile = NULL, *inpSysName = NULL; UINT32 addr; Title(); if (argc <= 1) { Help(); return 0; } // Create default logger CFileLogger Logger(DEBUG_LOG_FILE, ERROR_LOG_FILE); Logger.ClearLogs(); SetLogger(&Logger); // Parse command line for (i = 1; i < argc; i++) { if (!strcmp(argv[i],"-h") || !strcmp(argv[i],"-?")) { Help(); return 0; } else if (!strcmp(argv[i],"-print-games")) cmd = cmdPrintGames = 1; else if (!strncmp(argv[i],"-ppc-frequency",14)) { int f; ret = sscanf(&argv[i][14],"=%d",&f); if (ret != 1) ErrorLog("-ppc-frequency requires a frequency."); else { if ((f<1) || (f>1000)) // limit to 1-1000MHz ErrorLog("PowerPC frequency must be between 1 and 1000 MHz. Ignoring."); else ppcFrequency = f*1000000; } } else if (!strncmp(argv[i],"-multi-threaded", 16)) cmd = cmdMultiThreaded = 1; #ifdef SUPERMODEL_DEBUGGER else if (!strncmp(argv[i],"-disable-debugger",17)) cmd = cmdDisableDebugger = 1; else if (!strncmp(argv[i],"-enter-debugger",15)) cmd = cmdEnterDebugger = 1; #endif // SUPERMODEL_DEBUGGER else if (!strncmp(argv[i],"-res",4)) { unsigned x, y; ret = sscanf(&argv[i][4],"=%d,%d",&x,&y); if (ret != 2) ErrorLog("-res requires both a width and a height."); else { xRes = x; yRes = y; } } else if (!strcmp(argv[i],"-fullscreen")) cmd = cmdFullScreen = 1; else if (!strcmp(argv[i],"-no-throttle")) cmd = cmdNoThrottle = 1; else if (!strcmp(argv[i],"-show-fps")) cmd = cmdShowFPS = 1; else if (!strncmp(argv[i],"-vert-shader=",13)) { if (argv[i][13] == '\0') ErrorLog("-vert-shader requires a file path."); else vsFile = &argv[i][13]; } else if (!strncmp(argv[i],"-frag-shader=",13)) { if (argv[i][13] == '\0') ErrorLog("-frag-shader requires a file path."); else fsFile = &argv[i][13]; } else if (!strncmp(argv[i],"-input-system=", 14)) { if (argv[i][14] == '\0') ErrorLog("-input-system requires an input system name."); else inpSysName = &argv[i][14]; } else if (!strcmp(argv[i],"-print-inputs")) cmd = cmdPrintInputs = 1; else if (!strcmp(argv[i],"-config-inputs")) cmd = cmdConfigInputs = 1; else if (!strncmp(argv[i],"-dis",4)) { ret = sscanf(&argv[i][4],"=%X,%X",&addr,&n); if (ret == 1) { n = 16; cmd = cmdDis = 1; } else if (ret == 2) cmd = cmdDis = 1; else ErrorLog("-dis requires address and, optionally, number of instructions."); } else if (!strcmp(argv[i],"-print-gl-info")) cmd = cmdPrintGLInfo = 1; else if (argv[i][0] == '-') ErrorLog("Ignoring invalid option: %s.", argv[i]); else { if (fileIdx) // already specified a file ErrorLog("Multiple files specified. Using %s, ignoring %s.", argv[i], argv[fileIdx]); else fileIdx = i; } } // Initialize SDL (individual subsystems get initialized later) if (SDL_Init(0) != 0) { ErrorLog("Unable to initialize SDL: %s\n", SDL_GetError()); return 1; } CInputSystem *InputSystem = NULL; CInputs *Inputs = NULL; int exitCode = 0; #ifdef SUPERMODEL_DEBUGGER CModel3 *Model3 = NULL; Debugger::CSupermodelDebugger *Debugger = NULL; #endif // SUPERMODEL_DEBUGGER // Create input system (default is SDL) if (inpSysName == NULL || stricmp(inpSysName, "sdl") == 0) InputSystem = new CSDLInputSystem(); #ifdef SUPERMODEL_WIN32 else if (stricmp(inpSysName, "dinput") == 0) InputSystem = new CDirectInputSystem(false, false, false); else if (stricmp(inpSysName, "xinput") == 0) InputSystem = new CDirectInputSystem(false, true, false); else if (stricmp(inpSysName, "rawinput") == 0) InputSystem = new CDirectInputSystem(true, false, false); #endif // SUPERMODEL_WIN32 else { ErrorLog("Unknown input system: '%s'.\n", inpSysName); exitCode = 1; goto Exit; } // Create inputs from input system (configuring them if required) Inputs = CreateInputs(InputSystem, cmdConfigInputs); if (Inputs == NULL) { exitCode = 1; goto Exit; } if (cmdPrintInputs) { Inputs->PrintInputs(NULL); InputSystem->PrintSettings(); } // Process commands that don't require ROM set if (cmd) { if (cmdPrintGames) { PrintGameList(); goto Exit; } if (cmdPrintGLInfo) { PrintGLInfo(FALSE); goto Exit; } } if (fileIdx == 0) { ErrorLog("No ROM set specified."); exitCode = 1; goto Exit; } // Process commands that require ROMs if (cmd) { if (cmdDis) { if (OKAY != DisassembleCROM(argv[fileIdx], addr, n)) exitCode = 1; goto Exit; } } #ifdef SUPERMODEL_DEBUGGER // Create Model3 Model3 = new CModel3(); // Create Supermodel debugger unless debugging is disabled if (!cmdDisableDebugger) { Debugger = new Debugger::CSupermodelDebugger(Model3, Inputs, &Logger); // If -enter-debugger option was set force debugger to break straightaway if (cmdEnterDebugger) Debugger->ForceBreak(true); } // Fire up Supermodel with debugger exitCode = Supermodel(argv[fileIdx],Model3,Inputs,Debugger,ppcFrequency,cmdMultiThreaded,xRes,yRes,TRUE,cmdFullScreen,cmdNoThrottle,cmdShowFPS,vsFile,fsFile); if (Debugger != NULL) delete Debugger; delete Model3; #else // Fire up Supermodel exitCode = Supermodel(argv[fileIdx],Inputs,ppcFrequency,cmdMultiThreaded,xRes,yRes,TRUE,cmdFullScreen,cmdNoThrottle,cmdShowFPS,vsFile,fsFile); #endif // SUPERMODEL_DEBUGGER Exit: if (Inputs != NULL) delete Inputs; if (InputSystem != NULL) delete InputSystem; SDL_Quit(); return exitCode; }