#pragma warning restore CS0162

    /*
     * /// <summary>
     * /// Leaves the serial connection open
     * /// Will attempt to detect /HALT notifications from Unirom
     * /// and catch crash/exception events
     * /// </summary>
     * public static void DoMonitor(){
     *
     * // Note:
     * // Mono hasn't implemented the activeSerial.ReceivedBytesThreshold methods yet
     * // so we can't really use events. Instead the max we'll wait is 1ms to enter the
     * // tight inner loop. Should be fine if you're not filling a ~2kb buffer in 1ms
     *
     *      // a rolling buffer of the last 4 things recieved
     *      string lastMonitorBytes = "";
     *
     *      while (true)
     *      {
     *
     *              while (activeSerial.BytesToRead > 0)
     *              {
     *
     *                      // echo things to the screen
     *
     *                      int responseByte = activeSerial.ReadByte();
     *                      Console.Write((char)(responseByte));
     *
     *                      // check if the PSX has crashed
     *
     *      // TODO: use a more appropriate data collection, lol
     *                      lastMonitorBytes += (char)responseByte;
     *                      while ( lastMonitorBytes.Length > 4 )
     *                              lastMonitorBytes = lastMonitorBytes.Remove( 0, 1 );
     *
     *                      if ( lastMonitorBytes == "HLTD" ){
     *
     *                              while ( true ){
     *
     *                                      Console.WriteLine( "\nThe PSX may have crashed, enter debug mode? (y/n)" );
     *              Console.WriteLine( "(Also starts a TCP/SIO bridge on port 3333." );
     *                                      ConsoleKeyInfo c = Console.ReadKey();
     *                                      if ( c.KeyChar.ToString().ToLowerInvariant() == "y" ){
     *                  GDB.GetRegs();
     *                  GDB.DumpRegs();
     *                                              GDB.Init( 3333 );
     *                                              return;
     *                                      } else {
     *                                              Console.WriteLine( "\nReturned to monitor mode." );
     *                                              break;
     *                                      }
     *
     *                              }
     *
     *
     *                      }
     *
     *              }
     *
     *  Thread.Sleep( 1 );
     *
     *      }
     *
     *
     * }
     */

    /// <summary>
    /// Puts Unirom into /debug mode and wipes everything from the end of the kernel
    /// to the stack, where it will crash.
    //  0x80010000 -> 0x801FFF??
    /// </summary>
    /// <param name="wipeValue">32 bit value to fill ram with</param>
    /// <returns></returns>
    public static bool Command_WipeMem(UInt32 wipeAddress, UInt32 wipeValue)
    {
        // if it returns true, we might enter /m (monitor) mode, etc
        if (
            !TransferLogic.ChallengeResponse(CommandMode.DEBUG)
            )
        {
            Console.WriteLine("Couldn't determine if Unirom is in debug mode.");
            return(false);
        }

        Thread.Sleep(200);

        byte[] buffer = new byte[0x80200000 - wipeAddress];           // just shy of 2MB

        for (int i = 0; i < buffer.Length / 4; i++)
        {
            BitConverter.GetBytes(wipeValue).CopyTo(buffer, i * 4);
        }

        Command_SendBin(wipeAddress, buffer);

        // It won't return.
        return(true);
    }
