static unsafe void Main(string[] args)
        {
#if DOTNET2
            Console.Title = "Target Virtual Machine";
#endif

            Console.WriteLine("Target Assembler/Virtual Machine v{0}.", assemblyVersion);
            Console.WriteLine("Copyright (c) 2006, Peter Wright <*****@*****.**>");
            Console.WriteLine("");

            Decoder cpu = new Decoder(new Core());

            // Parse any arguments the user has specified on the command-line
            #region argument parsing
            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] != null && args[i].StartsWith("--"))
                {
                    string arg = args[i].ToLower();
                    args[i] = null; // Prevent the assembler from processing this instruction

                    switch (arg)
                    {
                    case "--help":
                    case "--usage":
                        Console.WriteLine("Usage:");
                        Console.WriteLine("vm.exe {options} objectCodeFile {objectCodeFile}");
                        Console.WriteLine("Options:");
                        Console.WriteLine("--monitor   Toggles the monitor (default {0})", cpu.debug);
                        Console.WriteLine("--break #   Sets a breakpoint at addr #");
                        Console.WriteLine("--haltBreak Toggles breaking on HALT (default {0})", cpu.haltBreak);
                        Console.WriteLine("--noopBreak Toggles breaking on NOOP (default {0})", cpu.noopBreak);
                        Console.WriteLine("--decode    Toggles monitor's automatic decoding (default {0})", cpu.autoDecode);
                        Console.WriteLine("--echoasm   Toggles assembler's asm echo (default {0})", Assembler.echoAssembledInstructions);
                        Console.WriteLine("--pc #      Sets initial PC value to # (default {0})", cpu.vm.PC);
                        Console.WriteLine("--fp #      Sets initial FP value to # (default {0})", cpu.vm.FP);
                        Console.WriteLine("--sp #      Sets initial SP value to # (default {0})", cpu.vm.SP);
                        Console.WriteLine("--mp #      Sets initial MP value to # (default {0})", cpu.vm.MP);
                        Console.WriteLine("--bp #      Sets initial BP value to # (default {0})", cpu.vm.BP);
                        Console.WriteLine("--time      Toggles performance timing (default {0})", timeExecution);
                        Console.WriteLine("--trace     Saves a trace to file f");
                        Console.WriteLine("--logio     Logs all IO to file f");
                        Console.WriteLine("Option arguments are not error-handled.");

                        return;

                    case "--noopbreak":
                    case "--nb":
                        cpu.noopBreak = !cpu.noopBreak;
                        break;

                    case "--haltbreak":
                    case "--hb":
                        cpu.haltBreak = !cpu.haltBreak;
                        break;

                    case "--pc":
                        cpu.vm.PC   = ushort.Parse(args[i + 1]);
                        args[i + 1] = null;     // Prevent processing of the next argument
                        break;

                    case "--sp":
                        cpu.vm.SP   = ushort.Parse(args[i + 1]);
                        args[i + 1] = null;     // Prevent processing of the next argument
                        break;

                    case "--mp":
                        cpu.vm.MP   = ushort.Parse(args[i + 1]);
                        args[i + 1] = null;     // Prevent processing of the next argument
                        break;

                    case "--fp":
                        cpu.vm.FP   = ushort.Parse(args[i + 1]);
                        args[i + 1] = null;     // Prevent processing of the next argument
                        break;

                    case "--bp":
                        cpu.vm.BP   = ushort.Parse(args[i + 1]);
                        args[i + 1] = null;     // Prevent processing of the next argument
                        break;

                    case "--time":
                        timeExecution = !timeExecution;
                        break;

                    case "--monitor":
                    case "--debug":     // Display monitor immediately
                        cpu.debug = !cpu.debug;
                        break;

                    case "--logio":
                    case "--lio":     // Log IO to disk
                        cpu.startIOLog(args[i + 1]);
                        args[i + 1] = null;
                        break;

                    case "--trace":
                    case "--tracefile":     // Save traces to disk
                        cpu.startTrace(args[i + 1]);
                        args[i + 1] = null;
                        break;

                    case "--echoasm":
                        Assembler.echoAssembledInstructions = !Assembler.echoAssembledInstructions;
                        break;

                    case "--break":     // Enable the monitor, place a breakpoint at the address specified in the next argument:
                        cpu.debug     = true;
                        cpu.addrBreak = ushort.Parse(args[i + 1]);
                        args[i + 1]   = null;   // Prevent processing of the next argument
                        break;

                    default:
                        Console.WriteLine("Unknown argument: " + args[0]);
                        return;
                    }
                }
            }
            #endregion

            // Now assemble the files and dump the result into main memory
            DateTime  asmStart = DateTime.Now;
            Assembler a        = new Assembler(args);
            DateTime  asmStop  = DateTime.Now;
            cpu.vm.memory = a.getAssembled();


            Console.WriteLine("\nExecuting code...");

            // Execute until the CPU is halted
            DateTime runStart = DateTime.Now;
            cpu.execute();
            DateTime runStop = DateTime.Now;


            if (timeExecution)
            {
                Console.WriteLine("\nTIMING INFORMATION (+ MONITOR TIME)");
                TimeSpan tAsm = asmStop - asmStart;
                TimeSpan tRun = runStop - runStart;
                Console.WriteLine("  Assembly Duration: {0}", tAsm);
                Console.WriteLine(" Execution Duration: {0}", tRun);
                Console.WriteLine("       Instructions: {0}", cpu.ops);
                Console.WriteLine("        Comparisons: {0}", cpu.cmps);
                Console.WriteLine("            ops/sec: {0}", Math.Round(cpu.ops / tRun.TotalSeconds));
                Console.WriteLine("<PRESS ENTER>");
                Console.ReadLine();
            }

            Console.WriteLine("\nTargetVM: Normal Termination.");
        } // end app
        /// <summary>A simple debugging console</summary>
        public unsafe void monitor()
        {
            #region Monitor disappearance conditions
            if (addrBreak != -1)   // If we have been instructed to break at a specific address:
            {
                if (vm.PC - 2 != addrBreak)
                {
                    return; // Do not display the monitor yet
                }
                else
                {
                    addrBreak = -1;
                }
            }
            else if (monitorSkipInstructions != 0)
            {
                if (--monitorSkipInstructions > 0)
                {
                    return; // Do not display the monitor yet
                }
            }
            #endregion

            #region Handle coexistance with the UI nicely
            // If the CPU has performed IO since the last execution, ensure we're on a new line
            if (ioSinceMonitor)
            {
                Console.WriteLine();
                ioSinceMonitor = false;
            }
            #endregion

            // If the user requested it, automatically display the decode of the instruction
            if (autoDecode)
            {
                Console.WriteLine("\t{1}", (vm.PC - 2), op.ToString());
            }
            // Keep accepting arguments until we receive a terminal command

            #region Monitor switch
            while (true)
            {
                Console.Write("[{0}] Monitor> ", (vm.PC - 2));
                string[] cmds = TargetVM.std.ArgParser.parse(Console.ReadLine());

                if (cmds.Length == 0)
                {
                    return;
                }

                switch (cmds[0].ToLower())
                {
                case null:
                case "":
                case "c":
                case "continue":
                    return;

                case "next":
                case "n":
                    debug = true;
                    return;

                case "r":
                case "run":
                    debug = false;
                    return;

                case "k":
                case "skip":     // Skip n executions:
                    if (cmds.Length == 2)
                    {
                        monitorSkipInstructions = parseAddr(cmds[1]);
                        Console.WriteLine("Skipping monitor for next {0} instruction(s).", monitorSkipInstructions);
                        return;
                    }
                    else
                    {
                        Console.WriteLine("Target Monitor: skip requires an argument. Example: skip 5");
                        break;
                    }

                case "b":
                case "break":     // Break at a specific address
                    if (cmds.Length == 2)
                    {
                        addrBreak = parseAddr(cmds[1]);
                        Console.WriteLine("Setting breakpoint at address {0}.", addrBreak);
                        return;
                    }
                    else
                    {
                        Console.WriteLine("Target Monitor: break requires an argument. Example: break 210");
                        break;
                    }

                case "s":
                case "halt":
                case "stop":
                case "exit":
                case "quit":
                case "q":     // halt execution and terminate
                    closeFiles();

                    Console.WriteLine("Target Monitor: goodbye.");
                    System.Environment.Exit(0);
                    return;

                case "search":     // Search for the occurrances of n in memory
                    if (cmds.Length == 2)
                    {
                        ushort val = parseValue(cmds[1]);

                        Console.WriteLine("Searching memory...");
                        for (int i = vm.memory.Length - 1; i != 0; --i)
                        {
                            if (vm.memory[i] == val)
                            {
                                Console.WriteLine("{0}: {1}\t('{2}')", i, val, (char)val);
                            }
                        }
                        Console.WriteLine("Complete.");
                    }
                    else
                    {
                        Console.WriteLine("Target Monitor: search requires one argument.");
                    }
                    break;

                case "hb":
                case "haltbreak":     // examines / sets haltbreak
                    Console.WriteLine("haltBreak={0}", haltBreak);

                    if (cmds.Length == 2)
                    {
                        haltBreak = parseBool(cmds[1]);
                        Console.WriteLine("haltBreak={0}\tCHANGED", haltBreak);
                    }
                    break;

                case "nb":
                case "noopbreak":     // examines / sets noopBreak
                    Console.WriteLine("noopBreak={0}", noopBreak);

                    if (cmds.Length == 2)
                    {
                        noopBreak = parseBool(cmds[1]);
                        Console.WriteLine("noopBreak={0}\tCHANGED", noopBreak);
                    }
                    break;

                case "setsp":
                    vm.SP = parseAddr(cmds[1]);
                    Console.WriteLine("SP={0}\tCHANGED", vm.SP);
                    break;

                case "setfp":
                    vm.FP = parseAddr(cmds[1]);
                    Console.WriteLine("FP={0}\tCHANGED", vm.FP);
                    break;

                case "setmp":
                    vm.MP = parseAddr(cmds[1]);
                    Console.WriteLine("MP={0}\tCHANGED", vm.MP);
                    break;

                case "store":
                    if (cmds.Length == 3)
                    {
                        ushort storeAddr = parseAddr(cmds[1]);
                        ushort storeVal  = parseValue(cmds[2]);
                        vm.memory[storeAddr] = storeVal;

                        Console.WriteLine("{0}:\t{1}", storeAddr, storeVal);
                    }
                    else
                    {
                        Console.WriteLine("Target Monitor: store takes 2 arguments (address, value)");
                    }
                    break;


                case "push":
                    ushort data = parseAddr(cmds[1]);;
                    vm.push(data);
                    Console.WriteLine("Pushed {0} onto the stack.", data);
                    break;

                case "pop":
                    if (vm.SP > 0)
                    {
                        Console.WriteLine("Popped {0} from the stack.", vm.pop());
                    }
                    else
                    {
                        Console.WriteLine("Stack at top of address space. Cannot pop.");
                    }
                    break;

                case "j":
                case "jump":
                    if (cmds.Length == 2)
                    {
                        vm.PC = parseAddr(cmds[1]);
                        Console.WriteLine("Jumping to {0}", vm.PC);
                        this.op = vm.getNextInstruction();     // Decode the instruction and execute it instead
                    }
                    else
                    {
                        Console.WriteLine("Target Monitor: jump requires an argument. Example: jump 50");
                    }
                    break;

                case "d":
                case "decode":
                    for (int i = 1; i < cmds.Length; i++)
                    {
                        ushort         addr  = parseAddr(cmds[i]);
                        CpuInstruction memop = new CpuInstruction();
                        memop[0] = vm.memory[addr];
                        memop[1] = vm.memory[addr + 1];
                        Console.WriteLine("{0}:\t{1}", addr, memop.ToString());
                    }

                    if (cmds.Length == 1)
                    {
                        Console.WriteLine("{0}\t{1}", (vm.PC - 2), op.ToString());
                    }

                    break;

                case "i":
                case "inspect":
                    for (int i = 1; i < cmds.Length; i++)
                    {
                        ushort addr = parseAddr(cmds[i]);

                        if (signedAccess)
                        {
                            if (addr < vm.memory.Length)
                            {
                                Console.WriteLine("{0}:\t0x{1:X4} == {1}s", addr, (short)vm.memory[addr]);
                            }
                            else
                            {
                                Console.WriteLine("{0}:\tOUT OF BOUNDS");
                            }
                        }
                        else
                        {
                            if (addr < vm.memory.Length)
                            {
                                Console.WriteLine("{0}:\t0x{1:X4} == {1}u", addr, vm.memory[addr]);
                            }
                            else
                            {
                                Console.WriteLine("{0}:\tOUT OF BOUNDS");
                            }
                        }
                    }
                    break;

                case "p":
                case "peek":
                    ushort pitems     = 1;
                    bool   peekSigned = signedAccess;
                    if (cmds.Length == 2)
                    {
                        pitems = parseAddr(cmds[1]);
                    }

                    // Check we're not about to read out of the memory bounds
                    if (vm.SP - pitems < 0)
                    {
                        Console.WriteLine("Target Monitor: {0} word{1} back from SP ({2}) is an illegal address.", pitems, (pitems != 1 ? "s" : ""), vm.SP);
                        break;
                    }

                    for (int offset = 1; offset <= pitems; offset++)
                    {
                        ushort w = vm.memory[vm.SP - offset];     // vm.memGetWord(vm.SP - offset);
                        if (peekSigned)
                        {
                            Console.WriteLine("{0}:\t0x{1:X4} == {1}s", vm.SP - offset, (short)w);
                        }
                        else
                        {
                            Console.WriteLine("{0}:\t0x{1:X4} == {1}u", vm.SP - offset, w);
                        }
                    }

                    break;

                case "g":
                case "reg":
                case "registers":
                case "register":
                case "calc":     // make it easier for the user to think about using reg to calculate values
                    if (cmds.Length == 1)
                    {
                        Hashtable core = vm.coreDump();
                        foreach (string key in core.Keys)
                        {
                            Console.WriteLine("{0}\t= {1}", key, core[key]);
                        }
                    }
                    else
                    {
                        for (int i = 1; i < cmds.Length; i++)
                        {
                            ushort val = parseAddr(cmds[i]);
                            Console.WriteLine("{0} = {1}", cmds[i].ToUpper(), val);
                        }
                    }

                    break;

                case "assemble":
                case "asm":
                    if (cmds.Length >= 2)
                    {
                        // Assemble each file passed in as an argument
                        for (int i = 1; i < cmds.Length; i++)
                        {
                            if (File.Exists(cmds[i]))
                            {
                                Assembler a = new Assembler(vm.memory, cmds[i]);
                                vm.memory = a.getAssembled();     // technically unnecessary

                                // Re-decode this instruction (in case it's changed)
                                vm.PC  -= 2;
                                this.op = vm.getNextInstruction();
                            }
                            else
                            {
                                Console.WriteLine("Arg #{0} Non-existant file {1}", i, cmds[i]);
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("Monitor: assemble requires a parameter. Please quote paths with spaces in them.");
                    }
                    break;

                case "t":
                case "trace":
                case "savetrace":
                    if (cmds.Length == 2)
                    {
                        Console.WriteLine("Monitor: Tracing Enabled");
                        startTrace(cmds[1]);
                    }
                    else
                    {
                        Console.WriteLine("Monitor: trace requires one parameter");
                    }
                    break;

                case "logio":
                case "lio":
                case "io":
                case "l":
                    if (cmds.Length == 2)
                    {
                        Console.WriteLine("Monitor: IO Logging Enabled");
                        startIOLog(cmds[1].Replace('_', ' '));
                    }
                    else
                    {
                        Console.WriteLine("Monitor: logio requires one parameter. Please quote paths with spaces in them.");
                    }
                    break;

                case "signed":
                    signedAccess = true;
                    Console.WriteLine("Monitor: memory display set to signed");
                    break;

                case "unsigned":
                    signedAccess = false;
                    Console.WriteLine("Monitor: memory display set to unsigned");
                    break;

                case "?":
                case "help":
#if DOTNET2
                    Console.BackgroundColor = ConsoleColor.Blue;
                    Console.ForegroundColor = ConsoleColor.White;
#endif
                    Console.WriteLine("TARGET MONITOR - QUICK HELP");
#if DOTNET2
                    Console.ResetColor();
#endif
                    Console.WriteLine("Copyright (c) 2006, Peter Wright <*****@*****.**>");
                    Console.WriteLine("Commands that take n can understand 'sp', '0,[fp,1]', etc.");
                    Console.WriteLine("d {n}      - Displays a decode of the instruction[s] at n.");
                    Console.WriteLine("             Current instruction displayed if none are");
                    Console.WriteLine("             specified (decode)");
                    Console.WriteLine("p [-][n]   - Displays the top n items on the stack. (peek)");
                    Console.WriteLine("i [-]{n}   - Displays values stored in memory location[s] n.");
                    Console.WriteLine("j addr     - Branches immediately to ADDR. (jump)");
                    Console.WriteLine("q          - Terminates the VM immediately");
                    Console.WriteLine("c          - Resumes execution; monitor state unchanged");
                    Console.WriteLine("             (continue, <ENTER>)");
                    Console.WriteLine("r          - Resumes execution; monitor disabled");
                    Console.WriteLine("n          - Resumes execution; monitor enabled");
                    Console.WriteLine("k n        - Hides monitor for another n operations. (skip)");
                    Console.WriteLine("b n        - Hides monitor until operation at n. (break)");
                    Console.WriteLine("g          - Displays all registers (registers)");
                    Console.WriteLine("g {n}      - Displays specific register values");
                    Console.WriteLine("calc {n}   - Calculates the address n");
                    Console.WriteLine("asm f      - Assembles file f");
                    Console.WriteLine("t f        - Starts saving trace data to file f");
                    Console.WriteLine("l f        - Logs all IO to file f");
                    Console.WriteLine("signed     - Changes memory display to signed mode");

                    break;

                default:
                    Console.WriteLine("Monitor: Unknown command");
                    break;
                }
            }
            #endregion
        }