git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

Core/printer.c

      1 #include "gb.h"
      2 #include <string.h>
      3 
      4 /* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface.
      5          Incorrect usage is not correctly emulated, as it's not well documented, nor do I
      6          have my own GB Printer to figure it out myself.
      7  
      8          It also does not currently emulate communication timeout, which means that a bug
      9          might prevent the printer operation until the Game Boy is restarted.
     10  
     11          Also, field mask values are assumed. */
     12 
     13 static void handle_command(GB_gameboy_t *gb)
     14 {
     15     switch (gb->printer.command_id) {
     16         case GB_PRINTER_INIT_COMMAND:
     17             gb->printer.status = 0;
     18             gb->printer.image_offset = 0;
     19             break;
     20             
     21         case GB_PRINTER_START_COMMAND:
     22             if (gb->printer.command_length == 4) {
     23                 gb->printer.status = 6; /* Printing */
     24                 uint32_t image[gb->printer.image_offset];
     25                 uint8_t palette = gb->printer.command_data[2];
     26                 uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF),
     27                                       gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA),
     28                                       gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55),
     29                                       gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)};
     30                 for (unsigned i = 0; i < gb->printer.image_offset; i++) {
     31                     image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
     32                 }
     33                 
     34                 // One second per 8-pixel row
     35                 gb->printer.time_remaining = gb->printer.image_offset / 160 * GB_get_unmultiplied_clock_rate(gb) / 256 / 8;
     36                 
     37                 if (gb->printer_callback) {
     38                     gb->printer_callback(gb, image, gb->printer.image_offset / 160,
     39                                          gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
     40                                          gb->printer.command_data[3] & 0x7F);
     41                 }
     42                 
     43                 gb->printer.image_offset = 0;
     44             }
     45             break;
     46             
     47         case GB_PRINTER_DATA_COMMAND:
     48             if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) {
     49                 gb->printer.image_offset %= sizeof(gb->printer.image);
     50                 gb->printer.status = 8; /* Received 0x280 bytes */
     51                 
     52                 uint8_t *byte = gb->printer.command_data;
     53                 
     54                 for (unsigned row = 2; row--; ) {
     55                     for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) {
     56                         for (unsigned y = 0; y < 8; y++, byte += 2) {
     57                             for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) {
     58                                     gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] =
     59                                         ((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1);
     60                                     (*byte) <<= 1;
     61                                     (*(byte + 1)) <<= 1;
     62                             }
     63                         }
     64                     }
     65                     
     66                     gb->printer.image_offset += 8 * 160;
     67                 }
     68             }
     69             
     70         case GB_PRINTER_NOP_COMMAND:
     71         default:
     72             break;
     73     }
     74 }
     75 
     76 
     77 static void byte_receive_completed(GB_gameboy_t *gb, uint8_t byte_received)
     78 {
     79     gb->printer.byte_to_send = 0;
     80     switch (gb->printer.command_state) {
     81         case GB_PRINTER_COMMAND_MAGIC1:
     82             if (byte_received != 0x88) {
     83                 return;
     84             }
     85             gb->printer.status &= ~1;
     86             gb->printer.command_length = 0;
     87             gb->printer.checksum = 0;
     88             break;
     89             
     90         case GB_PRINTER_COMMAND_MAGIC2:
     91             if (byte_received != 0x33) {
     92                 if (byte_received != 0x88) {
     93                     gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
     94                 }
     95                 return;
     96             }
     97             break;
     98             
     99         case GB_PRINTER_COMMAND_ID:
    100             gb->printer.command_id = byte_received & 0xF;
    101             break;
    102             
    103         case GB_PRINTER_COMMAND_COMPRESSION:
    104             gb->printer.compression = byte_received & 1;
    105             break;
    106             
    107         case GB_PRINTER_COMMAND_LENGTH_LOW:
    108             gb->printer.length_left = byte_received;
    109             break;
    110             
    111         case GB_PRINTER_COMMAND_LENGTH_HIGH:
    112             gb->printer.length_left |= (byte_received & 3) << 8;
    113             break;
    114             
    115         case GB_PRINTER_COMMAND_DATA:
    116             if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) {
    117                 if (gb->printer.compression) {
    118                     if (!gb->printer.compression_run_lenth) {
    119                         gb->printer.compression_run_is_compressed = byte_received & 0x80;
    120                         gb->printer.compression_run_lenth = (byte_received & 0x7F) + 1 + gb->printer.compression_run_is_compressed;
    121                     }
    122                     else if (gb->printer.compression_run_is_compressed) {
    123                         while (gb->printer.compression_run_lenth) {
    124                             gb->printer.command_data[gb->printer.command_length++] = byte_received;
    125                             gb->printer.compression_run_lenth--;
    126                             if (gb->printer.command_length == GB_PRINTER_MAX_COMMAND_LENGTH) {
    127                                 gb->printer.compression_run_lenth = 0;
    128                             }
    129                         }
    130                     }
    131                     else {
    132                         gb->printer.command_data[gb->printer.command_length++] = byte_received;
    133                         gb->printer.compression_run_lenth--;
    134                     }
    135                 }
    136                 else {
    137                     gb->printer.command_data[gb->printer.command_length++] = byte_received;
    138                 }
    139             }
    140             gb->printer.length_left--;
    141             break;
    142             
    143         case GB_PRINTER_COMMAND_CHECKSUM_LOW:
    144             gb->printer.checksum ^= byte_received;
    145             break;
    146             
    147         case GB_PRINTER_COMMAND_CHECKSUM_HIGH:
    148             gb->printer.checksum ^= byte_received << 8;
    149             if (gb->printer.checksum) {
    150                 gb->printer.status |= 1; /* Checksum error*/
    151                 gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
    152                 return;
    153             }
    154             gb->printer.byte_to_send = 0x81;
    155 
    156             break;
    157         case GB_PRINTER_COMMAND_ACTIVE:
    158             if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) {
    159                 /* Games expect INIT commands to return 0? */
    160                 gb->printer.byte_to_send = 0;
    161             }
    162             else {
    163                 if (gb->printer.status == 6 && gb->printer.time_remaining == 0) {
    164                     gb->printer.status = 4; /* Done */
    165                 }
    166                 gb->printer.byte_to_send = gb->printer.status;
    167             }
    168             break;
    169         case GB_PRINTER_COMMAND_STATUS:
    170             gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
    171             handle_command(gb);
    172             return;
    173     }
    174 
    175     if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) {
    176         gb->printer.checksum += byte_received;
    177     }
    178 
    179     if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) {
    180         gb->printer.command_state++;
    181     }
    182 
    183     if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) {
    184         if (gb->printer.length_left == 0) {
    185             gb->printer.command_state++;
    186         }
    187     }
    188 }
    189 
    190 static void serial_start(GB_gameboy_t *gb, bool bit_received)
    191 {
    192     if (gb->printer.idle_time > GB_get_unmultiplied_clock_rate(gb)) {
    193         gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
    194         gb->printer.bits_received = 0;
    195     }
    196     gb->printer.idle_time = 0;
    197     gb->printer.byte_being_received <<= 1;
    198     gb->printer.byte_being_received |= bit_received;
    199     gb->printer.bits_received++;
    200     if (gb->printer.bits_received == 8) {
    201         byte_receive_completed(gb, gb->printer.byte_being_received);
    202         gb->printer.bits_received = 0;
    203         gb->printer.byte_being_received = 0;
    204     }
    205 }
    206 
    207 static bool serial_end(GB_gameboy_t *gb)
    208 {
    209     bool ret = gb->printer.bit_to_send;
    210     gb->printer.bit_to_send = gb->printer.byte_to_send & 0x80;
    211     gb->printer.byte_to_send <<= 1;
    212     return ret;
    213 }
    214 
    215 void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback, GB_printer_done_callback_t done_callback)
    216 {
    217     GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
    218     memset(&gb->printer, 0, sizeof(gb->printer));
    219     GB_set_serial_transfer_bit_start_callback(gb, serial_start);
    220     GB_set_serial_transfer_bit_end_callback(gb, serial_end);
    221     gb->printer_callback = callback;
    222     gb->printer_done_callback = done_callback;
    223     gb->accessory = GB_ACCESSORY_PRINTER;
    224 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.