Example #2
0
    public static void DebugInit(UInt32 localPort)
    {
        Console.WriteLine("Checking if Unirom is in debug mode...");

        // if it returns true, we might enter /m (monitor) mode, etc
        if (
            !TransferLogic.ChallengeResponse(CommandMode.DEBUG)
            )
        {
            Console.WriteLine("Couldn't determine if Unirom is in debug mode.");
            return;
        }

        Console.WriteLine("Grabbing initial state...");

        TransferLogic.Command_DumpRegs();

        Console.WriteLine("Monitoring sio...");

        Console.WriteLine("********************** WARNING ***************************");
        Console.WriteLine("THE TCP BRIDGE DOES NOT CURRENTLY ACCEPT COMMANDS FROM GDB");
        Console.WriteLine("********************** ******* ***************************");

        Init(localPort);
    }
    //
    // Memcard Functions
    //

    /// <summary>
    /// Writes an entire memcard's contents
    /// </summary>
    /// <param name="inCard">0/1</param>
    public static bool Command_MemcardUpload(UInt32 inCard, byte[] inFile)
    {
        if (!TransferLogic.ChallengeResponse(CommandMode.MCUP))
        {
            return(Error("No response from Unirom. Are you using 8.0.E or higher?"));
        }

        Console.WriteLine("Uploading card data...");

        // send the card number
        activeSerial.Write(BitConverter.GetBytes(inCard), 0, 4);
        // file size in bytes, let unirom handle it
        activeSerial.Write(BitConverter.GetBytes(inFile.Length), 0, 4);
        activeSerial.Write(BitConverter.GetBytes(CalculateChecksum(inFile)), 0, 4);

        if (TransferLogic.WriteBytes(inFile, false))
        {
            Console.WriteLine("File uploaded, check your screen...");
        }
        else
        {
            return(Error("Couldn't upload to unirom - no write attempt will be made", false));
        }

        return(true);
    }
    /// <summary>
    /// Reads and dumps a memcard to disc
    /// </summary>
    /// <param name="inCard">0/1</param>
    public static bool Command_MemcardDownload(UInt32 inCard, string fileName)
    {
        if (!TransferLogic.ChallengeResponse(CommandMode.MCDOWN))
        {
            return(Error("No response from Unirom. Are you using 8.0.E or higher?"));
        }

        // send the card number
        activeSerial.Write(BitConverter.GetBytes(inCard), 0, 4);

        Console.WriteLine("Reading card to ram...");

        // it'll send this when it's done dumping to ram
        if (!TransferLogic.WaitResponse("MCRD", false))
        {
            return(Error("Please see screen or SIO for error!"));
        }

        Console.WriteLine("Ready, reading....");

        UInt32 addr = TransferLogic.read32();

        Console.WriteLine("Data is 0x" + addr.ToString("x"));

        UInt32 size = TransferLogic.read32();

        Console.WriteLine("Size is 0x" + size.ToString("x"));


        Console.WriteLine("Dumping...");

        byte[] lastReadBytes = new byte[size];
        TransferLogic.ReadBytes(addr, size, lastReadBytes);


        if (System.IO.File.Exists(fileName))
        {
            string newFilename = fileName + GetSpan().TotalSeconds.ToString();

            Console.Write("\n\nWARNING: Filename " + fileName + " already exists! - Dumping to " + newFilename + " instead!\n\n");

            fileName = newFilename;
        }

        try
        {
            File.WriteAllBytes(fileName, lastReadBytes);
        }
        catch (Exception e)
        {
            return(Error("Couldn't write to the output file + " + fileName + " !\nThe error returned was: " + e, false));
        }

        Console.WriteLine("File written to: " + fileName);
        Console.WriteLine("It is raw .mcd format used by PCSX-redux, no$psx, etc");
        return(true);
    }
Example #5
0
        // final method that handles msg from the source, whether it's from wndproc or callbacks
        void HandleSourceMsg(Message msg)
        {
            PlatformInfo.Current.Log.Debug("Got TWAIN msg " + msg);
            switch (msg)
            {
            case Message.XferReady:
                if (State < 6)
                {
                    State = 6;
                }
                TransferLogic.DoTransferRoutine(this);
                break;

            case Message.DeviceEvent:
                TWDeviceEvent de;
                var           rc = ((ITwainSessionInternal)this).DGControl.DeviceEvent.Get(out de);
                if (rc == ReturnCode.Success)
                {
                    SafeSyncableRaiseOnEvent(OnDeviceEvent, DeviceEvent, new DeviceEventArgs(de));
                }
                break;

            case Message.CloseDSReq:
            case Message.CloseDSOK:
                DisableReason = msg;
                // even though it says closeDS it's really disable.
                // dsok is sent if source is enabled with uionly

                // some sources send this at other states so do a step down
                if (State > 5)
                {
                    // rather than do a close here let the transfer logic handle the close down now
                    //ForceStepDown(4);
                    _closeRequested = true;
                }
                else if (State == 5)
                {
                    // needs this state check since some source sends this more than once
                    ((ITwainSessionInternal)this).DisableSource();
                }
                break;
            }
        }
