예제 #1
0
        private static void RenderInstructionHistory(IntPtr surface, PacManPCB pcb)
        {
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}------------------------------[HISTORY]-----------------------------------------", 0, 0 * ROW_HEIGHT);

            var recentInstructions = FormatRecentInstructions(pcb._addressHistory, pcb, 50);

            for (var i = 0; i < recentInstructions.Count && i < 50; i++)
            {
                FontRenderer.RenderString(surface, recentInstructions[i], 0, (i + 2) * ROW_HEIGHT);
            }

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}--------------------------------------------------------------------------------", 0, (50 + 3) * ROW_HEIGHT);

            FontRenderer.RenderString(surface, $"Press {COLOR_BRIGHT_YELLOW}[ESCAPE] {COLOR_WHITE}to go back.", 0, (50 + 4) * ROW_HEIGHT);

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}--------------------------------------------------------------------------------", 0, (50 + 9) * ROW_HEIGHT);
        }
예제 #2
0
        private static void RenderCpuStateAndDisassembly(IntPtr surface, DebuggerState state, PacManPCB pcb, bool showAnnotatedDisassembly)
        {
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}------------------------------[STATS]-------------------------------------------", 0, 0 * ROW_HEIGHT);

            var statusColor = $"{COLOR_GREEN}";
            var status      = $"Running";
            var cycleCount  = (state == DebuggerState.Breakpoint ? String.Format("{0:n0}", pcb._totalCycles) : "---").PadRight(13);
            var opcodeCount = (state == DebuggerState.Breakpoint ? String.Format("{0:n0}", pcb._totalOpcodes) : "---").PadRight(13);

            if (state == DebuggerState.Breakpoint)
            {
                statusColor = $"{COLOR_BRIGHT_RED}";
                status      = "Breakpoint";
            }
            else if (state == DebuggerState.SingleStepping)
            {
                statusColor = $"{COLOR_BRIGHT_YELLOW}";
                status      = "Stepping...";
            }

            status = status.PadRight(12);

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[Status]: {statusColor}{status} {COLOR_BRIGHT_WHITE}[Cycles]: {COLOR_WHITE}{cycleCount}  {COLOR_BRIGHT_WHITE}[Opcodes]: {COLOR_WHITE}{opcodeCount}", 0, 2 * ROW_HEIGHT);

            FontRenderer.RenderString(surface, "------------------------------[CPU STATE]---------------------------------------", 0, 4 * ROW_HEIGHT);

            var pc             = EMPTY_16BIT_HEX_VALUE_DISPLAY;
            var sp             = EMPTY_16BIT_HEX_VALUE_DISPLAY;
            var regA           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regB           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regC           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regD           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regE           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regH           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regL           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regDE          = EMPTY_16BIT_HEX_VALUE_DISPLAY;
            var regHL          = EMPTY_16BIT_HEX_VALUE_DISPLAY;
            var regIX          = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regIY          = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var regF           = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var memDE          = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var memHL          = EMPTY_8BIT_HEX_VALUE_DISPLAY;
            var sign           = EMPTY_BIT_VALUE_DISPLAY;
            var zero           = EMPTY_BIT_VALUE_DISPLAY;
            var parityOverflow = EMPTY_BIT_VALUE_DISPLAY;
            var subtract       = EMPTY_BIT_VALUE_DISPLAY;
            var carry          = EMPTY_BIT_VALUE_DISPLAY;
            var halfCarry      = EMPTY_BIT_VALUE_DISPLAY;
            var interrupts     = EMPTY_8BIT_HEX_VALUE_DISPLAY;

            if (state == DebuggerState.Breakpoint && pcb._cpu != null)
            {
                pc    = String.Format("{0:X4}", pcb._cpu.Registers.PC);
                sp    = String.Format("{0:X4}", pcb._cpu.Registers.SP);
                regA  = String.Format("{0:X2}", pcb._cpu.Registers.A);
                regB  = String.Format("{0:X2}", pcb._cpu.Registers.B);
                regC  = String.Format("{0:X2}", pcb._cpu.Registers.C);
                regD  = String.Format("{0:X2}", pcb._cpu.Registers.D);
                regE  = String.Format("{0:X2}", pcb._cpu.Registers.E);
                regH  = String.Format("{0:X2}", pcb._cpu.Registers.H);
                regL  = String.Format("{0:X2}", pcb._cpu.Registers.L);
                regDE = String.Format("{0:X4}", pcb._cpu.Registers.DE);
                regHL = String.Format("{0:X4}", pcb._cpu.Registers.HL);
                regIX = String.Format("{0:X2}", pcb._cpu.Registers.IX);
                regIY = String.Format("{0:X2}", pcb._cpu.Registers.IY);
                regF  = String.Format("{0:X2}", pcb._cpu.Flags.ToByte());

                try {
                    var value = pcb._cpu.Memory.Read(pcb._cpu.Registers.DE);
                    memDE = String.Format("{0:X2}", value);
                }
                catch {
                    memDE = EMPTY_8BIT_HEX_VALUE_DISPLAY;
                }

                try {
                    var value = pcb._cpu.Memory.Read(pcb._cpu.Registers.HL);
                    memHL = String.Format("{0:X2}", value);
                }
                catch {
                    memDE = EMPTY_8BIT_HEX_VALUE_DISPLAY;
                }

                sign           = pcb._cpu.Flags.Sign ? "1" : "0";
                zero           = pcb._cpu.Flags.Zero ? "1" : "0";
                parityOverflow = pcb._cpu.Flags.ParityOverflow ? "1" : "0";
                subtract       = pcb._cpu.Flags.Subtract ? "1" : "0";
                carry          = pcb._cpu.Flags.Carry ? "1" : "0";
                halfCarry      = pcb._cpu.Flags.HalfCarry ? "1" : "0";

                interrupts = $"{(pcb._cpu.InterruptsEnabled ? "Enabled" : "Disabled" )} (Mode {(int)pcb._cpu.InterruptMode})";
            }

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[PC]: {COLOR_WHITE}{pc}        {COLOR_BRIGHT_WHITE}[SP]: {COLOR_WHITE}{sp}        {COLOR_BRIGHT_WHITE}[Flags]: {COLOR_WHITE}{regF}       {COLOR_BRIGHT_WHITE}[INT]: {COLOR_WHITE}{interrupts}", 0, 6 * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[A]: {COLOR_WHITE}{regA}  {COLOR_BRIGHT_WHITE}[B]: {COLOR_WHITE}{regB}  {COLOR_BRIGHT_WHITE}[C]: {COLOR_WHITE}{regC}  {COLOR_BRIGHT_WHITE}[D]: {COLOR_WHITE}{regD}  {COLOR_BRIGHT_WHITE}[E]: {COLOR_WHITE}{regE}  {COLOR_BRIGHT_WHITE}[H]: {COLOR_WHITE}{regH}  {COLOR_BRIGHT_WHITE}[L]: {COLOR_WHITE}{regL}", 0, 8 * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[DE]: {COLOR_WHITE}{regDE}        {COLOR_BRIGHT_WHITE}[HL]: {COLOR_WHITE}{regHL}        {COLOR_BRIGHT_WHITE}[(DE)]: {COLOR_WHITE}{memDE}        {COLOR_BRIGHT_WHITE}[(HL)]: {COLOR_WHITE}{memHL}", 0, 10 * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[Sign]: {COLOR_WHITE}{sign}         {COLOR_BRIGHT_WHITE}[Zero]: {COLOR_WHITE}{zero}         {COLOR_BRIGHT_WHITE}[Parity/Overflow]: {COLOR_WHITE}{parityOverflow}", 0, 12 * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[Subtract]: {COLOR_WHITE}{subtract}     {COLOR_BRIGHT_WHITE}[Carry]: {COLOR_WHITE}{carry}        {COLOR_BRIGHT_WHITE}[Half-Carry]: {COLOR_WHITE}{halfCarry}", 0, 14 * ROW_HEIGHT);

            FontRenderer.RenderString(surface, "------------------------------[DISASSEMBLY]-------------------------------------", 0, 16 * ROW_HEIGHT);

            if (state == DebuggerState.Breakpoint)
            {
                var disassembly = Disassembler.FormatDisassemblyForDisplay(pcb._cpu.Registers.PC, pcb._cpu.Memory, 16, 16, showAnnotatedDisassembly ? pcb.Annotations : null);

                var disassemblyLines = disassembly.Split(Environment.NewLine.ToCharArray());

                for (var i = 0; i < DIASSEMBLY_ROW_COUNT; i++)
                {
                    if (i >= disassemblyLines.Length)
                    {
                        continue;
                    }

                    // Double tabs followed by a semi-colon indicate a split between the address/instruction disassembly
                    // and the generated psuedocode or annotations.
                    var disassemblyLineParts   = disassemblyLines[i].Split("\t\t; ".ToCharArray());
                    var disassemblyInstruction = disassemblyLineParts[0];
                    var disassemblyComments    = disassemblyLineParts.Length > 1 ? disassemblyLineParts[1] : "";

                    // Convert tabs to spaces (there is no tab character in the font set, only 8x8 glyphs).
                    disassemblyInstruction = disassemblyInstruction.Replace("\t", "     ");

                    var instructionColor = $"{COLOR_WHITE}";
                    var commentColor     = $"{COLOR_BLUE}";

                    if (disassemblyInstruction.Contains(Disassembler.CURRENT_LINE_MARKER))
                    {
                        instructionColor = $"{COLOR_BRIGHT_WHITE}";

                        if (showAnnotatedDisassembly)
                        {
                            commentColor = $"{COLOR_BRIGHT_GREEN}";
                        }
                        else
                        {
                            commentColor = $"{COLOR_BRIGHT_BLUE}";
                        }
                    }
                    else
                    {
                        if (showAnnotatedDisassembly)
                        {
                            commentColor = $"{COLOR_GREEN}";
                        }
                    }

                    disassemblyInstruction = disassemblyInstruction.PadRight(30);
                    var line = $"{instructionColor}{disassemblyInstruction} {commentColor};{disassemblyComments}";

                    FontRenderer.RenderString(surface, line, 0, (DISASSEMBLY_START_ROW + i) * ROW_HEIGHT);
                }
            }

            var commandsStartRow = (DISASSEMBLY_START_ROW + DIASSEMBLY_ROW_COUNT);

            FontRenderer.RenderString(surface, "------------------------------[COMMANDS]----------------------------------------", 0, (commandsStartRow + 1) * ROW_HEIGHT);

            if (state == DebuggerState.Breakpoint)
            {
                var stepBackwards = pcb.ReverseStepEnabled && pcb._executionHistory.Count > 0 ? $"{COLOR_BRIGHT_WHITE}[F9] {COLOR_WHITE}Step Backwards" : "                   ";

                FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[F1] {COLOR_WHITE}Save State     {COLOR_BRIGHT_WHITE}[F2] {COLOR_WHITE}Load State     {COLOR_BRIGHT_WHITE}[F4] {COLOR_WHITE}Edit Breakpoints", 0, (commandsStartRow + 3) * ROW_HEIGHT);
                FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[F5] {COLOR_WHITE}Continue       {stepBackwards} {COLOR_BRIGHT_WHITE}[F10] {COLOR_WHITE}Single Step", 0, (commandsStartRow + 4) * ROW_HEIGHT);
                FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}[F11] {COLOR_WHITE}Toggle Annotated Disassembly      {COLOR_BRIGHT_WHITE}[F12] {COLOR_WHITE}Print Last 50 Opcodes", 0, (commandsStartRow + 5) * ROW_HEIGHT);
            }
            else if (state == DebuggerState.SingleStepping)
            {
                FontRenderer.RenderString(surface, $" {COLOR_BRIGHT_YELLOW}Single stepping; please wait...", 0, (commandsStartRow + 4) * ROW_HEIGHT);
            }
            else
            {
                FontRenderer.RenderString(surface, $" {COLOR_YELLOW}Press [BREAK], [PAUSE], or [F3] to interrupt execution and start debugging...", 0, (commandsStartRow + 4) * ROW_HEIGHT);
            }

            FontRenderer.RenderString(surface, "--------------------------------------------------------------------------------", 0, (commandsStartRow + 7) * ROW_HEIGHT);
        }
