/// <summary> /// Waits while the Spectrum virtual machine starts and reaches its termination point /// </summary> /// <returns>True, if started within timeout; otherwise, false</returns> private async Task <bool> WaitStart() { const int TIME_OUT_IN_SECONDS = 5; var counter = 0; while (Package.MachineViewModel.VmState != VmState.Paused && counter < TIME_OUT_IN_SECONDS * 10) { await Task.Delay(100); counter++; } if (Package.MachineViewModel.VmState != VmState.Paused) { var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); var message = $"The ZX Spectrum virtual machine did not start within {TIME_OUT_IN_SECONDS} seconds."; pane.WriteLine(message); VsxDialogs.Show(message, "Unexpected issue", MessageBoxButton.OK, VsxMessageBoxIcon.Error); Package.MachineViewModel.StopVm(); return(false); } return(true); }
/// <summary> /// Compiles the file with the specified file name /// </summary> /// <param name="filename">Test file to compile</param> /// <param name="createLog">Signs if build log should be created</param> /// <returns>Test plan</returns> public TestFilePlan CompileFile(string filename, bool createLog = true) { var start = DateTime.Now; var pane = OutputWindow.GetPane <Z80BuildOutputPane>(); if (createLog) { pane.WriteLine("Z80 Test Compiler"); pane.WriteLine($"Compiling {filename}"); } var compiler = new Z80TestCompiler { DefaultSourceFolder = Path.GetDirectoryName(filename) }; if (createLog) { var duration = (DateTime.Now - start).TotalMilliseconds; pane.WriteLine($"Compile time: {duration}ms"); } return(compiler.CompileFile(filename)); }
/// <summary> /// Loads the specified .vmstate file /// </summary> /// <param name="stateFile">Full name of the .vmstate file</param> public void LoadVmStateFile(string stateFile) { var state = File.ReadAllText(stateFile); try { Package.MachineViewModel.SpectrumVm.SetVmState(state, Package.CodeDiscoverySolution.CurrentProject.ModelName); Package.MachineViewModel.SpectrumVm.BeeperDevice.Reset(); Package.MachineViewModel.SpectrumVm.BeeperProvider.Reset(); var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); pane.WriteLine($"Forcing Paused state from {Package.MachineViewModel.VmState}"); Package.MachineViewModel.ForcePauseVmAfterStateRestore(); } catch (InvalidVmStateException e) { VsxDialogs.Show(e.OriginalMessage, "Error loading virtual machine state"); } catch (Exception e) { VsxDialogs.Show($"Unexpected error: {e.Message}", "Error loading virtual machine state"); } }
/// <summary> /// Compiles the code. /// </summary> /// <returns>True, if compilation successful; otherwise, false</returns> /// <param name="createLog">Signs if build log should be created</param> public TestProjectPlan CompileAllFiles(bool createLog = true) { Package.ErrorList.Clear(); var result = new TestProjectPlan(); var testFiles = Package.CodeDiscoverySolution.CurrentProject.Z80TestProjectItems; if (testFiles.Count == 0) { return(result); } var testManager = Package.TestManager; var start = DateTime.Now; var pane = OutputWindow.GetPane <Z80BuildOutputPane>(); if (createLog) { pane.WriteLine("Z80 Test Compiler"); } foreach (var file in testFiles) { var filename = file.Filename; if (createLog) { pane.WriteLine($"Compiling {filename}"); } var testPlan = testManager.CompileFile(filename); result.Add(testPlan); } if (createLog) { var duration = (DateTime.Now - start).TotalMilliseconds; pane.WriteLine($"Compile time: {duration}ms"); } return(result); }
/// <summary> /// Stops the Spectrum VM, displays confirmation, if required /// </summary> /// <returns>Tru, if start confirmed; otherwise, false</returns> public async Task <bool> StopSpectrumVm(bool needConfirm) { var vm = Package.MachineViewModel; var machineState = vm.VmState; if (machineState == VmState.Running || machineState == VmState.Paused) { if (needConfirm) { var answer = VsxDialogs.Show("Are you sure, you want to restart " + "the ZX Spectrum virtual machine?", "The ZX Spectum virtual machine is running", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return(false); } } // --- Stop the machine and allow 50ms to stop. Package.MachineViewModel.StopVm(); await Package.MachineViewModel.MachineController.CompletionTask; if (vm.VmState == VmState.Stopped) { return(true); } const string MESSAGE = "The ZX Spectrum virtual machine did not stop."; var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); pane.WriteLine(MESSAGE); VsxDialogs.Show(MESSAGE, "Unexpected issue", MessageBoxButton.OK, VsxMessageBoxIcon.Error); return(false); } return(true); }
/// <summary> /// Compiles the Z80 code file /// </summary> protected override async Task ExecuteAsync() { // --- Prepare the appropriate file to compile/run GetCodeItem(out var hierarchy, out var itemId); // --- Step #1: Compile the code if (!CompileCode(hierarchy, itemId)) { return; } // --- Step #2: Check machine compatibility var modelName = SpectNetPackage.Default.CodeDiscoverySolution?.CurrentProject?.ModelName; SpectrumModelType modelType; if (Output.ModelType == null) { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: modelType = SpectrumModelType.Spectrum48; break; case SpectrumModels.ZX_SPECTRUM_128: modelType = SpectrumModelType.Spectrum128; break; case SpectrumModels.ZX_SPECTRUM_P3_E: modelType = SpectrumModelType.SpectrumP3; break; case SpectrumModels.ZX_SPECTRUM_NEXT: modelType = SpectrumModelType.Next; break; default: modelType = SpectrumModelType.Spectrum48; break; } } else { modelType = Output.ModelType.Value; } if (!SpectNetPackage.IsCurrentModelCompatibleWith(modelType)) { VsxDialogs.Show("The model type defined in the code is not compatible with the " + "Spectum virtual machine of this project.", "Cannot run code."); return; } // --- Step #3: Check for zero code length if (Output.Segments.Sum(s => s.EmittedCode.Count) == 0) { VsxDialogs.Show("The lenght of the compiled code is 0, " + "so there is no code to inject into the virtual machine and run.", "No code to run."); return; } // --- Step #4: Check non-zero displacements var options = Package.Options; if (Output.Segments.Any(s => (s.Displacement ?? 0) != 0) && options.ConfirmNonZeroDisplacement) { var answer = VsxDialogs.Show("The compiled code contains non-zero displacement" + "value, so the displaced code may fail. Are you sure you want to run the code?", "Non-zero displacement found", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Step #5: Stop the virtual machine if required await SwitchToMainThreadAsync(); Package.ShowToolWindow <SpectrumEmulatorToolWindow>(); var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); var vm = Package.MachineViewModel; var stopped = await Package.CodeManager.StopSpectrumVm(options.ConfirmMachineRestart); if (!stopped) { return; } // --- Step #6: Start the virtual machine so that later we can load the program pane.WriteLine("Starting the virtual machine in code injection mode."); // --- Use specific startup for each model. bool started = true; try { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: await Package.StateFileManager.SetSpectrum48StartupState(); break; case SpectrumModels.ZX_SPECTRUM_128: if (modelType == SpectrumModelType.Spectrum48) { await Package.StateFileManager.SetSpectrum128In48StartupState(); } else { await Package.StateFileManager.SetSpectrum128In128StartupState(); } break; case SpectrumModels.ZX_SPECTRUM_P3_E: if (modelType == SpectrumModelType.Spectrum48) { await Package.StateFileManager.SetSpectrumP3In48StartupState(); } else { await Package.StateFileManager.SetSpectrumP3InP3StartupState(); } break; case SpectrumModels.ZX_SPECTRUM_NEXT: // --- Implement later return; default: // --- Implement later return; } } catch (Exception) { started = false; } if (!started) { return; } // --- Step #7: Inject the code into the memory, and force // --- new disassembly pane.WriteLine("Injecting code into the Spectrum virtual machine."); Package.CodeManager.InjectCodeIntoVm(Output); // --- Step #8: Jump to execute the code var continuationPoint = GetContinuationAddress(); if (continuationPoint.HasValue) { vm.SpectrumVm.Cpu.Registers.PC = continuationPoint.Value; pane.WriteLine($"Resuming code execution at address {vm.SpectrumVm.Cpu.Registers.PC:X4}."); } ResumeVm(); }
public static void Report(Exception ex) { var pane = OutputWindow.GetPane <SpectNetIdeOutputPane>(); pane.WriteLine($"Exception occured in SpectNetIde: {ex.Message}\nFull message: ${ex.ToString()}"); }
/// <summary> /// Overrid this method to handle vm state changes within the controller /// </summary> /// <param name="oldState">Old VM state</param> /// <param name="newState">New VM state</param> protected override void OnVmStateChanged(VmState oldState, VmState newState) { var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); pane.WriteLine($"Machine state changed: {oldState} --> {newState}."); }
/// <summary> /// Responds to the event when the Z80 assembler releases a message /// </summary> private void OnAssemblerMessage(object sender, AssemblerMessageArgs e) { var pane = OutputWindow.GetPane <Z80AssemblerOutputPane>(); pane.WriteLine(e.Message); }
/// <summary> /// Compiles the Z80 code file /// </summary> protected override async Task ExecuteAsync() { // --- Prepare the appropriate file to compile/run CodeInjected = false; // --- Step #1: Compile the code var success = await Task.Run(() => CompileCode()); if (!success) { return; } // --- Step #2: Check machine compatibility var modelName = SpectNetPackage.Default.Solution.ActiveProject?.ModelName; SpectrumModelType modelType; if (Output.ModelType == null) { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: modelType = SpectrumModelType.Spectrum48; break; case SpectrumModels.ZX_SPECTRUM_128: modelType = SpectrumModelType.Spectrum128; break; case SpectrumModels.ZX_SPECTRUM_P3_E: modelType = SpectrumModelType.SpectrumP3; break; case SpectrumModels.ZX_SPECTRUM_NEXT: modelType = SpectrumModelType.Next; break; default: modelType = SpectrumModelType.Spectrum48; break; } } else { modelType = Output.ModelType.Value; } // --- We may display dialogs, so we go back to the main thread await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (!SpectrumModels.IsModelCompatibleWith(modelName, modelType)) { VsxDialogs.Show("The model type defined in the code is not compatible with the " + "Spectum virtual machine of this project.", "Cannot run code."); return; } // --- Step #3: Check for zero code length if (Output.Segments.Sum(s => s.EmittedCode.Count) == 0) { VsxDialogs.Show("The lenght of the compiled code is 0, " + "so there is no code to inject into the virtual machine and run.", "No code to run."); return; } // --- Step #4: Check non-zero displacements var options = HostPackage.Options; if (Output.Segments.Any(s => (s.Displacement ?? 0) != 0) && options.ConfirmNonZeroDisplacement) { var answer = VsxDialogs.Show("The compiled code contains non-zero displacement" + "value, so the displaced code may fail. Are you sure you want to run the code?", "Non-zero displacement found", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } var vm = HostPackage.EmulatorViewModel; var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); HostPackage.ShowToolWindow <SpectrumEmulatorToolWindow>(); // --- Step #5: Prepare the machine to be in the appropriate mode if (IsInInjectMode) { if (vm.MachineState == VmState.Running || vm.MachineState != VmState.Paused && vm.MachineState != VmState.Stopped && vm.MachineState != VmState.None) { VsxDialogs.Show("To inject the code into the virtual machine, please pause it first.", "The virtual machine is running"); return; } } else { var stopped = await SpectrumVmManager.StopSpectrumVmAsync(options.ConfirmMachineRestart); if (!stopped) { return; } } // --- Step #6: Start the virtual machine and run it to the injection point if (vm.MachineState == VmState.Stopped || vm.MachineState == VmState.None) { await pane.WriteLineAsync("Starting the virtual machine in code injection mode."); // --- Use specific startup for each model. var started = true; try { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: await VmStateFileManager.SetSpectrum48StartupState(); break; case SpectrumModels.ZX_SPECTRUM_128: if (modelType == SpectrumModelType.Spectrum48) { await VmStateFileManager.SetSpectrum128In48StartupState(); } else { await VmStateFileManager.SetSpectrum128In128StartupState(); } break; case SpectrumModels.ZX_SPECTRUM_P3_E: if (modelType == SpectrumModelType.Spectrum48) { await VmStateFileManager.SetSpectrumP3In48StartupState(); } else { await VmStateFileManager.SetSpectrumP3InP3StartupState(); } break; case SpectrumModels.ZX_SPECTRUM_NEXT: // --- Implement later return; default: // --- Implement later return; } } catch (Exception) { started = false; } if (!started) { return; } } // --- Step #7: Inject the code into the memory, and force // --- new disassembly await pane.WriteLineAsync("Injecting code into the Spectrum virtual machine."); HostPackage.CodeManager.InjectCodeIntoVm(Output); CodeInjected = true; // --- Step #8: Jump to execute the code var continuationPoint = GetContinuationAddress(); if (continuationPoint.HasValue) { vm.Machine.SpectrumVm.Cpu.Registers.PC = continuationPoint.Value; await pane.WriteLineAsync($"Resuming code execution at address {vm.Machine.SpectrumVm.Cpu.Registers.PC:X4}."); } vm.MemViewPoint = (ushort)MemoryStartAddress; vm.DisAssViewPoint = (ushort)DisassemblyStartAddress; vm.StackDebugSupport.ClearStepOutStack(); GetAffectedItem(out var hierarchy, out var itemId); var ext = string.Empty; if (hierarchy is IVsProject project) { project.GetMkDocument(itemId, out var itemFullPath); ext = Path.GetExtension(itemFullPath) ?? string.Empty; } if (ext.ToLower() == ".zxbas") { // --- Push the MAIN_EXECUTION loop address to the stack var memDevice = vm.Machine.SpectrumVm.MemoryDevice; var spValue = vm.Machine.SpectrumVm.Cpu.Registers.SP; var mainExec = VmStateFileManager.SP48_MAIN_EXEC_ADDR; memDevice.Write((ushort)(spValue - 1), (byte)(mainExec >> 8)); memDevice.Write((ushort)(spValue - 2), (byte)mainExec); vm.Machine.SpectrumVm.Cpu.Registers.SP -= 2; } ResumeVm(); }
/// <summary> /// Logs a message /// </summary> /// <param name="message">Message to log</param> public static void LogMessage(string message) { var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); pane.WriteLine(message); }
/// <summary> /// Compiles the Z80 code file /// </summary> protected override async Task ExecuteAsync() { GetItem(out var hierarchy, out var itemId); if (hierarchy == null) { return; } var codeManager = Package.CodeManager; var options = Package.Options; // --- Step #1: Compile var start = DateTime.Now; var pane = OutputWindow.GetPane <Z80OutputPane>(); pane.WriteLine("Z80 Assembler"); _output = codeManager.Compile(hierarchy, itemId); var duration = (DateTime.Now - start).TotalMilliseconds; pane.WriteLine($"Compile time: {duration}ms"); if (_output.ErrorCount != 0) { // --- Compilation completed with errors return; } if (_output.Segments.Sum(s => s.EmittedCode.Count) == 0) { VsxDialogs.Show("The lenght of the compiled code is 0, " + "so there is no code to inject into the virtual machine and run.", "No code to run."); return; } // --- Step #2: Check non-zero displacements if (_output.Segments.Any(s => (s.Displacement ?? 0) != 0) && options.ConfirmNonZeroDisplacement) { var answer = VsxDialogs.Show("The compiled code contains non-zero displacement" + "value, so the displaced code may fail. Are you sure you want to run the code?", "Non-zero displacement found", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Step #3: Stop the virtual machine if required await SwitchToMainThreadAsync(); Package.ShowToolWindow <SpectrumEmulatorToolWindow>(); var vm = Package.MachineViewModel; var machineState = vm.VmState; if ((machineState == VmState.Running || machineState == VmState.Paused)) { if (options.ConfirmMachineRestart) { var answer = VsxDialogs.Show("Are you sure, you want to restart " + "the ZX Spectrum virtual machine?", "The ZX Spectum virtual machine is running", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Stop the machine and allow 3 frames' time to stop. Package.MachineViewModel.StopVmCommand.Execute(null); await Task.Delay(60); if (vm.VmState != VmState.Stopped) { VsxDialogs.Show("The ZX Spectrum virtual machine did not stop.", "Unexpected issue", MessageBoxButton.OK, VsxMessageBoxIcon.Error); return; } } // --- Step #4: Start the virtual machine so that later we can load the program vm.StartVmWithCodeCommand.Execute(null); const int timeOutInSeconds = 5; var counter = 0; while (vm.VmState != VmState.Paused && counter < timeOutInSeconds * 10) { await Task.Delay(100); counter++; } if (vm.VmState != VmState.Paused) { VsxDialogs.Show($"The ZX Spectrum virtual machine did not start within {timeOutInSeconds} seconds.", "Unexpected issue", MessageBoxButton.OK, VsxMessageBoxIcon.Error); vm.StopVmCommand.Execute(null); } // --- Step #5: Inject the code into the memory codeManager.InjectCodeIntoVm(_output); // --- Step #6: Jump to execute the code vm.SpectrumVm.Cpu.Registers.PC = _output.EntryAddress ?? _output.Segments[0].StartAddress; ResumeVm(); }
/// <summary> /// Compiles the Z80 code file /// </summary> protected override async Task ExecuteAsync() { // --- Prepare the appropriate file to compile/run GetCodeItem(out var hierarchy, out var itemId); // --- Step #1: Compile the code if (!CompileCode(hierarchy, itemId)) { return; } // --- Step #2: Check machine compatibility var modelName = SpectNetPackage.Default.CodeDiscoverySolution?.CurrentProject?.ModelName; SpectrumModelType modelType; if (Output.ModelType == null) { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: modelType = SpectrumModelType.Spectrum48; break; case SpectrumModels.ZX_SPECTRUM_128: modelType = SpectrumModelType.Spectrum128; break; case SpectrumModels.ZX_SPECTRUM_P3: modelType = SpectrumModelType.SpectrumP3; break; case SpectrumModels.ZX_SPECTRUM_NEXT: modelType = SpectrumModelType.Next; break; default: modelType = SpectrumModelType.Spectrum48; break; } } else { modelType = Output.ModelType.Value; } if (!SpectNetPackage.IsCurrentModelCompatibleWith(modelType)) { VsxDialogs.Show("The model type defined in the code is not compatible with the " + "Spectum virtual machine of this project.", "Cannot run code."); return; } // --- Step #3: Check for zero code length if (Output.Segments.Sum(s => s.EmittedCode.Count) == 0) { VsxDialogs.Show("The lenght of the compiled code is 0, " + "so there is no code to inject into the virtual machine and run.", "No code to run."); return; } // --- Step #4: Check non-zero displacements var options = Package.Options; if (Output.Segments.Any(s => (s.Displacement ?? 0) != 0) && options.ConfirmNonZeroDisplacement) { var answer = VsxDialogs.Show("The compiled code contains non-zero displacement" + "value, so the displaced code may fail. Are you sure you want to run the code?", "Non-zero displacement found", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Step #5: Stop the virtual machine if required await SwitchToMainThreadAsync(); Package.ShowToolWindow <SpectrumEmulatorToolWindow>(); var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); var vm = Package.MachineViewModel; var machineState = vm.VmState; if ((machineState == VmState.Running || machineState == VmState.Paused)) { if (options.ConfirmMachineRestart) { var answer = VsxDialogs.Show("Are you sure, you want to restart " + "the ZX Spectrum virtual machine?", "The ZX Spectum virtual machine is running", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Stop the machine and allow 50ms to stop. Package.MachineViewModel.StopVm(); await Task.Delay(50); if (vm.VmState != VmState.Stopped) { const string MESSAGE = "The ZX Spectrum virtual machine did not stop."; pane.WriteLine(MESSAGE); VsxDialogs.Show(MESSAGE, "Unexpected issue", MessageBoxButton.OK, VsxMessageBoxIcon.Error); return; } } // --- Step #6: Start the virtual machine so that later we can load the program pane.WriteLine("Starting the virtual machine in code injection mode."); // --- Use specific startup for each model. switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: // --- Run in ZX Spectrum 48K mode vm.RestartVmAndRunToTerminationPoint(0, SP48_MAIN_EXEC_ADDR); if (!await WaitStart()) { return; } break; case SpectrumModels.ZX_SPECTRUM_128: // --- Wait while the main menu appears vm.RestartVmAndRunToTerminationPoint(0, SP128_MAIN_WAITING_LOOP); if (!await WaitStart()) { return; } if (modelType == SpectrumModelType.Spectrum48) { vm.RunVmToTerminationPoint(1, SP48_MAIN_EXEC_ADDR); // --- Move to Spectrum 48 mode QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.Enter); if (!await WaitStart()) { return; } } else { vm.RunVmToTerminationPoint(0, SP128_RETURN_TO_EDITOR); // --- Move to Spectrum 128 mode QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.Enter); if (!await WaitStart()) { return; } } break; case SpectrumModels.ZX_SPECTRUM_P3: // --- Implement later return; case SpectrumModels.ZX_SPECTRUM_NEXT: // --- Implement later return; default: // --- Implement later return; } // --- Step #7: Inject the code into the memory, and force // --- new disassembly pane.WriteLine("Injecting code into the Spectrum virtual machine."); Package.CodeManager.InjectCodeIntoVm(Output); // --- Step #8: Jump to execute the code var continuationPoint = GetContinuationAddress(); if (continuationPoint.HasValue) { vm.SpectrumVm.Cpu.Registers.PC = continuationPoint.Value; pane.WriteLine($"Resuming code execution at address {vm.SpectrumVm.Cpu.Registers.PC:X4}."); } ResumeVm(); }
/// <summary> /// Logs a message /// </summary> /// <param name="message">Message to log</param> protected override void LogMessage(string message) { var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); pane.WriteLine(message); }
/// <summary> /// Displays the TRACE pragma messages of the compiler /// </summary> private static void DisplayTraceMessage(object sender, AssemblerMessageArgs e) { var pane = OutputWindow.GetPane <Z80BuildOutputPane>(); pane.WriteLine($"TRACE: {e.Message}"); }