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