commit 631bb306892d4f93fa123bc07c24312deb4913f0 Author: mid-kid Date: Wed Jul 15 23:42:09 2020 +0200 Initial commit diff --git a/arduino/Makefile b/arduino/Makefile new file mode 100644 index 0000000..61e6d11 --- /dev/null +++ b/arduino/Makefile @@ -0,0 +1,55 @@ +name := GBCartRead + +dir_source := source +dir_build := build + +TARGET_ARCH := -mmcu=atmega328p +TARGET_AVRDUDE := -patmega328p -carduino +SERIAL := /dev/ttyACM0 + +CC := avr-gcc +OBJCOPY := avr-objcopy +OBJDUMP := avr-objdump +AVRDUDE := avrdude + +OPTIM := -Os -g -fdata-sections -ffunction-sections -flto -fuse-linker-plugin -fipa-pta #-fgraphite-identity -floop-nest-optimize +CFLAGS := $(OPTIM) -Wall -Wextra -std=c17 -DF_CPU=16000000L +LDFLAGS := $(OPTIM) -Wl,--gc-sections -Wl,--print-gc-sections + +rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2)) +objects := $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, $(call rwildcard, $(dir_source)/, *.c)) + +.SECONDEXPANSION: + +.PHONY: all +all: $(name).hex $(name).lst + +.PHONY: clean +clean: + rm -rf $(dir_build) $(name).hex $(name).lst + +.PHONY: upload +upload: $(name).hex $(name).lst + $(AVRDUDE) -v $(TARGET_AVRDUDE) -P$(SERIAL) -Uflash:w:$<:i + +.PHONY: screen +screen: upload + minicom -D $(SERIAL) -b 2000000 + +%.hex: $(dir_build)/%.elf + $(OBJCOPY) -O ihex -R .eeprom $< $@ + +%.lst: $(dir_build)/%.elf + $(OBJDUMP) -h -S $< > $@ + +$(dir_build)/$(name).elf: $(objects) | $$(dir $$@) + $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +$(dir_build)/%.o: $(dir_source)/%.c | $$(dir $$@) + $(COMPILE.c) -MMD -MF $(@:.o=.d) $(OUTPUT_OPTION) $< + +.PRECIOUS: %/ +%/: + mkdir -p $@ + +-include $(patsubst %.o, %.d, $(objects)) diff --git a/arduino/source/cart.c b/arduino/source/cart.c new file mode 100644 index 0000000..a370fdd --- /dev/null +++ b/arduino/source/cart.c @@ -0,0 +1,226 @@ +#include "cart.h" + +#include +#include +#include + +#include "utils.h" +#include "pins.h" +#include "spi.h" +#include "rom.h" + + +// Most of the pulses described here are arbitrarily chosen, +// to satisfy my idiotic UX requirements with the leds. +// Here's a pretty cool bus analysis: +// https://dhole.github.io/post/gameboy_cartridge_emu_1/ + +static void cart_address(const uint16_t address) +{ + spi_transfer(address >> 8); + spi_transfer(address & 0xFF); + writepin(PIN_STORE, HIGH); + __asm__ volatile ("nop"); + writepin(PIN_STORE, LOW); +} + +uint8_t cart_read(const uint16_t address) +{ + cart_address(address); + __asm__ volatile ("nop"); + return PINS_DAT; +} + +void cart_write(const uint16_t address, const uint8_t byte) +{ + cart_address(address); + PORTB = (PORTB & ~PINS_DATB) | (byte >> 6); + PORTD = (PORTD & ~PINS_DATD) | (byte << 2); + writepin(PIN_WRITE, LOW); + __asm__ volatile ("nop"); + writepin(PIN_WRITE, HIGH); +} + +void cart_set_read(void) +{ + // Set the data pins as pull-up inputs + DDRB &= ~PINS_DATB; + DDRD &= ~PINS_DATD; + PORTB |= PINS_DATB; + PORTD |= PINS_DATD; + + writepin(PIN_READ, LOW); + writepin(PIN_WRITE, HIGH); +} + +void cart_set_write(void) +{ + // Set the data pins as low outputs + DDRB |= PINS_DATB; + DDRD |= PINS_DATD; + PORTB &= ~PINS_DATB; + PORTD &= ~PINS_DATD; + + writepin(PIN_READ, HIGH); + writepin(PIN_WRITE, HIGH); +} + +void cart_set_standby(void) +{ + // Set the data pins as pull-up inputs + DDRB &= ~PINS_DATB; + DDRD &= ~PINS_DATD; + PORTB |= PINS_DATB; + PORTD |= PINS_DATD; + + writepin(PIN_READ, HIGH); + writepin(PIN_WRITE, HIGH); +} + +void cart_select_rom(void) +{ + writepin(PIN_CS, HIGH); +} + +void cart_select_ram(void) +{ + writepin(PIN_CS, LOW); +} + +void cart_init(void) +{ + // Set the appropriate pins as output + DDRB |= pin(PIN_STORE); + DDRC |= pin(PIN_READ) | pin(PIN_WRITE) | pin(PIN_CS); + + // Initialize the cart as standby + cart_set_standby(); + cart_select_rom(); + + // Initialize the shift register + writepin(PIN_STORE, LOW); + spi_init(SPI_DIV8); + cart_address(0); +} + +void cart_off(void) +{ + cart_address(0); + spi_off(); + PORTC &= ~pin(PIN_READ) & ~pin(PIN_CS) & ~pin(PIN_WRITE); + writepin(PIN_STORE, LOW); +} + +int cart_boot(struct cart_info *info) +{ + cart_set_read(); + + // "simulate" the GB power-up process, by reading the logo once, before comparing. + // This might be compatible with carts that hijack the logo on bootup. + // There is no point in simulating this more accurately (i.e. timing) + // if we don't even simulate the RD/WR pulses properly. + // http://gbdev.gg8.se/wiki/articles/Gameboy_Bootstrap_ROM + + // Read the logo once (to display on the screen) + for (unsigned i = rom_offset(logo); + i < rom_offset_end(logo); i++) cart_read(i); + + // Read the logo again (to verify it) + for (unsigned i = rom_offset(logo); i < rom_offset_end(logo); i++) { + if (rom_logo[i - rom_offset(logo)] != cart_read(i)) { + cart_set_standby(); + return 1; + } + } + + // Read and verify the checksum (and save some info from the header) + uint8_t checksum = rom_offset(header_checksum) - rom_offset(title); + for (unsigned i = rom_offset(title); + i < rom_offset_end(header_checksum); i++) { + uint8_t c = cart_read(i); + checksum += c; + + if (i == rom_offset(type)) info->type = c; + if (i == rom_offset(rom_size)) info->rom_size = c; + if (i == rom_offset(ram_size)) info->ram_size = c; + } + + cart_set_standby(); + + return checksum; +} + +// https://ia801906.us.archive.org/19/items/GameBoyProgManVer1.1/GameBoyProgManVer1.1.pdf (page 215) + +// NOTE: These functions only support MBC1, MBC2, MBC3 and MBC5. +// Thankfully, that covers most of the licensed carts. + +void cart_rom_bank(__attribute__((unused)) struct cart_info *info, const unsigned bank) +{ + // MBC1: 0x2000-0x3FFF (bit 0-4) 0x4000-0x5FFF (bit 5-6, if 0x6000-0x7FFF is set to 0) + // MBC2: 0x2100-0x21FF (bit 0-3) + // MBC3: 0x2000-0x3FFF (bit 0-6) + // MBC5: 0x2000-0x2FFF (bit 0-7) 0x3000-0x3FFF (bit 8) + cart_set_write(); + + if (info->type >= ROM_TYPE_MBC1 && + info->type <= ROM_TYPE_MBC1_RAM_BATTERY) { + cart_write(0x2000, bank & 0x1F); + cart_write(0x6000, 0); + cart_write(0x4000, bank >> 5); + return; + } + + cart_write(0x2100, bank); + if (info->type >= ROM_TYPE_MBC5 && + info->type <= ROM_TYPE_MBC5_RUMBLE_RAM_BATTERY) { + cart_write(0x3000, bank >> 8); + } +} + +void cart_ram_enable(__attribute__((unused)) struct cart_info *info) +{ + // MBC1: 0x0000-0x1FFF = 0x0A + // MBC2: 0x0000-0x0FFF = 0x0A + // MBC3: 0x0000-0x1FFF = 0x0A + // MBC5: 0x0000-0x1FFF = 0x0A + cart_set_write(); + cart_write(0x0000, 0x0A); + cart_select_ram(); +} + +void cart_ram_disable(__attribute__((unused)) struct cart_info *info) +{ + // MBC1: 0x0000-0x1FFF = 0x00 + // MBC2: 0x0000-0x0FFF = 0x00 + // MBC3: 0x0000-0x1FFF = 0x00 + // MBC5: 0x0000-0x1FFF = 0x00 + cart_set_write(); + cart_select_rom(); + cart_write(0x0000, 0x00); +} + +void cart_ram_bank(__attribute__((unused)) struct cart_info *info, const unsigned bank) +{ + // MBC1: 0x4000-0x5FFF (bit 0-1, if 0x6000-0x7FFF is set to 1) + // MBC2: None, it has only one bank. (Writing to 0x4000-0x5FFF should have no effect) + // MBC3: 0x4000-0x5FFF (bit 0-1) NOTE: Writing values 0x08-0x0C opens up possible problems with the RTC! + // MBC5: 0x4000-0x5FFF (bit 0-3) + cart_set_write(); + + if (info->type >= ROM_TYPE_MBC1 && + info->type <= ROM_TYPE_MBC1_RAM_BATTERY) { + cart_write(0x6000, 1); + } + + // Since writing 0x08-0x0C here opens up the timer, + // it opens up the possibility for corruption. + // As such, it's a good idea to check for it... + if (info->type >= ROM_TYPE_MBC3_TIMER_BATTERY && + info->type <= ROM_TYPE_MBC3_RAM_BATTERY) { + cart_write(0x4000, bank & 0x07); + return; + } + + cart_write(0x4000, bank); +} diff --git a/arduino/source/cart.h b/arduino/source/cart.h new file mode 100644 index 0000000..ca2b21e --- /dev/null +++ b/arduino/source/cart.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +struct cart_info { + int type; + int rom_size; + int ram_size; +}; + +uint8_t cart_read(const uint16_t address); +void cart_write(const uint16_t address, const uint8_t byte); +void cart_set_read(void); +void cart_set_write(void); +void cart_set_standby(void); +void cart_select_rom(void); +void cart_select_ram(void); +void cart_init(void); +void cart_off(void); +int cart_boot(struct cart_info *info); +void cart_rom_bank(struct cart_info *info, unsigned bank); +void cart_ram_enable(struct cart_info *info); +void cart_ram_disable(struct cart_info *info); +void cart_ram_bank(struct cart_info *info, unsigned bank); diff --git a/arduino/source/main.c b/arduino/source/main.c new file mode 100644 index 0000000..5e7e990 --- /dev/null +++ b/arduino/source/main.c @@ -0,0 +1,263 @@ +// AVR-libc documentation: +// http://www.nongnu.org/avr-libc/user-manual/modules.html + +// AVR assembly instructions: +// https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_instruction_list.html + +// ATmega datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega328_P%20AVR%20MCU%20with%20picoPower%20Technology%20Data%20Sheet%2040001984A.pdf + +/* + * FIXME: The current hardware design is questionable. + * + * The leds. Why does the power (MOSFET) led even have a mosfet to begin + * with? It doesn't seem to be doing much. + * Furthermore, why aren't the RD and WR leds inverted? Both of these pins + * on the cart are active low, not high. + */ + +#include +#include +#include +#include + +#include "utils.h" +#include "pins.h" +#include "serial.h" +#include "cart.h" +#include "proto.h" + +void proto_send_u32(const uint32_t val) +{ + char *cval = (char *)&val; + serial_putchar(cval[0]); + serial_putchar(cval[1]); + serial_putchar(cval[2]); + serial_putchar(cval[3]); +} + +uint32_t proto_read_u32(void) +{ + uint32_t val; + char *cval = (char *)&val; + cval[0] = serial_getchar(); + cval[1] = serial_getchar(); + cval[2] = serial_getchar(); + cval[3] = serial_getchar(); + return val; +} + +struct proto_client_packet proto_read_packet(void) +{ + struct proto_client_packet packet; + packet.command = serial_getchar(); + packet.address = proto_read_u32(); + packet.size = proto_read_u32(); + return packet; +} + +void client_read_raw(uint32_t address, uint32_t size) +{ + if (address > (uint32_t)0xFFFF || address + size > (uint32_t)0xFFFF) { + serial_putchar(PROTO_READER_FAIL); + return; + } + + serial_putchar(PROTO_READER_OK); + cart_set_read(); + if (address & 0x8000) cart_select_ram(); + for (unsigned i = address; i < address + size; i++) { + serial_putchar_inline(cart_read(i)); + } + cart_select_rom(); +} + +void client_write_raw(uint32_t address, uint32_t size) +{ + if (address > (uint32_t)0xFFFF || address + size > (uint32_t)0xFFFF) { + serial_putchar(PROTO_READER_FAIL); + return; + } + + serial_putchar(PROTO_READER_OK); + cart_set_write(); + if (address & 0x8000) cart_select_ram(); + for (unsigned i = address; i < address + size; i++) { + cart_write(i, serial_getchar_inline()); + } + cart_select_rom(); +} + +void client_read_rom(struct cart_info *cart, uint32_t address, uint32_t size) +{ + // 0x1FF = max bank (MBC5, 9 bits) + if (address > (uint32_t)0x4000 * 0x200 || + address + size > (uint32_t)0x4000 * 0x200) { + serial_putchar(PROTO_READER_FAIL); + return; + } + + unsigned bank = address / (uint32_t)0x4000; + unsigned bank_end = (address + size - 1) / (uint32_t)0x4000; + unsigned offset = 0; // Offset for the first bank we read, which can be bank 0. + if (bank) { + cart_rom_bank(cart, bank); + + // Any bank other than 0 starts at 0x4000. + offset = 0x4000; + } + + // If we're not reading cross-bank, just do one read. + if (bank == bank_end) return client_read_raw(offset + address % 0x4000, size); + + serial_putchar(PROTO_READER_OK); + cart_set_read(); + + // Read the current bank until the end + for (unsigned i = offset + address % 0x4000; i < offset + 0x4000; i++) { + serial_putchar_inline(cart_read(i)); + } + + // Read all the entire banks + for (unsigned i = bank + 1; i < bank_end; i++) { + cart_rom_bank(cart, i); + cart_set_read(); + for (unsigned i = 0x4000; i < (unsigned)0x4000 + 0x4000; i++) { + serial_putchar_inline(cart_read(i)); + } + } + + // Read the final bank until the end + cart_rom_bank(cart, bank_end); + cart_set_read(); + for (unsigned i = 0x4000; i <= 0x4000 + (size - 1) % 0x4000; i++) { + serial_putchar_inline(cart_read(i)); + } +} + +void client_read_ram(struct cart_info *cart, uint32_t address, uint32_t size) +{ + // 0xF = max bank (MBC5, 4 bits) + if (address > (uint32_t)0x2000 * 0x10 || + address + size > (uint32_t)0x2000 * 0x10) { + serial_putchar(PROTO_READER_FAIL); + return; + } + + unsigned bank = address / (uint32_t)0x2000; + unsigned bank_end = (address + size - 1) / (uint32_t)0x2000; + cart_ram_enable(cart); + cart_ram_bank(cart, bank); + + // If we're not reading cross-bank, just do one read. + if (bank == bank_end) return client_read_raw(0xA000 + address % 0x2000, size); + + serial_putchar(PROTO_READER_OK); + cart_set_read(); + + // Read the current bank until the end + for (unsigned i = 0xA000 + address % 0x2000; i < 0xA000 + 0x2000; i++) { + serial_putchar_inline(cart_read(i)); + } + + // Read all the entire banks + for (unsigned i = bank + 1; i < bank_end; i++) { + cart_ram_bank(cart, i); + cart_set_read(); + for (unsigned i = 0xA000; i < 0xA000 + 0x2000; i++) { + serial_putchar_inline(cart_read(i)); + } + } + + // Read the final bank until the end + cart_ram_bank(cart, bank_end); + cart_set_read(); + for (unsigned i = 0xA000; i <= 0xA000 + (size - 1) % 0x2000; i++) { + serial_putchar_inline(cart_read(i)); + } + + cart_ram_disable(cart); +} + +int main(void) +{ + struct cart_info cart; + + // Configure reset button interrupt + //PCICR = 0; + //PCMSK1 = _BV(PCINT9); + + // Initialize the program + serial_init(2000000); + pinmode(PIN_MOSFET, OUTPUT); + writepin(PIN_MOSFET, LOW); + cart_init(); + sei(); + + int checksum = cart_boot(&cart); + if (checksum) { + // Notify the user that the cart couldn't be read properly. + writepin(PIN_READ, LOW); + writepin(PIN_WRITE, LOW); + } + + sbi(PCICR, PCIE1); + while (serial_getchar() != PROTO_CLIENT_INIT); + cbi(PCICR, PCIE1); + serial_putchar(checksum ? PROTO_READER_FAIL : PROTO_READER_OK); + + // Interpret commands + for (;;) { + serial_drain(); + cart_set_standby(); + sbi(PCICR, PCIE1); + struct proto_client_packet packet = proto_read_packet(); + cbi(PCICR, PCIE1); + + switch (packet.command) { + case PROTO_CLIENT_READ_RAW: + client_read_raw(packet.address, packet.size); + break; + + case PROTO_CLIENT_READ_ROM: + client_read_rom(&cart, packet.address, packet.size); + break; + + case PROTO_CLIENT_READ_RAM: + client_read_ram(&cart, packet.address, packet.size); + break; + + case PROTO_CLIENT_WRITE_RAW: + client_write_raw(packet.address, packet.size); + break; + + case PROTO_CLIENT_WRITE_ROM: + case PROTO_CLIENT_WRITE_RAM: + default: + serial_putchar(PROTO_READER_FAIL); + } + } +} + +void exit(__attribute__((unused)) int status) +{ + serial_drain(); + //sbi(PCICR, PCIE1); + cart_off(); + for (;;); +} + +//ISR(PCINT1_vect) { + //// Debounce it + //int pressed = readpin(PIN_SWITCH); + //int debounce = 0x2DAB; + //while (debounce--) { + //if (pressed != readpin(PIN_SWITCH)) return; + //} + + //// Check if it was released + //if (pressed) return; + + //_delay_ms(10); + //__asm__ volatile ("jmp 0"); +//} diff --git a/arduino/source/pins.h b/arduino/source/pins.h new file mode 100644 index 0000000..9664f5e --- /dev/null +++ b/arduino/source/pins.h @@ -0,0 +1,58 @@ +#pragma once + +// Pin mappings as on the arduino board +#define PIN_D0 D, 0 +#define PIN_D1 D, 1 +#define PIN_D2 D, 2 +#define PIN_D3 D, 3 +#define PIN_D4 D, 4 +#define PIN_D5 D, 5 +#define PIN_D6 D, 6 +#define PIN_D7 D, 7 +#define PIN_D8 B, 0 +#define PIN_D9 B, 1 +#define PIN_D10 B, 2 +#define PIN_D11 B, 3 +#define PIN_D12 B, 4 +#define PIN_D13 B, 5 + +#define PIN_A0 C, 0 +#define PIN_A1 C, 1 +#define PIN_A2 C, 2 +#define PIN_A3 C, 3 +#define PIN_A4 C, 4 +#define PIN_A5 C, 5 + +// Alternative names for certain pins +#define PIN_SPI_SS PIN_D10 +#define PIN_SPI_MOSI PIN_D11 +#define PIN_SPI_MISO PIN_D12 +#define PIN_SPI_SCK PIN_D13 +#define PIN_WIRE_SDA PIN_A4 +#define PIN_WIRE_SCL PIN_A5 +#define LED_BUILTIN PIN_D13 + +// Button +#define PIN_MOSFET PIN_A0 +#define PIN_SWITCH PIN_A1 + +// Cart +#define PIN_WRITE PIN_A3 +#define PIN_CS PIN_A4 +#define PIN_READ PIN_A5 + +// Shift registers +#define PIN_STORE PIN_D10 + +// Data pins +#define PIN_DAT0 PIN_D2 +#define PIN_DAT1 PIN_D3 +#define PIN_DAT2 PIN_D4 +#define PIN_DAT3 PIN_D5 +#define PIN_DAT4 PIN_D6 +#define PIN_DAT5 PIN_D7 +#define PIN_DAT6 PIN_D8 +#define PIN_DAT7 PIN_D9 +#define PINS_DATB (pin(PIN_DAT7) | pin(PIN_DAT6)) +#define PINS_DATD (pin(PIN_DAT5) | pin(PIN_DAT4) | pin(PIN_DAT3) | pin(PIN_DAT2) | pin(PIN_DAT1) | pin(PIN_DAT0)) +#define PINS_DAT (PINB << 6 | PIND >> 2) diff --git a/arduino/source/proto.h b/arduino/source/proto.h new file mode 100644 index 0000000..e61712e --- /dev/null +++ b/arduino/source/proto.h @@ -0,0 +1,55 @@ +#pragma once + +enum proto_reader { + // Sent when the operation was successful + PROTO_READER_OK, + + // Sent after a failed operation + PROTO_READER_FAIL +}; + +enum proto_client { + // Packet: + // u8 - command + // Response: + // u8 - status + PROTO_CLIENT_INIT, + + // Packet: + // u8 - command + // u32 - start address + // u32 - size + // Response: + // u8 - status + // u8[] - data (if status == PROTO_READER_OK) + + // Read a raw address with whatever current state + // (useful for RTC and other cart addons) + PROTO_CLIENT_READ_RAW, + // Read an absolute ROM address, switching banks as needed + PROTO_CLIENT_READ_ROM, + // Read an absolute SRAM address, switching banks as needed + PROTO_CLIENT_READ_RAM, + + // Packet: + // u8 - command + // u32 - start address + // u32 - size + // u8[] - data + // Response: + // u8 - status + + // Write a raw address with whatever current state + // (useful for RTC and other cart addons) + PROTO_CLIENT_WRITE_RAW, + // Write an absolute ROM address, switching banks as needed + PROTO_CLIENT_WRITE_ROM, + // Write an absolute SRAM address, switching banks as needed + PROTO_CLIENT_WRITE_RAM +}; + +struct proto_client_packet { + uint8_t command; + uint32_t address; + uint32_t size; +} __attribute__((packed)); diff --git a/arduino/source/rom.c b/arduino/source/rom.c new file mode 100644 index 0000000..f78696e --- /dev/null +++ b/arduino/source/rom.c @@ -0,0 +1,9 @@ +#include "rom.h" + +#include + +uint8_t rom_logo[] = { + 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, + 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E +}; diff --git a/arduino/source/rom.h b/arduino/source/rom.h new file mode 100644 index 0000000..aa67da4 --- /dev/null +++ b/arduino/source/rom.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#define rom_offset(member) (0x100 + offsetof(struct rom_header, member)) +#define rom_offset_end(member) (0x100 + offsetof(struct rom_header, member) + sizeof(((struct rom_header *)0)->member)) + +enum rom_cgb { + ROM_CGB_SUPPORTED = 0x80, + ROM_CGB_ONLY = 0xC0 +}; + +enum rom_sgb { + ROM_SGB_NO = 0x00, + ROM_SGB_SUPPORTED = 0x03 +}; + +enum rom_type { + ROM_TYPE_ROM_ONLY = 0x00, + + ROM_TYPE_MBC1 = 0x01, + ROM_TYPE_MBC1_RAM = 0x2, + ROM_TYPE_MBC1_RAM_BATTERY = 0x03, + + // UNUSED = 0x04 + + ROM_TYPE_MBC2 = 0x05, + ROM_TYPE_MBC2_BATTERY = 0x06, + + // UNUSED = 0x07 + + ROM_TYPE_ROM_RAM = 0x08, + ROM_TYPE_ROM_RAM_BATTERY = 0x09, + + // UNUSED = 0x0A + + ROM_TYPE_MMM01 = 0x0B, + ROM_TYPE_MMM01_RAM = 0x0C, + ROM_TYPE_MMM01_RAM_BATTERY = 0x0D, + + // UNUSED = 0x0E + + ROM_TYPE_MBC3_TIMER_BATTERY = 0x0F, + ROM_TYPE_MBC3_TIMER_RAM_BATTERY = 0x10, + ROM_TYPE_MBC3 = 0x11, + ROM_TYPE_MBC3_RAM = 0x12, + ROM_TYPE_MBC3_RAM_BATTERY = 0x13, + + // UNUSED = 0x14 + + // Doesn't exist, but included for "completeness". + ROM_TYPE_MBC4 = 0x15, + ROM_TYPE_MBC4_RAM = 0x16, + ROM_TYPE_MBC4_RAM_BATTERY = 0x17, + + // UNUSED = 0x18 + + ROM_TYPE_MBC5 = 0x19, + ROM_TYPE_MBC5_RAM = 0x1A, + ROM_TYPE_MBC5_RAM_BATTERY = 0x1B, + ROM_TYPE_MBC5_RUMBLE = 0x1C, + ROM_TYPE_MBC5_RUMBLE_RAM = 0x1D, + ROM_TYPE_MBC5_RUMBLE_RAM_BATTERY = 0x1E, + + // UNUSED = 0x1F + + ROM_TYPE_MBC6_RAM_BATTERY = 0x20, + + // UNUSED = 0x21 + + ROM_TYPE_MBC7_ACCEL_RAM_BATTERY = 0x22, + + // UNUSED ... + + ROM_TYPE_POCKET_CAMERA = 0xFC, + ROM_TYPE_BANDAI_TAMA5 = 0xFD, + ROM_TYPE_HUC3 = 0xFE, + ROM_TYPE_HUC1_RAM_BATTERY = 0xFF +}; + +enum rom_region { + ROM_REGION_JAPANESE = 0x00, + ROM_REGION_INTERNATIONAL = 0x01 +}; + +// http://gbdev.gg8.se/files/docs/mirrors/pandocs.html#thecartridgeheader +// https://raw.githubusercontent.com/AntonioND/giibiiadvance/master/docs/TCAGBD.pdf (page 51) +struct rom_header { + uint8_t entry[4]; + uint8_t logo[0x30]; + uint8_t title[0xB]; + uint8_t manufacturer[4]; + uint8_t cgb_flag; + uint8_t licensee[2]; + uint8_t sgb_flag; + uint8_t type; + uint8_t rom_size; // 0x8000 << x + uint8_t ram_size; // MBC2 uses 0, but has a size of 512 * 4 bytes + uint8_t region; + uint8_t old_licensee; // If 0x33, use the new licensee + uint8_t version; + uint8_t header_checksum; + uint8_t global_checksum[2]; // Big endian +} __attribute__((packed)); + +extern uint8_t rom_logo[]; diff --git a/arduino/source/serial.c b/arduino/source/serial.c new file mode 120000 index 0000000..3f397ae --- /dev/null +++ b/arduino/source/serial.c @@ -0,0 +1 @@ +/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/serial.c \ No newline at end of file diff --git a/arduino/source/serial.h b/arduino/source/serial.h new file mode 120000 index 0000000..b56b899 --- /dev/null +++ b/arduino/source/serial.h @@ -0,0 +1 @@ +/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/serial.h \ No newline at end of file diff --git a/arduino/source/spi.c b/arduino/source/spi.c new file mode 120000 index 0000000..ff5bb1a --- /dev/null +++ b/arduino/source/spi.c @@ -0,0 +1 @@ +/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/spi.c \ No newline at end of file diff --git a/arduino/source/spi.h b/arduino/source/spi.h new file mode 120000 index 0000000..bfdf735 --- /dev/null +++ b/arduino/source/spi.h @@ -0,0 +1 @@ +/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/spi.h \ No newline at end of file diff --git a/arduino/source/utils.h b/arduino/source/utils.h new file mode 120000 index 0000000..7a808f2 --- /dev/null +++ b/arduino/source/utils.h @@ -0,0 +1 @@ +/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/utils.h \ No newline at end of file diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..51b7b66 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,47 @@ +name := GBCartRead + +dir_source := source +dir_build := build + +CFLAGS := -O0 -g -Wall -Wextra -std=gnu17 $(CFLAGS) + +CFLAGS += $(shell pkg-config --cflags libserialport) +LDLIBS += $(shell pkg-config --libs libserialport) + +SANIT := -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fsanitize=undefined +OPTIM := -Os -fdata-sections -ffunction-sections -flto -fuse-linker-plugin -fipa-pta -Wl,--gc-sections -Wl,--print-gc-sections #-fgraphite-identity -floop-nest-optimize + +rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2)) +objects := $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, $(call rwildcard, $(dir_source)/, *.c)) + +.SECONDEXPANSION: + +.PHONY: all +all: $(name) + +.PHONY: clean +clean: + rm -rf $(dir_build) $(name) + +.PHONY: sanit +sanit: CFLAGS += $(SANIT) +sanit: LDFLAGS += $(SANIT) +sanit: $(name) + +.PHONY: optim +optim: CFLAGS += $(OPTIM) +optim: LDFLAGS += $(OPTIM) +optim: $(name) + strip --strip-all --strip-unneeded $(name) + +$(name): $(objects) + $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +$(dir_build)/%.o: $(dir_source)/%.c | $$(dir $$@) + $(COMPILE.c) -MMD -MF $(@:.o=.d) $(OUTPUT_OPTION) $< + +.PRECIOUS: %/ +%/: + mkdir -p $@ + +-include $(patsubst %.o, %.d, $(objects)) diff --git a/client/source/main.c b/client/source/main.c new file mode 100644 index 0000000..bad04d6 --- /dev/null +++ b/client/source/main.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __GNUC__ +#define A_UNUSED __attribute__((unused)) +#else +#define A_UNUSED +#endif + +struct sp_port *serial_guess_port(void) +{ + struct sp_port **ports; + + if (sp_list_ports(&ports) != SP_OK || ports[0] == NULL) { + puts("No serial devices detected"); + return NULL; + } + + if (ports[1] != NULL) { + puts("Please select a port"); + return NULL; + } + + return ports[0]; +} + +char *program_name; + +void serial_error(char *message) +{ + char *error = sp_last_error_message(); + fprintf(stderr, "%s: %s: %s\n", program_name, message, error); + sp_free_error_message(error); +} + +int main(int argc, char *argv[]) +{ + program_name = argv[0]; + setlocale(LC_ALL, ""); + + long save = 0; + if (argc > 1) { + save = strtol(argv[1], NULL, 0); + } + + struct sp_port *port; + + if (!(port = serial_guess_port())) return 1; + puts(sp_get_port_name(port)); + + if (sp_open(port, SP_MODE_READ_WRITE) != SP_OK || + sp_set_baudrate(port, 2000000) != SP_OK || + sp_set_flowcontrol(port, SP_FLOWCONTROL_NONE) != SP_OK) { + serial_error("Failed to open port"); + return 1; + } + + usleep(100000LL); + sp_flush(port, SP_BUF_BOTH); + + fputs("Waiting for device... ", stdout); + fflush(stdout); + for (;;) { + char c = 0; + sp_blocking_write(port, &c, 1, 0); + if (sp_blocking_read(port, &c, 1, 1000) == 1) { + if (c == 0) { + puts("ROM OK!"); + } else { + puts("ROM ERROR"); + } + break; + } + } + + char command = 1; + uint32_t address = 0x147; + uint32_t size = 3; + sp_blocking_write(port, &command, 1, 0); + sp_blocking_write(port, &address, 4, 0); + sp_blocking_write(port, &size, 4, 0); + + sp_blocking_read(port, &command, 1, 0); + if (command != 0) { + puts("An error has occurred"); + return 1; + } + + char cart_type = 0; + sp_blocking_read(port, &cart_type, 1, 0); + printf("Cart type: %02X\n", cart_type); + + char rom_size = 0; + sp_blocking_read(port, &rom_size, 1, 0); + printf("Rom size: %02X\n", rom_size); + + char ram_size = 0; + sp_blocking_read(port, &ram_size, 1, 0); + printf("Ram size: %02X\n", ram_size); + + command = save ? 3 : 2; + address = 0; + if (!save) { + size = 0x8000 << rom_size; + } else { + size = save; + } + sp_blocking_write(port, &command, 1, 0); + sp_blocking_write(port, &address, 4, 0); + sp_blocking_write(port, &size, 4, 0); + sp_blocking_read(port, &command, 1, 0); + if (command != 0) { + puts("An error has occurred"); + return 1; + } + + struct timeval begin, end; + gettimeofday(&begin, NULL); + + FILE *file = fopen("out.bin", "w"); + for (unsigned x = 0; x < size; x += 0x200) { + char c[0x200]; + sp_blocking_read(port, &c, 0x200, 0); + if (!save) { + printf("\r%02X:%04X", x / 0x4000, x % 0x4000); + } else { + printf("\r%02X:%04X", x / 0x2000, x % 0x2000); + } + fflush(stdout); + fwrite(&c, 0x200, 1, file); + } + putchar('\n'); + fclose(file); + + gettimeofday(&end, NULL); + double dt = (double)end.tv_sec - begin.tv_sec + ((double)end.tv_usec - begin.tv_usec) / 1000000; + printf("Average speed: %f KB/s\n", size / 1000 / dt); +} diff --git a/toolchain-arduino.sh b/toolchain-arduino.sh new file mode 100755 index 0000000..2dd4251 --- /dev/null +++ b/toolchain-arduino.sh @@ -0,0 +1,87 @@ +#!/bin/sh +set -e + +PREFIX="$PWD/toolchain" +TARGET=avr +export CFLAGS='-Os -pipe' +export CXXFLAGS="$CFLAGS" +export PATH="$PREFIX/bin:$PATH" +export MAKEFLAGS="-j$(nproc)" + +mkdir -p "$PREFIX/src" +( cd "$PREFIX/src" + wget -c https://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.xz + wget -c https://ftp.gnu.org/gnu/gcc/gcc-8.1.0/gcc-8.1.0.tar.xz + wget -c https://ftp.gnu.org/gnu/gmp/gmp-6.1.2.tar.xz + wget -c https://ftp.gnu.org/gnu/mpfr/mpfr-4.0.1.tar.xz + wget -c https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz + wget -c http://isl.gforge.inria.fr/isl-0.19.tar.xz + wget -c https://download.savannah.gnu.org/releases/avr-libc/avr-libc-2.0.0.tar.bz2 + wget -c https://download.savannah.gnu.org/releases/avrdude/avrdude-6.3.tar.gz +) + +( cd "$PREFIX/src" + rm -rf binutils-*/ + tar xf binutils-*.tar.xz + cd binutils-*/ + mkdir build + cd build + ../configure --prefix="$PREFIX" --target="$TARGET" --disable-nls + make + make install +) + +( cd "$PREFIX/src" + rm -rf gcc-*/ + tar xf gcc-*.tar.xz + cd gcc-*/ + tar xf ../gmp-*.tar.xz + tar xf ../mpfr-*.tar.xz + tar xf ../mpc-*.tar.gz + tar xf ../isl-*.tar.xz + mv gmp-*/ gmp + mv mpfr-*/ mpfr + mv mpc-*/ mpc + mv isl-*/ isl + sed -i -e 's/\.\/fixinc\.sh/-c true/' gcc/Makefile.in + mkdir -p "$PREFIX/$TARGET/include" + mkdir build + cd build + ../configure --prefix="$PREFIX" --target="$TARGET" --enable-languages=c,c++ --disable-nls + make + make install +) + +( cd "$PREFIX/src" + rm -rf avr-libc-*/ + tar xf avr-libc-*.tar.bz2 + cd avr-libc-*/ + ./configure --prefix="$PREFIX" --host="$TARGET" + make + make install +) + +( cd "$PREFIX/src" + rm -rf avrdude-*/ + tar xf avrdude-*.tar.gz + cd avrdude-*/ + mkdir build + cd build + ../configure --prefix="$PREFIX" + make + make install +) + +( cd "$PREFIX/src" + rm -rf binutils-*/ + rm -rf gcc-*/ + rm -rf avr-libc-*/ + rm -rf avrdude-*/ + #find "$PREFIX" -type f -name '*.la' -delete +) + +cat > "$PREFIX/env" << EOF +export PATH="$PREFIX/bin:\$PATH" +EOF + +echo 'Run "source toolchain/env" before running make!' diff --git a/toolchain-musl.sh b/toolchain-musl.sh new file mode 100755 index 0000000..2881f94 --- /dev/null +++ b/toolchain-musl.sh @@ -0,0 +1,111 @@ +#!/bin/sh +set -e + +PREFIX="$PWD/toolchain" +TARGET=x86_64-linux-musl +ARCH=x86_64 +export CFLAGS='-Os -pipe' +export CXXFLAGS="$CFLAGS" +export PATH="$PREFIX/bin:$PATH" +export MAKEFLAGS="-j$(nproc)" + +mkdir -p "$PREFIX/src" +( cd "$PREFIX/src" + wget -c https://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.xz + wget -c https://ftp.gnu.org/gnu/gcc/gcc-8.1.0/gcc-8.1.0.tar.xz + wget -c https://ftp.gnu.org/gnu/gmp/gmp-6.1.2.tar.xz + wget -c https://ftp.gnu.org/gnu/mpfr/mpfr-4.0.1.tar.xz + wget -c https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz + wget -c http://isl.gforge.inria.fr/isl-0.19.tar.xz + wget -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.16.7.tar.xz + wget -c https://www.musl-libc.org/releases/musl-1.1.19.tar.gz + wget -c http://sigrok.org/download/source/libserialport/libserialport-0.1.1.tar.gz +) + +( cd "$PREFIX/src" + rm -rf binutils-*/ + tar xf binutils-*.tar.xz + cd binutils-*/ + mkdir build + cd build + ../configure --prefix="$PREFIX" --target="$TARGET" --disable-nls --disable-multilib + make + make install +) + +( cd "$PREFIX/src" + rm -rf gcc-*/ + tar xf gcc-*.tar.xz + cd gcc-*/ + tar xf ../gmp-*.tar.xz + tar xf ../mpfr-*.tar.xz + tar xf ../mpc-*.tar.gz + tar xf ../isl-*.tar.xz + mv gmp-*/ gmp + mv mpfr-*/ mpfr + mv mpc-*/ mpc + mv isl-*/ isl + mkdir build + cd build + ../configure --prefix="$PREFIX" --target="$TARGET" --disable-nls --disable-multilib --disable-libsanitizer --disable-libmpx --enable-languages=c,c++ + make all-gcc + make install-gcc +) + +( cd "$PREFIX/src" + rm -rf linux-*/ + tar xf linux-*.tar.xz + cd linux-*/ + make ARCH="$ARCH" INSTALL_HDR_PATH="$PWD/install" headers_install + find install -type f -a ! -name '*.h' -delete + cp -aT install/include "$PREFIX/$TARGET/include" +) + +( cd "$PREFIX/src" + rm -rf musl-*/ + tar xf musl-*.tar.gz + cd musl-*/ + ./configure --prefix="$PREFIX/$TARGET" --host="$TARGET" --syslibdir='$(prefix)/lib' + make install-headers +) + +( cd "$PREFIX/src" + cd gcc-*/build + make MAKE='make enable_shared=no' all-target-libgcc + make MAKE='make enable_shared=no' install-target-libgcc +) + +( cd "$PREFIX/src" + cd musl-*/ + make LIBCC="$PREFIX/lib/gcc/$TARGET/*/libgcc.a" install +) + +( cd "$PREFIX/src" + cd gcc-*/build + make + make install +) + +( cd "$PREFIX/src" + rm -rf libserialport-*/ + tar xf libserialport-*.tar.gz + cd libserialport-*/ + ./configure --prefix="$PREFIX/$TARGET" --host="$TARGET" + make + make install +) + +( cd "$PREFIX/src" + rm -rf binutils-*/ + rm -rf gcc-*/ + rm -rf linux-*/ + rm -rf musl-*/ + rm -rf libserialport-*/ + #find "$PREFIX" -type f -name '*.la' -delete +) + +cat > "$PREFIX/env" << EOF +export PATH="$PREFIX/bin:\$PATH" +EOF + +echo 'Run "source toolchain/env" before running make!'