Example #6
0
    public static bool SetRegs()
    {
        // read the pointer to TCB[0]
        byte[] ptrBuffer = new byte[4];
        if (!TransferLogic.ReadBytes(0x80000110, 4, ptrBuffer))
        {
            return(false);
        }

        UInt32 tcbPtr = BitConverter.ToUInt32(ptrBuffer, 0);

        Console.WriteLine("TCB PTR " + tcbPtr.ToString("X"));

        // Convert regs back to a byte array and bang them back out
        byte[] tcbBytes = new byte[TCB_LENGTH_BYTES];
        Buffer.BlockCopy(tcb.regs, 0, tcbBytes, 0, TCB_LENGTH_BYTES);

        TransferLogic.Command_SendBin(tcbPtr, tcbBytes);

        return(true);
    }
Example #7
0
    public static bool GetRegs()
    {
        // read the pointer to TCB[0]
        byte[] ptrBuffer = new byte[4];
        if (!TransferLogic.ReadBytes(0x80000110, 4, ptrBuffer))
        {
            return(false);
        }

        UInt32 tcbPtr = BitConverter.ToUInt32(ptrBuffer, 0);

        Console.WriteLine("TCB PTR " + tcbPtr.ToString("X"));

        byte[] tcbBytes = new byte[TCB_LENGTH_BYTES];
        if (!TransferLogic.ReadBytes(tcbPtr, (int)GPR.COUNT * 4, tcbBytes))
        {
            return(false);
        }

        Buffer.BlockCopy(tcbBytes, 0, tcb.regs, 0, tcbBytes.Length);

        return(true);
    }
