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. readonly TI83 Parent; private FileStream CurrentFile; //private int FileBytesLeft; private byte[] VariableData; private Action NextStep; private Queue CurrentData = new Queue(); 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) { this.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; if (NextStep != null) NextStep(); } } 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; if (NextStep != null) NextStep(); } } 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 = new byte[] { 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(); } } }