/** * Used to restore the state of the CPU and restore all fields to allow the emulator * to continue execution from a previously saved state. */ public void LoadState(EmulatorState state) { _cpu.Registers = state.Registers; _cpu.Flags = state.Flags; _cpu.Halted = state.Halted; _cpu.InterruptsEnabled = state.InterruptsEnabled; _cpu.InterruptsEnabledPreviousValue = state.InterruptsEnabledPreviousValue; _cpu.InterruptMode = state.InterruptMode; _memory = state.Memory; _spriteCoordinates = state.SpriteCoordinates; _totalCycles = state.TotalCycles; _totalOpcodes = state.TotalOpcodes; _cyclesSinceLastInterrupt = state.CyclesSinceLastInterrupt; _audio.LoadState(state.AudioHardwareState); }
/** * Used to start execution of the CPU with the given ROM and optional emulator state. * The emulator's hardware loop will run on a spereate thread, and therefore, this method * is non-blocking. */ public void Start(ROMData romData, EmulatorState state = null) { if (_thread != null) { throw new Exception("Emulator cannot be started because it was already running."); } if (romData == null || romData.Data == null || romData.Data.Count == 0) { throw new Exception("romData is required."); } if (ROMSet != ROMSet.PacMan && ROMSet != ROMSet.MsPacMan) { throw new ArgumentException($"Unexpected ROM set: {ROMSet}"); } // The initial configuration of the CPU. var cpuConfig = new CPUConfig() { Registers = new CPURegisters() { PC = 0x0000, // Hardcode the stackpointer to the top of the RAM. // TODO: Is this different for each game that runs on the Pac-Man hardware? SP = 0x4FEF, }, // Interrupts are initially disabled, and will be enabled by the program ROM when ready. InterruptsEnabled = false, // Diagnostics is only for unit tests. EnableDiagnosticsMode = false, }; // Initialize the CPU and subscribe to device events. _cpu = new CPU(cpuConfig); _cpu.OnDeviceRead += CPU_OnDeviceRead; _cpu.OnDeviceWrite += CPU_OnDeviceWrite; _cyclesSinceLastInterrupt = 0; // Fetch the ROM data; we trust the contents were validated with a CRC32 check elsewhere, but // since the CRC check can be bypassed, we at least need to ensure the file sizes are correct // since this classes' implementation of IMemory is expecting certain addreses. var codeRom1 = romData.Data[ROMs.PAC_MAN_CODE_1.FileName]; var codeRom2 = romData.Data[ROMs.PAC_MAN_CODE_2.FileName]; var codeRom3 = romData.Data[ROMs.PAC_MAN_CODE_3.FileName]; var codeRom4 = romData.Data[ROMs.PAC_MAN_CODE_4.FileName]; if (codeRom1.Length != 4096 || codeRom2.Length != 4096 || codeRom3.Length != 4096 || codeRom4.Length != 4096) { throw new Exception("All code ROMs must be exactly 4KB in size."); } // Define our addressable memory space, which includes the game code ROMS and RAM. var addressableMemorySize = codeRom1.Length // Code ROM 1 + codeRom2.Length // Code ROM 2 + codeRom3.Length // Code ROM 3 + codeRom4.Length // Code ROM 4 + 1024 // Video RAM (tile information) + 1024 // Video RAM (tile palettes) + 2032 // RAM + 16; // Sprite numbers _memory = new byte[addressableMemorySize]; // Map the code ROM into the lower 16K of the memory space. Array.Copy(codeRom1, 0, _memory, 0, codeRom1.Length); Array.Copy(codeRom2, 0, _memory, codeRom1.Length, codeRom2.Length); Array.Copy(codeRom3, 0, _memory, codeRom1.Length + codeRom2.Length, codeRom3.Length); Array.Copy(codeRom4, 0, _memory, codeRom1.Length + codeRom2.Length + codeRom3.Length, codeRom4.Length); // Load and decrypt the Ms. Pac-Man daughterboard ROMs and apply patches to the base Pac-Man ROMs. // The patches will be applied to a seperate memory instance because we still need the original, // unmodified Pac-Man code ROMs present to boot and pass the self-test. See PacManPCB::Read(). if (ROMSet == ROMSet.MsPacMan) { _auxBoard = new MsPacManAuxBoard(); _auxBoard.LoadAuxROMs(romData); } // This class implements the IMemory interface, which the CPU needs to determine how to read and // write data. We set the reference to this class instance (whose implementation uses _memory). _cpu.Memory = this; // Initialize video hardware. _video = new VideoHardware(romData, ROMSet); _video.Initialize(); // Initialize audio hardware. _audio = new AudioHardware(romData, ROMSet); if (state != null) { LoadState(state); } _cancelled = false; _thread = new Thread(new ThreadStart(HardwareLoop)); _thread.Name = "Pac-Man Hardware"; _thread.Start(); }
public static void Start(EmulatorConfig config) { if (String.IsNullOrWhiteSpace(config.RomPath)) { throw new Exception("A directory containing Pac-Man arcade hardware compatible ROM files is required."); } if (!Directory.Exists(config.RomPath)) { throw new Exception($"Could not locate a directory at path {config.RomPath}"); } // Load and validate all of the ROM files needed. var enforceValidChecksums = !config.SkipChecksums; var romData = ROMLoader.LoadFromDisk(config.RomSet, config.RomPath, enforceValidChecksums); // Name the current thread so we can distinguish between the emulator's // CPU thread when using a debugger. Thread.CurrentThread.Name = "Platform (SDL)"; // Initialize the platform wrapper which allows us to interact with // the platform's graphics, audio, and input devices. Wire an event // handler that will handle receiving user input as well as sending // the framebuffer to be rendered. _platform = new Platform(); _platform.OnTick += Platform_OnTick; _platform.OnDebugCommand += Platform_OnDebugCommand; _platform.Initialize("Pac-Man Arcade Hardware Emulator", VideoHardware.RESOLUTION_WIDTH, VideoHardware.RESOLUTION_HEIGHT, GAME_WINDOW_SCALE_X, GAME_WINDOW_SCALE_Y); // Initialize the Pac-Man arcade hardware/emulator and wire event // handlers to receive the framebuffer/samples to be rendered/played. _game = new PacManPCB(); _game.ROMSet = config.RomSet; _game.AllowWritableROM = config.WritableRom; _game.OnRender += PacManPCB_OnRender; _game.OnAudioSample += PacManPCB_OnAudioSample; _game.OnBreakpointHitEvent += PacManPCB_OnBreakpointHit; #region Set Game Options // Use the default values for the hardware DIP switches. var dipSwitchState = new DIPSwitches(); // Look in the current working directory for a settings file. var dipSwitchesPath = "dip-switches.json"; // If the user specified a path for the settings file, use it instead. if (!String.IsNullOrWhiteSpace(config.DipSwitchesConfigPath)) { dipSwitchesPath = config.DipSwitchesConfigPath; } if (File.Exists(dipSwitchesPath)) { // We found a settings file! Parse and load it. var json = File.ReadAllText(dipSwitchesPath); try { dipSwitchState = JsonConvert.DeserializeObject <DIPSwitches>(json); } catch (Exception parseException) { throw new Exception($"Error parsing DIP switch settings JSON file at: {dipSwitchesPath}", parseException); } } else { // If the file doesn't exist and the user specified the path, fail fast. // There must have been a typo or something. In any case the user should fix it. if (!String.IsNullOrWhiteSpace(config.DipSwitchesConfigPath)) { throw new ArgumentException($"Unable to locate a DIP switch settings JSON file at: {dipSwitchesPath}"); } } _game.DIPSwitchState = dipSwitchState; #endregion #region Load State // If the path to a save state was specified to be loaded, deserialize // it from disk and ensure it gets passed into the emulator on start. EmulatorState state = null; if (!String.IsNullOrWhiteSpace(config.LoadStateFilePath)) { var json = File.ReadAllText(config.LoadStateFilePath); state = JsonConvert.DeserializeObject <EmulatorState>(json); } #endregion #region Set Debugging Flags if (config.Debug) { _game.Debug = true; _platform.InitializeDebugger(DEBUG_WINDOW_SCALE_X, DEBUG_WINDOW_SCALE_Y); if (config.Breakpoints != null) { _game.BreakAtAddresses = config.Breakpoints; } if (config.ReverseStep) { _game.ReverseStepEnabled = true; } if (!String.IsNullOrWhiteSpace(config.AnnotationsFilePath)) { if (!File.Exists(config.AnnotationsFilePath)) { throw new Exception($"Could not locate an annotations file at path {config.AnnotationsFilePath}"); } try { var annotations = Annotations.ParseFile(config.AnnotationsFilePath); _game.Annotations = annotations; } catch (Exception ex) { throw new Exception($"Error parsing annotations file.", ex); } } } #endregion // Start the emulation; this occurs in a seperate thread and // therefore this call is non-blocking. _game.Start(romData, state); // Starts the event loop for the user interface; this occurs on // the same thread and is a blocking call. Once this method returns // we know that the user closed the window or quit the program via // the OS (e.g. ALT+F4 / CMD+Q). _platform.StartLoop(); // Ensure the platform resources are cleaned up and stop the emulation. _platform.Dispose(); _game.Stop(); }