Example #8
0
    public static void Init()
    {
        Console.WriteLine("Checking if Unirom is in debug mode...");

        // if it returns true, we might enter /m (monitor) mode, etc
        if (
            !TransferLogic.ChallengeResponse(CommandMode.DEBUG)
            )
        {
            Console.WriteLine("Couldn't determine if Unirom is in debug mode.");
            return;
        }

        Console.WriteLine("Grabbing initial state...");

        TransferLogic.Command_DumpRegs();

        Console.WriteLine("Monitoring sio...");

        InitSocket();

        MonitorSIOAndSocket();
    }
    /// <summary>
    /// We've parsed everything from stdin, let's start processing it.
    /// </summary>
    /// <returns>success?</returns>
    private static bool DoStuff()
    {
        PrintUsage(true);

        //
        // Process the secondary/standalone commands
        // /fast, /slow, /m, etc
        //

        if (enterFastMode)
        {
            // start in normal mode and send a "FAST" command
            // unirom doesn't have to respond, so if it's already in fast mode, no biggie
            if (!NewSIO(SIOSPEED.SLOW))
            {
                return(false);
            }

            // The bytes "FAST" with no null terminator
            activeSerial.Write(BitConverter.GetBytes(0x54534146), 0, 4);
            Thread.Sleep(100);

            // Switch to fast
            if (!NewSIO(SIOSPEED.FAST))
            {
                return(false);
            }
        }
        else if (enterSlowMode)
        {
            // As above, but returns to 115200 so you don't have to 'nops /fast' all the time

            // going to assume Unirom's in fast mode.. so start there and negotiate back down
            if (!NewSIO(SIOSPEED.FAST))
            {
                return(false);
            }

            // The bytes "FAST" with no null terminator
            activeSerial.Write(BitConverter.GetBytes(0x574F4C53), 0, 4);
            Thread.Sleep(100);

            // Now switch back
            if (!NewSIO(SIOSPEED.SLOW))
            {
                return(false);
            }
        }
        else
        {
            if (!NewSIO(SIOSPEED.SLOW))
            {
                return(false);
            }
        }


        // If we got /m on its own
        if (monitorComms && argCommand == CommandMode.NOT_SET)
        {
            GDB.MonitorSerialToSocket();
            return(true);
        }

        // we just did 'nops /fast /m' or something
        if (allArgsAreSecondary)
        {
            return(true);
        }


        //
        // A little cleanup...
        //

        if (usingCachedComPort)
        {
            Console.WriteLine("Using port " + argComPort + " from comport.txt\n");
        }

        // Clear the SIO buffer incase the last program has been spamming

        Console.WriteLine("Emptying buffer... ");
        while (activeSerial.BytesToRead != 0)
        {
            Console.Write("" + (char)activeSerial.ReadByte());
        }
        Console.WriteLine("...done!\n\n");


        //
        // Process the primary commands
        //

        // Upload

        if (argCommand == CommandMode.SEND_EXE)
        {
            TransferLogic.Command_SendEXE(inFile);
        }

        if (argCommand == CommandMode.SEND_BIN)
        {
            TransferLogic.Command_SendBin(argAddr, inFile);
        }

        if (argCommand == CommandMode.SEND_ROM)
        {
            TransferLogic.Command_SendROM(argAddr, inFile);
        }

        // Program flow

        if (argCommand == CommandMode.JUMP_JMP)
        {
            TransferLogic.Command_JumpAddr(argAddr);
        }

        if (argCommand == CommandMode.JUMP_CALL)
        {
            TransferLogic.Command_CallAddr(argAddr);
        }

        // Pokeypoke

        if (argCommand == CommandMode.POKE8)
        {
            byte[] bytes = { (byte)(argValue & 0xFF) };
            TransferLogic.Command_SendBin(argAddr, bytes);
        }

        if (argCommand == CommandMode.POKE16)
        {
            short  shorty = (byte)(argValue & 0xFFFF);
            byte[] bytes  = BitConverter.GetBytes(shorty);
            TransferLogic.Command_SendBin(argAddr, bytes);
        }

        if (argCommand == CommandMode.POKE32)
        {
            byte[] bytes = BitConverter.GetBytes(argValue);
            TransferLogic.Command_SendBin(argAddr, bytes);
        }

        // Utility

        if (argCommand == CommandMode.PING)
        {
            TransferLogic.ChallengeResponse(argCommand);
        }

        if (argCommand == CommandMode.RESET)
        {
            TransferLogic.WriteChallenge(argCommand.challenge());
        }

        // Debug functions

        if (argCommand == CommandMode.DEBUG)
        {
            // if it returns true, we might enter /m (monitor) mode, etc
            if (
                !TransferLogic.ChallengeResponse(argCommand)
                )
            {
                return(false);
            }
        }

        if (argCommand == CommandMode.HALT)
        {
            TransferLogic.ChallengeResponse(argCommand);
        }

        if (argCommand == CommandMode.CONT)
        {
            TransferLogic.ChallengeResponse(argCommand);
        }

        if (argCommand == CommandMode.REGS)
        {
            TransferLogic.Command_DumpRegs();
        }

        if (argCommand == CommandMode.SETREG)
        {
            if (!TransferLogic.Command_SetReg(argRegister, argValue))
            {
                return(Error("Couldn't set reg " + argRegister + " to " + argValue, false));
            }
        }


        // Hook functions

        if (
            argCommand == CommandMode.HOOKREAD ||
            argCommand == CommandMode.HOOKWRITE ||
            argCommand == CommandMode.HOOKEXEC
            )
        {
            if (TransferLogic.ChallengeResponse(argCommand))
            {
                activeSerial.Write(BitConverter.GetBytes(argAddr), 0, 4);
            }
        }

        // Memory card

        if (argCommand == CommandMode.MCUP)
        {
            if (!TransferLogic.Command_MemcardUpload(argCard, inFile))
            {
                return(false);
            }
        }

        if (argCommand == CommandMode.WIPEMEM)
        {
            if (!TransferLogic.Command_WipeMem(argAddr, argValue))
            {
                return(false);
            }
        }

        if (argCommand == CommandMode.MCDOWN)
        {
            if (!TransferLogic.Command_MemcardDownload(argCard, argFileName))
            {
                return(false);
            }
        }

        // Peek / Read functions

        if (argCommand == CommandMode.DUMP)
        {
            if (!TransferLogic.Command_Dump(argAddr, argSize))
            {
                return(false);
            }
        }

        if (argCommand == CommandMode.WATCH)
        {
            TransferLogic.Watch(argAddr, argSize);
            return(true);
        }


        //
        // Major work in progress
        //

        if (argCommand == CommandMode.GDB)
        {
            GDB.DebugInit(argPort);
        }

        if (argCommand == CommandMode.BRIDGE)
        {
            GDB.Init(argPort);
        }

        //
        // All done, are we leaving the comms monitor open?
        //

        if (monitorComms)
        {
            GDB.MonitorSerialToSocket();
        }
        else
        {
            Console.WriteLine("\n Bye!");
            activeSerial.Close();
        }

        return(true);
    }     // void Transfer