예제 #3
0
        public static void Render(IntPtr surface, DebuggerState state, string inputString, List <string> fileList, PacManPCB pcb, bool showAnnotatedDisassembly)
        {
            SDL.SDL_SetRenderDrawColor(surface, 0, 0, 0, 255);
            SDL.SDL_RenderClear(surface);

            if (state == DebuggerState.EditBreakpoints) // User editing breakpoints
            {
                RenderBreakpointEditor(surface, inputString, pcb);
            }
            else if (state == DebuggerState.SaveState || state == DebuggerState.LoadState) // Save/Load state menu
            {
                RenderSavedStateFileBrowser(surface, state, inputString, fileList, pcb);
            }
            else if (state == DebuggerState.InstructionHistory) // "Show Last 50 Opcodes" menu
            {
                RenderInstructionHistory(surface, pcb);
            }
            else // Running, at a breakpoint, or stepping over a breakpoint.
            {
                RenderCpuStateAndDisassembly(surface, state, pcb, showAnnotatedDisassembly);
            }

            SDL.SDL_RenderPresent(surface);
        }
예제 #4
0
        private static void RenderSavedStateFileBrowser(IntPtr surface, DebuggerState state, string inputString, List <string> fileList, PacManPCB pcb)
        {
            var title = state == DebuggerState.LoadState ? $"{COLOR_BRIGHT_RED}LOAD STATE" : $"{COLOR_BRIGHT_GREEN}SAVE STATE";

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}------------------------------[{title}{COLOR_BRIGHT_WHITE}]--------------------------------------", 0, 0 * ROW_HEIGHT);

            for (var i = 0; i < fileList.Count && i < 50; i++)
            {
                var file     = fileList[i];
                var selected = file == inputString;
                var color    = selected ? $"{COLOR_BRIGHT_BLUE}" : $"{COLOR_WHITE}";

                FontRenderer.RenderString(surface, $"{color}{(selected ? "--->" : "    ")} {file}", 0, (i + 2) * ROW_HEIGHT);
            }

            if (pcb.BreakAtAddresses.Count >= 50)
            {
                FontRenderer.RenderString(surface, $"   {COLOR_YELLOW}(only showing the first 50 of {fileList.Count} files)", 0, (50 + 2) * ROW_HEIGHT);
            }

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}--------------------------------------------------------------------------------", 0, (50 + 3) * ROW_HEIGHT);

            FontRenderer.RenderString(surface, $"{COLOR_WHITE}Enter a file name or use the arrows to select and then", 0, (50 + 4) * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"{COLOR_WHITE}press {COLOR_BRIGHT_GREEN}[ENTER] {COLOR_WHITE}to {title}{COLOR_WHITE}.", 0, (50 + 5) * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"> {inputString}", 0, (50 + 7) * ROW_HEIGHT);

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}--------------------------------------------------------------------------------", 0, (50 + 9) * ROW_HEIGHT);
        }
