/** * 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(byte[] rom, EmulatorState state = null) { if (_thread != null) { throw new Exception("Emulator cannot be started because it was already running."); } _cyclesSinceLastInterrupt = 0; _nextInterrupt = Interrupt.One; _cpu = new CPU(_cpuConfig); _cpu.OnDeviceRead += CPU_OnDeviceRead; _cpu.OnDeviceWrite += CPU_OnDeviceWrite; // Map the ROM into the lower 8K of the memory space. var memory = new byte[_cpuConfig.MemorySize]; Array.Copy(rom, memory, rom.Length); _cpu.LoadMemory(memory); _shiftRegister = new ShiftRegister(); _renderEventArgs = new RenderEventArgs() { ShouldRender = false, FrameBuffer = new byte[FRAME_BUFFER_SIZE], }; _soundEventArgs = new SoundEventArgs(); if (state != null) { LoadState(state); } _cancelled = false; _thread = new Thread(new ThreadStart(HardwareLoop)); _thread.Name = "Emulator: Hardware Loop"; _thread.Start(); }
private static void Run(CommandLineApplication command) { command.Description = "Runs the emulator using the given ROM file."; command.HelpOption("-?|-h|--help"); var romPathArg = command.Argument("[ROM path]", "The path to a directory containing the Space Invaders ROM set to load."); var sfxOption = command.Option("-sfx|--sound-effects", "The path to a directory containing the WAV sound effects to be used.", CommandOptionType.SingleValue); var shipsOption = command.Option("-ss|--starting-ships", "Specify the number of ships the player starts with; 3 (default), 4, 5, or 6.", CommandOptionType.SingleValue); var extraShipOption = command.Option("-es|--extra-ship", "Specify the number points needed to get an extra ship; 1000 (default) or 1500.", CommandOptionType.SingleValue); var loadStateOption = command.Option("-l|--load-state", "Loads an emulator save state from the given path.", CommandOptionType.SingleValue); var debugOption = command.Option("-d|--debug", "Run in debug mode; enables internal statistics and logs useful when debugging.", CommandOptionType.NoValue); var breakOption = command.Option("-b|--break", "Used with debug, will break at the given address and allow single stepping opcode execution (e.g. --break 0x0248)", CommandOptionType.MultipleValue); var rewindOption = command.Option("-r|--rewind", "Used with debug, allows for single stepping in reverse to rewind opcode execution.", CommandOptionType.NoValue); var annotationsPathOption = command.Option("-a|--annotations", "Used with debug, a path to a text file containing memory address annotations for interactive debugging (line format: 0x1234 .... ; Annotation)", CommandOptionType.SingleValue); command.OnExecute(() => { if (String.IsNullOrWhiteSpace(romPathArg.Value)) { throw new Exception("A directory containing invaders.e though .h files is required."); } if (!Directory.Exists(romPathArg.Value)) { throw new Exception($"Could not locate a directory at path {romPathArg.Value}"); } var rom = ReadRomFiles(romPathArg.Value); var sfx = sfxOption.HasValue() ? GetSoundEffects(sfxOption.Value()) : null; Thread.CurrentThread.Name = "GUI Loop"; // Initialize the user interface (window) and wire an event handler // that will handle receiving user input as well as sending the // framebuffer to be rendered. Note that we pass the width/height // parameters backwards on purpose because the CRT screen in the // cabinet is rotated to the left (-90 degrees) for a 3:4 display. var gui = new GUI(); gui.OnTick += GUI_OnTick; gui.Initialize("Space Invaders Emulator", SpaceInvaders.RESOLUTION_HEIGHT, SpaceInvaders.RESOLUTION_WIDTH, 2, 2); // Initialize sound effects if the sfx option was passed. if (sfx != null) { gui.InitializeAudio(sfx); } // Initialize the Space Invaders hardware/emulator and wire event // handlers to receive the framebuffer/sfx to be rendered/played. _game = new SpaceInvaders(); _game.OnRender += SpaceInvaders_OnRender; _game.OnSound += SpaceInvaders_OnSound; #region Set Game Options if (shipsOption.HasValue()) { switch (shipsOption.Value()) { case "6": _game.StartingShips = StartingShipsSetting.Six; break; case "5": _game.StartingShips = StartingShipsSetting.Five; break; case "4": _game.StartingShips = StartingShipsSetting.Four; break; case "3": _game.StartingShips = StartingShipsSetting.Three; break; default: throw new ArgumentException("Invaild value specified via --starting-ships command line option."); } } if (extraShipOption.HasValue()) { switch (extraShipOption.Value()) { case "1000": _game.ExtraShipAt = ExtraShipAtSetting.Points1000; break; case "1500": _game.ExtraShipAt = ExtraShipAtSetting.Points1500; break; default: throw new ArgumentException("Invaild value specified via --extra-ship command line option."); } } #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 (loadStateOption.HasValue()) { var json = File.ReadAllText(loadStateOption.Value()); state = JsonSerializer.Deserialize <EmulatorState>(json); } #endregion #region Set Debugging Flags if (debugOption.HasValue()) { _game.Debug = true; if (breakOption.HasValue()) { var addresses = new List <UInt16>(); foreach (var addressString in breakOption.Values) { UInt16 address = Convert.ToUInt16(addressString, 16); addresses.Add(address); } _game.BreakAtAddresses = addresses; } if (rewindOption.HasValue()) { _game.RewindEnabled = true; } if (annotationsPathOption.HasValue()) { var annotationsFilePath = annotationsPathOption.Value(); if (!File.Exists(annotationsFilePath)) { throw new Exception($"Could not locate an annotations file at path {annotationsFilePath}"); } try { var annotations = ParseAnnotationsFile(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(rom, 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). gui.StartLoop(); // Ensure the GUI resources are cleaned up and stop the emulation. gui.Dispose(); _game.Stop(); return(0); }); }