Example #10
0
    private static bool Transfer()
    {
        PrintUsage(true);


        activeSerial = new SerialPort(argComPort, 115200, Parity.None, 8, StopBits.Two);
        // Required for e.g. SharkLink & Yaroze cable compat. Doesn't interfere with the 3-wire setups
        activeSerial.Handshake = Handshake.None;
        activeSerial.DtrEnable = true;
        activeSerial.RtsEnable = true;

        if (fastMode)
        {
            activeSerial.ReadTimeout  = TIMEOUT;
            activeSerial.WriteTimeout = TIMEOUT;

            try {
                activeSerial.Open();
            } catch (Exception exception) {
                Console.WriteLine("Error opening temporary serial port on " + argComPort + "!");
                Console.WriteLine(exception.Message);

                return(false);
            }

            // The bytes "FAST" with no null terminator
            activeSerial.Write(BitConverter.GetBytes(0x54534146), 0, 4);


            Thread.Sleep(100);


            activeSerial.Close();

            // We need to find a suitable overlap in frequencies which can be divided
            // from the Playstation's clock and from the FTDI/clone, with some wiggle room.
            //
            // PSX/libs uses whole integer divisions of 2073600 for anything over 115200
            // giving us 518400 close to the half-megabyte mark.
            //
            // Most FTDI/clones seem to operate in 2xinteger divisions of 48mhz
            // giving us 510000 or 521000 close to the half-megabyte mark. e.g. (48m/47/2) or (48m/46/2)
            //
            // 5210000 (and even 518400) is a little fast, but the lower end
            // of things (510000 to 518300) seems pretty stable.
            //
            // note: psx @ 518400, pc @ 510000
            //
            activeSerial = new SerialPort(argComPort, 510000, Parity.None, 8, StopBits.Two);
            // Required for e.g. SharkLink & Yaroze cable compat. Doesn't interfere with the 3-wire setups
            activeSerial.Handshake = Handshake.None;
            activeSerial.DtrEnable = true;
            activeSerial.RtsEnable = true;
        }

        // Now the main serial port

        activeSerial.ReadTimeout  = TIMEOUT;
        activeSerial.WriteTimeout = TIMEOUT;

        try {
            activeSerial.Open();
        } catch (Exception exception) {
            Console.WriteLine("Error opening the serial port on " + argComPort + "!");
            Console.WriteLine(exception.Message);

            return(false);
        }



        // just lets us skip a ton of ifs
        if (monitorComms && argCommand == CommandMode.NOT_SET)
        {
            TransferLogic.DoMonitor();
            return(true);
        }


        if (usingCachedComPort)
        {
            Console.WriteLine("Using port " + argComPort + " from comport.txt\n");
        }

        // Clear the SIO buffer incase the last program has been spamming

        Console.WriteLine("Emptying buffer... ");
        while (activeSerial.BytesToRead != 0)
        {
            Console.Write("" + (char)activeSerial.ReadByte());
        }
        Console.WriteLine("...done!\n\n");


        if (argCommand == CommandMode.SEND_EXE)
        {
            TransferLogic.Command_SendEXE(argAddr, inFile, CalculateChecksum(inFile, true));
        }

        if (argCommand == CommandMode.SEND_BIN)
        {
            TransferLogic.Command_SendBin(argAddr, inFile, CalculateChecksum(inFile));
        }

        // Unirom 8 mode - requires a response after checking that
        // things will fit on the cart.
        if (argCommand == CommandMode.SEND_ROM)
        {
            TransferLogic.Command_SendROM(argAddr, inFile, CalculateChecksum(inFile));
        }


        if (argCommand == CommandMode.RESET)
        {
            TransferLogic.WriteChallenge(argCommand.challenge());
        }

        // Unirom 8.0.4 and up, enables kernel-resident SIO
        if (argCommand == CommandMode.DEBUG)
        {
            // if it returns true, we might enter /m (monitor) mode, etc
            if (
                !TransferLogic.ChallengeResponse(argCommand)
                )
            {
                return(false);
            }
        }


        if (argCommand == CommandMode.DUMP)
        {
            lastReadBytes = new byte[argSize];

            TransferLogic.Command_DumpBytes(argAddr, argSize, lastReadBytes);

            string fileName = "DUMP_" + argAddr.ToString("X8") + "_to_" + argSize.ToString("X8") + ".bin";

            if (System.IO.File.Exists(fileName))
            {
                string newFilename = fileName + GetSpan().TotalSeconds.ToString();

                Console.Write("\n\nWARNING: Filename " + fileName + " already exists! - Dumping to " + newFilename + " instead!\n\n");

                fileName = newFilename;
            }

            try{
                File.WriteAllBytes(fileName, lastReadBytes);
            } catch (Exception e) {
                Error("Couldn't write to the output file + " + fileName + " !\nThe error returned was: " + e, false);
                return(false);
            }
        }         // DUMP

        if (argCommand == CommandMode.PING)
        {
            TransferLogic.ChallengeResponse(argCommand);
        }

        if (argCommand == CommandMode.GDB)
        {
            GDB.Init();
        }

        if (argCommand == CommandMode.JUMP_JMP)
        {
            TransferLogic.Command_JumpAddr(argAddr);
        }

        if (argCommand == CommandMode.JUMP_CALL)
        {
            TransferLogic.Command_CallAddr(argAddr);
        }

        if (argCommand == CommandMode.HALT)
        {
            TransferLogic.ChallengeResponse(argCommand);
        }

        if (argCommand == CommandMode.CONT)
        {
            TransferLogic.ChallengeResponse(argCommand);
        }

        if (argCommand == CommandMode.REGS)
        {
            TransferLogic.Command_DumpRegs();
        }

        if (argCommand == CommandMode.SETREG)
        {
            TransferLogic.Command_SetReg(argRegister, argAddr);
        }

        if (argCommand == CommandMode.WATCH)
        {
            TransferLogic.Watch(argAddr, argSize);
            return(true);
        }         // WATCH

        if (monitorComms)
        {
            TransferLogic.DoMonitor();
        }
        else
        {
            Console.WriteLine("\n This is where we part ways!");
            activeSerial.Close();
        }

        return(true);
    }     // void Transfer
