BizHawk/BizHawk.Emulation.Cores/Calculator/TI83LinkPort.cs

377 lines
8.5 KiB
C#

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<byte> CurrentData = new Queue<byte>();
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();
}
}
}