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

407 lines
8.7 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.
private readonly TI83 Parent;
private readonly Queue<byte> CurrentData = new Queue<byte>();
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();
}
}
}