Example #11
0
    /// <summary>
    /// The monitor recieved 0x00, 'p' ...
    /// Read the command ID bytes following that and process them.
    /// </summary>
    public static bool ReadCommand()
    {
        Console.WriteLine("Got PCDRV ...");

        // Wait till we get the PCDrv function code
        while (serial.BytesToRead == 0)
        {
        }
        PCDrvCodes funcCode = (PCDrvCodes)TransferLogic.read32();

        Console.WriteLine("Got function code: " + funcCode);

        // TODO: split these off into discrete functions?

        // PCInit
        if (funcCode == PCDrvCodes.PCINIT_101)
        {
            serial.Write("OKAY");
            serial.Write(new byte[] { 0 }, 0, 1);
            return(true);
        }

        // PCCreat
        if (funcCode == PCDrvCodes.PCCREAT_102)
        {
            // tell unirom to start with the filename, etc
            serial.Write("OKAY");

            string fileName = GetFilename();
            if (fileName == "")
            {
                return(false);
            }
            UInt32 parameters = TransferLogic.read32();

            bool isDir = ((parameters & 16) != 0);

            Console.WriteLine($"PCCreat( {fileName}, {parameters} )");

            PCFile pcFile = GetOpenFile(fileName);

            if (pcFile != null)
            {
                // We're already tracking this file, just return it's handle
                Console.WriteLine("File already open, handle=" + pcFile.handle);
                serial.Write("OKAY");
                serial.Write(BitConverter.GetBytes(pcFile.handle), 0, 4);
                return(true);
            }

            FileStream fStream;
            try {
                if (isDir)
                {
                    throw new Exception("Directories are not supported!");
                }
                else
                {
                    if (!File.Exists(fileName))
                    {
                        FileStream tempStream = File.Create(fileName);
                        tempStream.Flush();
                        tempStream.Close();
                        tempStream.Dispose();
                    }
                    else
                    {
                        Console.WriteLine($"File {fileName} already exists, using that...");
                    }
                    fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                }

                // open it read/write until otherwise specified via PCOpen
                UInt32 handle = NextHandle();
                TrackFile(fileName, handle, fStream, PCFileMode.READWRITE);

                serial.Write("OKAY");
                serial.Write(BitConverter.GetBytes(handle), 0, 4);
                return(true);
            } catch (Exception e) {
                Console.WriteLine($"Error creating file '{fileName}', ex={e}");
                serial.Write("NOPE");
                return(false);
            }
        }         // PCCREAT_102

        // PCOpen
        if (funcCode == PCDrvCodes.PCOPEN_103)
        {
            serial.Write("OKAY");

            string fileName = GetFilename();
            if (fileName == "")
            {
                return(false);
            }

            PCFileMode fileModeParams = (PCFileMode)TransferLogic.read32();

            Console.WriteLine($"PCOpen( {fileName}, {fileModeParams} )");

            PCFile f = GetOpenFile(fileName);

            if (f != null)
            {
                // just return the handle for this file...

                if (f.fileMode != fileModeParams)
                {
                    Console.WriteLine($"File {f.handle} already open, switching params to {fileModeParams}");
                    f.fileMode = fileModeParams;
                }
                else
                {
                    Console.WriteLine("File already open, handle=" + f.handle);
                }

                serial.Write("OKAY");
                serial.Write(BitConverter.GetBytes(f.handle), 0, 4);
                return(true);
            }

            if (!File.Exists(fileName))
            {
                Console.WriteLine("File doesn't exist!");
                goto nope;
            }

            FileStream fs = null;
            try{
                fs = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            } catch (Exception e) {
                Console.WriteLine($"Error opening file '{fileName}', ex={e}");
                goto nope;
            }

            UInt32 handle = NextHandle();
            TrackFile(fileName, handle, fs, fileModeParams);
            Console.WriteLine("Returning file, handle=" + handle);

            serial.Write("OKAY");
            serial.Write(BitConverter.GetBytes(handle), 0, 4);
            return(true);


            nope :;
            Console.WriteLine("Failed..");
            serial.Write("NOPE");
            return(false);
        }         // PCOPEN_103

        // PCClose
        if (funcCode == PCDrvCodes.PCCLOSE_104)
        {
            serial.Write("OKAY");

            // unirom sends extra params to save kernel
            // space by grouping similar commands together.
            UInt32 handle  = TransferLogic.read32();
            UInt32 unused1 = TransferLogic.read32();
            UInt32 unused2 = TransferLogic.read32();

            Console.WriteLine($"PCClose( {handle} ) unusedParams: {unused1},{unused2}");

            PCFile f = GetOpenFile(handle);

            try{
                if (f == null)
                {
                    Console.WriteLine("No such file... great success!");
                    serial.Write("OKAY");                         // v0
                    serial.Write(BitConverter.GetBytes(0), 0, 4); // v1
                    return(true);
                }
                else
                {
                    Console.WriteLine($"Closing file {f.fileName} with handle {f.handle}...");
                    serial.Write("OKAY");                                // v0
                    serial.Write(BitConverter.GetBytes(f.handle), 0, 4); // v1																			   // let the garbage collector deal with it
                    ClosePCFile(f.handle);
                    return(true);
                }
            } catch (Exception e) {
                Console.WriteLine("Error closing file..." + e);
                serial.Write("NOPE");                   // v0
                // don't need to send v1
                return(false);
            }
        }         // PCCLOSE_104



        // PCRead
        if (funcCode == PCDrvCodes.PCREAD_105)
        {
            serial.Write("OKAY");

            // unirom sends extra params to save kernel
            // space by grouping similar commands together,
            // so 'memaddr' is for debugging only.

            // PCRead() takes (handle, buff*, len )
            // but interally passes it to _SN_Read as
            // ( 0, handle, len, buff* ), essentially
            // shuffling regs A0,A1,A2 into A1,A3,A2.
            // or just ( handle, len, buff* ).  lol.

            UInt32 handle   = TransferLogic.read32();
            int    inLength = (int)TransferLogic.read32();
            UInt32 memaddr  = TransferLogic.read32();                   // not used, debugging only

            //Console.WriteLine( $"PCRead( {handle}, len={inLength}, dbg=0x{memaddr.ToString("X")} )" );

            PCFile pcFile = GetOpenFile(handle);

            Console.WriteLine($"PCRead( {handle}, len=0x{inLength.ToString("X")} ); MemAddr=0x{memaddr.ToString("X")}, File={pcFile}");

            if (pcFile == null)
            {
                Console.WriteLine($"No file with handle 0x{handle.ToString("X")}, returning!");
                serial.Write("NOPE");                   // v0
                // don't need to send v1
                return(false);
            }
            else
            {
                Console.WriteLine("Reading file " + pcFile.fileName);
            }

            long streamLength = 0;

            try{
                FileStream fs = pcFile.fileStream;

                streamLength = fs.Length;

                byte[] bytes     = new byte[inLength];
                int    bytesRead = fs.Read(bytes, 0, inLength);


                if (bytesRead <= 0)
                {
                    //throw new Exception( "Read 0 bytes from the file.." );
                    Console.WriteLine("Warning - no bytes were read from the file - returning zeros...");
                }

                // if we returned fewer bytes than requested, no biggie, the byte array is already set

                serial.Write("OKAY");                                    // v0
                serial.Write(BitConverter.GetBytes(bytes.Length), 0, 4); // v1

                // Then
                UInt32 check = TransferLogic.CalculateChecksum(bytes, false);
                serial.Write(BitConverter.GetBytes(check), 0, 4);
                TransferLogic.WriteBytes(bytes, false, true);

                // again the weird order reflects a desire to save space within the psx kernel
                // and reuse some functions
            } catch (Exception e) {
                Console.WriteLine($"Error reading file {pcFile.fileName} at pos={pcFile.fileStream.Position}, streamLength={streamLength} e={e}");
                serial.Write("NOPE");
                return(false);
            }
        }         // PCREAD_105

        // PCWrite
        if (funcCode == PCDrvCodes.PCWRITE_106)
        {
            serial.Write("OKAY");

            // PCWrite() takes (handle, buff*, len )
            // but interally passes it to _SN_Write as
            // ( 0, handle, len, buff* ), essentially
            // shuffling regs A0,A1,A2 into A1,A3,A2.
            // or just ( handle, len, buff* ).  lol.

            UInt32 handle   = TransferLogic.read32();
            int    inLength = (int)TransferLogic.read32();
            UInt32 memaddr  = TransferLogic.read32();                   // not used, debugging only

            PCFile pcFile = GetOpenFile(handle);

            if (pcFile == null)
            {
                Console.WriteLine($"No file with handle 0x{handle.ToString("X")}, returning!");
                serial.Write("NOPE");                   // v0
                // don't need to send v1
                return(false);
            }

            Console.WriteLine($"PCWrite( {handle}, len={inLength} ); fileName={pcFile.fileName} SourceAddr={memaddr.ToString( "X" )}, File={pcFile}");

            if (pcFile.fileMode == PCFileMode.READONLY)
            {
                Console.WriteLine("Error: File is readonly!");
                serial.Write("NOPE");
                return(false);
            }
            serial.Write("OKAY");

            try {
                FileStream fs = pcFile.fileStream;

                byte[] bytes   = new byte[inLength];
                bool   didRead = TransferLogic.ReadBytes_Raw((UInt32)inLength, bytes);

                if (!didRead)
                {
                    throw new Exception("there was an error reading the stream from the psx!");
                }

                Console.WriteLine($"Read {inLength} bytes, flushing to {pcFile.fileName}...");

                fs.Write(bytes, 0, inLength);
                fs.Flush(true);

                serial.Write("OKAY");                                    // v0
                serial.Write(BitConverter.GetBytes(bytes.Length), 0, 4); // v1

                // again the weird order reflects a desire to save space within the psx kernel
                // and reuse some functions
            } catch (Exception e) {
                Console.WriteLine($"Error writing file {pcFile.fileName}, streamLength={inLength} e={e}");
                serial.Write("NOPE");
                return(false);
            }

            return(true);
        }         //PCWRITE 106

        if (funcCode == PCDrvCodes.PCSEEK_107)
        {
            serial.Write("OKAY");

            UInt32     handle     = TransferLogic.read32();
            int        seekPos    = (int)TransferLogic.read32();
            SeekOrigin seekOrigin = (SeekOrigin)TransferLogic.read32();

            PCFile pcFile = GetOpenFile(handle);

            string fileName = pcFile != null ? pcFile.fileName : "";
            Console.WriteLine($"PCSeek file {handle} to {seekPos}, type={seekOrigin}, fileName={fileName}");

            if (pcFile == null)
            {
                Console.WriteLine("Error: There is no file with handle 0x" + handle.ToString("X"));
                serial.Write("NOPE");
                return(false);
            }

            // Let's actually open the file and seek it to see if we bump into any issues
            try {
                FileStream fs = pcFile.fileStream;
                //fs.Seek( pcFile.filePointer, pcFile.seekMode );
                fs.Seek(seekPos, seekOrigin);

                Console.WriteLine("Seeked position " + fs.Position);

                serial.Write("OKAY");
                serial.Write(BitConverter.GetBytes(fs.Position), 0, 4);
            } catch (System.Exception e) {
                Console.WriteLine($"Exception when seeking file {handle}, e={e}");
                serial.Write("NOPE");
                return(false);
            }
        }         //PCSEEK_107


        return(true);
    }