Exemplo n.º 1
0
 /**
  * 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);
 }
Exemplo n.º 2
0
        /**
         * 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();
        }
Exemplo n.º 3
0
        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();
        }