BizHawk/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambattePrinter.cs

315 lines
7.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{
/// <summary>
/// Emulate the gameboy printer in managed code
/// </summary>
public class GambattePrinter
{
// A loose c->c# port of SameBoy's printer code
enum CommandState : byte
{
GB_PRINTER_COMMAND_MAGIC1,
GB_PRINTER_COMMAND_MAGIC2,
GB_PRINTER_COMMAND_ID,
GB_PRINTER_COMMAND_COMPRESSION,
GB_PRINTER_COMMAND_LENGTH_LOW,
GB_PRINTER_COMMAND_LENGTH_HIGH,
GB_PRINTER_COMMAND_DATA,
GB_PRINTER_COMMAND_CHECKSUM_LOW,
GB_PRINTER_COMMAND_CHECKSUM_HIGH,
GB_PRINTER_COMMAND_ACTIVE,
GB_PRINTER_COMMAND_STATUS,
}
enum CommandID : byte
{
GB_PRINTER_INIT_COMMAND = 1,
GB_PRINTER_START_COMMAND = 2,
GB_PRINTER_DATA_COMMAND = 4,
GB_PRINTER_NOP_COMMAND = 0xF,
}
const int GB_PRINTER_MAX_COMMAND_LENGTH = 0x280;
const int GB_PRINTER_DATA_SIZE = 0x280;
const ushort SerialIRQAddress = 0x58;
Gameboy gb;
PrinterCallback callback;
LibGambatte.LinkCallback linkCallback;
CommandState command_state;
CommandID command_id;
bool compression;
ushort length_left;
byte[] command_data = new byte[GB_PRINTER_MAX_COMMAND_LENGTH];
ushort command_length;
ushort checksum;
byte status;
byte[] image = new byte[160 * 200];
ushort image_offset;
byte compression_run_lenth;
bool compression_run_is_compressed;
public GambattePrinter(Gameboy gb, PrinterCallback callback)
{
this.gb = gb;
this.callback = callback;
linkCallback = OnSerial;
LibGambatte.gambatte_setlinkcallback(gb.GambatteState, linkCallback);
// connect the cable
LibGambatte.gambatte_linkstatus(gb.GambatteState, 259);
}
public void Disconnect()
{
if (gb.GambatteState != IntPtr.Zero)
LibGambatte.gambatte_setlinkcallback(gb.GambatteState, null);
}
void OnSerial()
{
if (LibGambatte.gambatte_linkstatus(gb.GambatteState, 256) != 0) // ClockTrigger
{
LibGambatte.gambatte_linkstatus(gb.GambatteState, 257); // ack
byte output = HandleSerial((byte)LibGambatte.gambatte_linkstatus(gb.GambatteState, 258)); // GetOut
LibGambatte.gambatte_linkstatus(gb.GambatteState, output); // ShiftIn
}
}
byte HandleSerial(byte byte_received)
{
byte byte_to_send = 0;
switch (command_state)
{
case CommandState.GB_PRINTER_COMMAND_MAGIC1:
if (byte_received != 0x88)
{
return byte_to_send;
}
status &= 254;
command_length = 0;
checksum = 0;
break;
case CommandState.GB_PRINTER_COMMAND_MAGIC2:
if (byte_received != 0x33)
{
if (byte_received != 0x88)
{
command_state = CommandState.GB_PRINTER_COMMAND_MAGIC1;
}
return byte_to_send;
}
break;
case CommandState.GB_PRINTER_COMMAND_ID:
command_id = (CommandID)(byte_received & 0xF);
break;
case CommandState.GB_PRINTER_COMMAND_COMPRESSION:
compression = (byte_received & 1) != 0;
break;
case CommandState.GB_PRINTER_COMMAND_LENGTH_LOW:
length_left = byte_received;
break;
case CommandState.GB_PRINTER_COMMAND_LENGTH_HIGH:
length_left |= (ushort)((byte_received & 3) << 8);
break;
case CommandState.GB_PRINTER_COMMAND_DATA:
if (command_length != GB_PRINTER_MAX_COMMAND_LENGTH)
{
if (compression)
{
if (compression_run_lenth == 0)
{
compression_run_is_compressed = (byte_received & 0x80) != 0;
compression_run_lenth = (byte)((byte_received & 0x7F) + 1 + (compression_run_is_compressed ? 1 : 0));
}
else if (compression_run_is_compressed)
{
while (compression_run_lenth > 0)
{
command_data[command_length++] = byte_received;
compression_run_lenth--;
if (command_length == GB_PRINTER_MAX_COMMAND_LENGTH)
{
compression_run_lenth = 0;
}
}
}
else
{
command_data[command_length++] = byte_received;
compression_run_lenth--;
}
}
else
{
command_data[command_length++] = byte_received;
}
}
length_left--;
break;
case CommandState.GB_PRINTER_COMMAND_CHECKSUM_LOW:
checksum ^= byte_received;
break;
case CommandState.GB_PRINTER_COMMAND_CHECKSUM_HIGH:
checksum ^= (ushort)(byte_received << 8);
if (checksum != 0)
{
status |= 1; /* Checksum error*/
command_state = CommandState.GB_PRINTER_COMMAND_MAGIC1;
return byte_to_send;
}
break;
case CommandState.GB_PRINTER_COMMAND_ACTIVE:
byte_to_send = 0x81;
break;
case CommandState.GB_PRINTER_COMMAND_STATUS:
if (((int)command_id & 0xF) == (byte)CommandID.GB_PRINTER_INIT_COMMAND)
{
/* Games expect INIT commands to return 0? */
byte_to_send = 0;
}
else
{
byte_to_send = status;
}
/* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */
if (status == 6)
{
status = 4; /* Done */
}
command_state = CommandState.GB_PRINTER_COMMAND_MAGIC1;
HandleCommand();
return byte_to_send;
}
if (command_state >= CommandState.GB_PRINTER_COMMAND_ID && command_state < CommandState.GB_PRINTER_COMMAND_CHECKSUM_LOW)
{
checksum += byte_received;
}
if (command_state != CommandState.GB_PRINTER_COMMAND_DATA)
{
command_state++;
}
if (command_state == CommandState.GB_PRINTER_COMMAND_DATA)
{
if (length_left == 0)
{
command_state++;
}
}
return byte_to_send;
}
void HandleCommand()
{
switch (command_id)
{
case CommandID.GB_PRINTER_INIT_COMMAND:
status = 0;
image_offset = 0;
break;
case CommandID.GB_PRINTER_START_COMMAND:
if (command_length == 4)
{
status = 6; /* Printing */
uint[] outputImage = new uint[image_offset];
int palette = command_data[2];
uint[] colors = new uint[] {
0xFFFFFFFFU,
0xFFAAAAAAU,
0xFF555555U,
0xFF000000U
};
for (int i = 0; i < image_offset; i++)
{
outputImage[i] = colors[(palette >> (image[i] * 2)) & 3];
}
if (callback != null)
{
// The native-friendly callback almost seems silly now :P
unsafe
{
fixed (uint* imagePtr = outputImage)
{
callback((IntPtr)imagePtr, (byte)(image_offset / 160),
(byte)(command_data[1] >> 4), (byte)(command_data[1] & 7),
(byte)(command_data[3] & 0x7F));
}
}
}
image_offset = 0;
}
break;
case CommandID.GB_PRINTER_DATA_COMMAND:
if (command_length == GB_PRINTER_DATA_SIZE)
{
image_offset %= (ushort)image.Length;
status = 8; /* Received 0x280 bytes */
int data_index = 0;
for (int row = 2; row > 0; row--)
{
for (int tile_x = 0; tile_x < 160 / 8; tile_x++)
{
for (int y = 0; y < 8; y++, data_index += 2)
{
for (int x_pixel = 0; x_pixel < 8; x_pixel++)
{
image[image_offset + tile_x * 8 + x_pixel + y * 160] =
(byte)((command_data[data_index] >> 7) | ((command_data[data_index + 1] >> 7) << 1));
command_data[data_index] <<= 1;
command_data[data_index + 1] <<= 1;
}
}
}
image_offset += 8 * 160;
}
}
break;
default:
break;
}
}
}
}