public async Task <IActionResult> Scalar(CancellationToken cancellationToken, [FromRoute] string project, [FromRoute] string app, [FromRoute] string endpoint, [FromRoute] string schema, [FromRoute] string routine) { ExecOptions execOptions = null; try { execOptions = new ExecOptions() { CancellationToken = cancellationToken, project = project, application = app, endpoint = endpoint, schema = schema, routine = routine, type = ExecType.Scalar }; return(await execAsync(execOptions)); } catch (OperationCancelledByUserException) { return(Ok()); } catch (Exception ex) { var requestHeaders = this.Request.Headers.Select(kv => new { kv.Key, Value = kv.Value.FirstOrDefault() }).ToDictionary(kv => kv.Key, kv => kv.Value); var appTitle = requestHeaders.Val("app-title"); var appVersion = requestHeaders.Val("app-ver"); var exceptionResponse = ApiResponse.ExecException(ex, execOptions, out var exceptionId, null, appTitle, appVersion); return(Ok(exceptionResponse)); } }
public ExecState Run(ExecOptions execOptions) { return(CPU.Execute( this.Mem, execOptions )); }
public void Run(ExecOptions execOptions) { CPU.Execute( this.Mem, execOptions ); }
private void WriteExecutionResult(ScintillaControl sci, ExecOptions options, object data) { if (options.Set(ExecOptions.PrintResult)) { var outp = App.GetService <IOutputService>(); outp.WriteLine(OutputFormat.Header, "Execution result:"); outp.WriteLine((data ?? "[unit]").ToString()); } if (options.Set(ExecOptions.TipResult) && sci != null) { sci.ShowCallTip(sci.GetSelection().CaretPosition, (data ?? "[unit]").ToString()); } }
public virtual bool RunCode <T>(T compiled, ExecOptions options, params ExtendedOption[] extOptions) where T : ICompiledAssembly { var type = typeof(T); var runnerObj = default(Object); if (type == typeof(ICompiledAssembly)) { type = compiled.GetType(); } if (!RunnerInstances.TryGetValue(type, out runnerObj)) { throw new ElideException("Unable to find runner for '{0}'.", type); } var runner = (ICodeRunner <T>)runnerObj; runner.App = App; return(Run(compiled, options, runner, extOptions)); }
protected abstract bool Run <T>(T compiled, ExecOptions options, ICodeRunner <T> runner, params ExtendedOption[] extOptions) where T : ICompiledAssembly;
//(object/*ApiResponse | IActionResult*/, RoutineExecution, CommonReturnValue) public static async Task <ExecuteRoutineAsyncResult> ExecuteRoutineAsync(ExecOptions execOptions, Dictionary <string, string> requestHeaders, string referer, string remoteIpAddress, string appTitle, string appVersion) { string debugInfo = null; Project project = null; Application app = null; Endpoint endpoint = null; Dictionary <string, string> responseHeaders = null; List <ExecutionPlugin> pluginList = null; RoutineExecution routineExecutionMetric = null; responseHeaders = new Dictionary <string, string>(); try { if (!ControllerHelper.GetProjectAndAppAndEndpoint(execOptions.project, execOptions.application, execOptions.endpoint, out project, out app, out endpoint, out var resp)) { return(new ExecuteRoutineAsyncResult(resp, null, null, responseHeaders)); } routineExecutionMetric = new RoutineExecution(endpoint, execOptions.schema, execOptions.routine); RealtimeTrackerThread.Instance.Enqueue(routineExecutionMetric); // debugInfo += $"[{execOptions.schema}].[{execOptions.routine}]"; string jsDALApiKey = null; if (requestHeaders.ContainsKey("api-key")) { jsDALApiKey = requestHeaders["api-key"]; } // make sure the source domain/IP is allowed access var mayAccess = app.MayAccessDbSource(referer, jsDALApiKey); if (!mayAccess.IsSuccess) { return(new ExecuteRoutineAsyncResult(null, null, mayAccess, responseHeaders)); } Dictionary <string, dynamic> outputParameters; int commandTimeOutInSeconds = 60; // PLUGINS var pluginsInitMetric = routineExecutionMetric.BeginChildStage("Init plugins"); pluginList = InitPlugins(app, execOptions.inputParameters, requestHeaders); pluginsInitMetric.End(); //////////////////// // Auth stage /////////////////// { // ask all ExecPlugins to authenticate foreach (var plugin in pluginList) { if (!plugin.IsAuthenticated(execOptions.schema, execOptions.routine, out var error)) { responseHeaders.Add("Plugin-AuthFailed", plugin.Name); return(new ExecuteRoutineAsyncResult(new UnauthorizedObjectResult(error), null, null, responseHeaders)); } } } var execRoutineQueryMetric = routineExecutionMetric.BeginChildStage("execRoutineQuery"); string dataCollectorEntryShortId = DataCollectorThread.Enqueue(endpoint, execOptions); /////////////////// // Database call /////////////////// OrmDAL.ExecutionResult executionResult = null; try { executionResult = await OrmDAL.ExecRoutineQueryAsync( execOptions.CancellationToken, execOptions.type, execOptions.schema, execOptions.routine, endpoint, execOptions.inputParameters, requestHeaders, remoteIpAddress, pluginList, commandTimeOutInSeconds, execRoutineQueryMetric, responseHeaders ); outputParameters = executionResult.OutputParameterDictionary; responseHeaders = executionResult.ResponseHeaders; execRoutineQueryMetric.End(); ulong?rows = null; if (executionResult?.RowsAffected.HasValue ?? false) { rows = (ulong)executionResult.RowsAffected.Value; } DataCollectorThread.End(dataCollectorEntryShortId, rowsAffected: rows, durationInMS: execRoutineQueryMetric.DurationInMS, bytesReceived: executionResult.BytesReceived, networkServerTimeMS: executionResult.NetworkServerTimeInMS); } catch (Exception execEx) { DataCollectorThread.End(dataCollectorEntryShortId, ex: execEx); // if (execOptions != null && endpoint != null) // { // var wrapEx = new Exception($"Failed to execute {endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}", execEx); // // create a fake frame to include the exec detail - this way any logger logging the StackTrace will always include the relevant execution detail // StackFrame sf = new($"{execOptions.type} {endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}", 0); // StackTrace st = new(sf); // //?wrapEx.SetStackTrace(st); // var allFields = wrapEx.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); // var fffff =string.Join("\r\n", allFields.Select(f=>f.Name).ToArray()); // var fi = wrapEx.GetType().GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance); // fi.SetValue(wrapEx, $"{endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}"); // throw wrapEx; // } // else throw; throw; // rethrow } var prepareResultsMetric = routineExecutionMetric.BeginChildStage("Prepare results"); if (!string.IsNullOrEmpty(executionResult.userError)) { return(new ExecuteRoutineAsyncResult(ApiResponse.ExclamationModal(executionResult.userError), routineExecutionMetric, mayAccess, responseHeaders)); } var retVal = (IDictionary <string, object>) new System.Dynamic.ExpandoObject(); var ret = ApiResponse.Payload(retVal); retVal.Add("OutputParms", outputParameters); if (outputParameters != null) { // TODO: Consider making this a plugin var possibleUEParmNames = (new string[] { "usererrormsg", "usrerrmsg", "usererrormessage", "usererror", "usererrmsg" }).ToList(); var ueKey = outputParameters.Keys.FirstOrDefault(k => possibleUEParmNames.Contains(k.ToLower())); // if a user error msg is defined. if (!string.IsNullOrWhiteSpace(ueKey) && !string.IsNullOrWhiteSpace(outputParameters[ueKey])) { ret.Message = outputParameters[ueKey]; ret.Title = "Action failed"; ret.Type = ApiResponseType.ExclamationModal; } } if (execOptions.type == ExecType.Query) { if (executionResult.ReaderResults != null) { var keys = executionResult.ReaderResults.Keys.ToList(); for (var i = 0; i < keys.Count; i++) { retVal.Add(keys[i], executionResult.ReaderResults[keys[i]]); } retVal.Add("HasResultSets", keys.Count > 0); retVal.Add("ResultSetKeys", keys.ToArray()); } else { var dataSet = executionResult.DataSet; var dataContainers = dataSet.ToJsonDS(); var keys = dataContainers.Keys.ToList(); for (var i = 0; i < keys.Count; i++) { retVal.Add(keys[i], dataContainers[keys[i]]); } retVal.Add("HasResultSets", keys.Count > 0); retVal.Add("ResultSetKeys", keys.ToArray()); } } else if (execOptions.type == ExecType.NonQuery) { // nothing to do } else if (execOptions.type == ExecType.Scalar) { if (executionResult.ScalarValue is DateTime) { var dt = (DateTime)executionResult.ScalarValue; // convert to Javascript Date ticks var ticks = dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; ret = ApiResponseScalar.Payload(ticks, true); } else { ret = ApiResponse.Payload(executionResult.ScalarValue); } } prepareResultsMetric.End(); routineExecutionMetric.End(executionResult.RowsAffected ?? 0); // enqueue a second time as we now have an End date and rowsAffected RealtimeTrackerThread.Instance.Enqueue(routineExecutionMetric); return(new ExecuteRoutineAsyncResult(ret, routineExecutionMetric, mayAccess, responseHeaders)); } catch (SqlException ex) when(execOptions.CancellationToken.IsCancellationRequested && ex.Number == 0 && ex.State == 0 && ex.Class == 11) { // if we ended up here with a SqlException and a Cancel has been requested, we are very likely here because of the exception "Operation cancelled by user." // since MS does not provide an easy way (like a specific error code) to detect this scenario we have to guess routineExecutionMetric?.Exception(ex); throw new OperationCancelledByUserException(ex); } catch (Exception ex) { routineExecutionMetric?.Exception(ex); Connection dbConn = null; if (endpoint != null) { // TODO: Fix! dbConn = endpoint.GetSqlConnection(); // if (debugInfo == null) debugInfo = ""; // if (dbConn != null) // { // debugInfo = $"{ endpoint.Pedigree } - { dbConn.InitialCatalog } - { debugInfo }"; // } // else // { // debugInfo = $"{ endpoint.Pedigree } - (no connection) - { debugInfo }"; // } } var exceptionResponse = ApiResponse.ExecException(ex, execOptions, out var exceptionId, debugInfo, appTitle, appVersion); if (debugInfo == null) { debugInfo = ""; } debugInfo = exceptionId + " " + debugInfo; // TODO: Get Execution plugin list specifically if (pluginList != null) { string externalRef; if (dbConn != null) { using (var con = new SqlConnection(dbConn.ConnectionStringDecrypted)) { try { string additionalInfo = debugInfo; if (execOptions?.inputParameters != null) { additionalInfo += " "; additionalInfo += string.Join(",", execOptions.inputParameters.Select(kv => $"{kv.Key}={kv.Value}").ToArray()); } con.Open(); ProcessPluginExecutionExceptionHandlers(pluginList, con, ex, additionalInfo, appTitle, appVersion, out externalRef); ((dynamic)exceptionResponse.Data).ExternalRef = externalRef; } catch (Exception e) { ExceptionLogger.LogException(e, "ProcessPluginExecutionExceptionHandlers", "jsdal-server"); } } } // else: TODO: Log fact that we dont have a proper connection string.. or should plugins handle that? } // return it as "200 (Ok)" because the exception has been handled return(new ExecuteRoutineAsyncResult(exceptionResponse, routineExecutionMetric, null, responseHeaders)); } }
private async Task <IActionResult> execAsync(ExecOptions execOptions) { try { Interlocked.Increment(ref ExceutionsInFlight); var res = this.Response; var req = this.Request; string body = null; // always start off not caching whatever we send back res.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0"; res.Headers["Pragma"] = "no-cache"; // HTTP 1.0. res.Headers["Content-Type"] = "application/json"; res.Headers["Expires"] = "-1"; // TODO: log remote IP with exception and associate with request itself? var remoteIpAddress = this.HttpContext.Features.Get <IHttpConnectionFeature>()?.RemoteIpAddress.ToString(); // var remoteIpAddress2 = req.HttpContext.Connection?.RemoteIpAddress?.ToString() ?? ""; // convert request headers to normal Dictionary var requestHeaders = req.Headers.Select(kv => new { kv.Key, Value = kv.Value.FirstOrDefault() }).ToDictionary(kv => kv.Key, kv => kv.Value); var referer = requestHeaders.Val("Referer"); var appTitle = requestHeaders.Val("app-title"); var appVersion = requestHeaders.Val("app-ver"); var isPOST = req.Method.Equals("POST", StringComparison.OrdinalIgnoreCase); if (isPOST) { using (var sr = new StreamReader(req.Body)) { body = sr.ReadToEnd(); execOptions.inputParameters = JsonConvert.DeserializeObject <Dictionary <string, string> >(body); } } else { execOptions.inputParameters = req.Query.ToDictionary(t => t.Key, t => t.Value.ToString()); } if (execOptions.OverridingInputParameters != null) { execOptions.inputParameters = execOptions.OverridingInputParameters; } if (execOptions.inputParameters == null) { execOptions.inputParameters = new Dictionary <string, string>(); } //(var result, var routineExecutionMetric, var mayAccess) // out var responseHeaders var result = await ExecuteRoutineAsync(execOptions, requestHeaders, referer, remoteIpAddress, appTitle, appVersion); var responseHeaders = result.ResponseHeaders; if (responseHeaders != null && responseHeaders.Count > 0) { foreach (var kv in responseHeaders) { res.Headers[kv.Key] = kv.Value; } } if ((result?.MayAccess != null && !result.MayAccess.IsSuccess)) { res.ContentType = "text/plain"; res.StatusCode = 403; // Forbidden return(this.Content(result?.MayAccess?.userErrorVal)); } // TODO: Only output this if "debug mode" is enabled on the jsDALServer Config (so will come through as a debug=1 or something parameter/header) if (result.RoutineExecutionMetric != null) { res.Headers.Add("Server-Timing", result.RoutineExecutionMetric.GetServerTimeHeader()); } if (result is IActionResult) { return(result as IActionResult); } return(Ok(result.ApiResponse)); } finally { Interlocked.Decrement(ref ExceutionsInFlight); } }
public Computer(Memory mem, CPU cpu, ExecOptions defaultExecOptions) { Mem = mem; CPU = cpu; DefaultExecOptions = defaultExecOptions; }
public Computer() { Mem = new Memory(); CPU = new CPU(); DefaultExecOptions = new ExecOptions(); }
public static void Run() { Console.WriteLine($"-----------------------------------------------------"); Console.WriteLine($"Run 6502 code that copies data between two addresses."); Console.WriteLine($"-----------------------------------------------------"); string prgFileName = "../.cache/ConsoleTestPrograms/testprogram.prg"; Console.WriteLine($"Program binary file: {prgFileName}"); if (!File.Exists(prgFileName)) { Console.WriteLine($"File does not exist."); return; } // Load binary file byte[] fileData = File.ReadAllBytes(prgFileName); // First two bytes of binary file is assumed to be start address, little endian notation. ushort startPos = ByteHelpers.ToLittleEndianWord(fileData[0], fileData[1]); // The rest of the bytes are considered the code byte[] code = new byte[fileData.Length - 2]; Array.Copy(fileData, 2, code, 0, fileData.Length - 2); Console.WriteLine($"Code memory address: {startPos:X4} / {startPos}"); Console.WriteLine($"Code length (bytes): {code.Length:X4} / {code.Length}"); Console.WriteLine("Creating memory..."); Memory mem = new(); ushort testDataAddress = 0x1000; Console.WriteLine($"Loading test-data into memory address: {testDataAddress:X4}"); mem.WriteByte(ref testDataAddress, 0x01); mem.WriteByte(ref testDataAddress, 0x02); mem.WriteByte(ref testDataAddress, 0x03); testDataAddress = 0x10fe; mem.WriteByte(ref testDataAddress, 0xa3); mem.WriteByte(ref testDataAddress, 0xa2); mem.WriteByte(ref testDataAddress, 0xa1); //Console.WriteLine("Press Enter to start"); //Console.ReadLine(); // Load code in to memory at start position Console.WriteLine("Loading code into memory..."); mem.StoreData(startPos, code); // Initialize CPU, set PC to start position Console.WriteLine("Initializing CPU..."); CPU cpu = new(); cpu.PC = startPos; var execOptions = new ExecOptions { UnknownInstructionThrowsException = true, ExecuteUntilInstruction = OpCodeId.BRK }; // Execute program Console.WriteLine("Executing code..."); //var consumedCycles = cpu.Execute(mem, 10000, maxNumberOfInstructions: 1 + (4*256)); var consumedCycles = cpu.Execute(mem, execOptions); Console.WriteLine("Program ended."); Console.WriteLine($"Consumed cycles: {consumedCycles}"); ushort verifySrcAddress = 0x1000; ushort verifyDestAddress = 0x2000; Console.WriteLine($"Comparing data in source {verifySrcAddress:X4} with destination {verifyDestAddress:X4}"); Console.WriteLine($"{verifySrcAddress:X4} = {mem.FetchByte(ref verifySrcAddress):X2}, {verifyDestAddress:X4} = {mem.FetchByte(ref verifyDestAddress):X2} "); Console.WriteLine($"{verifySrcAddress:X4} = {mem.FetchByte(ref verifySrcAddress):X2}, {verifyDestAddress:X4} = {mem.FetchByte(ref verifyDestAddress):X2} "); Console.WriteLine($"{verifySrcAddress:X4} = {mem.FetchByte(ref verifySrcAddress):X2}, {verifyDestAddress:X4} = {mem.FetchByte(ref verifyDestAddress):X2} "); verifySrcAddress = 0x10fe; verifyDestAddress = 0x20fe; Console.WriteLine($"Comparing data in source {verifySrcAddress:X4} with destination {verifyDestAddress:X4}"); Console.WriteLine($"{verifySrcAddress:X4} = {mem.FetchByte(ref verifySrcAddress):X2}, {verifyDestAddress:X4} = {mem.FetchByte(ref verifyDestAddress):X2} "); Console.WriteLine($"{verifySrcAddress:X4} = {mem.FetchByte(ref verifySrcAddress):X2}, {verifyDestAddress:X4} = {mem.FetchByte(ref verifyDestAddress):X2} "); Console.WriteLine($"{verifySrcAddress:X4} = {mem.FetchByte(ref verifySrcAddress):X2}, {verifyDestAddress:X4} = {mem.FetchByte(ref verifyDestAddress):X2} "); }
static void Main(string[] args) { if (args.Length != 3) { Info.ShowUsage(); return; } var Conn = SqlConnet(args[0], args[1], args[2]); var setting = new Setting(Conn); var filesOptions = new FilesOptions(Conn, setting); var execOptions = new ExecOptions(Conn, setting); try { do { Console.Write("SQL> "); string str = Console.ReadLine(); if (str.ToLower() == "exit") { Conn.Close(); break; } else if (str.ToLower() == "help") { Info.ShowModuleUsage(); continue; } string[] cmdline = str.Split(new char[] { ' ' }, 3); String s = String.Empty; for (int i = 1; i < cmdline.Length; i++) { s += cmdline[i] + " "; } switch (cmdline[0].ToLower()) { case "enable_xp_cmdshell": setting.Enable_xp_cmdshell(); break; case "disable_xp_cmdshell": setting.Disable_xp_cmdshell(); break; case "xp_cmdshell": execOptions.xp_cmdshell(s); break; case "enable_ole": setting.Enable_ola(); break; case "disable_ole": setting.Disable_ole(); break; case "sp_cmdshell": execOptions.sp_cmdshell(s); break; case "upload": filesOptions.UploadFiles(cmdline[1], cmdline[2]); break; case "download": filesOptions.DownloadFiles(cmdline[2], cmdline[1]); break; default: Console.WriteLine(Batch.RemoteExec(Conn, str, true)); break; } if (!ConnectionState.Open.Equals(Conn.State)) { Console.WriteLine("[!] Disconnect...."); break; } }while (true); } catch (Exception ex) { Conn.Close(); Console.WriteLine("[!] Error log: \r\n" + ex.Message); } }
internal bool Execute <T>(T compiled, ExecOptions options, ICodeRunner <T> runner, params ExtendedOption[] extOptions) where T : ICompiledAssembly { var output = App.GetService <IOutputService>(); var con = App.GetService <IConsoleService>(); var sci = App.Editor().Control as ScintillaControl; if (options.Set(ExecOptions.Console)) { App.OpenView("Console"); var sess = new ConsoleSessionInfo { SessionName = compiled.MainUnit.Name, Banner = String.Format("Running module {0}", compiled.MainUnit.Name) }; con.StartSession(sess); } var ah = new ManualResetEvent(false); var success = false; var th = new Thread(() => { var res = default(Object); try { res = runner.Execute(compiled, extOptions); success = true; ah.Set(); WriteExecutionResult(sci, options, res); } catch (CodeException ex) { ah.Set(); sci.Invoke(() => { output.WriteLine(OutputFormat.Header, "Unhandled run-time error:"); output.WriteLine(OutputFormat.Error, ex.ToString()); var nav = App.GetService <IDocumentNavigatorService>(); if (options.Set(ExecOptions.TipError) && sci != null) { sci.ShowCallTip(ex.Message); } if (options.Set(ExecOptions.Annotation) && sci != null && ex.Document != null && nav.SetActive(ex.Document)) { sci.Styles.Annotation1.Font = "Segoe UI"; sci.SetAnnotation(ex.Line - 1, ex.Message, TextStyle.Annotation1); sci.CaretPosition = sci.GetPositionFromLine(ex.Line); sci.ScrollToCaret(); } if (options.Set(ExecOptions.Console) && con != null) { con.EndSession("Session terminated because of an unhandled run-time error."); } if (options.Set(ExecOptions.ShowOutput)) { App.OpenView("Output"); } }); return; } finally { abortCallBack = null; } if (options.Set(ExecOptions.Console)) { con.EndSession(res); } }); abortCallBack = () => { ah.Set(); th.Abort(); if (options.Set(ExecOptions.Console) && con != null) { con.EndSession("Session terminated."); } }; if (options.Set(ExecOptions.LimitTime)) { try { th.Start(); if (!ah.WaitOne(500)) { var s = success; if (!s) { try { th.Abort(); abortCallBack = null; } catch { } } return(s); } return(true); } catch { return(false); } } else { th.Start(); return(true); } }
protected override bool Run <T>(T compiled, ExecOptions options, ICodeRunner <T> runner, params ExtendedOption[] extOptions) { return(Execute(compiled, options, runner, extOptions)); }
public void Execute_And_Verify( AddrMode addrMode , bool ZP_X_Should_Wrap_Over_Byte = false , bool ZP_Y_Should_Wrap_Over_Byte = false , bool FullAddress_Should_Cross_Page_Boundary = false ) { if (!InsEffect.HasValue) { InsEffect = InstrEffect.Reg; } if (addrMode == AddrMode.Accumulator && FinalValue.HasValue) { throw new DotNet6502Exception($"If {nameof(AddrMode)} is {nameof(AddrMode.Accumulator)}, {nameof(FinalValue)} cannot be used."); } if (addrMode == AddrMode.Implied && FinalValue.HasValue) { throw new DotNet6502Exception($"If {nameof(addrMode)} is {nameof(AddrMode.Implied)}, {nameof(FinalValue)} cannot be used."); } if (addrMode != AddrMode.Indirect && FinalValueWord.HasValue) { throw new DotNet6502Exception($"If {nameof(AddrMode)} is other than {nameof(AddrMode.Indirect)}, {nameof(FinalValueWord)} cannot be used."); } if (InsEffect == InstrEffect.Reg) { if (ExpectedMemVal.HasValue) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.Reg)}, {nameof(ExpectedMemVal)} is not supposed to be set (only used for comparing memory address changed by write)"); } if (addrMode == AddrMode.Accumulator && (ExpectedX.HasValue || ExpectedY.HasValue)) { throw new DotNet6502Exception($"If {nameof(addrMode)} is {nameof(AddrMode.Accumulator)}, {nameof(ExpectedX)} or {nameof(ExpectedY)} cannot be used, because addressing mode {nameof(AddrMode.Accumulator)} only can affect A register."); } if (ExpectedA.HasValue && !A.HasValue) { throw new DotNet6502Exception($"If {nameof(ExpectedA)} is set, {nameof(A)} must be set to an initial value"); } if (ExpectedX.HasValue && !X.HasValue) { throw new DotNet6502Exception($"If {nameof(ExpectedX)} is set, {nameof(X)} must be set to an initial value"); } if (ExpectedY.HasValue && !Y.HasValue) { throw new DotNet6502Exception($"If {nameof(ExpectedY)} is set, {nameof(Y)} must be set to an initial value"); } } else if (InsEffect == InstrEffect.Mem) { if (addrMode == AddrMode.Implied) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.Mem)}, {nameof(addrMode)} {nameof(AddrMode.Implied)} cannot be used."); } if (addrMode == AddrMode.Accumulator) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.Mem)}, {nameof(addrMode)} {nameof(AddrMode.Accumulator)} cannot be used."); } if (ExpectedMemVal.HasValue && addrMode == AddrMode.I) { throw new DotNet6502Exception($"{nameof(ExpectedMemVal)} cannot be used with addressing mode {nameof(AddrMode.I)}"); } if (ExpectedMemVal.HasValue && addrMode == AddrMode.Accumulator) { throw new DotNet6502Exception($"{nameof(ExpectedMemVal)} cannot be used with addressing mode {nameof(AddrMode.Accumulator)}"); } if (ExpectedMemVal.HasValue & !FinalValue.HasValue) { throw new DotNet6502Exception($"If {nameof(ExpectedMemVal)} is set, {nameof(FinalValue)} must also be set to an initial value that the memory will have before the instruction executes"); } if (ExpectedA.HasValue) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.Mem)}, {nameof(ExpectedA)} is not supposed to be set (only used for comparing register change after instruction executed)"); } if (ExpectedX.HasValue) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.Mem)}, {nameof(ExpectedX)} is not supposed to be set (only used for comparing register change after instruction executed)"); } if (ExpectedY.HasValue) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.Mem)}, {nameof(ExpectedY)} is not supposed to be set (only used for comparing register change after instruction executed)"); } } else if (InsEffect == InstrEffect.RegAndMem) { if (addrMode == AddrMode.Implied) { throw new DotNet6502Exception($"If {nameof(InsEffect)} is {nameof(InstrEffect.RegAndMem)}, {nameof(addrMode)} {nameof(AddrMode.Implied)} cannot be used."); } } // If processor flags aren't configured to be initialized by test, we automatically set them to the opposite of what was defined as the expected result if (ExpectedC.HasValue && !C.HasValue) { C = !ExpectedC.Value; } if (ExpectedZ.HasValue && !Z.HasValue) { Z = !ExpectedZ.Value; } if (ExpectedI.HasValue && !I.HasValue) { I = !ExpectedI.Value; } if (ExpectedD.HasValue && !D.HasValue) { D = !ExpectedZ.Value; } if (ExpectedB.HasValue && !B.HasValue) { B = !ExpectedB.Value; } if (ExpectedU.HasValue && !U.HasValue) { U = !ExpectedU.Value; } if (ExpectedV.HasValue && !V.HasValue) { V = !ExpectedV.Value; } if (ExpectedN.HasValue && !N.HasValue) { N = !ExpectedN.Value; } // Shorthand variables to cpu and memory var computer = TestContext.Computer; var cpu = computer.CPU; var mem = computer.Mem; // Init Program Counter if (PC.HasValue) { cpu.PC = PC.Value; } // We will start writing the instruction, and then operand, starting at the specified PC address var codeMemPos = cpu.PC; // Write instruction at start address mem.WriteByte(ref codeMemPos, OpCode); ushort ZPAddressX; ushort ZPAddressY; ushort fullAddressX; ushort fullAddressY; ushort?finalAddressUsed = null; switch (addrMode) { case AddrMode.Implied: // Implied instructions does not have operand. The complete instruction takes 1 byte. break; case AddrMode.Accumulator: // Accumulator addressing mode means operate on A register (instead of memory). No instruction operand is used. break; case AddrMode.Indirect: // JMP is the only instruction that uses Indirect addressing. // The instruction contains a 16 bit address which identifies the location of the least significant byte // of another 16 bit memory address which is the real target of the instruction. // Define memory address the instruction should use if (!FullAddress.HasValue) { FullAddress = 0xab12; } finalAddressUsed = FullAddress.Value; // Initialize memory the instruction should read or write to if (!FinalValueWord.HasValue) { FinalValueWord = 0x1265; // If not specified, initialize default value to be at final memory location } mem.WriteWord(finalAddressUsed.Value, FinalValueWord.Value); // Write instruction operand mem.WriteWord(ref codeMemPos, FullAddress.Value); break; case AddrMode.Relative: // Relative addressing mode is used by branching instruction such as BEQ and BNE. if (!FinalValue.HasValue) { FinalValue = 0xd0; // If not specified, initialize default value to be at final relative memory location to branch to } mem.WriteByte(ref codeMemPos, FinalValue.Value); break; case AddrMode.I: // Initialize memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(ref codeMemPos, FinalValue.Value); break; case AddrMode.ZP: // To avoid testing mistakes, setting up a test as ZP (parameter AddrMode), when the instruction being run (property Instruction) is ZP_X or ZP_Y, // we initialize registers that should not be used with ZP to non-default values. if (!X.HasValue) { X = 111; } if (!Y.HasValue) { Y = 15; } // Define memory address the instruction should calculate if (!ZeroPageAddress.HasValue) { ZeroPageAddress = 0x0010; } finalAddressUsed = ZeroPageAddress.Value; if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } // Initialize memory the instruction should read or write to mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteByte(ref codeMemPos, ZeroPageAddress.Value.Lowbyte()); // Only least significant byte of the address is used in the instruction. break; case AddrMode.ZP_X: // Define memory address the instruction should calculate if (!ZeroPageAddress.HasValue) { ZeroPageAddress = 0x0010; } // Use a default value for X index if not specified in test if (!X.HasValue) { if (!ZP_X_Should_Wrap_Over_Byte) { X = 0x05; } else { X = 0xf5; // Force final ZP+X address bigger than one byte (0x0010 + 0xf5 = 0x0105) } } // Calculate ZeroPage + X address ZPAddressX = (ushort)(ZeroPageAddress + X); // Adjust that we expect the final address to wrap when getting larger than a byte if (ZP_X_Should_Wrap_Over_Byte) { ZPAddressX = (ushort)(ZPAddressX & 0xff); } finalAddressUsed = ZPAddressX; // Initialize memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteByte(ref codeMemPos, ZeroPageAddress.Value.Lowbyte()); // Only least significant byte of the address is used in the instruction. break; case AddrMode.ZP_Y: // Define memory address the instruction should calculate if (!ZeroPageAddress.HasValue) { ZeroPageAddress = 0x0010; } // Use a default value for Y index if not specified in test if (!Y.HasValue) { if (!ZP_Y_Should_Wrap_Over_Byte) { Y = 0x05; } else { Y = 0xf5; // Force final ZP+Y address bigger than one byte (0x0010 + 0xf5 = 0x0105) } } // Calculate ZeroPage + Y address ZPAddressY = (ushort)(ZeroPageAddress + Y); // Adjust that we expect the final address to wrap when getting larger than a byte if (ZP_Y_Should_Wrap_Over_Byte) { ZPAddressY = (ushort)(ZPAddressY & 0xff); } finalAddressUsed = ZPAddressY; // Initialize memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteByte(ref codeMemPos, ZeroPageAddress.Value.Lowbyte()); // Only least significant byte of the address is used in the instruction. break; case AddrMode.ABS: // To avoid testing mistakes, setting up a test as ABS (parameter AddrMode), when the instruction being run (property Instruction) is ABX_X or ABS_Y, // we initialize registers that should not be used with Absolute addressing to non-default values. if (!X.HasValue) { X = 60; } if (!Y.HasValue) { Y = 100; } // Define memory address the instruction should use if (!FullAddress.HasValue) { FullAddress = 0xab12; } finalAddressUsed = FullAddress.Value; // Initialize memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteWord(ref codeMemPos, FullAddress.Value); break; case AddrMode.ABS_X: // Define memory address the instruction should use if (!FullAddress.HasValue) { FullAddress = 0xab12; } // Use a default value for X index if not specified in test if (!X.HasValue) { if (!FullAddress_Should_Cross_Page_Boundary) { X = 0x05; } else { X = 0xf0; // Force final FullAddress+X address crosses page boundary (0xab12 + 0xf0 = 0xac02) } } // Calculate final address with X offset fullAddressX = (ushort)(FullAddress + X); finalAddressUsed = fullAddressX; // Initialize memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteWord(ref codeMemPos, FullAddress.Value); break; case AddrMode.ABS_Y: // Define memory address the instruction should use if (!FullAddress.HasValue) { FullAddress = 0xab12; } // Use a default value for X index if not specified in test if (!Y.HasValue) { if (!FullAddress_Should_Cross_Page_Boundary) { Y = 0x05; } else { Y = 0xf0; // Force final FullAddress+X address crosses page boundary (0xab12 + 0xf0 = 0xac02) } } // Calculate final address with X offset fullAddressY = (ushort)(FullAddress + Y); finalAddressUsed = fullAddressY; // Initialize memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteWord(ref codeMemPos, FullAddress.Value); break; case AddrMode.IX_IND: // Define memory address the instruction should use if (!FullAddress.HasValue) { FullAddress = 0xab12; } // Use a default value for X index if not specified in test if (!X.HasValue) { if (!ZP_X_Should_Wrap_Over_Byte) { X = 0x05; } else { X = 0xf0; // Force final ZeroPage address +X to wrap around a byte (0x20 + 0xf0 = 0x10) } } //Calculate locations used to calculate the actual address to read from (fullAddress) // index_indirect_base_address = zero page address if (!ZeroPageAddress.HasValue) { ZeroPageAddress = 0x20; } // We should allways wrap around indirect ZeroPage address + X if exceeds one byte. Can be be done by truncating address to byte. ushort index_indirect_zeropage_address = (byte)(ZeroPageAddress + X); // Initialize zero page address + X the instruction will read final address from mem.WriteWord(index_indirect_zeropage_address, FullAddress.Value); finalAddressUsed = FullAddress.Value; // Initialize final memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteByte(ref codeMemPos, (byte)ZeroPageAddress.Value); break; case AddrMode.IND_IX: // Initialize memory we will read from // We calculate a full address to store in a zero page address, adjusted by -Y (because the instruction will add Y when it retrieves it) // If we want to force crossing page boundary FullAddress + Y, we will hard code FullAddress and Y values if (FullAddress_Should_Cross_Page_Boundary) { // The address to be found in zero page address FullAddress = 0xab12; // Adding Y to the address found in zero page will cross page boundary Y = 0xff; } // Use a default value for FullAddress if not specified in test if (!FullAddress.HasValue) { FullAddress = 0xab12; } // Use a default value for Y index if not specified in test if (!Y.HasValue) { Y = 0x05; } //Calculate locations used to calculate the actual address to read from (fullAddress) // We use ZeroPage address as the "indirect indexed" zeropage address if (!ZeroPageAddress.HasValue) { ZeroPageAddress = 0x86; } // The address to be found in the zero page address should be the final address - the Y register. ushort address_at_zeropage_address = (ushort)(FullAddress - Y); // Initialize indirect indexed zero page address mem.WriteWord(ZeroPageAddress.Value, address_at_zeropage_address); finalAddressUsed = FullAddress.Value; // Initialize final memory the instruction should read or write to if (!FinalValue.HasValue) { FinalValue = 0x12; // If not specified, initialize default value to be at final memory location } mem.WriteByte(finalAddressUsed.Value, FinalValue.Value); // Write instruction operand mem.WriteByte(ref codeMemPos, (byte)ZeroPageAddress.Value); break; default: throw new DotNet6502Exception($"Unhandled addressing mode: {addrMode}"); } // Before we execute intruction, verify internal consistency of number of bytes the OpCode takes by our test-setup code above // with the value specified in OpCode.Bytes class. Assert.Equal(cpu.PC + cpu.InstructionList.GetOpCode(OpCode.ToByte()).Size, codeMemPos); // Init registers and flags if (A.HasValue) { cpu.A = A.Value; } if (X.HasValue) { cpu.X = X.Value; } if (Y.HasValue) { cpu.Y = Y.Value; } if (SP.HasValue) { cpu.SP = SP.Value; } if (C.HasValue) { cpu.ProcessorStatus.Carry = C.Value; } if (Z.HasValue) { cpu.ProcessorStatus.Zero = Z.Value; } if (I.HasValue) { cpu.ProcessorStatus.InterruptDisable = I.Value; } if (D.HasValue) { cpu.ProcessorStatus.Decimal = D.Value; } if (B.HasValue) { cpu.ProcessorStatus.Break = B.Value; } if (U.HasValue) { cpu.ProcessorStatus.Unused = U.Value; } if (V.HasValue) { cpu.ProcessorStatus.Overflow = V.Value; } if (N.HasValue) { cpu.ProcessorStatus.Negative = N.Value; } // Act var execOptions = new ExecOptions(); if (ExpectedCycles.HasValue) { execOptions.CyclesRequested = ExpectedCycles.Value; execOptions.MaxNumberOfInstructions = 1; } else { execOptions.CyclesRequested = null; execOptions.MaxNumberOfInstructions = 1; } var thisExecState = cpu.Execute(mem, execOptions); // Assert // Check that we didn't find any unknown opcode Assert.True(thisExecState.UnknownOpCodeCount == 0); // Verify Program Counter if (ExpectedPC.HasValue) { Assert.Equal(ExpectedPC.Value, cpu.PC); } else { // If no PC check has been defined, by default verify PC has been moved forward as many bytes we used up for the instruction. // But skip default verification if be used a branching or jump instruction // -- All that uses Relative addressing mode // -- Some other instructions in Implied addressing mode // -- JMP instructions // -- JSR instruction // if (addrMode != AddrMode.Relative && OpCode != OpCodeId.BRK && // Implied addressing mode OpCode != OpCodeId.RTS && // Implied addressing mode OpCode != OpCodeId.RTI && // Implied addressing mode OpCode != OpCodeId.JMP_IND && // Indirect addressing mode OpCode != OpCodeId.JMP_ABS && // Absolute addressing mode OpCode != OpCodeId.JSR // Absolute addressing mode ) { Assert.Equal(codeMemPos, cpu.PC); } } // Verify expected # of cycles if (ExpectedCycles.HasValue) { Assert.Equal(ExpectedCycles, thisExecState.CyclesConsumed); } // Verify registers (operations that affects registers) if (InsEffect == InstrEffect.Reg || InsEffect == InstrEffect.RegAndMem) { if (ExpectedA.HasValue) { Assert.Equal(ExpectedA.Value, cpu.A); } if (ExpectedX.HasValue) { Assert.Equal(ExpectedX.Value, cpu.X); } if (ExpectedY.HasValue) { Assert.Equal(ExpectedY.Value, cpu.Y); } } // Verify affected memory (operations that affect memory) else if (InsEffect == InstrEffect.Mem || InsEffect == InstrEffect.RegAndMem) { if (!finalAddressUsed.HasValue) { throw new DotNet6502Exception($"Incorrect use of TestSpec class, or bug in TestSpec class. Is correct addressing mode being tested? Variable {nameof(finalAddressUsed)} must be initialized in all addressing mode code paths that can modify memory"); } if (ExpectedMemVal.HasValue) { Assert.Equal(ExpectedMemVal.Value, mem[finalAddressUsed.Value]); } } // Verify stack pointer if (ExpectedSP.HasValue) { Assert.Equal(ExpectedSP.Value, cpu.SP); } // Verify status flags if (ExpectedC.HasValue) { Assert.Equal(ExpectedC.Value, cpu.ProcessorStatus.Carry); } if (ExpectedZ.HasValue) { Assert.Equal(ExpectedZ.Value, cpu.ProcessorStatus.Zero); } if (ExpectedI.HasValue) { Assert.Equal(ExpectedI.Value, cpu.ProcessorStatus.InterruptDisable); } if (ExpectedD.HasValue) { Assert.Equal(ExpectedD.Value, cpu.ProcessorStatus.Decimal); } if (ExpectedB.HasValue) { Assert.Equal(ExpectedB.Value, cpu.ProcessorStatus.Break); } if (ExpectedV.HasValue) { Assert.Equal(ExpectedV.Value, cpu.ProcessorStatus.Overflow); } if (ExpectedN.HasValue) { Assert.Equal(ExpectedN.Value, cpu.ProcessorStatus.Negative); } }
public static CommandLineApplication Configure(Mon mon) { var app = new CommandLineApplication { Name = "", Description = "DotNet 6502 machine code monitor for the DotNet 6502 emulator library." + Environment.NewLine + "By Highbyte 2021" + Environment.NewLine + "Source at: https://github.com/highbyte/dotnet-6502", UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.StopParsingAndCollect }; // Fix: Use custom HelpTextGentorator to avoid name/description of the application to be shown each time help text is shown. app.HelpTextGenerator = new CustomHelpTextGenerator(); // Fix: To avoid CommandLineUtils to the name of the application at the end of the help text: Don't use HelpOption on app-level, instead set it on each command below. //app.HelpOption(inherited: true); app.Command("l", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Load a 6502 binary into emulator memory."; cmd.AddName("load"); var fileName = cmd.Argument("filename", "Name of the binary file.") .IsRequired() .Accepts(v => v.ExistingFile()); var address = cmd.Argument("address", "Memory address (hex) to load the file into. If not specified, it's assumed the first two bytes of the file contains the load address."); address.Validators.Add(new MustBe16BitHexValueValidator()); cmd.OnExecute(() => { ushort loadedAtAddres; if (string.IsNullOrEmpty(address.Value)) { mon.LoadBinary(fileName.Value, out loadedAtAddres); } else { mon.LoadBinary(fileName.Value, out loadedAtAddres, forceLoadAddress: ushort.Parse(address.Value)); } Console.WriteLine($"File loaded at {loadedAtAddres.ToHex()}"); return(0); }); }); app.Command("d", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Disassembles 6502 code from emulator memory."; var start = cmd.Argument("start", "Start address (hex). If not specified, the current PC address is used."); start.Validators.Add(new MustBe16BitHexValueValidator()); var end = cmd.Argument("end", "End address (hex). If not specified, a default number of addresses will be shown from start."); end.Validators.Add(new MustBe16BitHexValueValidator()); cmd.OnExecute(() => { ushort startAddress; if (string.IsNullOrEmpty(start.Value)) { startAddress = mon.Cpu.PC; } else { startAddress = ushort.Parse(start.Value, NumberStyles.AllowHexSpecifier, null); } ushort endAddress; if (string.IsNullOrEmpty(end.Value)) { endAddress = (ushort)(startAddress + 0x10); } else { endAddress = ushort.Parse(end.Value, NumberStyles.AllowHexSpecifier, null); if (endAddress < startAddress) { endAddress = startAddress; } } ushort currentAddress = startAddress; while (currentAddress <= endAddress) { Console.WriteLine(OutputGen.GetInstructionDisassembly(mon.Cpu, mon.Mem, currentAddress)); var opCodeByte = mon.Mem[currentAddress]; int insSize; if (!mon.Cpu.InstructionList.OpCodeDictionary.ContainsKey(opCodeByte)) { insSize = 1; } else { insSize = mon.Cpu.InstructionList.GetOpCode(opCodeByte).Size; } currentAddress += (ushort)insSize; } return(0); }); }); app.Command("m", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Show contents of emulator memory in bytes."; cmd.AddName("mem"); var start = cmd.Argument("start", "Start address (hex). If not specified, the 0000 address is used."); start.Validators.Add(new MustBe16BitHexValueValidator()); var end = cmd.Argument("end", "End address (hex). If not specified, a default number of memory locations will be shown from start."); end.Validators.Add(new MustBe16BitHexValueValidator()); cmd.OnExecute(() => { ushort startAddress; if (string.IsNullOrEmpty(start.Value)) { startAddress = 0x0000; } else { startAddress = ushort.Parse(start.Value, NumberStyles.AllowHexSpecifier, null); } ushort endAddress; if (string.IsNullOrEmpty(end.Value)) { endAddress = (ushort)(startAddress + (16 * 8) - 1); } else { endAddress = ushort.Parse(end.Value, NumberStyles.AllowHexSpecifier, null); if (endAddress < startAddress) { endAddress = startAddress; } } var list = OutputMemoryGen.GetFormattedMemoryList(mon.Mem, startAddress, endAddress); foreach (var line in list) { Console.WriteLine(line); } return(0); }); }); app.Command("r", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Show processor status and registers. CY = #cycles executed."; cmd.AddName("reg"); cmd.Command("a", setRegisterCmd => { setRegisterCmd.Description = "Sets A register."; var regVal = setRegisterCmd.Argument("value", "Value of A register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.A = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetRegisters(mon.Cpu)}"); return(0); }); }); cmd.Command("x", setRegisterCmd => { setRegisterCmd.Description = "Sets X register."; var regVal = setRegisterCmd.Argument("value", "Value of X register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.X = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetRegisters(mon.Cpu)}"); return(0); }); }); cmd.Command("y", setRegisterCmd => { setRegisterCmd.Description = "Sets Y register."; var regVal = setRegisterCmd.Argument("value", "Value of Y register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.Y = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetRegisters(mon.Cpu)}"); return(0); }); }); cmd.Command("sp", setRegisterCmd => { setRegisterCmd.Description = "Sets SP (Stack Pointer)."; var regVal = setRegisterCmd.Argument("value", "Value of SP (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.SP = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetPCandSP(mon.Cpu)}"); return(0); }); }); cmd.Command("ps", setRegisterCmd => { setRegisterCmd.Description = "Sets processor status register."; var regVal = setRegisterCmd.Argument("value", "Value of processor status register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.ProcessorStatus.Value = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"PS={value}"); Console.WriteLine($"{OutputGen.GetStatus(mon.Cpu)}"); return(0); }); }); cmd.Command("pc", setRegisterCmd => { setRegisterCmd.Description = "Sets PC (Program Counter)."; var regVal = setRegisterCmd.Argument("value", "Value of PC (hex).").IsRequired(); regVal.Validators.Add(new MustBe16BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.PC = ushort.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetPCandSP(mon.Cpu)}"); return(0); }); }); cmd.OnExecute(() => { Console.WriteLine(OutputGen.GetProcessorState(mon.Cpu, includeCycles: true)); return(0); }); }); app.Command("g", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Change the PC (Program Counter) to the specified address and execute code."; cmd.AddName("goto"); var address = cmd.Argument("address", "The address (hex) to start executing code at.").IsRequired(); address.Validators.Add(new MustBe16BitHexValueValidator()); var dontStopOnBRK = cmd.Option("--no-brk|-nb", "Prevent execution stop when BRK instruction encountered.", CommandOptionType.NoValue); cmd.OnExecute(() => { mon.Cpu.PC = ushort.Parse(address.Value, NumberStyles.AllowHexSpecifier, null); ExecOptions execOptions; if (dontStopOnBRK.HasValue()) { execOptions = new ExecOptions(); Console.WriteLine($"Will never stop."); } else { execOptions = new ExecOptions { ExecuteUntilInstruction = OpCodeId.BRK, }; Console.WriteLine($"Will stop on BRK instruction."); } Console.WriteLine($"Staring executing code at {mon.Cpu.PC.ToHex("",lowerCase:true)}"); mon.Computer.Run(execOptions); Console.WriteLine($"Stopped at {mon.Cpu.PC.ToHex("",lowerCase:true)}"); Console.WriteLine($"{OutputGen.GetLastInstructionDisassembly(mon.Cpu, mon.Mem)}"); return(0); }); }); app.Command("z", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Single step through instructions. Optionally execute a specified number of instructions."; var inscount = cmd.Argument <ulong>("inscount", "Number of instructions to execute. Defaults to 1."); inscount.DefaultValue = 1; cmd.OnExecute(() => { Console.WriteLine($"Executing code at {mon.Cpu.PC.ToHex("",lowerCase:true)} for {inscount.Value} instruction(s)."); var execOptions = new ExecOptions { MaxNumberOfInstructions = ulong.Parse(inscount.Value), }; mon.Computer.Run(execOptions); Console.WriteLine($"Last instruction:"); Console.WriteLine($"{OutputGen.GetLastInstructionDisassembly(mon.Cpu, mon.Mem)}"); return(0); }); }); app.Command("f", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Fill memory att specified address with a list of bytes. Example: f 1000 20 ff ab 30"; cmd.AddName("fill"); var memAddress = cmd.Argument("address", "Memory address (hex).").IsRequired(); memAddress.Validators.Add(new MustBe16BitHexValueValidator()); var memValues = cmd.Argument("values", "List of byte values (hex). Example: 20 ff ab 30").IsRequired(); memValues.MultipleValues = true; memValues.Validators.Add(new MustBe8BitHexValueValidator()); cmd.OnExecute(() => { var address = ushort.Parse(memAddress.Value, NumberStyles.AllowHexSpecifier, null); List <byte> bytes = new(); foreach (var val in memValues.Values) { bytes.Add(byte.Parse(val, NumberStyles.AllowHexSpecifier, null)); } foreach (var val in bytes) { mon.Mem[address++] = val; } return(0); }); }); app.Command("q", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Quit monitor."; cmd.AddName("quit"); cmd.AddName("x"); cmd.AddName("exit"); cmd.OnExecute(() => { Console.WriteLine($"Quiting."); return(2); }); }); app.OnExecute(() => { Console.WriteLine("Unknown command."); Console.WriteLine("Help: ?|help|-?|--help"); //app.ShowHelp(); return(1); }); return(app); }