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.