using System; using System.Collections.Generic; using System.IO; namespace BizHawk.Emulation.Cores.Calculators { public class TI83LinkPort { // Emulates TI linking software. // See http://www.ticalc.org/archives/files/fileinfo/294/29418.html for documentation // Note: Each hardware read/write to the link port calls tthe update method. private readonly TI83 Parent; private readonly Queue CurrentData = new Queue(); private FileStream _currentFile; private byte[] _variableData; private Action _nextStep; private ushort _bytesToSend; private byte _bitsLeft; private byte _currentByte; private byte _stepsLeft; private Status _currentStatus = Status.Inactive; private enum Status { Inactive, PrepareReceive, PrepareSend, Receive, Send } public TI83LinkPort(TI83 parent) { Parent = parent; } public void Update() { if (_currentStatus == Status.PrepareReceive) { // Get the first byte, and start sending it. _currentByte = CurrentData.Dequeue(); _currentStatus = Status.Receive; _bitsLeft = 8; _stepsLeft = 5; } if (_currentStatus == Status.PrepareSend && Parent.LinkState != 3) { _currentStatus = Status.Send; _bitsLeft = 8; _stepsLeft = 5; _currentByte = 0; } if (_currentStatus == Status.Receive) { switch (_stepsLeft) { case 5: // Receive step 1: Lower the other device's line. Parent.LinkInput = ((_currentByte & 1) == 1) ? 2 : 1; _currentByte >>= 1; _stepsLeft--; break; case 4: // Receive step 2: Wait for the calc to lower the other line. if ((Parent.LinkState & 3) == 0) { _stepsLeft--; } break; case 3: // Receive step 3: Raise the other device's line back up. Parent.LinkInput = 0; _stepsLeft--; break; case 2: // Receive step 4: Wait for the calc to raise its line back up. if ((Parent.LinkState & 3) == 3) { _stepsLeft--; } break; case 1: // Receive step 5: Finish. _bitsLeft--; if (_bitsLeft == 0) { if (CurrentData.Count > 0) { _currentStatus = Status.PrepareReceive; } else { _currentStatus = Status.Inactive; _nextStep?.Invoke(); } } else { // Next bit in the current byte. _stepsLeft = 5; } break; } } else if (_currentStatus == Status.Send) { switch (_stepsLeft) { case 5: // Send step 1: Calc lowers a line. if (Parent.LinkState != 3) { int bit = Parent.LinkState & 1; int shift = 8 - _bitsLeft; _currentByte |= (byte)(bit << shift); _stepsLeft--; } break; case 4: // Send step 2: Lower our line. Parent.LinkInput = Parent.LinkOutput ^ 3; _stepsLeft--; break; case 3: // Send step 3: wait for the calc to raise its line. if ((Parent.LinkOutput & 3) == 0) { _stepsLeft--; } break; case 2: // Send step 4: raise the other devices lines. Parent.LinkInput = 0; _stepsLeft--; break; case 1: // Send step 5: Finish _bitsLeft--; if (_bitsLeft == 0) { _bytesToSend--; CurrentData.Enqueue(_currentByte); if (_bytesToSend > 0) { _currentStatus = Status.PrepareSend; } else { _currentStatus = Status.Inactive; _nextStep?.Invoke(); } } else { // Next bit in the current byte. _stepsLeft = 5; } break; } } } public void SendFileToCalc(FileStream fs, bool verify) { if (verify) { VerifyFile(fs); } fs.Seek(55, SeekOrigin.Begin); _currentFile = fs; SendNextFile(); } private void VerifyFile(FileStream fs) { // Verify the file format. byte[] expected = { 0x2a, 0x2a, 0x54, 0x49, 0x38, 0x33, 0x2a, 0x2a, 0x1a, 0x0a, 0x00 }; byte[] actual = new byte[11]; fs.Seek(0, SeekOrigin.Begin); fs.Read(actual, 0, 11); // Check the header. for (int n = 0; n < 11; n++) { if (expected[n] != actual[n]) { fs.Close(); throw new IOException("Invalid Header."); } } // Seek to the end of the comment. fs.Seek(53, SeekOrigin.Begin); int size = fs.ReadByte() + (fs.ReadByte() * 256); if (fs.Length != size + 57) { fs.Close(); throw new IOException("Invalid file length."); } // Verify the checksum. ushort checksum = 0; for (int n = 0; n < size; n++) { checksum += (ushort)fs.ReadByte(); } ushort actualChecksum = (ushort)(fs.ReadByte() + (fs.ReadByte() * 256)); if (checksum != actualChecksum) { fs.Close(); throw new IOException("Invalid Checksum."); } } private void SendNextFile() { byte[] header = new byte[13]; if (!_currentFile.CanRead || _currentFile.Read(header, 0, 13) != 13) { // End of file. _currentFile.Close(); return; } int size = header[2] + (header[3] * 256); _variableData = new byte[size + 2]; _currentFile.Read(_variableData, 0, size + 2); // Request to send the file. CurrentData.Clear(); CurrentData.Enqueue(0x03); CurrentData.Enqueue(0xC9); foreach (byte b in header) { CurrentData.Enqueue(b); } // Calculate the checksum for the command. ushort checksum = 0; for (int n = 2; n < header.Length; n++) { checksum += header[n]; } CurrentData.Enqueue((byte)(checksum % 256)); CurrentData.Enqueue((byte)(checksum / 256)); // Finalize the command. _currentStatus = Status.PrepareReceive; _nextStep = ReceiveReqAck; Parent.LinkActive = true; } private void ReceiveReqAck() { Parent.LinkActive = false; CurrentData.Clear(); // Prepare to receive the Aknowledgement response from the calculator. _bytesToSend = 8; _currentStatus = Status.PrepareSend; _nextStep = SendVariableData; } private void SendVariableData() { // Check to see if out of memory first. CurrentData.Dequeue(); CurrentData.Dequeue(); CurrentData.Dequeue(); CurrentData.Dequeue(); CurrentData.Dequeue(); if (CurrentData.Dequeue() == 0x36) { OutOfMemory(); } else { CurrentData.Clear(); CurrentData.Enqueue(0x03); CurrentData.Enqueue(0x56); CurrentData.Enqueue(0x00); CurrentData.Enqueue(0x00); CurrentData.Enqueue(0x03); CurrentData.Enqueue(0x15); // Add variable data. foreach (byte b in _variableData) { CurrentData.Enqueue(b); } // Calculate the checksum. ushort checksum = 0; for (int n = 2; n < _variableData.Length; n++) { checksum += _variableData[n]; } CurrentData.Enqueue((byte)(checksum % 256)); CurrentData.Enqueue((byte)(checksum / 256)); _currentStatus = Status.PrepareReceive; _nextStep = ReceiveDataAck; Parent.LinkActive = true; } } private void ReceiveDataAck() { Parent.LinkActive = false; CurrentData.Clear(); // Prepare to receive the Aknowledgement response from the calculator. _bytesToSend = 4; _currentStatus = Status.PrepareSend; _nextStep = EndTransmission; } private void EndTransmission() { CurrentData.Clear(); // Send the end transmission command. CurrentData.Enqueue(0x03); CurrentData.Enqueue(0x92); CurrentData.Enqueue(0x00); CurrentData.Enqueue(0x00); _currentStatus = Status.PrepareReceive; _nextStep = FinalizeFile; Parent.LinkActive = true; } private void OutOfMemory() { _currentFile.Close(); Parent.LinkActive = false; CurrentData.Clear(); // Prepare to receive the Aknowledgement response from the calculator. _bytesToSend = 3; _currentStatus = Status.PrepareSend; _nextStep = EndOutOfMemory; } private void EndOutOfMemory() { CurrentData.Clear(); // Send the end transmission command. CurrentData.Enqueue(0x03); CurrentData.Enqueue(0x56); CurrentData.Enqueue(0x01); CurrentData.Enqueue(0x00); _currentStatus = Status.PrepareReceive; _nextStep = FinalizeFile; Parent.LinkActive = true; } private void FinalizeFile() { // Resets the link software, and checks to see if there is an additional file to send. CurrentData.Clear(); Parent.LinkActive = false; _nextStep = null; SendNextFile(); } } }