예제 #5
0
        private static void RenderBreakpointEditor(IntPtr surface, string inputString, PacManPCB pcb)
        {
            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}------------------------------[BREAKPOINTS]-------------------------------------", 0, 0 * ROW_HEIGHT);

            for (var i = 0; i < pcb.BreakAtAddresses.Count && i < 50; i++)
            {
                var address = String.Format("0x{0:X4}", pcb.BreakAtAddresses[i]);
                FontRenderer.RenderString(surface, $"{COLOR_WHITE} * {address}", 0, (i + 2) * ROW_HEIGHT);
            }

            if (pcb.BreakAtAddresses.Count >= 50)
            {
                FontRenderer.RenderString(surface, $"   {COLOR_YELLOW}(only showing the first 50 of {pcb.BreakAtAddresses.Count} breakpoints)", 0, (50 + 2) * ROW_HEIGHT);
            }

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}--------------------------------------------------------------------------------", 0, (50 + 3) * ROW_HEIGHT);

            FontRenderer.RenderString(surface, $"{COLOR_WHITE}Enter an address in hexidecimal format and press {COLOR_BRIGHT_GREEN}[ENTER] {COLOR_WHITE}to toggle a breakpoint.", 0, (50 + 4) * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"{COLOR_WHITE}Type \"{COLOR_BRIGHT_RED}clear{COLOR_WHITE}\" to remove all breakpoints. Press {COLOR_BRIGHT_YELLOW}[ESCAPE] {COLOR_WHITE}to abort.", 0, (50 + 5) * ROW_HEIGHT);
            FontRenderer.RenderString(surface, $"> {inputString}", 0, (50 + 7) * ROW_HEIGHT);

            FontRenderer.RenderString(surface, $"{COLOR_BRIGHT_WHITE}--------------------------------------------------------------------------------", 0, (50 + 9) * ROW_HEIGHT);
        }
