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));
            }
        }
Example #2
0
 public ExecState Run(ExecOptions execOptions)
 {
     return(CPU.Execute(
                this.Mem,
                execOptions
                ));
 }
Example #3
0
 public void Run(ExecOptions execOptions)
 {
     CPU.Execute(
         this.Mem,
         execOptions
         );
 }
Example #4
0
        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());
            }
        }
Example #5
0
        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));
        }
Example #6
0
 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);
            }
        }
Example #9
0
 public Computer(Memory mem, CPU cpu, ExecOptions defaultExecOptions)
 {
     Mem = mem;
     CPU = cpu;
     DefaultExecOptions = defaultExecOptions;
 }
Example #10
0
 public Computer()
 {
     Mem = new Memory();
     CPU = new CPU();
     DefaultExecOptions = new ExecOptions();
 }
Example #11
0
        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}  ");
        }
Example #12
0
        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);
            }
        }
Example #13
0
        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);
            }
        }
Example #14
0
 protected override bool Run <T>(T compiled, ExecOptions options, ICodeRunner <T> runner, params ExtendedOption[] extOptions)
 {
     return(Execute(compiled, options, runner, extOptions));
 }
Example #15
0
        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);
            }
        }
Example #16
0
        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);
        }