예제 #6
0
 /**
  * Used to start the interactive debugger session with the given game PCB state.
  * This enables the user to enter commands (continue, step, etc) and examine CPU state etc.
  */
 public void StartInteractiveDebugger(PacManPCB pcb)
 {
     _debuggerPcb   = pcb;
     _debuggerState = DebuggerState.Breakpoint;
     SignalDebuggerNeedsRendering();
 }
예제 #7
0
        private void HandleDebuggerEventForBreakpointState(SDL.SDL_Event sdlEvent)
        {
            if (sdlEvent.type != SDL.SDL_EventType.SDL_KEYDOWN)
            {
                return;
            }

            var keycode = sdlEvent.key.keysym.sym;

            switch (keycode)
            {
            case SDL.SDL_Keycode.SDLK_F1:     // F1 = Save State
                _debuggerState       = DebuggerState.SaveState;
                _debuggerFileList    = GetDebuggerSaveStateFileList();
                _debuggerInputString = String.Empty;
                SignalDebuggerNeedsRendering();
                break;

            case SDL.SDL_Keycode.SDLK_F2:     // F2 = Load State
                _debuggerState       = DebuggerState.LoadState;
                _debuggerFileList    = GetDebuggerSaveStateFileList();
                _debuggerInputString = _debuggerFileList.Count > 0 ? _debuggerFileList.Last() : String.Empty;
                SignalDebuggerNeedsRendering();
                break;

            case SDL.SDL_Keycode.SDLK_F4:     // F4 = Edit Breakpoints
                _debuggerState = DebuggerState.EditBreakpoints;
                SignalDebuggerNeedsRendering();
                break;

            case SDL.SDL_Keycode.SDLK_F5:     // F5 = Continue
                _debuggerState = DebuggerState.Idle;
                _debuggerPcb   = null;
                SignalDebuggerNeedsRendering();
                OnDebugCommand?.Invoke(new DebugCommandEventArgs()
                {
                    Action = DebugAction.ResumeContinue
                });
                break;

            case SDL.SDL_Keycode.SDLK_F9:     // F9 = Step Backwards

                if (_debuggerPcb.ReverseStepEnabled && _debuggerPcb._executionHistory.Count > 0)
                {
                    _debuggerState = DebuggerState.SingleStepping;
                    _debuggerPcb   = null;
                    SignalDebuggerNeedsRendering();
                    OnDebugCommand?.Invoke(new DebugCommandEventArgs()
                    {
                        Action = DebugAction.ReverseStep
                    });
                }

                break;

            case SDL.SDL_Keycode.SDLK_F10:     // F10 = Single Step
                _debuggerState = DebuggerState.SingleStepping;
                _debuggerPcb   = null;
                SignalDebuggerNeedsRendering();
                OnDebugCommand?.Invoke(new DebugCommandEventArgs()
                {
                    Action = DebugAction.ResumeStep
                });
                break;

            case SDL.SDL_Keycode.SDLK_F11:     // F11 = Toggle Annotated Disassembly
                _debuggerShowAnnotatedDisassembly = !_debuggerShowAnnotatedDisassembly;
                SignalDebuggerNeedsRendering();
                break;

            case SDL.SDL_Keycode.SDLK_F12:     // F12 = Print Last 50 Opcodes
                _debuggerState = DebuggerState.InstructionHistory;
                SignalDebuggerNeedsRendering();
                break;
            }
        }
예제 #8
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();
        }