├── .gitignore ├── License.md ├── Makefile ├── README.md ├── assets ├── logo16x16.png ├── logo8x8.png ├── logobanner.png ├── spriteSheet.png ├── superTestBoy.gb └── textMap.png ├── config.h ├── controller.c ├── controller.h ├── core.c ├── core.h ├── cpu.c ├── cpu.h ├── debug.c ├── debug.h ├── docs ├── TransferPakReference.md └── rspPpuRundown ├── eeprom.c ├── eeprom.h ├── emu.c ├── emu.h ├── fps.c ├── fps.h ├── gbc_state.c ├── gbc_state.h ├── gbz80ops.c ├── gbz80ops.h ├── gbz80ops.mips ├── global.h ├── hwdefs.h ├── init.c ├── init.h ├── lcd.c ├── lcd.h ├── link.c ├── link.h ├── logger.c ├── logger.h ├── menu.c ├── menu.h ├── mksprite.sh ├── mmu.c ├── mmu.h ├── options.c ├── options.h ├── play.c ├── play.h ├── polyfill.c ├── ppu.c ├── ppu.h ├── progressBar.c ├── progressBar.h ├── resources.c ├── resources.h ├── rsp.c ├── rsp.h ├── rsp ├── Makefile ├── init.rsp ├── old_rdp.rsp ├── old_rspIncludes.rsp ├── ppu ├── ppuDMG.rsp ├── ppuGBC.rsp ├── ppuSGB.rsp ├── rdp.rsp ├── renderer.rsp ├── rspIncludes.rsp └── vectorShifts.rsp ├── rtc.c ├── rtc.h ├── screen.c ├── screen.h ├── sgbDefs.h ├── sound.c ├── sound.h ├── state.c ├── state.h ├── superGameboy.c ├── superGameboy.h ├── text.c ├── text.h ├── tpakio.c ├── tpakio.h ├── transferboy.c ├── transferboy.ld ├── transferboy.mips ├── transferboy.z64 └── types.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | filesystem/ 4 | .git 5 | *.cbp 6 | *.depend 7 | *.layout 8 | *.elf 9 | *.dfs 10 | *.bin 11 | *.z64 12 | *.data 13 | *.text 14 | !transferboy64.z64 15 | !transferboy.z64 16 | transferboy_generated.ld 17 | test.sh 18 | *.o 19 | *~ 20 | *.sym 21 | *.eeprom 22 | *.exe 23 | *.dll 24 | *.dsm 25 | .vscode/* -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Joel Roberts All Rights Reserved 2 | 3 | The above notice does not apply to the following, as they are not my original work: 4 | * The Makefile - Adapted from Libdragon, which is released under the Unlicense https://github.com/DragonMinded/libdragon/blob/master/LICENSE.md 5 | * The following files, which were adapted from koenk's gbc emulator, under the MIT license https://github.com/koenk/gbc/blob/master/LICENSE 6 | * cpu.c/h 7 | * emu.c/h 8 | * lcd.c/h 9 | * hwdefs.c 10 | * mmu.c/h 11 | * gbc_state.h 12 | * gbz80ops.c 13 | 14 | * fps.c/h, which comes under the apache license. 15 | 16 | * assets/textMap.png Which contains a font that is Licensed as Freeware https://www.fontspace.com/help/#license-3 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | N64_INST = /home/joeldipops/Projects/tools/n64inst 2 | ROOTDIR = $(N64_INST) 3 | GCCN64PREFIX = $(ROOTDIR)/bin/mips64-elf- 4 | CHKSUM64PATH = /home/joeldipops/Projects/tools/libdragon/tools/chksum64 5 | MKDFSPATH = /home/joeldipops/Projects/tools/libdragon/tools/mkdfs/mkdfs 6 | HEADERPATH = $(ROOTDIR)/mips64-elf/lib 7 | N64TOOL = /home/joeldipops/Projects/tools/libdragon/tools/n64tool 8 | HEADERNAME = header 9 | PROG_NAME = transferboy 10 | 11 | PRE_LD_FILE = $(PROG_NAME).ld 12 | LD_FILE = $(PROG_NAME)_generated.ld 13 | LINK_FLAGS = -L$(ROOTDIR)/mips64-elf/lib -ldragon -lm -lc -ldragonsys -T./$(LD_FILE) 14 | 15 | #O3FLAGS = -fgcse-after-reload -finline-functions -fipa-cp-clone -floop-interchange -floop-unroll-and-jam -fpredictive-commoning -fsplit-paths -ftree-loop-distribute-patterns -ftree-loop-distribution -ftree-loop-vectorize -ftree-partial-pre -ftree-slp-vectorize -funswitch-loops -fvect-cost-model -fversion-loops-for-strides 16 | OPTIMISATION_FLAGS = -O3 17 | CFLAGS = -std=gnu99 -march=vr4300 -mtune=vr4300 $(OPTIMISATION_FLAGS) -Wall -Wno-unused -Werror -I$(CURDIR) -I$(ROOTDIR)/mips64-elf/include 18 | ASFLAGS = -mtune=vr4300 -march=vr4300 19 | CC = $(GCCN64PREFIX)gcc 20 | AS = $(GCCN64PREFIX)as 21 | LD = $(GCCN64PREFIX)ld 22 | 23 | OBJCOPY = $(GCCN64PREFIX)objcopy 24 | 25 | ifeq ($(N64_BYTE_SWAP),true) 26 | ROM_EXTENSION = .v64 27 | N64_FLAGS = -b -l 2M -h $(HEADERPATH)/$(HEADERNAME) -o $(PROG_NAME)$(ROM_EXTENSION) $(PROG_NAME).bin 28 | else 29 | ROM_EXTENSION = .z64 30 | N64_FLAGS = -l 2M -h $(HEADERPATH)/$(HEADERNAME) -o $(PROG_NAME)$(ROM_EXTENSION) $(PROG_NAME).bin 31 | endif 32 | 33 | all: $(PROG_NAME)$(ROM_EXTENSION) $(PROG_NAME).mips 34 | 35 | $(CURDIR)/rsp/ppuDMG.o: 36 | make -C $(CURDIR)/rsp rsp 37 | 38 | $(CURDIR)/rsp/renderer.o: 39 | make -C $(CURDIR)/rsp rsp 40 | 41 | $(PROG_NAME)$(ROM_EXTENSION): $(PROG_NAME).elf $(PROG_NAME).dfs 42 | $(OBJCOPY) $(PROG_NAME).elf $(PROG_NAME).bin -O binary 43 | rm -f $(PROG_NAME)$(ROM_EXTENSION) 44 | $(N64TOOL) $(N64_FLAGS) -t "$(PROG_NAME)" -s 1M $(PROG_NAME).dfs 45 | $(CHKSUM64PATH) $(PROG_NAME)$(ROM_EXTENSION) 46 | 47 | LD_OFILES = $(CURDIR)/obj/core.o 48 | LD_OFILES += $(CURDIR)/obj/debug.o 49 | LD_OFILES += $(CURDIR)/obj/ppu.o 50 | LD_OFILES += $(CURDIR)/obj/progressBar.o 51 | LD_OFILES += $(CURDIR)/obj/fps.o 52 | LD_OFILES += $(CURDIR)/obj/resources.o 53 | LD_OFILES += $(CURDIR)/obj/init.o 54 | LD_OFILES += $(CURDIR)/obj/play.o 55 | LD_OFILES += $(CURDIR)/obj/menu.o 56 | LD_OFILES += $(CURDIR)/obj/options.o 57 | LD_OFILES += $(CURDIR)/obj/logger.o 58 | LD_OFILES += $(CURDIR)/obj/text.o 59 | LD_OFILES += $(CURDIR)/obj/controller.o 60 | LD_OFILES += $(CURDIR)/obj/screen.o 61 | LD_OFILES += $(CURDIR)/obj/state.o 62 | LD_OFILES += $(CURDIR)/obj/eeprom.o 63 | LD_OFILES += $(CURDIR)/obj/link.o 64 | LD_OFILES += $(CURDIR)/obj/sound.o 65 | LD_OFILES += $(CURDIR)/obj/tpakio.o 66 | LD_OFILES += $(CURDIR)/obj/superGameboy.o 67 | LD_OFILES += $(CURDIR)/obj/cpu.o 68 | LD_OFILES += $(CURDIR)/obj/emu.o 69 | LD_OFILES += $(CURDIR)/obj/lcd.o 70 | LD_OFILES += $(CURDIR)/obj/mmu.o 71 | LD_OFILES += $(CURDIR)/obj/c_gbz80ops.o 72 | LD_OFILES += $(CURDIR)/obj/s_gbz80ops.o 73 | LD_OFILES += $(CURDIR)/obj/gbc_state.o 74 | LD_OFILES += $(CURDIR)/obj/polyfill.o 75 | LD_OFILES += $(CURDIR)/obj/rtc.o 76 | LD_OFILES += $(CURDIR)/obj/rsp.o 77 | 78 | # Produces the disassembly, with symbols included. 79 | $(PROG_NAME).dsm: $(PROG_NAME).elf 80 | mips-linux-gnu-objdump $(PROG_NAME).elf -m mips -D > $(PROG_NAME).dsm 81 | 82 | $(PROG_NAME).elf : $(CURDIR)/rsp/ppuDMG.o $(CURDIR)/rsp/ppuDMGData.o $(PROG_NAME).o $(LD_FILE) 83 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/debug.o $(CURDIR)/debug.c 84 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/ppu.o $(CURDIR)/ppu.c 85 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/progressBar.o $(CURDIR)/progressBar.c 86 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/fps.o $(CURDIR)/fps.c 87 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/rtc.o $(CURDIR)/rtc.c 88 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/polyfill.o $(CURDIR)/polyfill.c 89 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/core.o $(CURDIR)/core.c 90 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/resources.o $(CURDIR)/resources.c 91 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/init.o $(CURDIR)/init.c 92 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/play.o $(CURDIR)/play.c 93 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/menu.o $(CURDIR)/menu.c 94 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/options.o $(CURDIR)/options.c 95 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/logger.o $(CURDIR)/logger.c 96 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/text.o $(CURDIR)/text.c 97 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/screen.o $(CURDIR)/screen.c 98 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/state.o $(CURDIR)/state.c 99 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/eeprom.o $(CURDIR)/eeprom.c 100 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/link.o $(CURDIR)/link.c 101 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/sound.o $(CURDIR)/sound.c 102 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/tpakio.o $(CURDIR)/tpakio.c 103 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/superGameboy.o $(CURDIR)/superGameboy.c 104 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/controller.o $(CURDIR)/controller.c 105 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/c_gbz80ops.o $(CURDIR)/gbz80ops.c 106 | $(CC) $(CFLAGS) -c -x assembler-with-cpp \ 107 | -o $(CURDIR)/obj/s_gbz80ops.o $(CURDIR)/gbz80ops.mips 108 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/cpu.o $(CURDIR)/cpu.c 109 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/emu.o $(CURDIR)/emu.c 110 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/lcd.o $(CURDIR)/lcd.c 111 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/mmu.o $(CURDIR)/mmu.c 112 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/gbc_state.o $(CURDIR)/gbc_state.c 113 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/rsp.o $(CURDIR)/rsp.c 114 | $(CC) $(CFLAGS) -c -o $(CURDIR)/obj/transferboy.o $(CURDIR)/transferboy.c 115 | 116 | $(LD) -o $(PROG_NAME).elf $(CURDIR)/rsp/ppuDMG.o $(CURDIR)/rsp/ppuDMGData.o $(CURDIR)/rsp/renderer.o $(CURDIR)/obj/$(PROG_NAME).o $(LD_OFILES) $(LINK_FLAGS) 117 | 118 | $(LD_FILE) : $(PRE_LD_FILE) 119 | cpp $(PRE_LD_FILE) | grep -v '^#' >>$(LD_FILE) 120 | 121 | $(PROG_NAME).dfs: 122 | ./mksprite.sh 123 | cp ./assets/*.gb ./filesystem/ 124 | $(MKDFSPATH) $(PROG_NAME).dfs ./filesystem/ 125 | 126 | clean: 127 | rm -f *.v64 *.z64 *.elf *.o *.bin *.dfs $(LD_FILE) 128 | rm -f ./obj/*.o 129 | $(MAKE) -C ./rsp/ clean 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TransferBoy 2 | Game Boy Emulator for N64 using Transfer Pak. "The SuperGameBoy64 Nintendo never made" 3 | Core emulation logic wasn't written by me but adapted from https://github.com/koenk/gbc by koenk 4 | 5 | Runs on https://github.com/DragonMinded/libdragon by DragonMinded. 6 | 7 | Other than those, credit to 8 | * saturnu and https://github.com/saturnu/libgbpak 9 | * Rasky, for these macros https://gist.github.com/rasky/d41e1ca2150b3274151a6fe7c76c0f2f 10 | * vieux - I borrowed some of his code ffrom https://github.com/vieux/Memory64-N64 11 | * AntonioND for "The Cycle Accurate Game Boy Docs" - https://github.com/AntonioND/giibiiadvance/tree/master/docs 12 | * Gekkio for https://github.com/Gekkio/mooneye-gb/tree/master/tests/emulator-only and https://gekkio.fi/files/gb-docs/gbctr.pdf 13 | * Anyone who worked on implementing the Transfer Pak in cen64 14 | * imanoleas for their breakdown of Super Gameboy communication https://imanoleasgames.blogspot.com/2016/12/games-aside-1-super-game-boy.html 15 | * The maintainers of https://github.com/gbdev/awesome-gbdev and pretty much anyone who has contributed to anything linked to from that page. 16 | 17 | Font is Polygon Party https://fonts2u.com/polygon-party.font 18 | 19 | For anyone that stumbles across this and feels like trying it out, beware that while it does successfully read in ROMs from the T-Pak and "emulate" the gameboy, it currently "runs" at about 10 frames per second, (slightly faster if you don't bother rending half the frames) not to mention a bunch of graphical issues and just plain ugliness that I've decided not to fix until I get performance under control. 20 | 21 | In parallel I'm writing **SuperTestBoy** a game boy ROM intended to exercise each of the Super Gameboy features to make sure they are implemented as correctly as possible in Transfer Boy 22 | 23 | I will try to put any updates on my twitter https://twitter.com/joeldipops 24 | -------------------------------------------------------------------------------- /assets/logo16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/assets/logo16x16.png -------------------------------------------------------------------------------- /assets/logo8x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/assets/logo8x8.png -------------------------------------------------------------------------------- /assets/logobanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/assets/logobanner.png -------------------------------------------------------------------------------- /assets/spriteSheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/assets/spriteSheet.png -------------------------------------------------------------------------------- /assets/superTestBoy.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/assets/superTestBoy.gb -------------------------------------------------------------------------------- /assets/textMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/assets/textMap.png -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_INCLUDED 2 | #define CONFIG_INCLUDED 3 | #include "core.h" 4 | 5 | 6 | static const bool VALIDATE_CHECKSUMS = false; 7 | // If this is true, carts above a certain size cannot be loaded. Should only be false for debugging purposes. 8 | static const bool RESERVE_WORKING_MEMORY = true; 9 | static const bool USE_ANTIALIASING = false; 10 | static const bool HIDE_DISABLED_OPTIONS = true; 11 | 12 | //#define FRAMES_TO_SKIP 1 13 | #define SHOW_FRAME_COUNT 1 14 | // Abysmal performance aside, 2 player mode is fundamentally broken until the RSP knows how to handle it. 15 | #define MAX_PLAYERS 1 16 | //#define LOCK_CPU_TO_PPU 1 17 | //#define IS_AUDIO_ENABLED 1 18 | //#define IS_SGB_ENABLED 1 19 | //#define IS_PROFILING 1 20 | 21 | // Compile with debug features. 22 | //#define IS_DEBUGGING 1 23 | // Is currently in debug mode. 24 | static bool IsDebugging = false; 25 | 26 | // Current implementation of double buffering is slow as fuck, so leaving it out for now while 27 | // I track down other performance bottlenecks. 28 | //#define IS_DOUBLE_BUFFERED 29 | 30 | 31 | #endif -------------------------------------------------------------------------------- /controller.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "core.h" 3 | #include "controller.h" 4 | 5 | 6 | const sByte STICK_DEADZONE = 42; 7 | 8 | /** 9 | * Converts the libdragon controller_data structure in to an array indexed by button indicating which are pressed. 10 | * @param input The controller object from libdragon. 11 | * @param controllerNumber The controller we want to know the buttons from. 12 | * @out Array of pressed buttons. 13 | */ 14 | void getPressedButtons(const N64ControllerState* input, const byte controllerNumber, bool* output) { 15 | output[NoButton] = false; 16 | output[A] = true && input->c[controllerNumber].A; 17 | output[B] = true && input->c[controllerNumber].B; 18 | output[L] = true && input->c[controllerNumber].L; 19 | output[R] = true && input->c[controllerNumber].R; 20 | output[Z] = true && input->c[controllerNumber].Z; 21 | output[Start] = true && input->c[controllerNumber].start; 22 | output[DUp] = true && input->c[controllerNumber].up; 23 | output[DDown] = true && input->c[controllerNumber].down; 24 | output[DLeft] = true && input->c[controllerNumber].left; 25 | output[DRight] = true && input->c[controllerNumber].right; 26 | output[CUp] = true && input->c[controllerNumber].C_up; 27 | output[CDown] = true && input->c[controllerNumber].C_down; 28 | output[CLeft] = true && input->c[controllerNumber].C_left; 29 | output[CRight] = true && input->c[controllerNumber].C_right; 30 | 31 | sByte x = input->c[controllerNumber].x; 32 | if (x < STICK_DEADZONE * -1) { 33 | output[StickLeft] = true; 34 | } else if (x > STICK_DEADZONE) { 35 | output[StickRight] = true; 36 | } else { 37 | output[StickLeft] = false; 38 | output[StickRight] = false; 39 | } 40 | 41 | sByte y = input->c[controllerNumber].y; 42 | if (y < STICK_DEADZONE * -1) { 43 | output[StickDown] = true; 44 | } else if (y > STICK_DEADZONE) { 45 | output[StickUp] = true; 46 | } else { 47 | output[StickUp] = false; 48 | output[StickDown] = false; 49 | } 50 | 51 | output[Up] = (output[StickUp] || output[DUp]) && !(output[StickDown] || output[DDown]); 52 | output[Down] = (output[StickDown] || output[DDown]) && !(output[StickUp] || output[DUp]); 53 | output[Left] = (output[StickLeft] || output[DLeft]) && !(output[StickRight] || output[DRight]); 54 | output[Right] = (output[StickRight] || output[DRight]) && !(output[StickLeft] || output[DLeft]); 55 | } -------------------------------------------------------------------------------- /controller.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLER_INCLUDED 2 | #define CONTROLLER_INCLUDED 3 | 4 | #include "core.h" 5 | #include 6 | 7 | #define N64_BUTTON_COUNT 23 8 | 9 | typedef enum { 10 | NoButton = 0, 11 | A, B, L, R, Z, Start, DUp, DDown, DLeft, DRight, CUp, CDown, CLeft, CRight, StickUp, StickDown, StickLeft, StickRight, 12 | Up, Down, Left, Right // Either stick or d-pad. StickLeft & DRight will cancel each other out. 13 | } N64Button; 14 | 15 | typedef enum { 16 | GbNoButton = 0, 17 | GbA, GbB, GbUp, GbDown, GbLeft, GbRight, GbStart, GbSelect, GbSystemMenu 18 | } GbButton; 19 | 20 | typedef struct controller_data N64ControllerState; 21 | 22 | /** 23 | * Converts the libdragon controller_data structure in to an array indexed by button indicating which are pressed. 24 | * @param input The controller object from libdragon. 25 | * @param controllerNumber The controller we want to know the buttons from. 26 | * @out Array of pressed buttons. 27 | */ 28 | void getPressedButtons(const N64ControllerState* input, const byte controllerNumber, bool* output); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /core.c: -------------------------------------------------------------------------------- 1 | #include "core.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | uLong totalEstimatedMemory = 0; 8 | 9 | /** 10 | * Calculates the largest block of memory that can be allocated (give or take) at start up 11 | * to understand how much memory is available overall 12 | * @return The estimated size of the memory space. 13 | */ 14 | uLong getMemoryLimit() { 15 | if (!totalEstimatedMemory) { 16 | totalEstimatedMemory = getCurrentMemory(); 17 | } 18 | return totalEstimatedMemory; 19 | } 20 | 21 | /** 22 | * Calculates the largest block of memory that can currently be allocated (give or take) 23 | * @return The estimates amount of remaining memory. 24 | */ 25 | uLong getCurrentMemory() { 26 | uLong limit = 16000000; 27 | 28 | void* freeMe = 0; 29 | while (!(freeMe = malloc(limit))) { 30 | limit /= 1.01; 31 | } 32 | free(freeMe); 33 | 34 | return limit; 35 | } 36 | 37 | /** 38 | * Cleans up memory held by a ByteArray and resets the values. 39 | * @param arr The array. 40 | */ 41 | void freeByteArray(ByteArray* arr) { 42 | free(arr->Data); 43 | arr->Data = 0; 44 | arr->Size = 0; 45 | } 46 | 47 | /** 48 | * Reads a number from a substring, stopping after a certain number of characters. 49 | * @param start pointer to the start of the number in the string. 50 | * @param maxLength max length of the number as characters. 51 | * @param base base of the number eg. 10, 16 52 | * @return The parsed byte. 53 | */ 54 | byte parseByte(const char* start, const byte maxLength, const byte base) { 55 | // sprite is 2 digit hex, we need to parse it from the string. 56 | string code = {}; 57 | memcpy(code, start, maxLength); 58 | char* end = 0; 59 | return strtol(code, &end, base); 60 | } 61 | 62 | /** 63 | * Allocates memory aligned to a given number of bytes. 64 | * @param size Size of memory to allocate. 65 | * @param alignment Number of bytes to align to. 66 | * @returns struct where p is your aligned pointer. 67 | */ 68 | AlignedPointer malloc_aligned(size_t size, byte alignment) { 69 | byte offset = alignment - 1; 70 | AlignedPointer result; 71 | result.a = malloc(size + offset); 72 | result.p = (void*)(((uintptr_t)result.a + offset) & ~(uintptr_t)offset); 73 | 74 | return result; 75 | } 76 | 77 | /** 78 | * Frees memory allocated with malloc_aligned 79 | * @param ptr Holds pointer to memory to be freed. 80 | */ 81 | void free_aligned(AlignedPointer ptr) { 82 | free(ptr.a); 83 | } 84 | -------------------------------------------------------------------------------- /core.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_INCLUDED 2 | #define CORE_INCLUDED 3 | 4 | #include 5 | 6 | #define null 0 7 | 8 | typedef uint8_t byte; 9 | typedef int8_t sByte; 10 | typedef int16_t sShort; 11 | typedef uint16_t natural; 12 | typedef int32_t sInt; 13 | typedef uint32_t uInt; 14 | typedef uint64_t uLong; 15 | 16 | typedef char string[128]; 17 | 18 | natural GLOBAL_BACKGROUND_COLOUR; 19 | natural GLOBAL_TEXT_COLOUR; 20 | natural SELECTED_MENU_ITEM_COLOUR; 21 | natural SELECTED_OPTIONS_ITEM_COLOUR; 22 | static const uInt AUDIO_SAMPLE_RATE = 44100; 23 | 24 | typedef struct { 25 | uLong Size; 26 | byte* Data; 27 | } ByteArray; 28 | 29 | typedef enum { Quit, Init, Play, Menu, Options } Mode; 30 | typedef enum { BorderNone } Border; 31 | 32 | /** 33 | * Reads a number from a substring, stopping after a certain number of characters. 34 | * @param start pointer to the start of the number in the string. 35 | * @param maxLength max length of the number as characters. 36 | * @param base base of the number eg. 10, 16 37 | * @return The parsed byte. 38 | */ 39 | byte parseByte(const char* start, const byte maxLength, const byte base); 40 | 41 | /** 42 | * Cleans up memory held by a ByteArray and resets the values. 43 | * @param arr The array. 44 | */ 45 | void freeByteArray(ByteArray* arr); 46 | 47 | /** 48 | * Calculates the largest block of memory that can be allocated (give or take) at start up 49 | * to understand how much memory is available overall 50 | * @return The estimated size of the memory space. 51 | */ 52 | uLong getMemoryLimit(); 53 | 54 | /** 55 | * Calculates the largest block of memory that can currently be allocated (give or take) 56 | * @return The estimates amount of remaining memory. 57 | */ 58 | uLong getCurrentMemory(); 59 | 60 | /** 61 | * There's no threading in libdragon yet, so we're just gonna busy-wait until 62 | * that becomes a terrible idea. 63 | */ 64 | unsigned int sleep(unsigned int seconds); 65 | 66 | typedef struct { 67 | void* p; 68 | void* a; 69 | } AlignedPointer; 70 | 71 | /** 72 | * Allocates memory aligned to a given number of bytes. 73 | * @param size Size of memory to allocate. 74 | * @param alignment Number of bytes to align to. 75 | * @returns struct where p is your aligned pointer. 76 | */ 77 | AlignedPointer malloc_aligned(size_t size, byte alignment); 78 | 79 | /** 80 | * Frees memory allocated with malloc_aligned 81 | * @param ptr Holds pointer to memory to be freed. 82 | */ 83 | void free_aligned(AlignedPointer ptr); 84 | 85 | typedef enum { 86 | PROFILE_JUNK, // 0 87 | PROFILE_C_SCAN, // 1 88 | PROFILE_CPU, // 2 89 | PROFILE_LCD, // 3 90 | PROFILE_MMU, // 4 91 | PROFILE_TIMERS, // 5 92 | PROFILE_INTERRUPTS, // 6 93 | PROFILE_DEVICE, // 7 94 | PROFILE_DRAW // 8 95 | } ProfileSection; 96 | #endif 97 | -------------------------------------------------------------------------------- /cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef GBC_H 2 | #define GBC_H 3 | 4 | #include "types.h" 5 | 6 | void cpu_init_emu_cpu_state(GbState *s); 7 | void cpu_reset_state(GbState *s); 8 | void cpu_step(GbState *s); 9 | void cpu_handle_interrupts(GbState *s); 10 | 11 | /** 12 | * Updates the timer registers and sets the timer interrupt when in GBC double speed mode. 13 | */ 14 | void timersStepDouble(GbState* s); 15 | 16 | /** 17 | * Updates the timer registers and sets the timer interrupt. 18 | */ 19 | void timersStep(GbState* s); 20 | 21 | /** 22 | * Called the cycle after the timer overflows. Sets the interrupt flag to actually trigger. 23 | * Then continues with the timer step as normal. 24 | */ 25 | void timersStepPendingInterrupt(GbState* s); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "debug.h" 4 | #include "state.h" 5 | #include "logger.h" 6 | #include 7 | 8 | void startDebugging() { 9 | IsDebugging = true; 10 | } 11 | 12 | void stopDebugging() { 13 | IsDebugging = false; 14 | } 15 | 16 | #ifdef IS_DEBUGGING 17 | 18 | static long offset = 0; 19 | 20 | // This is SRAM, which is only 0x8000 bytes. I know I can write to the entire cart-space, but I don't know if CEN64 can give me the related log file. 21 | #define LOGFILE = 0xA8000000; 22 | 23 | /** 24 | * If in debugging mode, print gb registers and message then wait for input. 25 | */ 26 | void debug(GbState *s, string message) { 27 | if (IsDebugging) { 28 | u32 length = strlen(message); 29 | dma_write(message, 0xA8000000 + offset, length); 30 | offset += length; 31 | } 32 | } 33 | 34 | #endif 35 | 36 | #ifdef IS_PROFILING 37 | 38 | #define MAX_ENTRIES 64 39 | 40 | static ProfileEntry entries[MAX_ENTRIES]; 41 | static long long lastTime = 0; 42 | 43 | /** 44 | * Reset all profile information back to 0 to start a new profiling session. 45 | */ 46 | void resetProfile() { 47 | memset(&entries, 0, sizeof(entries)); 48 | lastTime = timer_ticks(); 49 | } 50 | 51 | /** 52 | * Update profile data for a given profile section. 53 | * @param id Identifies the profile section. 54 | */ 55 | void updateProfile(int id) { 56 | long long now = timer_ticks(); 57 | entries[id].sum += now - lastTime; 58 | entries[id].count++; 59 | lastTime = now; 60 | } 61 | 62 | /** 63 | * Prints out all available profile data. 64 | */ 65 | void displayProfile() { 66 | for (int i = 0; i < MAX_ENTRIES; i++) { 67 | if (entries[i].count) { 68 | long avg = entries[i].sum / entries[i].count; 69 | string line = ""; 70 | sprintf(line, "%d: %lu ticks", i, avg); 71 | graphics_draw_text(1, 30, i * 20, line); 72 | graphics_draw_text(2, 30, i * 20, line); 73 | } 74 | } 75 | while(true); 76 | } 77 | #endif -------------------------------------------------------------------------------- /debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_INCLUDED 2 | #define DEBUG_INCLUDED 3 | 4 | #include "config.h" 5 | #include "state.h" 6 | 7 | void startDebugging(); 8 | void stopDebugging(); 9 | void isDebugging(); 10 | 11 | /** 12 | * Print current instruction for debugging. 13 | */ 14 | void debug(GbState *s, string message); 15 | 16 | #ifdef IS_PROFILING 17 | 18 | #include 19 | 20 | #define UPDATE_PROFILE(id) updateProfile(id); 21 | #define INIT_PROFILE resetProfile(); 22 | #define DISPLAY_PROFILE displayProfile(); 23 | typedef struct { 24 | int id; 25 | long sum; 26 | long count; 27 | } ProfileEntry; 28 | 29 | /** 30 | * Update profile data for a given profile section. 31 | * @param id Identifies the profile section. 32 | */ 33 | void updateProfile(int id); 34 | 35 | /** 36 | * Reset all profile information back to 0 to start a new profiling session. 37 | */ 38 | void resetProfile(); 39 | 40 | /** 41 | * Prints out all available profile data. 42 | */ 43 | void displayProfile(); 44 | 45 | #else 46 | #define UPDATE_PROFILE(id) 47 | #define INIT_PROFILE 48 | #define DISPLAY_PROFILE 49 | #endif 50 | #endif -------------------------------------------------------------------------------- /docs/rspPpuRundown: -------------------------------------------------------------------------------- 1 | This document explains what was involved so far in my attempt to emulate the Gameboy's Pixel Processining Unit (PPU) on the N64 RSP. 2 | 3 | **CPU** 4 | Once the CPU has loaded the RSP script in to IMEM, it them provides it with a bunch of memory addresses that the RSP will need, such as the location of 5 | the Gameboy's VRAM, and the address of the N64's Frame Buffer. Currently it does that the really dodgy way of using a linker script to enforce a 6 | shared memory address where once-off configuration can DMA'd from. Turns out that the CPU can just write directly to DMEM, so sooner or 7 | later I'll fix it to just do that. 8 | 9 | When the emulation starts, the CPU then sets the flag to start the RSP running and tracks the PPU mode until it gets to H-Blank. 10 | Any time VRAM is written to, I set a flag. Come H-Blank, I need to make sure that D-Cache is written back to DRAM so that the RSP can DMA in 11 | the most recent values in OAM, VRAM and the IO registers. For OAM and HRAM this doesn't make much of a splash, but VRAM is quite a big chunk of 12 | memory and can take time, so the flag ensures I only do the cache write-back when necessary. 13 | After that, I can wait for the RSP to finish rendering the previous line by checking the semaphores at 0xA4040000, then tell it that the CPU has 14 | hit H-Blank by setting a different semaphore at that address. 15 | 16 | Oh the CPU also raises/lowers another semaphore to indicate which of the two frame buffers the RSP/RDP should be writing to. 17 | 18 | **RSP** 19 | What I've got here is a long way from being fully optimised, but hopefully it gives you some ideas. 20 | 21 | So the CPU ensures that the RSP knows where to find the VRAM, I/O registers and OAM of the emulated gameboy, as well as an address set aside in DRAM to write data to 22 | for the RDP to use and the address of the two frame buffers. The CPU also lets the RSP know whenever it's in H-Blank. 23 | 24 | The RSP waits for H-Blank and then DMAs in all the relevant I/O registers and caches a few of the most often used values in registers 25 | 26 | Next, it DMAs in the section of the background tile map in VRAM representing the current scan-line and one by one DMAs each tile that would appear on that line (adjusting for SCX etc.) 27 | and puts the data that would appear on that scan-line in to a buffer. 28 | 29 | Next up is where the Vector unit comes in to play. The gameboy tile data is stored as abcdefgh ABCDEFGH but for the N64 to do anything with those aB bits it needs them next to 30 | each other ie aAbBcCdD eEfFgGhH. So I load the same 16bytes from the buffer in to 8 of the vector registers and shift the bits as needed. This lets me shift the bits of 8 pixels at a time 31 | eg. $v01 shifts the bits so that you end up with a register that contains 0b000000a A00000000... for 8 pixels and $v02 ends up as 0b0000000b B0000000... 32 | Then I put the data back in to memory using the suv instruction which only takes bits 14-7. 33 | 34 | Iterate through the bits using an index to determine the correct place in the output buffer, translating to colour palette values as appropriate, 35 | and setting a bit if the colour was in position 0 for determining transparency. 36 | 37 | Do more or less the same thing again for the Window and something similar for any sprites that appear on that scan line 38 | (though I haven't figured out how to use the VU to speed up the sprites yet). 39 | 40 | Now that I have a full scan-line of pixels, we DMA this back to DRAM and now it's the RDP's turn to take over. 41 | 42 | **RDP** 43 | The RSP sends commands for the RDP to do the following 44 | 45 | * Set the appropriate output frame buffer address (SetColorImage) 46 | * Set the location that we DMA'd the scan-line buffer to as the RDPs texture location. (SetTextureImage) 47 | * Set up a tile to draw the appropriate size, various other options. (SetTile) 48 | * Load the scan line buffer in to TMEM (LoadTile) 49 | * Draw the scan line (TextureRectangle) 50 | 51 | (For the DMG implementation at least, I need to figure out how to use TLUT as well to save a few instructins translating 2bpp into n64 colours.) 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /eeprom.c: -------------------------------------------------------------------------------- 1 | #include "eeprom.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static byte cursorPosition = 0; 7 | static const byte EEPROM_BLOCKS = 64; 8 | static const byte EEPROM_BLOCK_SIZE = 8; 9 | 10 | /** 11 | * Gets the next available eeprom block. 12 | * @return open eeprom block. 13 | */ 14 | byte getEepromCursorPosition() { 15 | if (cursorPosition >= EEPROM_BLOCKS) { 16 | cursorPosition = 0; 17 | } 18 | return cursorPosition; 19 | } 20 | 21 | /** 22 | * Writes to eeprom 23 | * @param block The 8-byte block of memory to start with. 24 | * @param data The data to write. 25 | * @return 0 if successful, otherwise an sub-zero error code. 26 | */ 27 | sByte writeToEeprom(const byte blockNumber, const ByteArray* stream) { 28 | if (!eeprom_present()) { 29 | return -1; 30 | } 31 | 32 | natural index = 0; 33 | 34 | while(index <= stream->Size / EEPROM_BLOCK_SIZE) { 35 | natural start = index * EEPROM_BLOCK_SIZE; 36 | natural end = start + EEPROM_BLOCK_SIZE; 37 | byte* block = calloc(EEPROM_BLOCK_SIZE, sizeof(byte)); 38 | if (end <= stream->Size) { 39 | memcpy(block, stream->Data + start, EEPROM_BLOCK_SIZE); 40 | } else { 41 | // pad out the rest of the block with zeroes. 42 | byte diff = end - stream->Size; 43 | memcpy(block, stream->Data + start, EEPROM_BLOCK_SIZE - diff); 44 | } 45 | 46 | eeprom_write(blockNumber + index, block); 47 | free(block); 48 | block = null; 49 | 50 | index++; 51 | } 52 | 53 | cursorPosition += index; 54 | return 0; 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /eeprom.h: -------------------------------------------------------------------------------- 1 | #ifndef EEPROM_DEFINED 2 | #define EEPROM_DEFINED 3 | 4 | #include "core.h" 5 | 6 | /** 7 | * Gets the next available eeprom block. 8 | * @return open eeprom block. 9 | */ 10 | byte getEepromCursorPosition(); 11 | 12 | /** 13 | * Writes to eeprom 14 | * @param block The 8-byte block of memory to start with. 15 | * @param data The data to write. 16 | * @return 0 if successful, otherwise an sub-zero error code. 17 | */ 18 | sByte writeToEeprom(const byte blockNumber, const ByteArray* data); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /emu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "types.h" 7 | #include "hwdefs.h" 8 | #include "emu.h" 9 | #include "gbc_state.h" 10 | #include "cpu.h" 11 | #include "mmu.h" 12 | #include "lcd.h" 13 | #include "state.h" 14 | #include "ppu.h" 15 | #include "config.h" 16 | #include "debug.h" 17 | /** 18 | * Initialize the emulator state of the gameboy. This state belongs to the 19 | * emulator, not the state of the emulated hardware. 20 | */ 21 | void emu_init(GbState *s) { 22 | 23 | } 24 | 25 | void emu_step(PlayerState* state) { 26 | GbState* s = &state->EmulationState; 27 | UPDATE_PROFILE(PROFILE_JUNK); 28 | cpu_handle_interrupts(s); 29 | UPDATE_PROFILE(PROFILE_INTERRUPTS); 30 | cpu_step(s); 31 | UPDATE_PROFILE(PROFILE_CPU); 32 | lcd_step(state); 33 | UPDATE_PROFILE(PROFILE_LCD); 34 | mmu_step(s); 35 | UPDATE_PROFILE(PROFILE_MMU); 36 | s->TimersStep(s); 37 | UPDATE_PROFILE(PROFILE_TIMERS); 38 | } 39 | 40 | void emu_process_inputs(GbState *s, struct player_input *input) { 41 | #define BTN(type, button, bit) \ 42 | do { \ 43 | if (input->button_ ## button) \ 44 | s->io_buttons_ ## type &= ~(1 << (bit)); \ 45 | else \ 46 | s->io_buttons_ ## type |= 1 << (bit); \ 47 | } while (0) 48 | 49 | BTN(buttons, start, 3); 50 | BTN(buttons, select, 2); 51 | BTN(buttons, b, 1); 52 | BTN(buttons, a, 0); 53 | BTN(dirs, down, 3); 54 | BTN(dirs, up, 2); 55 | BTN(dirs, left, 1); 56 | BTN(dirs, right, 0); 57 | 58 | #undef BTN 59 | } 60 | -------------------------------------------------------------------------------- /emu.h: -------------------------------------------------------------------------------- 1 | #ifndef EMU_H 2 | #define EMU_H 3 | 4 | #include "types.h" 5 | #include "state.h" 6 | 7 | /** 8 | * Initialize the emulator state of the gameboy. This state belongs to the 9 | * emulator, not the state of the emulated hardware. 10 | */ 11 | void emu_init(GbState *s); 12 | void emu_step(PlayerState* state); 13 | void emu_process_inputs(GbState *s, struct player_input *input_state); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /fps.c: -------------------------------------------------------------------------------- 1 | /* fps.c -- fps helpers implementation 2 | * 3 | * Copyright (C) 2017 Victor Vieux 4 | * 5 | * This software may be modified and distributed under the terms 6 | * of the Apache license. See the LICENSE file for details. 7 | */ 8 | 9 | #include 10 | 11 | static volatile bool fps_refresh = false; 12 | static volatile uint8_t fps; 13 | 14 | void fps_frame() 15 | { 16 | static uint8_t frame_count = 0; 17 | 18 | frame_count++; 19 | if (fps_refresh) { 20 | fps = frame_count; 21 | frame_count = 0; 22 | fps_refresh = false; 23 | } 24 | } 25 | 26 | uint8_t fps_get() 27 | { 28 | return fps; 29 | } 30 | 31 | void fps_timer() 32 | { 33 | fps_refresh = true; 34 | } -------------------------------------------------------------------------------- /fps.h: -------------------------------------------------------------------------------- 1 | /* fps.h -- fps helpers header 2 | * 3 | * Copyright (C) 2017 Victor Vieux 4 | * 5 | * This software may be modified and distributed under the terms 6 | * of the Apache license. See the LICENSE file for details. 7 | */ 8 | 9 | #ifndef __FPS_H__ 10 | #define __FPS_H__ 11 | 12 | void fps_frame(); 13 | uint8_t fps_get(); 14 | void fps_timer(); 15 | 16 | #endif // __FPS_H__ -------------------------------------------------------------------------------- /gbc_state.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "state.h" 7 | #include "mmu.h" 8 | #include "hwdefs.h" 9 | #include "gbc_state.h" 10 | 11 | static sByte setInfo(GbState* s) { 12 | // Cart info from header 13 | if (s->Cartridge.Rom.Size < 0x0150) { 14 | // Cartridge too small 15 | return LOAD_ERR_TOO_SMALL; 16 | } 17 | 18 | switch (s->Cartridge.Header.cartridge_type) { 19 | case 0x00: s->mbc = 0; break; 20 | case 0x01: s->mbc = 1; break; 21 | case 0x02: s->mbc = 1; s->hasSRAM = 1; break; 22 | case 0x03: s->mbc = 1; s->hasSRAM = 1; s->hasBattery = 1; break; 23 | case 0x05: s->mbc = 2; s->hasSRAM = 1; break; 24 | case 0x06: s->mbc = 2; s->hasSRAM = 1; s->hasBattery = 1; break; 25 | case 0x08: s->mbc = 0; s->hasSRAM = 1; break; 26 | case 0x09: s->mbc = 0; s->hasSRAM = 1; s->hasBattery = 1; break; 27 | case 0x0f: s->mbc = 3; s->hasBattery = 1; s->hasRTC = 1; break; 28 | case 0x10: s->mbc = 3; s->hasSRAM = 1; s->hasBattery = 1; s->hasRTC = 1; break; 29 | case 0x11: s->mbc = 3; break; 30 | case 0x12: s->mbc = 3; s->hasSRAM = 1; break; 31 | case 0x13: s->mbc = 3; s->hasSRAM = 1; s->hasBattery = 1; break; 32 | //case 0x15: s->mbc = 4; break; 33 | //case 0x16: s->mbc = 4; s->hasSRAM = 1; break; 34 | //case 0x17: s->mbc = 4; s->hasSRAM = 1; s->hasBattery = 1; break; 35 | case 0x19: s->mbc = 5; break; 36 | case 0x1a: s->mbc = 5; s->hasSRAM = 1; break; 37 | case 0x1b: s->mbc = 5; s->hasSRAM = 1; s->hasBattery = 1; break; 38 | case 0x1c: s->mbc = 5; s->hasRumble = 1; break; 39 | case 0x1d: s->mbc = 5; s->hasSRAM = 1; s->hasRumble = 1; break; 40 | case 0x1e: s->mbc = 5; s->hasSRAM = 1; s->hasBattery = 1; s->hasRumble = 1;break; 41 | case 0x20: s->mbc = 6; break; 42 | /* MMM01 unsupported */ 43 | /* MBC7 Sensor not supported */ 44 | /* Camera not supported */ 45 | /* Bandai TAMA5 not supported */ 46 | /* HuCn not supported */ 47 | default: 48 | // Unsupported cartridge. 49 | return LOAD_ERR_UNSUPPORTED; 50 | } 51 | 52 | if (s->Cartridge.IsGbcSupported) { 53 | s->WRAMBankCount = 8; 54 | s->VRAMBankCount = 2; 55 | } else { 56 | s->WRAMBankCount = 2; 57 | s->VRAMBankCount = 1; 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | /** 64 | * Initialises all the memory to emulate a gameboy cartridge. 65 | */ 66 | sByte loadCartridge(GbState* s) { 67 | sByte result = setInfo(s); 68 | if (result) { 69 | return result; 70 | } 71 | 72 | // Initialise cartridge memory. 73 | s->ROM0 = s->Cartridge.Rom.Data; 74 | s->ROMX = s->Cartridge.Rom.Data + ROM_BANK_SIZE; 75 | 76 | // SRAM starts out disabled. 77 | s->SRAM = (byte*) disabledRAMPage; 78 | 79 | if (s->hasRTC) { 80 | s->Cartridge.RTCTimeStopped = 0; 81 | s->Cartridge.RTCStopTime = 0; 82 | } 83 | 84 | s->VRAMBanks = calloc(s->VRAMBankCount, VRAM_BANK_SIZE); 85 | s->VRAM = s->VRAMBanks; 86 | s->WRAMBanks = calloc(s->WRAMBankCount, WRAM_BANK_SIZE); 87 | s->WRAM0 = s->WRAMBanks; 88 | s->WRAMX = s->WRAMBanks + 0x1000; 89 | 90 | mmu_install_mbc(s); 91 | 92 | return 0; 93 | } 94 | 95 | /** 96 | * Adds BIOS to the state - should be called only after creating fresh state 97 | * from rom. Overwrites some state (such as PC) to facilitate running of BIOS. 98 | * 99 | */ 100 | sByte applyBios(GbState* s, ByteArray* bios) { 101 | if (bios->Size > ROM_BANK_SIZE) { 102 | return LOAD_ERR_TOO_LARGE; 103 | } 104 | 105 | s->BiosFile = bios->Data; 106 | // Overlay the BIOS data on top of the ROM0 data. 107 | s->BIOS = malloc(ROM_BANK_SIZE); 108 | memcpy(s->BIOS, s->ROM0, ROM_BANK_SIZE); 109 | memcpy(s->BIOS, bios->Data, bios->Size); 110 | 111 | s->ROM0 = s->BIOS; 112 | 113 | s->in_bios = 1; 114 | s->pc = 0; 115 | 116 | return 0; 117 | } -------------------------------------------------------------------------------- /gbc_state.h: -------------------------------------------------------------------------------- 1 | #ifndef STATE_H 2 | #define STATE_H 3 | 4 | #include "types.h" 5 | #include 6 | 7 | static const sByte LOAD_ERR_TOO_SMALL = -1; 8 | static const sByte LOAD_ERR_TOO_LARGE = -2; 9 | static const sByte LOAD_ERR_UNSUPPORTED = -3; 10 | static const sByte LOAD_ERR_RTC_UNAVAILABLE = -4; 11 | 12 | sByte loadCartridge(GbState* s); 13 | sByte applyBios(GbState* s, ByteArray* bios); 14 | 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /gbz80ops.h: -------------------------------------------------------------------------------- 1 | #ifndef GBZ80OPS_INCLUDED 2 | #define GBZ80OPS_INCLUDED 3 | 4 | #include "core.h" 5 | #include "types.h" 6 | 7 | void rlcR8(GbState* state, byte op); 8 | void rlcaHL(GbState* state, byte op); 9 | 10 | void rrcR8(GbState* state, byte op); 11 | void rrcaHL(GbState* state, byte op); 12 | 13 | void rlR8(GbState* state, byte op); 14 | void rlaHL(GbState* state, byte op); 15 | 16 | void rrR8(GbState* state, byte op); 17 | void rraHL(GbState* state, byte op); 18 | 19 | void slaR8(GbState* state, byte op); 20 | void slaaHL(GbState* state, byte op); 21 | 22 | void sraR8(GbState* state, byte op); 23 | void sraaHL(GbState* state, byte op); 24 | 25 | void swapR8(GbState* state, byte op); 26 | void swapaHL(GbState* state, byte op); 27 | 28 | void srlR8(GbState* state, byte op); 29 | void srlaHL(GbState* state, byte op); 30 | 31 | void bitU3R8(GbState* state, byte op); 32 | void bitU3aHL(GbState* state, byte op); 33 | 34 | void resU3R8(GbState* state, byte op); 35 | void resU3aHL(GbState* state, byte op); 36 | 37 | void setU3R8(GbState* state, byte op); 38 | void setU3aHL(GbState* state, byte op); 39 | 40 | void ext(GbState* state, byte op); 41 | 42 | void ldR8N8(GbState* state, byte op); 43 | void ldaHLN8(GbState* state, byte op); 44 | 45 | void ldR8R8(GbState* state, byte op); 46 | void ldaHLR8(GbState* state, byte op); 47 | void ldR8aHL(GbState* state, byte op); 48 | 49 | void ldAaBC(GbState* state, byte op); 50 | void ldAaDE(GbState* state, byte op); 51 | void ldAaN16(GbState* state, byte op); 52 | void ldaBCA(GbState* state, byte op); 53 | void ldaDEA(GbState* state, byte op); 54 | void ldaN16A(GbState* state, byte op); 55 | void ldAaC(GbState* state, byte op); 56 | void ldaCA(GbState* state, byte op); 57 | void lddAaHL(GbState* state, byte op); 58 | void lddaHLA(GbState* state, byte op); 59 | void ldiaHLA(GbState* state, byte op); 60 | void ldiAaHL(GbState* state, byte op); 61 | void ldhAaN8(GbState* state, byte op); 62 | void ldhaN8A(GbState* state, byte op); 63 | void ldR16N16(GbState* state, byte op); 64 | void ldSPHL(GbState* state, byte op); 65 | void ldHLSPN8(GbState* state, byte op); 66 | void ldaN16SP(GbState* state, byte op); 67 | void pushR16(GbState* state, byte op); 68 | void popR16(GbState* state, byte op); 69 | 70 | void addAN8(GbState* state, byte op); 71 | void addAR8(GbState* state, byte op); 72 | void addAaHL(GbState* state, byte op); 73 | 74 | void adcAN8(GbState* state, byte op); 75 | void adcAR8(GbState* state, byte op); 76 | void adcAaHL(GbState* state, byte op); 77 | 78 | void subAN8(GbState* state, byte op); 79 | void subAR8(GbState* state, byte op); 80 | void subAaHL(GbState* state, byte op); 81 | 82 | void sbcAN8(GbState* state, byte op); 83 | void sbcAR8(GbState* state, byte op); 84 | void sbcAaHL(GbState* state, byte op); 85 | 86 | void andAN8(GbState* state, byte op); 87 | void andAR8(GbState* state, byte op); 88 | void andAaHL(GbState* state, byte op); 89 | 90 | void orAN8(GbState* state, byte op); 91 | void orAR8(GbState* state, byte op); 92 | void orAaHL(GbState* state, byte op); 93 | 94 | void xorAN8(GbState* state, byte op); 95 | void xorAR8(GbState* state, byte op); 96 | void xorAaHL(GbState* state, byte op); 97 | 98 | void cpAN8(GbState* state, byte op); 99 | void cpAR8(GbState* state, byte op); 100 | void cpAaHL(GbState* state, byte op); 101 | 102 | void incR8(GbState* state, byte op); 103 | void incaHL(GbState* state, byte op); 104 | 105 | void decR8(GbState* state, byte op); 106 | void decaHL(GbState* state, byte op); 107 | 108 | void incR16(GbState* state, byte op); 109 | void decR16(GbState* state, byte op); 110 | void addHLR16(GbState* state, byte op); 111 | void addSPN8(GbState* state, byte op); 112 | void daa(GbState* state, byte op); 113 | void cpl(GbState* state, byte op); 114 | void ccf(GbState* state, byte op); 115 | void scf(GbState* state, byte op); 116 | void rlcA(GbState* state, byte op); 117 | void rlA(GbState* state, byte op); 118 | void rrcA(GbState* state, byte op); 119 | void rrA(GbState* state, byte op); 120 | void nop(GbState* state, byte op); 121 | void halt(GbState* state, byte op); 122 | void stop(GbState* state, byte op); 123 | void di(GbState* state, byte op); 124 | void ei(GbState* state, byte op); 125 | void jpN16(GbState* state, byte op); 126 | void jpCCN16(GbState* state, byte op); 127 | void jpHL(GbState* state, byte op); 128 | void jrN8(GbState* state, byte op); 129 | void jrCCN8(GbState* state, byte op); 130 | void callN16(GbState* state, byte op); 131 | void callCCN16(GbState* state, byte op); 132 | void rstVec(GbState* state, byte op); 133 | void retCC(GbState* state, byte op); 134 | void reti(GbState* state, byte op); 135 | void ret(GbState* state, byte op); 136 | 137 | // handler for undefined op codes. 138 | void undefined(GbState* state, byte op); 139 | 140 | typedef void (*gbz80Operation)(GbState*, byte); 141 | static gbz80Operation opTable[] = { 142 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 143 | /* 0 */ nop, ldR16N16, ldaBCA, incR16, incR8, decR8, ldR8N8, rlcA, ldaN16SP, addHLR16, ldAaBC, decR16, incR8, decR8, ldR8N8, rrcA, 144 | /* 1 */ stop, ldR16N16, ldaDEA, incR16, incR8, decR8, ldR8N8, rlA, jrN8, addHLR16, ldAaDE, decR16, incR8, decR8, ldR8N8, rrA, 145 | /* 2 */ jrCCN8, ldR16N16, ldiaHLA, incR16, incR8, decR8, ldR8N8, daa, jrCCN8, addHLR16, ldiAaHL, decR16, incR8, decR8, ldR8N8, cpl, 146 | /* 3 */ jrCCN8, ldR16N16, lddaHLA, incR16, incaHL, decaHL, ldaHLN8, scf, jrCCN8, addHLR16, lddAaHL, decR16, incR8, decR8, ldR8N8, ccf, 147 | /* 4 */ ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, 148 | /* 5 */ ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, 149 | /* 6 */ ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, 150 | /* 7 */ ldaHLR8, ldaHLR8, ldaHLR8, ldaHLR8, ldaHLR8, ldaHLR8, halt, ldaHLR8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8R8, ldR8aHL, ldR8R8, 151 | /* 8 */ addAR8, addAR8, addAR8, addAR8, addAR8, addAR8, addAaHL, addAR8, adcAR8, adcAR8, adcAR8, adcAR8, adcAR8, adcAR8, adcAaHL, adcAR8, 152 | /* 9 */ subAR8, subAR8, subAR8, subAR8, subAR8, subAR8, subAaHL, subAR8, sbcAR8, sbcAR8, sbcAR8, sbcAR8, sbcAR8, sbcAR8, sbcAaHL, sbcAR8, 153 | /* A */ andAR8, andAR8, andAR8, andAR8, andAR8, andAR8, andAaHL, andAR8, xorAR8, xorAR8, xorAR8, xorAR8, xorAR8, xorAR8, xorAaHL, xorAR8, 154 | /* B */ orAR8, orAR8, orAR8, orAR8, orAR8, orAR8, orAaHL, orAR8, cpAR8, cpAR8, cpAR8, cpAR8, cpAR8, cpAR8, cpAaHL, cpAR8, 155 | /* C */ retCC, popR16, jpCCN16, jpN16, callCCN16, pushR16, addAN8, rstVec, retCC, ret, jpCCN16, ext, callCCN16, callN16, adcAN8, rstVec, 156 | /* D */ retCC, popR16, jpCCN16, undefined, callCCN16, pushR16, subAN8, rstVec, retCC, reti, jpCCN16, undefined, callCCN16, undefined, sbcAN8, rstVec, 157 | /* E */ ldhaN8A, popR16, ldaCA, undefined, undefined, pushR16, andAN8, rstVec, addSPN8, jpHL, ldaN16A, undefined, undefined, undefined, xorAN8, rstVec, 158 | /* F */ ldhAaN8, popR16, ldAaC, di, undefined, pushR16, orAN8 , rstVec, ldHLSPN8, ldSPHL, ldAaN16, ei, undefined, undefined, cpAN8, rstVec 159 | }; 160 | 161 | static gbz80Operation extendedOpTable[] = { 162 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 163 | /* 0 */ rlcR8, rlcR8, rlcR8, rlcR8, rlcR8, rlcR8, rlcaHL, rlcR8, rrcR8, rrcR8, rrcR8, rrcR8, rrcR8, rrcR8, rrcaHL, rrcR8, 164 | /* 1 */ rlR8, rlR8, rlR8, rlR8, rlR8, rlR8, rlaHL, rlR8, rrR8, rrR8, rrR8, rrR8, rrR8, rrR8, rraHL, rrR8, 165 | /* 2 */ slaR8, slaR8, slaR8, slaR8, slaR8, slaR8, slaaHL, slaR8, sraR8, sraR8, sraR8, sraR8, sraR8, sraR8, sraaHL, sraR8, 166 | /* 3 */ swapR8, swapR8, swapR8, swapR8, swapR8, swapR8, swapaHL,swapR8, srlR8, srlR8, srlR8, srlR8, srlR8, srlR8, srlaHL, srlR8, 167 | /* 4 */ bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8, 168 | /* 5 */ bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8, 169 | /* 6 */ bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8, 170 | /* 7 */ bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3R8,bitU3aHL,bitU3R8, 171 | /* 8 */ resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8, 172 | /* 9 */ resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8, 173 | /* A */ resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8, 174 | /* B */ resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3R8,resU3aHL,resU3R8, 175 | /* C */ setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8, 176 | /* D */ setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8, 177 | /* E */ setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8, 178 | /* F */ setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3R8,setU3aHL,setU3R8 179 | }; 180 | 181 | #endif -------------------------------------------------------------------------------- /gbz80ops.mips: -------------------------------------------------------------------------------- 1 | # Exports 2 | .globl nop 3 | 4 | nop: 5 | jr $ra 6 | 7 | -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | #ifndef SHARED_DEFINES 2 | #define SHARED_DEFINES 3 | 4 | /** 5 | * Defined globally and shared by ASM code and linker script 6 | * as address of the communication point between the CPU & RSP 7 | * IE. If CPU puts stuff here, it will be DMA'd into DMEM by RSP 8 | * We can reference this in our linker script if we run it through the cpp pre-compiler. 9 | */ 10 | #define RSP_INTERFACE_ADDRESS 0x80050000 11 | 12 | #define GB_FREQ 4194304 /* Hz */ 13 | #define GB_LCD_WIDTH 160 /* px */ 14 | #define GB_LCD_HEIGHT 144 /* px */ 15 | 16 | // RSP_STATUS manipulation 17 | #define SP_STATUS_HALT_OFF 0b00000001 18 | #define SP_STATUS_HALT_ON 0b00000010 19 | #define SP_STATUS_BROKE_OFF 0b00000100 20 | #define SP_STATUS_INTERRUPT_OFF 0b00001000 21 | #define SP_STATUS_INTERRUPT_ON 0b00010000 22 | 23 | // aka signal 0 24 | #define SP_STATUS_BUSY_OFF 0b00000010 << 8 25 | #define SP_STATUS_BUSY_ON 0b00000100 << 8 26 | #define SP_DATA_PENDING 0b00001000 << 8 27 | #define SP_DATA_READY 0b00010000 << 8 28 | #define SP_BUFFER_1 0b00100000 << 8 29 | #define SP_BUFFER_2 0b01000000 << 8 30 | 31 | #define SP_STATUS_GET_IS_READY 0b00000001 << 8 32 | #define SP_STATUS_GET_BUFFER 0b00000010 << 8 33 | 34 | #endif -------------------------------------------------------------------------------- /hwdefs.h: -------------------------------------------------------------------------------- 1 | #ifndef HWDEFS_H 2 | #define HWDEFS_H 3 | 4 | #include "global.h" 5 | 6 | static const int GB_LCD_LY_MAX = 153; 7 | 8 | static const int GB_HDMA_BLOCK_CLKS = 8; /* Per block of 0x10 bytes */ 9 | 10 | #define GB_LCD_MODE_0_CLKS 204 /* H-Blank */ 11 | #define GB_LCD_MODE_1_CLKS 4560 /* V-Blank */ 12 | #define GB_LCD_MODE_2_CLKS 80 /* OAM read */ 13 | #define GB_LCD_MODE_3_CLKS 172 /* Line rendering */ 14 | #define GB_LCD_FRAME_CLKS 70224 /* Total cycles per frame */ 15 | 16 | #define GB_DIV_FREQ 16384 /* Hz */ 17 | static const int GB_DIV_CYCLES_PER_TICK = (GB_FREQ / GB_DIV_FREQ); 18 | static const int GB_DIV_DOUBLE_CYCLES_PER_TICK = (GB_FREQ * 2) / GB_DIV_FREQ; 19 | static const int GB_TIMA_FREQS[] = { 4096, 262144, 65536, 16384 }; /* Hz */ 20 | static const int GB_TIMA_CYCLES_PER_TICK[] = {1024, 16, 64, 256 }; 21 | static const int GB_TIMA_DOUBLE_CYCLES_PER_TICK[] = { 2048, 32, 128, 512 }; 22 | 23 | static const double GB_SND_DUTY_PERC[] = { .125, .25, .50, .75 }; 24 | static const int GB_SND_ENVSTEP_CYC = GB_FREQ/64; /* n*(1/64)th seconds */ 25 | 26 | static const unsigned ROMHDR_TITLE = 0x134; 27 | static const unsigned ROMHDR_CGBFLAG = 0x143; 28 | static const unsigned ROMHDR_CARTTYPE = 0x147; 29 | static const unsigned ROMHDR_ROMSIZE = 0x148; 30 | static const unsigned ROMHDR_EXTRAMSIZE = 0x149; 31 | 32 | static const unsigned ROM_BANK_SIZE = 0x4000; /* 16K */ 33 | static const unsigned WRAM_BANK_SIZE = 0x1000; /* 4K */ 34 | static const unsigned VRAM_BANK_SIZE = 0x2000; /* 8K */ 35 | static const unsigned SRAM_BANK_SIZE = 0x2000; /* 8K */ 36 | #define HRAM_SIZE 0x0100 37 | 38 | static const unsigned OAM_SIZE = 0xa0; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "init.h" 4 | #include "screen.h" 5 | #include "play.h" 6 | #include "controller.h" 7 | #include "tpakio.h" 8 | #include "resources.h" 9 | #include "logger.h" 10 | #include "text.h" 11 | #include "hwdefs.h" 12 | #include "progressBar.h" 13 | #include "debug.h" 14 | #include 15 | 16 | 17 | /** 18 | * Gets screen and state ready for Playing GB software. 19 | * @param state The play state. 20 | * @param playerNumber identifies player that wants to move to play mode. 21 | */ 22 | static void preparePlayMode(byte playerNumber) { 23 | Rectangle screen = {}; 24 | getScreenPosition(playerNumber, &screen); 25 | resetPlayState(&rootState.Players[playerNumber]); 26 | rootState.Players[playerNumber].EmulationState.controllerSlot = playerNumber; 27 | rootState.Players[playerNumber].InitState = InitLoaded; 28 | rootState.Players[playerNumber].ActiveMode = Play; 29 | } 30 | 31 | /** 32 | * Waits for a Start Button pressed, then goes and loads a rom. 33 | * @param state program state. 34 | * @param playerNumber player in init mode. 35 | */ 36 | void initLogic(const byte playerNumber) { 37 | PlayerState* playerState = &rootState.Players[playerNumber]; 38 | GbState* s = &playerState->EmulationState; 39 | 40 | if (playerState->InitState == InitRestarting) { 41 | playerState->InitState = InitStart; 42 | rootState.RequiresRepaint = true; 43 | } 44 | 45 | if (playerState->InitState == InitStart) { 46 | sByte result = getCartridgeMetadata(playerNumber, &playerState->EmulationState.Cartridge); 47 | 48 | if (!result) { 49 | playerState->InitState = InitReady; 50 | rootState.RequiresRepaint = true; 51 | } else { 52 | playerState->InitState = InitError; 53 | rootState.ErrorCode = result; 54 | 55 | switch(result) { 56 | case TPAK_ERR_NO_TPAK: 57 | getText(TextNoTpak, playerState->ErrorMessage); 58 | break; 59 | case TPAK_ERR_NO_CARTRIDGE: 60 | getText(TextNoCartridge, playerState->ErrorMessage); 61 | break; 62 | case TPAK_ERR_CORRUPT_HEADER: 63 | case TPAK_ERR_CORRUPT_DATA: 64 | getText(TextChecksumFailed, playerState->ErrorMessage); 65 | break; 66 | case TPAK_ERR_UNSUPPORTED_CARTRIDGE: 67 | getText(TextUnsupportedCartridge, playerState->ErrorMessage); 68 | break; 69 | case TPAK_ERR_INSUFFICIENT_MEMORY: 70 | getText(TextExpansionPakRequired, playerState->ErrorMessage); 71 | break; 72 | default: 73 | sprintf(playerState->ErrorMessage, "Loading Cartridge failed with error: %d", rootState.ErrorCode); 74 | break; 75 | } 76 | } 77 | } 78 | 79 | if (playerState->InitState == InitError || playerState->InitState == InitChanging) { 80 | bool releasedButtons[N64_BUTTON_COUNT] = {}; 81 | getPressedButtons(&rootState.KeysReleased, playerNumber, releasedButtons); 82 | 83 | // Press A or Start to retry. 84 | if (releasedButtons[A] || releasedButtons[Start]) { 85 | rootState.RequiresControllerRead = true; 86 | rootState.RequiresRepaint = true; 87 | rootState.Players[playerNumber].InitState = InitRestarting; 88 | } else if (releasedButtons[CDown]) { 89 | // Load internal easter egg cartridge. 90 | rootState.RequiresControllerRead = true; 91 | rootState.RequiresRepaint = true; 92 | sInt result = loadInternalRom(&rootState.Players[0].EmulationState.Cartridge.Rom); 93 | if (result == -1) { 94 | logAndPauseFrame(0, "Error loading internal ROM"); 95 | } 96 | 97 | s->Cartridge.Type = ROM_ONLY; 98 | s->Cartridge.Header.is_sgb_supported = true; 99 | s->Cartridge.IsGbcSupported = false; 100 | s->Cartridge.RomBankCount = s->Cartridge.Rom.Size / ROM_BANK_SIZE; 101 | s->Cartridge.RamBankCount = 1; 102 | s->Cartridge.Ram.Size = 0; 103 | 104 | preparePlayMode(playerNumber); 105 | } 106 | } else if (playerState->InitState == InitReady) { 107 | // Show the loading message. 108 | rootState.RequiresRepaint = true; 109 | rootState.Players[playerNumber].InitState = InitLoading; 110 | } else if (playerState->InitState == InitLoading) { 111 | rootState.RequiresRepaint = true; 112 | 113 | startLoadProgressTimer(playerNumber); 114 | 115 | sByte result = importCartridge(playerNumber, &s->Cartridge); 116 | 117 | closeLoadProgressTimer(playerNumber); 118 | 119 | if (result) { 120 | string tmp; 121 | rootState.ErrorCode = result; 122 | getText(TextChecksumFailed, tmp); 123 | sprintf(playerState->ErrorMessage, tmp, result); 124 | playerState->InitState = InitError; 125 | return; 126 | } 127 | 128 | preparePlayMode(playerNumber); 129 | INIT_PROFILE; 130 | } 131 | } 132 | 133 | /** 134 | * Draws screen for init mode. 135 | * @param state program state. 136 | * @param playerNumber player in init mode. 137 | */ 138 | void initDraw(const byte playerNumber) { 139 | Rectangle screen = {}; 140 | getScreenPosition(playerNumber, &screen); 141 | 142 | prepareRdpForSprite(rootState.Frame); 143 | loadSprite(getSpriteSheet(), GB_BG_TEXTURE, MIRROR_XY); 144 | 145 | // The - 1 and - 8 crept in when I switched from software to hardware rendering. The RDP has some strange behaviours I can't understand. 146 | // If I don't subtract these here, there are grey lines along the right and bottom of the play screen in single player mode. 147 | rdp_draw_textured_rectangle(0, screen.Left, screen.Top, screen.Left + screen.Width - 1, screen.Top + screen.Height, true); 148 | 149 | natural textTop = screen.Top - TEXT_HEIGHT + (screen.Width / 2); 150 | 151 | string text = ""; 152 | string tmp = ""; 153 | switch (rootState.Players[playerNumber].InitState) { 154 | case InitStart: break; 155 | case InitLoaded: break; 156 | case InitReady: 157 | case InitRestarting: 158 | case InitLoading: 159 | getText(TextLoadingCartridge, text); 160 | break; 161 | case InitChanging: 162 | getText(TextLoadCartridge, text); 163 | break; 164 | case InitError: 165 | getText(TextRetryCartridgePrompt, tmp); 166 | sprintf(text, "%s %s", rootState.Players[playerNumber].ErrorMessage, tmp); 167 | break; 168 | default: 169 | strcpy(text, "Unknown Error!!!"); 170 | break; 171 | } 172 | 173 | drawTextParagraph(rootState.Frame, text, screen.Left + TEXT_WIDTH, textTop, 0.8, screen.Width - TEXT_WIDTH); 174 | 175 | // sleep just to give some indiciation that something has changed. 176 | // otherwise it may happen too fast and we might not know for sure. 177 | // that the t-pak was even retried. 178 | if (rootState.Players[playerNumber].InitState == InitRestarting) { 179 | // Seems to be some issue with this function, maybe when combined with my use of timer interrupts in this module. 180 | // This function never finishes executing. 181 | // So commenting out for now as it's not super important. 182 | //wait_ms(500); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /init.h: -------------------------------------------------------------------------------- 1 | #ifndef INIT_INCLUDED 2 | #define INIT_INCLUDED 3 | 4 | #include "state.h" 5 | 6 | typedef enum { 7 | InitStart = 0, 8 | InitReady, InitLoading, InitLoaded, InitError, InitRestarting, InitChanging 9 | } InitState; 10 | 11 | /** 12 | * Waits for a Start Button pressed, then goes and loads a rom. 13 | * @param state program state. 14 | * @param playerNumber player in init mode. 15 | */ 16 | void initLogic(const byte playerNumber); 17 | 18 | /** 19 | * Draws screen for init mode. 20 | * @param state program state. 21 | * @param playerNumber player in init mode. 22 | */ 23 | void initDraw(const byte playerNumber); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /lcd.h: -------------------------------------------------------------------------------- 1 | #ifndef LCD_H 2 | #define LCD_H 3 | 4 | #include "state.h" 5 | #include "types.h" 6 | 7 | int lcd_init(GbState *s); 8 | void lcd_step(PlayerState* state); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /link.c: -------------------------------------------------------------------------------- 1 | #include "link.h" 2 | 3 | #include 4 | #include "state.h" 5 | 6 | // bit 3 - 0000 1000 7 | const natural SERIAL_INTERRUPT_MAP = 0x08; 8 | 9 | /** 10 | * Checks if gameboys serial control byte is indicating there is data to send. 11 | * @param state gameboy state. 12 | * @return true if gameboy is ready to transfer a byte. 13 | */ 14 | bool isRequestingTransfer() { 15 | if (rootState.PlayerCount < 2) { 16 | return false; 17 | } 18 | 19 | bool readyToSend = false; 20 | 21 | for (byte i = 0; i < rootState.PlayerCount; i++) { 22 | // If a game isn't ready, don't do anything yet. 23 | // May be able to send even if this bit isn't set? 24 | if (!rootState.Players[i].EmulationState.IsLinkTransferAvailable) { 25 | return false; 26 | } 27 | 28 | // At least one game must be hosting the connection. 29 | if (rootState.Players[i].EmulationState.IsLinkClockExternal) { 30 | readyToSend = true; 31 | } 32 | } 33 | 34 | return readyToSend; 35 | } 36 | 37 | /** 38 | * Sends data between gameboy states, emulating serial link cable transfer. 39 | * @param states the gameboys to send data between. 40 | */ 41 | void exchangeLinkData(GbState* states[2]) { 42 | byte datum = states[0]->LinkData; 43 | states[0]->LinkData = states[1]->LinkData; 44 | states[1]->LinkData = datum; 45 | 46 | states[0]->IsLinkTransferAvailable = false; 47 | states[1]->IsLinkTransferAvailable = false; 48 | 49 | // Prepare the serial interrupt 50 | states[0]->LinkInterrupt = true; 51 | states[1]->LinkInterrupt = true; 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /link.h: -------------------------------------------------------------------------------- 1 | #ifndef LINK_INCLUDED 2 | #define LINK_INCLUDED 3 | 4 | #include "core.h" 5 | #include "state.h" 6 | 7 | /** 8 | * Checks if gameboy serial control bytes are indicating there is data to send and receive. 9 | * @return true if gameboy is ready to transfer a byte. 10 | */ 11 | bool isRequestingTransfer(); 12 | 13 | /** 14 | * Sends data between gameboy states, emulating serial link cable transfer. 15 | * @param states the gameboys to send data between. 16 | */ 17 | void exchangeLinkData(GbState* states[2]); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /logger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "config.h" 6 | #include "state.h" 7 | #include "logger.h" 8 | #include "core.h" 9 | #include "eeprom.h" 10 | #include 11 | 12 | const byte VERTICAL_MARGIN = 30; 13 | const byte HORIZONTAL_MARGIN = 30; 14 | 15 | /** 16 | * Prints the pre-sprintf'd text to the display. 17 | * @private 18 | * @param text The text ready to be displayed. 19 | */ 20 | static void printLog(const string text, display_context_t frame) { 21 | if (!frame) { 22 | while(!(frame = display_lock())); 23 | } 24 | 25 | graphics_draw_box(frame, 0, 0, 640, 40, GLOBAL_BACKGROUND_COLOUR); 26 | graphics_set_color(GLOBAL_TEXT_COLOUR, 0x0); 27 | 28 | graphics_draw_text(frame, HORIZONTAL_MARGIN, VERTICAL_MARGIN, text); 29 | 30 | display_show(frame); 31 | } 32 | 33 | /** 34 | * Print the next 32 bytes of memory from a given address and wait for "start". 35 | * @param caption Describes what memory is being displayed. 36 | * @param start the starting memory address. 37 | * @param frame display buffer to print to. 38 | */ 39 | void printSegmentToFrame(display_context_t frame, const string caption, const byte* start) { 40 | if (!frame) { 41 | while(!(frame = display_lock())); 42 | } 43 | 44 | graphics_fill_screen(frame, GLOBAL_BACKGROUND_COLOUR); 45 | graphics_draw_text(frame, 30, 10, caption); 46 | 47 | string text = ""; 48 | sprintf( 49 | text, 50 | "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", 51 | *start, *(start + 1), *(start + 2), *(start + 3), *(start + 4), *(start + 5), *(start + 6), *(start + 7), 52 | *(start + 8), *(start + 9), *(start + 10), *(start + 11), *(start + 12), *(start + 13), *(start + 14), *(start + 15) 53 | ); 54 | graphics_draw_text(frame, 30, 20, text); 55 | start += 16; 56 | sprintf( 57 | text, 58 | "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", 59 | *start, *(start + 1), *(start + 2), *(start + 3), *(start + 4), *(start + 5), *(start + 6), *(start + 7), 60 | *(start + 8), *(start + 9), *(start + 10), *(start + 11), *(start + 12), *(start + 13), *(start + 14), *(start + 15) 61 | ); 62 | graphics_draw_text(frame, 30, 30, text); 63 | start += 16; 64 | sprintf( 65 | text, 66 | "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", 67 | *start, *(start + 1), *(start + 2), *(start + 3), *(start + 4), *(start + 5), *(start + 6), *(start + 7), 68 | *(start + 8), *(start + 9), *(start + 10), *(start + 11), *(start + 12), *(start + 13), *(start + 14), *(start + 15) 69 | ); 70 | graphics_draw_text(frame, 30, 40, text); 71 | start += 16; 72 | sprintf( 73 | text, 74 | "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", 75 | *start, *(start + 1), *(start + 2), *(start + 3), *(start + 4), *(start + 5), *(start + 6), *(start + 7), 76 | *(start + 8), *(start + 9), *(start + 10), *(start + 11), *(start + 12), *(start + 13), *(start + 14), *(start + 15) 77 | ); 78 | graphics_draw_text(frame, 30, 50, text); 79 | 80 | bool isPaused = true; 81 | while(isPaused) { 82 | controller_scan(); 83 | N64ControllerState input = get_keys_pressed(); 84 | if (input.c[0].start) { 85 | isPaused = false; 86 | } 87 | } 88 | } 89 | 90 | 91 | /** 92 | * Throws a line of text up on to the screen a la printf and then stalls execution so you can read it. 93 | * @param text The text or format string. 94 | * @param ... Parameters for the format string. 95 | */ 96 | void logAndPause(const string text, ...) { 97 | va_list args; 98 | va_start(args, text); 99 | 100 | string output = ""; 101 | vsprintf(output, text, args); 102 | printLog(output, null); 103 | 104 | va_end(args); 105 | 106 | bool isPaused = true; 107 | while(isPaused) { 108 | controller_scan(); 109 | N64ControllerState input = get_keys_pressed(); 110 | if (input.c[0].start) { 111 | isPaused = false; 112 | } 113 | } 114 | } 115 | 116 | /** 117 | * Displays the current state of the Gameboy emulator registers. 118 | * @param s The emulator state structure. 119 | */ 120 | void printRegisters(GbState* s) { 121 | logAndPauseFrame( 122 | 0, 123 | "A=%02x F=%02x B=%02x C=%02x D=%02x E=%02x H=%02x L=%02x Z=%d N=%d HF=%d C=%d sp=%04x pc=%04x", 124 | s->reg8.A, s->reg8.F, s->reg8.B, s->reg8.C, 125 | s->reg8.D, s->reg8.E, s->reg8.H, s->reg8.L, 126 | s->flags.ZF, s->flags.NF, s->flags.HF, s->flags.CF, 127 | s->sp, s->pc//, s->LcdControl, s->LcdStatus 128 | ); 129 | } 130 | 131 | void logAndPauseFrame(display_context_t frame, const string text, ...) { 132 | va_list args; 133 | va_start(args, text); 134 | 135 | string output = ""; 136 | vsprintf(output, text, args); 137 | printLog(output, frame); 138 | 139 | va_end(args); 140 | 141 | bool isPaused = true; 142 | while(isPaused) { 143 | controller_scan(); 144 | N64ControllerState input = get_keys_pressed(); 145 | if (input.c[0].start) { 146 | isPaused = false; 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Throws a line of text up on to the screen a la printf. 153 | * @param text The text or format string. 154 | * @param ... Parameters for the format string. 155 | */ 156 | void logInfo(const string text, ... ) { 157 | va_list args; 158 | va_start(args, text); 159 | 160 | string formatted = ""; 161 | string tmp = ""; 162 | 163 | vsprintf(formatted, text, args); 164 | va_end(args); 165 | 166 | byte blockNumber = getEepromCursorPosition(); 167 | 168 | // Since it's a log file, whack a new line on the end. 169 | // TODO - timestamp. 170 | sprintf(tmp, "%s\n", formatted); 171 | strcpy(formatted, tmp); 172 | 173 | ByteArray textString; 174 | textString.Size = strlen(formatted); 175 | 176 | textString.Data = calloc(1, textString.Size + 1); 177 | 178 | memcpy(textString.Data, formatted, textString.Size); 179 | 180 | if(writeToEeprom(blockNumber, &textString)) { 181 | logAndPause("logging failed. Message was: %s", formatted); 182 | } 183 | 184 | free(textString.Data); 185 | textString.Data = null; 186 | } 187 | 188 | -------------------------------------------------------------------------------- /logger.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGGER_INCLUDED 2 | #define LOGGER_INCLUDED 3 | #include "core.h" 4 | #include "types.h" 5 | 6 | /** 7 | * Displays the current state of the Gameboy emulator registers. 8 | * @param s The emulator state structure. 9 | */ 10 | void printRegisters(GbState *s); 11 | 12 | /** 13 | * Throws a line of text up on to the screen a la printf. 14 | * @param text The text or format string. 15 | * @param ... Parameters for the format string. 16 | */ 17 | void logInfo(const string text, ... ); 18 | 19 | /** 20 | * Throws a line of text up on to the screen a la printf and then stalls execution so you can read it. 21 | * TODO: Unpause 22 | * @param text The text or format string. 23 | * @param ... Parameters for the format string. 24 | */ 25 | void logAndPause(const string text, ...); 26 | 27 | /** 28 | * Shows the log message if we're already in the middle of a frame. 29 | * @param frame The frame. 30 | * @param text Text or format string to show. 31 | * @param ... Parameters for the format string. 32 | */ 33 | void logAndPauseFrame(display_context_t frame, const string text, ...); 34 | 35 | /** 36 | * Print the next 32 bytes of memory from a given address and wait for "start". 37 | * @param caption Describes what memory is being displayed. 38 | * @param start the starting memory address. 39 | * @param frame display buffer to print to. 40 | */ 41 | void printSegmentToFrame(display_context_t frame, const string caption, const byte* start); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /menu.h: -------------------------------------------------------------------------------- 1 | #ifndef MENU_INCLUDED 2 | #define MENU_INCLUDED 3 | 4 | #include "state.h" 5 | 6 | /** 7 | * Handles the pause menu for given player. 8 | * @param playerNumber player in menu mode. 9 | */ 10 | void menuLogic(const byte playerNumber); 11 | 12 | /** 13 | * Displays the pause menu for given player. 14 | * @param playerNumber player in menu mode. 15 | */ 16 | void menuDraw(const byte playerNumber); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /mksprite.sh: -------------------------------------------------------------------------------- 1 | mksprite 16 14 7 ./assets/textMap.png ./filesystem/textMap.sprite 2 | mksprite 16 8 2 ./assets/spriteSheet.png ./filesystem/spriteSheet.sprite 3 | -------------------------------------------------------------------------------- /mmu.h: -------------------------------------------------------------------------------- 1 | #ifndef MMU_H 2 | #define MMU_H 3 | 4 | #include "types.h" 5 | 6 | /********************************* 7 | * Memory layout of the GameBoy: 8 | * $0000-$3FFF ROM (bank 0) 9 | * $4000-$7FFF ROM (bank 1..n, switchable) 10 | * $8000-$9FFF VRAM (bank 0 non-CGB, bank 0-1 on CGB) 11 | * $A000-$BFFF External RAM (cartridge, optional) 12 | * $C000-$CFFF Internal WRAM (bank 0) 13 | * $D000-$DFFF Internal WRAM (bank 1-7, switchable, CGB only) 14 | * $E000-$FDFF Echo RAM (reserved) 15 | * $FE00-$FE9F OAM - Object Attribute Memory 16 | * $FEA0-$FEFF Unusable 17 | * $FF00-$FF7F Hardware I/O Registers 18 | * $FF80-$FFFE Zero 19 | * $FFFF Interrupt Enable Flag 20 | */ 21 | 22 | /** 23 | * s->SRAM points here when SRAM is disabled. 24 | */ 25 | static const byte disabledRAMPage[0x2000] = {[0x0 ... 0x1FFF] = 0xFF }; 26 | 27 | /** 28 | * Performs mmu related tasks that are performed after every cpu operation. 29 | */ 30 | void mmu_step(GbState *s); 31 | 32 | /** 33 | * Reads from an address in the virtual gameboy memory. 34 | * @param s Gameboy state. 35 | * @param location address to read from. 36 | * @returns value at that address. 37 | */ 38 | u8 mmu_read(GbState *s, u16 location); 39 | 40 | /** 41 | * Writes to an address in the virtual gameboy memory. 42 | * @param s Gameboy state. 43 | * @param location address to write to. 44 | * @param value Value to write. 45 | */ 46 | void mmu_write(GbState *s, u16 location, u8 value); 47 | 48 | /** 49 | * Reads two bytes from an address in virtual gameboy memory. 50 | */ 51 | u16 mmu_read16(GbState *s, u16 location); 52 | 53 | /** 54 | * Writes two bytes to an address in virtual gameboy memory. 55 | */ 56 | void mmu_write16(GbState *s, u16 location, u16 value); 57 | 58 | /** 59 | * Reads two bytes from the stack pointer address then increments it twice. 60 | */ 61 | u16 mmu_pop16(GbState *s); 62 | 63 | /** 64 | * Decrements the stack pointer twice then writes to bytes to the new location. 65 | */ 66 | void mmu_push16(GbState *s, u16 value); 67 | 68 | /** 69 | * Installs different mmu behaviour for different Memory Bank Controller types. 70 | * @param s Gameboy state. 71 | */ 72 | void mmu_install_mbc(GbState* s); 73 | 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /options.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_INCLUDED 2 | #define OPTIONS_INCLUDED 3 | 4 | #include "state.h" 5 | 6 | typedef enum { OptionsAudio, OptionsStart, OptionsSelect, OptionsMenu, OptionsEnd } OptionType; 7 | 8 | /** 9 | * Displays the options menu for given player. 10 | * @param playerNumber player in options mode. 11 | */ 12 | void optionsDraw(const byte playerNumber); 13 | 14 | /** 15 | * Handles the options menu for given player. 16 | * @param playerNumber player in options mode. 17 | */ 18 | void optionsLogic(byte playerNumber); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /play.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "core.h" 6 | #include "config.h" 7 | #include "play.h" 8 | #include "controller.h" 9 | #include "screen.h" 10 | #include "link.h" 11 | #include "sound.h" 12 | #include "superGameboy.h" 13 | #include "gbc_state.h" 14 | #include "cpu.h" 15 | #include "emu.h" 16 | #include "lcd.h" 17 | #include "rsp.h" 18 | #include "hwdefs.h" 19 | #include "logger.h" 20 | #include "fps.h" 21 | #include "ppu.h" 22 | #include "debug.h" 23 | 24 | #include "rsp.h" 25 | 26 | #include 27 | 28 | typedef enum {GameboyPalette, SuperGameboyPalette, GameboyColorPalette } PaletteType; 29 | 30 | /** 31 | * Loads a gb bios file if one is available. 32 | * @param state state to copy the bios to. 33 | * @return 0 if load successful, non-zero for errors. 34 | */ 35 | static sByte loadBios(GbState* state) { 36 | sByte result = 0; 37 | byte* biosFile = null; 38 | 39 | dfs_init(DFS_DEFAULT_LOCATION); 40 | sInt filePointer = dfs_open("/bios.bin"); 41 | 42 | sInt biosSize = 0; 43 | if (filePointer >= 0) { 44 | biosSize = dfs_size(filePointer); 45 | } else { 46 | result = -1; 47 | } 48 | 49 | if (biosSize > 0) { 50 | biosFile = malloc(biosSize); 51 | dfs_read(biosFile, 1, biosSize, filePointer); 52 | ByteArray bios; 53 | bios.Data = biosFile; 54 | bios.Size = biosSize; 55 | applyBios(state, &bios); 56 | } else { 57 | result = -2; 58 | } 59 | 60 | dfs_close(filePointer); 61 | free(biosFile); 62 | biosFile = null; 63 | 64 | return result; 65 | } 66 | 67 | /** 68 | * Passes the gameboy cartridge data in to the emulator and fires it up. 69 | * @param state emulator state object. 70 | * @param romData ROM loaded from cartridge. 71 | * @param saveData Save file RAM loaded from cartridge. 72 | */ 73 | static void initialiseEmulator(GbState* state) { 74 | loadCartridge(state); 75 | cpu_reset_state(state); 76 | 77 | loadBios(state); 78 | 79 | emu_init(state); 80 | cpu_init_emu_cpu_state(state); 81 | lcd_init(state); 82 | } 83 | 84 | /** 85 | * Sets all emulation functions for this player back to a clean slate. 86 | * @param state The player to reset. 87 | */ 88 | void resetPlayState(PlayerState* state) { 89 | initialiseEmulator(&state->EmulationState); 90 | if (state->EmulationState.Cartridge.IsGbcSupported) { 91 | prepareMicrocode(FRAME_RENDERER); 92 | } else { 93 | ppuInit(state); 94 | } 95 | state->Meta.FrameCount = 0; 96 | resetSGBState(&state->SGBState); 97 | } 98 | 99 | /** 100 | * Converts buttons pressed on the n64 controller into equivalents on the gameboy's. 101 | * @param controllerNumber The controller to process. 102 | * @param buttonMap Array indexed by N64Button identifying which buttons go where. 103 | * @param n64Input struct containing which N64 buttons are currently pressed. 104 | * @out gbInput struct of gb buttons to fill in. 105 | * @private 106 | */ 107 | static void mapGbInputs(const char controllerNumber, const GbButton* buttonMap, const N64ControllerState* n64Input, bool* pressedButtons, GbController* gbInput) { 108 | getPressedButtons(n64Input, controllerNumber, pressedButtons); 109 | 110 | for (byte i = 0; i < N64_BUTTON_COUNT; i++) { 111 | if (!pressedButtons[i]) { 112 | continue; 113 | } 114 | 115 | switch(buttonMap[i]) { 116 | case GbA: gbInput->button_a = true; break; 117 | case GbB: gbInput->button_b = true; break; 118 | case GbStart: gbInput->button_start = true; break; 119 | case GbSelect: gbInput->button_select = true; break; 120 | case GbUp: gbInput->button_up = true; break; 121 | case GbDown: gbInput->button_down = true; break; 122 | case GbLeft: gbInput->button_left = true; break; 123 | case GbRight: gbInput->button_right = true; break; 124 | default: break; 125 | } 126 | } 127 | } 128 | 129 | void playAudio(const GbState* state) { 130 | if (!audio_can_write()) { 131 | return; 132 | } 133 | 134 | GbSoundChannel channels[4]; 135 | GbSoundControl soundControl; 136 | getSoundControl(state, &soundControl); 137 | 138 | if (!soundControl.Bits.IsSoundEnabled) { 139 | return; 140 | } 141 | 142 | sShort* buffer = calloc(sizeof(sShort), soundControl.BufferLength); 143 | 144 | for (byte i = 0; i < 4; i++) { 145 | getSoundChannel(state, i + 1, &channels[i]); 146 | prepareSoundBuffer(&soundControl, &channels[i], buffer); 147 | } 148 | 149 | audio_write(buffer); 150 | free(buffer); 151 | buffer = null; 152 | } 153 | 154 | /** 155 | * Handles gameboy emulation. 156 | * @param playerNumber player in play mode. 157 | */ 158 | void playLogic(const byte playerNumber) { 159 | PlayerState* playerState = &rootState.Players[playerNumber]; 160 | GbState* s = &playerState->EmulationState; 161 | 162 | #if MAX_PLAYERS >= 2 163 | if (rootState.PlayerCount == 2 && isRequestingTransfer()) { 164 | GbState* states[2] = { 165 | &rootState.Players[0].EmulationState, 166 | &rootState.Players[1].EmulationState 167 | }; 168 | exchangeLinkData(states); 169 | } 170 | #endif 171 | 172 | emu_step(playerState); 173 | 174 | #ifdef IS_SGB_ENABLED 175 | if (s->Cartridge.Header.is_sgb_supported) { 176 | processSGBData(playerState); 177 | performSGBFunctions(playerState); 178 | } 179 | #endif 180 | 181 | if (s->lcd_entered_vblank) { 182 | 183 | #ifdef SHOW_FRAME_COUNT 184 | fps_frame(); 185 | playerState->Meta.FrameCount++; 186 | #endif 187 | 188 | #ifdef FRAMES_TO_SKIP 189 | if (playerState->Meta.FrameCount % (FRAMES_TO_SKIP + 1)) { 190 | playerState->WasFrameSkipped = true; 191 | return; 192 | } else { 193 | playerState->WasFrameSkipped = false; 194 | } 195 | #endif 196 | 197 | #ifdef IS_SGB_ENABLED 198 | if (s->Cartridge.Header.is_sgb_supported) { 199 | applySGBPalettes( 200 | &playerState->SGBState, 201 | s->NextBuffer 202 | ); 203 | } 204 | #endif 205 | 206 | GbController* input = calloc(1, sizeof(GbController)); 207 | 208 | bool pressedButtons[N64_BUTTON_COUNT] = {}; 209 | 210 | mapGbInputs( 211 | playerNumber, 212 | playerState->ButtonMap, 213 | &rootState.KeysPressed, 214 | pressedButtons, 215 | input 216 | ); 217 | 218 | bool releasedButtons[N64_BUTTON_COUNT] = {}; 219 | getPressedButtons(&rootState.KeysReleased, playerNumber, releasedButtons); 220 | 221 | if (releasedButtons[playerState->SystemMenuButton]) { 222 | playerState->ActiveMode = Menu; 223 | rootState.RequiresRepaint = true; 224 | return; 225 | } 226 | 227 | emu_process_inputs(s, input); 228 | 229 | free(input); 230 | input = 0; 231 | 232 | // Write save file back to the cartridge if it has changed. 233 | if (s->isSRAMDirty) { 234 | sByte result = exportCartridgeRam(playerNumber, &s->Cartridge); 235 | if (result) { 236 | logAndPauseFrame(0, "saving to cartridge failed"); 237 | } 238 | 239 | s->isSRAMDirty = false; 240 | } 241 | 242 | // Audio off until I can test it properly. 243 | #ifdef IS_AUDIO_ENABLED 244 | if (playerState->AudioEnabled) { 245 | playAudio(s); 246 | } 247 | #endif 248 | 249 | rootState.RequiresRepaint = true; 250 | rootState.RequiresControllerRead = true; 251 | } 252 | UPDATE_PROFILE(PROFILE_DEVICE); 253 | } 254 | 255 | /** 256 | * @deprecated 257 | */ 258 | typedef struct { 259 | uintptr_t InAddress; 260 | uintptr_t OutAddress; 261 | Rectangle Screen; 262 | bool IsColour; 263 | byte IsColourPadding[3]; 264 | bool IsBusy; 265 | byte IsBusyPadding[3]; 266 | uint32_t WordPadding[2]; 267 | } Old_RspInterface; 268 | 269 | /** 270 | * Kicks off the RSP to render the next frame. 271 | * @param inBuffer gameboy screen buffer pixels are picked up by the RSP from here. 272 | * @param outBuffer after RSP generates a texture, it will DMA it back into DRAM at this address. 273 | * @param screen size and position of the textures drawn by the RSP. 274 | * @param isColour if true, inBuffer words represent 2 bit DMG pixels. Otherwise they are 16bit GBC pixels 275 | * @deprecated 276 | */ 277 | void renderFrame(uintptr_t inBuffer, uintptr_t outBuffer, Rectangle* screen, bool isColour) { 278 | Old_RspInterface* rspInterface = allocRspInterface(sizeof(Old_RspInterface)); 279 | data_cache_hit_invalidate(rspInterface, sizeof(Old_RspInterface)); 280 | // Let the RSP finish it's current frame & skip this one. 281 | //..if (rspInterface->IsBusy) { 282 | // return; 283 | //} 284 | 285 | //if (rootState.Frame) { 286 | // rdp_detach_display(); 287 | //display_show(rootState.Frame); 288 | //} 289 | //while(!(rootState.Frame = display_lock())); 290 | rdp_attach_display(rootState.Frame); 291 | 292 | haltRsp(); 293 | rspInterface->InAddress = inBuffer; 294 | rspInterface->OutAddress = outBuffer; 295 | rspInterface->Screen = *screen; 296 | rspInterface->IsColour = isColour; 297 | rspInterface->IsBusy = true; 298 | 299 | data_cache_hit_writeback(rspInterface, sizeof(Old_RspInterface)); 300 | 301 | run_ucode(); 302 | } 303 | 304 | /** 305 | * Draws gameboy screen. 306 | * @param playerNumber player in play mode. 307 | */ 308 | void playDraw(const byte playerNumber) { 309 | if (rootState.Players[playerNumber].EmulationState.Cartridge.IsGbcSupported) { 310 | Rectangle screen = {}; 311 | getScreenPosition(playerNumber, &screen); 312 | 313 | PaletteType palette = GameboyColorPalette; 314 | 315 | screen = (Rectangle) { screen.Left, screen.Top, 320, 12 }; 316 | 317 | renderFrame( 318 | (uintptr_t)rootState.Players[playerNumber].EmulationState.NextBuffer, 319 | (uintptr_t)rootState.Players[playerNumber].EmulationState.TextureBuffer, 320 | &screen, 321 | palette == GameboyColorPalette 322 | ); 323 | } 324 | 325 | #ifdef SHOW_FRAME_COUNT 326 | string text = ""; 327 | 328 | sprintf(text, "FPS: %d %lld", fps_get(), rootState.Players[playerNumber].Meta.FrameCount); 329 | graphics_set_color(GLOBAL_TEXT_COLOUR, 0x0); 330 | graphics_draw_box(rootState.Frame, 0, 450, 680, 10, GLOBAL_BACKGROUND_COLOUR); 331 | graphics_draw_text(rootState.Frame, 5, 450, text); 332 | #endif 333 | } -------------------------------------------------------------------------------- /play.h: -------------------------------------------------------------------------------- 1 | #ifndef PLAY_INCLUDED 2 | #define PLAY_INCLUDED 3 | 4 | #include "state.h" 5 | 6 | /** 7 | * Sets all emulation functions for this player back to a clean slate. 8 | * @param state The player to reset. 9 | */ 10 | void resetPlayState(PlayerState* state); 11 | 12 | /** 13 | * Handles gameboy emulation. 14 | * @param playerNumber player in play mode. 15 | */ 16 | void playLogic(const byte playerNumber); 17 | 18 | /** 19 | * Draws gameboy screen. 20 | * @param playerNumber player in play mode. 21 | */ 22 | void playDraw(const byte playerNumber); 23 | 24 | /** 25 | * Does any necessary cleanup after drawing. 26 | * @param playerNumber player in play mode. 27 | */ 28 | void playAfter(const byte playerNumber); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /polyfill.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /** 4 | * There's no threading in libdragon yet, so we're just gonna busy-wait until 5 | * that becomes a terrible idea. 6 | */ 7 | unsigned int sleep(unsigned int seconds) { 8 | wait_ms(seconds * 1000); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /ppu.c: -------------------------------------------------------------------------------- 1 | #include "global.h" 2 | #include "rsp.h" 3 | #include "hwdefs.h" 4 | #include "config.h" 5 | 6 | #include "logger.h" 7 | 8 | #include 9 | 10 | typedef struct { // Extends ppuInterface 11 | union { 12 | u32 Settings[8]; 13 | struct { 14 | uintptr_t VRamAddress; 15 | uintptr_t HRamAddress; 16 | uintptr_t OAMAddress; 17 | uintptr_t OutBuffer; 18 | Rectangle Screen; 19 | uintptr_t FrameBuffer[2]; 20 | }; 21 | }; 22 | } PpuInterface; 23 | 24 | extern void* __safe_buffer[]; 25 | 26 | PpuInterface* ppuInterface; 27 | 28 | void ppuInit(PlayerState* state) { 29 | Rectangle screen = {}; 30 | getScreenPosition(0, &screen); 31 | 32 | ppuInterface = allocRspInterface(sizeof(PpuInterface)); 33 | 34 | ppuInterface->Screen = (Rectangle) { screen.Left, screen.Top, 320, 2 }; 35 | 36 | ppuInterface->VRamAddress = (uintptr_t) state->EmulationState.VRAM; 37 | ppuInterface->HRamAddress = (uintptr_t) state->EmulationState.HRAM; 38 | ppuInterface->OAMAddress = (uintptr_t) state->EmulationState.OAM; 39 | ppuInterface->OutBuffer = (uintptr_t)state->EmulationState.TextureBuffer; 40 | ppuInterface->FrameBuffer[0] = (uintptr_t) (__safe_buffer[0]); 41 | ppuInterface->FrameBuffer[1] = (uintptr_t) (__safe_buffer[1]); 42 | 43 | data_cache_hit_writeback(ppuInterface, sizeof(PpuInterface)); 44 | 45 | prepareMicrocode(UCODE_DMG_PPU); 46 | 47 | setDataReady(false); 48 | run_ucode(); 49 | } 50 | 51 | void ppuStep(PlayerState* state) { 52 | if (state->EmulationState.lcd_entered_hblank) { 53 | 54 | if (state->EmulationState.CurrentLine >= GB_LCD_HEIGHT) { // VBlank 55 | return; 56 | } 57 | 58 | #ifdef FRAMES_TO_SKIP 59 | if ((state->Meta.FrameCount + 1) % (FRAMES_TO_SKIP + 1)) { 60 | return; 61 | } 62 | #endif 63 | 64 | data_cache_hit_writeback(state->EmulationState.OAM, OAM_SIZE); 65 | data_cache_hit_writeback(state->EmulationState.HRAM, HRAM_SIZE); 66 | if (state->EmulationState.isVramDirty) { 67 | data_cache_hit_writeback(state->EmulationState.VRAM, VRAM_BANK_SIZE); 68 | state->EmulationState.isVramDirty = false; 69 | } 70 | 71 | // Let the RSP finish its current job. 72 | #ifdef LOCK_CPU_TO_PPU 73 | while(isRspBusy()); 74 | #endif 75 | setDataReady(true); 76 | } 77 | } -------------------------------------------------------------------------------- /ppu.h: -------------------------------------------------------------------------------- 1 | #ifndef PPU_INCLUDED 2 | #define PPU_INCLUDED 3 | 4 | #include "state.h" 5 | 6 | void ppuInit(PlayerState* state); 7 | void ppuStep(PlayerState* state); 8 | 9 | #endif -------------------------------------------------------------------------------- /progressBar.c: -------------------------------------------------------------------------------- 1 | #include "core.h" 2 | #include "types.h" 3 | #include "screen.h" 4 | #include "text.h" 5 | #include "resources.h" 6 | #include 7 | #include 8 | 9 | static bool _isLoading[MAX_PLAYERS] = {0}; 10 | static timer_link_t* _timer; 11 | static const int UPDATE_TIME = 500000; 12 | 13 | /** 14 | * Fired periodically as we load a cartridge. It updates the display so we know 15 | * how much longer to wait (and that we haven't crashed.) 16 | * @param controllerNumber controller we are loading from. 17 | */ 18 | static void updateProgressIndicator(const byte controllerNumber) { 19 | byte loadPercent = getLoadProgress(controllerNumber); 20 | string text; 21 | getText(TextLoadingCartridge, text); 22 | sprintf(text, "%s - %d%%", text, loadPercent); 23 | 24 | Rectangle screen = {}; 25 | getScreenPosition(controllerNumber, &screen); 26 | 27 | natural textTop = screen.Top - TEXT_HEIGHT + (screen.Width / 2); 28 | 29 | while(!(rootState.Frame = display_lock())); 30 | 31 | // Hide the previous text. 32 | prepareRdpForSprite(rootState.Frame); 33 | loadSprite(getSpriteSheet(), GB_BG_TEXTURE, MIRROR_DISABLED); 34 | rdp_draw_textured_rectangle(0, screen.Left - 1, screen.Top, screen.Left + screen.Width, screen.Top + screen.Height, MIRROR_XY); 35 | drawTextParagraph(rootState.Frame, text, screen.Left + TEXT_WIDTH, textTop, 0.8, screen.Width - TEXT_WIDTH); 36 | 37 | display_show(rootState.Frame); 38 | } 39 | 40 | /** 41 | * Timer callback, it updates the progress bar of any player that is needs one. 42 | * @param ovfl 43 | */ 44 | static void onLoadProgressTimer(int ovfl) { 45 | for (byte i = 0; i < MAX_PLAYERS; i++) { 46 | if (_isLoading[i]) { 47 | updateProgressIndicator(i); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Sets up handlers to display a progress bar for the cartridge load. 54 | * @param playerNumber identifies player loading a cartridge. 55 | */ 56 | void startLoadProgressTimer(const byte playerNumber) { 57 | // The main loop is locked up with loading the rom, so we need to have an internal 58 | // display lock/show loop. 59 | // This needs to be balanced with the main loop or things get out of whack. 60 | display_show(rootState.Frame); 61 | _isLoading[playerNumber] = true; 62 | 63 | if (!_timer) { 64 | _timer = new_timer(TIMER_TICKS(UPDATE_TIME), TF_CONTINUOUS, onLoadProgressTimer); 65 | } 66 | } 67 | 68 | /** 69 | * Cleans up timer once a cartridge is loaded 70 | * @param playerNumber player no longer using the progress bar 71 | */ 72 | void closeLoadProgressTimer(const byte playerNumber) { 73 | _isLoading[playerNumber] = false; 74 | 75 | // Check if there are any other players still using the timer. 76 | bool isDone = true; 77 | for (byte i = 0; i < MAX_PLAYERS; i++) { 78 | if (_isLoading[i]) { 79 | isDone = false; 80 | } 81 | } 82 | 83 | // If this was the last one, clean up. 84 | if (isDone) { 85 | delete_timer(_timer); 86 | _timer = null; 87 | } 88 | 89 | // Balance with the main loop. 90 | while(!(rootState.Frame = display_lock())); 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /progressBar.h: -------------------------------------------------------------------------------- 1 | #ifndef PROGRESS_BAR_INCLUDED 2 | #define PROGRESS_BAR_INCLUDED 3 | 4 | #include 5 | 6 | /** 7 | * Sets up handlers to display a progress bar for the cartridge load. 8 | * @param playerNumber identifying player that will load a cartridge. 9 | */ 10 | void startLoadProgressTimer(const byte playerNumber); 11 | 12 | /** 13 | * Cleans up handlers for displaying the cartridge load progress bar. 14 | */ 15 | void closeLoadProgressTimer(const byte playerNumber); 16 | 17 | #endif -------------------------------------------------------------------------------- /resources.h: -------------------------------------------------------------------------------- 1 | #ifndef RESOURCES_INCLUDED 2 | #define RESOURCES_INCLUDED 3 | #include 4 | #include 5 | #include 6 | #include "core.h" 7 | 8 | static const byte GB_START_SPRITE = 0; 9 | static const byte GB_SELECT_SPRITE = 1; 10 | static const byte N64_START_SPRITE = 2; 11 | static const byte MENU_SPRITE = 3; 12 | static const byte N64_L_SPRITE = 4; 13 | static const byte N64_R_SPRITE = 5; 14 | static const byte N64_Z_SPRITE = 6; 15 | static const byte N64_C_SPRITE = 7; 16 | static const byte BLUE_BG_TEXTURE = 8; 17 | static const byte CREAM_BG_TEXTURE = 9; 18 | static const byte GB_BG_TEXTURE = 0x0A; 19 | static const byte ERROR_SPRITE = 0x0B; 20 | 21 | typedef enum { 22 | ROTATE_90 = '>', 23 | ROTATE_180 = 'v', 24 | ROTATE_270 = '<', 25 | FLIP_HORIZONTAL = 'W', 26 | FLIP_VERTICAL = 'V', 27 | FADE = '~' 28 | } Transformation; 29 | 30 | 31 | /** 32 | * Loads oft-used resources in to memory. 33 | * @return success/error code 34 | ** 0 initted successfully 35 | ** -1 could not find expected resource file. 36 | */ 37 | sByte initResources(); 38 | 39 | /** 40 | * Releases memory held by all resources. 41 | */ 42 | void freeResources(); 43 | 44 | /** 45 | * Frees up the cache but leaves the resources subsystem initialised. 46 | */ 47 | void emptyResourceCache(); 48 | 49 | /** 50 | * Gets sprite sheet of textual characters. 51 | * @return pointer to sprite sheet. 52 | */ 53 | sprite_t* getCharacterSheet(); 54 | 55 | /** 56 | * Gets sprite sheet of icons and textures. 57 | * @return pointer to sprite sheet. 58 | */ 59 | sprite_t* getSpriteSheet(); 60 | 61 | 62 | 63 | /** 64 | * Loads a ROM stored in the n64 filesystem rather than from the TPAK 65 | * @out output data for the rom goes here. 66 | * @returns success/error code 67 | ** 0 loaded successfully 68 | ** -1 file not found. 69 | */ 70 | sByte loadInternalRom(ByteArray* output); 71 | 72 | /** 73 | * Takes an existing sprite, transforms it, and stashes it in a cache for next time it's needed. 74 | * @param sheet Source sprite sheet. 75 | * @param spriteCode Identifies source sprite on the sheet. 76 | * @param rotation How the sprite should be transformed. 77 | * @return Pointer to the new transformed sprite. 78 | */ 79 | sprite_t* transformSprite(const sprite_t* sheet, const byte spriteCode, const Transformation transformation); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /rsp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "core.h" 4 | #include "rsp.h" 5 | #include "global.h" 6 | #include "logger.h" 7 | 8 | extern const char ppuDMG_code_start __attribute((section(".data"))); 9 | extern const char ppuDMG_code_end __attribute((section(".data"))); 10 | extern const char ppuDMG_code_size __attribute((section(".data"))); 11 | 12 | extern const char ppuDMG_data_start __attribute((section(".data"))); 13 | extern const char ppuDMG_data_end __attribute((section(".data"))); 14 | extern const char ppuDMG_data_size __attribute((section(".data"))); 15 | 16 | extern const char renderer_code_start __attribute((section(".data"))); 17 | extern const char renderer_code_end __attribute((section(".data"))); 18 | extern const char renderer_code_size __attribute((section(".data"))); 19 | 20 | /** 21 | * Memory Address that RSP will read from to get data shared between the two processors. 22 | */ 23 | volatile RspInterface rspInterface __attribute__ ((section (".rspInterface"))) __attribute__ ((__used__)); 24 | 25 | // Following taken from libdragon source since it doesn't provide direct access to these registers. 26 | typedef struct SP_regs_s { 27 | /** @brief RSP memory address (IMEM/DMEM) */ 28 | volatile void * RSP_addr; 29 | /** @brief RDRAM memory address */ 30 | volatile void * DRAM_addr; 31 | /** @brief RDRAM->RSP DMA length */ 32 | uint32_t rsp_read_length; 33 | /** @brief RDP->RDRAM DMA length */ 34 | uint32_t rsp_write_length; 35 | /** @brief RSP status */ 36 | uint32_t status; 37 | /** @brief RSP DMA full */ 38 | uint32_t rsp_dma_full; 39 | /** @brief RSP DMA busy */ 40 | uint32_t rsp_dma_busy; 41 | /** @brief RSP Semaphore */ 42 | uint32_t rsp_semaphore; 43 | } SP_regs_t; 44 | 45 | static volatile struct SP_regs_s* const SP_regs = (struct SP_regs_s *)0xA4040000; 46 | 47 | #define SP_DMA_IMEM 0x04001000 48 | #define SP_STATUS_GET_IS_BUSY 0x080 49 | 50 | 51 | 52 | /** 53 | * Called if the RSP hits a break instruction. 54 | */ 55 | static void onRSPException() { 56 | data_cache_hit_invalidate(&rspInterface, sizeof(RspInterface)); 57 | printSegmentToFrame(rootState.Frame, "RSP Exception Raised - dumping rspInterface", (byte*) &rspInterface); 58 | } 59 | 60 | /** 61 | * Extend interface 62 | */ 63 | void* allocRspInterface(size_t size) { 64 | if (size != sizeof(RspInterface)) { 65 | // Raise some sort of exception. 66 | return null; 67 | } 68 | 69 | return (void*) &rspInterface; 70 | } 71 | 72 | /** 73 | * DMAs a fixed set of instructions to the RSP ready to be run when we call run_ucode() 74 | */ 75 | s8 prepareMicrocode(const Microcode code) { 76 | register_SP_handler(&onRSPException); 77 | set_SP_interrupt(1); 78 | 79 | unsigned long size = 0; 80 | switch (code) { 81 | case UCODE_DMG_PPU: 82 | size = (unsigned long) &ppuDMG_data_size; 83 | load_data((void*)&ppuDMG_data_start, size); 84 | 85 | size = (unsigned long)&ppuDMG_code_size; 86 | load_ucode((void*)&ppuDMG_code_start, size); 87 | break; 88 | case FRAME_RENDERER: 89 | size = (unsigned long)&renderer_code_size; 90 | load_ucode((void*)&renderer_code_start, size); 91 | case UCODE_GBC_PPU: 92 | case UCODE_SGB_PPU: 93 | return RSP_ERR_UNIMPLEMENTED_UCODE; 94 | default: 95 | return RSP_ERR_INVALID_UCODE; 96 | } 97 | 98 | SP_regs->status = SP_STATUS_BUSY_OFF; 99 | 100 | return RSP_SUCCESS; 101 | } 102 | 103 | /** 104 | * Signals to the RSP whether there is more data to be processed. 105 | * @param value to set. 106 | */ 107 | void setDataReady(bool value) { 108 | SP_regs->status = value ? SP_DATA_READY : SP_DATA_PENDING; 109 | } 110 | 111 | /** 112 | * Gets whether the RSP has more data to be processed. 113 | */ 114 | bool getDataReady() { 115 | return SP_regs->status & SP_STATUS_GET_IS_READY; 116 | } 117 | 118 | /** 119 | * Sets which frame buffer the RSP should be targetting. 120 | * @param id identifies the buffer (1 or 2) 121 | */ 122 | void setFrameBufferId(display_context_t id) { 123 | SP_regs->status = id == 1 ? SP_BUFFER_1 : SP_BUFFER_2; 124 | } 125 | 126 | /** 127 | * Sets the RSP halt status so that it stops executing while we reload IMEM/DMEM 128 | */ 129 | void haltRsp() { 130 | SP_regs->status = SP_STATUS_HALT_ON | SP_STATUS_BUSY_OFF; 131 | } 132 | 133 | /** 134 | * Checks the RSP interface to determine what it's doing. 135 | * @returns True if the RSP is working, false if it's idle. 136 | */ 137 | bool isRspBusy() { 138 | return SP_regs->status & SP_STATUS_GET_IS_BUSY; 139 | } -------------------------------------------------------------------------------- /rsp.h: -------------------------------------------------------------------------------- 1 | #ifndef RSP_INCLUDED 2 | #define RSP_INCLUDED 3 | 4 | #include 5 | #include "screen.h" 6 | 7 | typedef enum { 8 | RSP_SUCCESS = 0, 9 | RSP_ERR_INVALID_UCODE = -127, 10 | RSP_ERR_UNIMPLEMENTED_UCODE 11 | } RspError; 12 | 13 | typedef struct { 14 | u32 Settings[8]; 15 | } RspInterface; 16 | 17 | typedef enum { NONE, UCODE_DMG_PPU, UCODE_GBC_PPU, UCODE_SGB_PPU, FRAME_RENDERER } Microcode; 18 | 19 | typedef void (*RspEventHandler)(); 20 | 21 | /** 22 | * DMAs a fixed set of instructions to the RSP ready to be run when we call run_ucode() 23 | * @returns Error Code 24 | ** 0 Success 25 | ** -1 Invalid microcode 26 | */ 27 | s8 prepareMicrocode(const Microcode code); 28 | 29 | /** 30 | * Sets the RSP halt status so that it stops executing while we reload IMEM/DMEM 31 | */ 32 | void haltRsp(); 33 | 34 | /** 35 | * Extend interface 36 | */ 37 | void* allocRspInterface(size_t size); 38 | 39 | /** 40 | * Checks the RSP interface to determine what it's doing. 41 | * @returns True if the RSP is working, false if it's idle. 42 | */ 43 | bool isRspBusy(); 44 | 45 | /** 46 | * Signals to the RSP whether there is more data to be processed. 47 | * @param value to set. 48 | */ 49 | void setDataReady(bool value); 50 | 51 | /** 52 | * Sets which frame buffer the RSP should be targetting. 53 | * @param id identifies the buffer (1 or 2) 54 | */ 55 | void setFrameBufferId(display_context_t id); 56 | 57 | /** 58 | * Gets whether the RSP has more data to be processed. 59 | */ 60 | bool getDataReady(); 61 | 62 | 63 | #endif -------------------------------------------------------------------------------- /rsp/Makefile: -------------------------------------------------------------------------------- 1 | N64_INST = /home/joeldipops/Projects/tools/n64inst 2 | N64PREFIX = $(N64_INST)/bin/mips64-elf- 3 | CC = $(N64PREFIX)gcc 4 | AS = $(N64PREFIX)as 5 | LD = $(N64PREFIX)ld 6 | AR = $(N64PREFIX)ar 7 | OBJCOPY = $(N64PREFIX)objcopy 8 | 9 | all: rsp 10 | clean: rsp-clean 11 | 12 | rsp: ppuDMG.o ppuDMGData.o renderer.o 13 | 14 | ppuDMG: ppuDMG.o 15 | 16 | ppuDMG.dsm: ppuDMGText.o 17 | mips-linux-gnu-objdump ppuDMGText.o -m mips -D > ppuDMG.dsm 18 | 19 | ppuDMGText.o: ppuDMG.rsp 20 | $(CC) -x assembler-with-cpp -c -o ppuDMGText.o ppuDMG.rsp 21 | 22 | ppuDMG.bin: ppuDMGText.o 23 | $(OBJCOPY) -O binary -j .data ppuDMGText.o ppuDMGData.bin 24 | $(OBJCOPY) -O binary -j .text ppuDMGText.o ppuDMG.bin 25 | 26 | ppuDMGData.o: ppuDMGData.bin 27 | $(OBJCOPY) -I binary -O elf32-bigmips -B mips4300 --redefine-sym _binary_ppuDMGData_bin_start=ppuDMG_data_start --redefine-sym _binary_ppuDMGData_bin_end=ppuDMG_data_end --redefine-sym _binary_ppuDMGData_bin_size=ppuDMG_data_size ppuDMGData.bin ppuDMGData.o 28 | 29 | ppuDMG.o: ppuDMG.bin 30 | $(OBJCOPY) -I binary -O elf32-bigmips -B mips4300 --redefine-sym _binary_ppuDMG_bin_start=ppuDMG_code_start --redefine-sym _binary_ppuDMG_bin_end=ppuDMG_code_end --redefine-sym _binary_ppuDMG_bin_size=ppuDMG_code_size --rename-section .text=.data ppuDMG.bin ppuDMG.o 31 | 32 | ppuDMG: ppuDMG.o 33 | 34 | ### LEGACY RENDERER SO CAN 'SUPPORT' GBC FOR NOW 35 | rendererText.o: renderer.rsp 36 | $(CC) -x assembler-with-cpp -c -o rendererText.o renderer.rsp 37 | 38 | renderer.bin: rendererText.o 39 | $(OBJCOPY) -O binary -j .data rendererText.o rendererData.bin 40 | $(OBJCOPY) -O binary -j .text rendererText.o renderer.bin 41 | 42 | renderer.o: renderer.bin 43 | $(OBJCOPY) -I binary -O elf32-bigmips -B mips4300 --redefine-sym _binary_renderer_bin_start=renderer_code_start --redefine-sym _binary_renderer_bin_end=renderer_code_end --redefine-sym _binary_renderer_bin_size=renderer_code_size --rename-section .text=.data renderer.bin renderer.o 44 | 45 | rsp-clean: 46 | rm -f *.o *.bin 47 | 48 | .PHONY : rsp rsp-clean -------------------------------------------------------------------------------- /rsp/init.rsp: -------------------------------------------------------------------------------- 1 | .text 2 | ### 3 | # A hack cos I haven't bothered to look deeper into how headers & linkage 4 | # work in assembly. 5 | ### 6 | entrypoint: 7 | j main -------------------------------------------------------------------------------- /rsp/old_rdp.rsp: -------------------------------------------------------------------------------- 1 | #include "old_rspIncludes.rsp" 2 | 3 | # TODO: execRdp needs to be changed from a macro to a routine to save on space. 4 | 5 | .eqv RGBA_FORMAT, 0 6 | .eqv COLOUR_DEPTH_16, 2 7 | 8 | .eqv CLAMP_DISABLAED, 0 9 | .eqv MIRROR_DISABLED, 0 10 | .eqv MASK_T, 0 11 | .eqv MASK_S, 0 12 | .eqv SHIFT_T, 0 13 | .eqv SHIFT_S, 0 14 | 15 | ### 16 | # Sends an RDP command by writing to the RDP registers. 17 | # Assumes command starts at DMEM address 0 18 | # @param {address} $a0 address after last word to send. 19 | ### 20 | _execRdp: 21 | 1: 22 | mfc0 $t0, $RDP_CMD_STATUS 23 | # bit 8 of $RDP_CMD_STATUS = RDP DMA is busy 24 | andi $t0, $t0, RDP_DMA_BUSY 25 | bne $t0, $0, 1b 26 | 27 | # send 2 to RDP_CMD_STATUS 28 | # in order to DMA from RSP memory (DMEM) 29 | addi $t0, $0, DMEM_RDP_DMA 30 | mtc0 $t0, $RDP_CMD_STATUS 31 | 32 | # Kick off the DMA by setting both start and end. 33 | mtc0 $0, $RDP_CMD_START 34 | mtc0 $a0, $RDP_CMD_END 35 | jr $ra 36 | 37 | 38 | ### 39 | # Sends an RDP command by writing to the RDP registers. 40 | # Assumes command starts at DMEM address 0 41 | # @input words The number of words to send 42 | ### 43 | .macro execRdp words 44 | # + 4 because end expects the next address after data ends. 45 | addi $a0, $0, (\words * 4) + 4 46 | jal _execRdp 47 | .endm 48 | 49 | ### 50 | # Executes the syncPipe RDP command 51 | # Command 0x27 52 | ### 53 | .macro syncPipe 54 | lui $t0, 0xE700 55 | sw $t0, 0x000($0) 56 | execRdp 1 57 | .endm 58 | 59 | ### 60 | # Executes the syncFull RDP command 61 | # Command 0x27 62 | ### 63 | .macro syncFull 64 | lui $t0, 0xE900 65 | sw $t0, 0x000($0) 66 | execRdp 1 67 | .endm 68 | 69 | ### 70 | # Executes the setFillColour RDP command 71 | # Command 0x37 72 | # @input upper The colour to set. Upper 16b for 32b colours. 73 | # @input? lower Lower 16b for 32b colours. 74 | ### 75 | .macro setFillColour upper, lower 76 | # "0x37 Set Fill Color" 77 | lui $t0, 0xF700 78 | sw $t0, 0x000($0) 79 | 80 | ## Second Byte - Packed Color 81 | lui $t0, \upper 82 | 83 | .ifb lower 84 | ori $t0, \lower 85 | .else 86 | ori $t0, \upper 87 | .endif 88 | sw $t0, 0x004($0) 89 | 90 | execRdp 2 91 | .endm 92 | 93 | ### 94 | # Fills specified rectangle with the colour set in setFillColour command (0x37) 95 | # Command 0x36 96 | # @input x1 x co-ord of top left corner. 97 | # @input y1 y co-ord of top left corner. 98 | # @input x2 x co-ord of bottom right corner 99 | # @input y2 y co-ord of bottom right corner 100 | .macro fillRectangle x1, y1, x2, y2 101 | # Command 0x36 (shifted right twice) 102 | # (yes for some reason the numbers we set 103 | # are divided by 4 to get the numbers that 104 | # are displayed. ?) 105 | lui $t0, 0x3D80 106 | 107 | add $t1, $0, \x2 108 | sll $t1, $t1, 12 109 | or $t0, $t0, $t1 110 | or $t0, $t0, \y2 111 | 112 | sll $t0, $t0, 2 113 | 114 | sw $t0, 0x000($0) 115 | 116 | add $t0, \x1, $0 117 | sll $t0, $t0, 12 118 | or $t0, $t0, \y1 119 | sll $t0, $t0, 2 120 | 121 | sw $t0, 0x004($0) 122 | 123 | execRdp 2 124 | .endm 125 | 126 | ### 127 | # 128 | # Command 0x24 Texture Rectangle 129 | # @input tileIndex 130 | # @input x1 x co-ord of bottom right 131 | # @input y1 y co-ord of bottom right 132 | # @input x2 x co-ord of top left 133 | # @input y2 y co-ord of top left 134 | # @input s s co-ord of texture top left 135 | # @input t t co-ord of texture top left 136 | # @input ds change in s / x 137 | # @input dt change in t / y 138 | ### 139 | .macro textureRectangle tileIndex x1 y1 x2 y2 s t ds dt 140 | lui $t0, 0xE400 141 | 142 | # We multiply these by four. why I don't know at this point. 143 | sll \x1, \x1, 2 144 | sll \x2, \x2, 2 145 | sll \y1, \y1, 2 146 | sll \y2, \y2, 2 147 | 148 | add $t1, $0, \x2 149 | sll $t1, $t1, 12 150 | or $t1, $t1, \y2 151 | or $t0, $t0, $t1 152 | 153 | sw $t0, 0x000($0) 154 | 155 | lui $t0, \tileIndex 156 | sll $t0, $t0, 8 157 | 158 | add $t1, $0, \x1 159 | sll $t1, $t1, 12 160 | or $t1, $t1, \y1 161 | or $t0, $t0, $t1 162 | 163 | sw $t0, 0x004($0) 164 | 165 | lui $t0, \s 166 | ori $t0, $t0, \t 167 | 168 | sw $t0, 0x008($0) 169 | 170 | lui $t0, \ds 171 | ori $t0, $t0, \dt 172 | 173 | sw $t0, 0x00C($0) 174 | execRdp 4 175 | .endm 176 | 177 | ### 178 | # 179 | # Command 0x34 Load Tile 180 | # @input tileIndex 181 | # @input SL low S co-ord 182 | # @input TL low T co-ord 183 | # @input SH high S co-ord 184 | # @input TH high T co-ord 185 | ### 186 | .macro loadTile tile, sl, tl, sh, th 187 | # Command 0x34 188 | lui $t0, 0xF400 189 | 190 | addi $t1, $0, \sl 191 | # multiply by 4, then shift 12. 192 | sll $t1, $t1, 12 + 2 193 | 194 | or $t0, $t0, $t1 195 | 196 | addi $t1, $0, \tl 197 | # Multiply by 4 because the RDP says so. 198 | sll $t1, $t1, 2 199 | or $t0, $t0, $t1 200 | 201 | sw $t0, 0x000($0) 202 | 203 | lui $t0, \tile 204 | sll $t0, $t0, 8 205 | # subtract 1 for the high values (again, because RDP says so) 206 | addi $t1, $0, \sh - 1 207 | sll $t1, $t1, 12 + 2 208 | or $t0, $t0, $t1 209 | addi $t1, $0, \th -1 210 | sll $t1, $t1, 2 211 | or $t0, $t0, $t1 212 | 213 | sw $t0, 0x004($0) 214 | 215 | execRdp 2 216 | .endm 217 | 218 | # 219 | # Command 0x35 Set Tile 220 | # @input format 221 | # @input depth colour depth 222 | # @input size size of tile line in 64b words 223 | # @input tmem address in tmem (in 64b words so 2 = address 0x008) 224 | # @input palette 225 | # @input {boolean} clampT on/off 226 | # @input {boolean} mirrorT on/off 227 | # etc 228 | ### 229 | .macro setTile \ 230 | tileIndex format depth size tmem \ 231 | palette clampT mirrorT maskT \ 232 | shiftT clampS mirrorS maskS shiftS 233 | 234 | # Command 0x35 235 | lui $t0, 0xF500 236 | 237 | lui $t1, \format 238 | lui $t2, \depth 239 | sll $t1, $t1, 5 240 | sll $t2, $t2, 3 241 | or $t1, $t1, $t2 242 | or $t0, $t0, $t1 243 | 244 | addi $t1, $0, \size 245 | sll $t1, $t1, 9 246 | ori $t1, $t1, \tmem 247 | 248 | or $t0, $t0, $t1 249 | 250 | sw $t0, 0x000($0) 251 | 252 | lui $t0, \tileIndex 253 | sll $t0, $t0, 8 254 | 255 | # All this is a waste of time & space for now since I'm setting it all to zero. 256 | # But can stay until it becomes a problem or I learn mips macros a bit better. 257 | lui $t1, \palette 258 | sll $t1, $t1, 4 259 | or $t0, $t0, $t1 260 | 261 | lui $t1, \clampT 262 | sll $t1, $t1, 3 263 | or $t0, $t0, $t1 264 | 265 | lui $t1, \mirrorT 266 | sll $t1, $t1, 2 267 | or $t0, $t0, $t1 268 | 269 | addi $t1, $0, \maskT 270 | sll $t1, $t1, 14 271 | or $t0, $t0, $t1 272 | 273 | addi $t1, $0, \shiftT 274 | sll $t1, $t1, 10 275 | or $t0, $t0, $t1 276 | 277 | addi $t1, $0, \clampS 278 | sll $t1, $t1, 9 279 | or $t0, $t0, $t1 280 | 281 | addi $t1, \mirrorS 282 | sll $t1, $t1, 8 283 | or $t0, $t0, $t1 284 | 285 | addi $t1, $0, \maskS 286 | sll $t1, $t1, 4 287 | or $t0, $t0, $t1 288 | 289 | ori $t0, $t0, \shiftS 290 | 291 | sw $t0, 0x004($0) 292 | 293 | execRdp 2 294 | 295 | .endm 296 | 297 | ### 298 | # 299 | # Command 0x3D 300 | # @input format 301 | # @input depth colour depth 302 | # @input width 303 | # @input address 304 | # @sideAffect registers $2, $t1, $t2 affected. 305 | # @sideAffect DMEM 0x000 - 0x008 affected. 306 | ### 307 | .macro setTextureImage format depth width address 308 | # Command 0x3D 309 | lui $t0, 0xFD00 310 | 311 | lui $t1, \format 312 | lui $t2, \depth 313 | sll $t1, $t1, 5 314 | sll $t2, $t2, 3 315 | or $t1, $t1, $t2 316 | or $t0, $t0, $t1 317 | 318 | ori $t0, $t0, (\width - 1) 319 | sw $t0, 0x000($0) 320 | 321 | ##if it's a register 322 | sw \address, 0x004($0) 323 | ##else it's a constant... 324 | #liw $t0, \address 325 | ##endif 326 | 327 | execRdp 2 328 | .endm -------------------------------------------------------------------------------- /rsp/old_rspIncludes.rsp: -------------------------------------------------------------------------------- 1 | 2 | # co-processor 0 registers 3 | .set $c0, $0 4 | .set $c1, $1 5 | .set $c2, $2 6 | .set $c3, $3 7 | .set $c4, $4 8 | .set $c5, $5 9 | .set $c6, $6 10 | .set $c7, $7 11 | .set $c8, $8 12 | .set $c9, $9 13 | .set $c10, $10 14 | .set $c11, $11 15 | .set $c12, $12 16 | .set $c13, $13 17 | .set $c14, $14 18 | .set $c15, $15 19 | 20 | # co-processor 0 register descriptions. 21 | .set $DMA_DMEM, $c0 22 | .set $DMA_DRAM, $c1 23 | .set $DMA_IN_LENGTH, $c2 24 | .set $DMA_OUT_LENGTH, $c3 25 | .set $RSP_STATUS, $c4 26 | .set $DMA_FULL, $c5 27 | .set $DMA_BUSY, $c6 28 | .set $RSP_RESERVED, $c7 29 | .set $RDP_CMD_START, $c8 30 | .set $RDP_CMD_END, $c9 31 | .set $RDP_CMD_CURRENT, $c10 32 | .set $RDP_CMD_STATUS, $c11 33 | .set $RDP_CMD_CLOCK, $c12 34 | .set $RDP_CMD_BUSY, $c13 35 | .set $RDP_CMD_PIPE_BUSY,$c14 36 | .set $RDP_CMD_TMEM_BUSY,$c15 37 | 38 | .equ DRAM_RDP_DMA, 1 39 | .equ DMEM_RDP_DMA, 2 40 | .equ RDP_DMA_BUSY, 0x0100 41 | 42 | # primary accumulator 43 | .set $zero, $0 44 | .set $at, $1 45 | 46 | # function return values 47 | .set $r0, $2 48 | .set $r1, $3 49 | 50 | # function arguments 51 | .set $a0, $4 52 | .set $a1, $5 53 | .set $a2, $6 54 | .set $a3, $7 55 | 56 | .set $t0, $8 57 | .set $t1, $9 58 | .set $t2, $10 59 | .set $t3, $11 60 | .set $t4, $12 61 | .set $t5, $13 62 | .set $t6, $14 63 | .set $t7, $15 64 | 65 | .set $s0, $16 66 | .set $s1, $17 67 | .set $s2, $18 68 | .set $s3, $19 69 | .set $s4, $20 70 | .set $s5, $21 71 | .set $s6, $22 72 | .set $s7, $23 73 | 74 | # We have so little memory, so intead of more temporary regs, a few more saved regs go a long way. 75 | .set $s8, $24 76 | .set $s9, $25 77 | 78 | # There's no kernal, so no need for kernal reigsters 79 | .set $s10, $26 80 | .set $s11, $27 81 | 82 | # We'll keep a stack pointer just in case, but other pointers aren't gonna be used. 83 | .set $s12, $28 84 | .set $s13, $29 85 | .set $sp, $30 86 | .set $ra, $31 -------------------------------------------------------------------------------- /rsp/ppu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/rsp/ppu -------------------------------------------------------------------------------- /rsp/ppuGBC.rsp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../global.h" 3 | #include "init.rsp" 4 | #include "rsp.inc" 5 | #include "rdp.h" 6 | 7 | ###------------------------------------------ 8 | # Renders a scan line of the gameboy screen. 9 | # 10 | ###------------------------------------------ 11 | 12 | ### 13 | # Constants 14 | ### 15 | 16 | .eqv Input_IN_ADDRESS, 0x00 17 | .eqv Input_OUT_ADDRESS, 0x04 18 | .eqv Input_SCREEN_POSITION, 0x08 19 | .eqv Input_RECTANGLE_SIZE, 0x0C 20 | .eqv Input_IS_BUSY, 0x14 21 | 22 | .eqv INTERFACE_SIZE, 0x20 23 | 24 | .eqv DMEM_SIZE, 0x1000 25 | .eqv BUFFER_SIZE, 0x0F00 26 | # Input pixels for the current rectangle go here. 27 | .eqv DMEM_GB_BASE, 0 28 | 29 | .eqv BUFFER_SIZE, GB_LCD_WIDTH * 2 # ( x2 for 16bit colours) 30 | 31 | ### 32 | # Register aliases 33 | ### 34 | 35 | # address of next batch of gameboy pixels to be processed in DRAM 36 | .set $GB_BUFFER_ADDRESS, $16 37 | 38 | # points to current gameboy pixel to be processed in DMEM 39 | .set $PIXEL_ADDRESS, $17 40 | 41 | # Number of pixels left to render in current iteration. 42 | .set $PIXEL_COUNTER, $19 43 | 44 | # Address to place next processed gameboy pixel in DMEM 45 | .set $N64_BUFFER_POINTER, $20 46 | 47 | # Address to store massaged pixels where they will be picked up by the RDP 48 | .set $OUT_BUFFER_ADDRESS, $22 49 | 50 | # Left and Top position of line to render. 51 | .set $START_POSITION, $23 52 | 53 | # Length & Width of the line to draw in N64 pixels so it can be scaled. 54 | .set $RECTANGLE_SIZE, $24 55 | 56 | # Cached Height may not be necessary 57 | .set $RECTANGLE_HEIGHT, $25 58 | 59 | ### 60 | # Takes the 16bit blue-endian GBC colour and converts 61 | # it in a red-endian n64 colour. 62 | # 63 | # @input reg contains the GBC colour. 64 | # @ouput reg contains the N64 colour. 65 | ### 66 | massageColour: 67 | # it's sign extended, so... 68 | andi $A, $A, 0x0000FFFF 69 | 70 | # blue 71 | andi $t1, $A, 0x7C00 72 | srl $t1, $t1, 0x09 73 | 74 | # green 75 | andi $t2, $A, 0x03E0 76 | sll $t2, $t2, 0x01 77 | 78 | # red 79 | andi $t3, $A, 0x001F 80 | sll $t3, $t3, 0x0B 81 | 82 | # smoosh the three colours together 83 | or $t1, $t1, $t2 84 | or $t1, $t1, $t3 85 | 86 | # set the transparency bit. There are no transparent pixels at this stage. 87 | ori $t1, $t1, 0x01 88 | 89 | ori $A, $t1, 0 90 | jr $ra 91 | 92 | ### 93 | # Sets the RDP to draw red rectangles. 94 | main: 95 | # Initialise this task 96 | 97 | ## Bring in the configuration. 98 | liw $A, RSP_INTERFACE_ADDRESS 99 | dmaIn $A, DMEM_GB_BASE, INTERFACE_SIZE 100 | 101 | ## Stash config in some registers so whole memory space is available. 102 | lw $GB_BUFFER_ADDRESS, Input_IN_ADDRESS($0) 103 | lw $OUT_BUFFER_ADDRESS, Input_OUT_ADDRESS($0) 104 | lw $SCREEN_POSITION, Input_SCREEN_POSITION($0) 105 | lw $RECTANGLE_SIZE, Input_RECTANGLE_SIZE($0) 106 | andi $RECTANGLE_HEIGHT, $RECTANGLE_SIZE, 0x0000FFFF 107 | 108 | # Build up this scanline to a buffer than can be rendered as a texture. 109 | 110 | ## First draw any sprites. 111 | 112 | ## Then the window 113 | 114 | ## Then the background 115 | 116 | addi $PIXEL_COUNTER, $0, GB_LCD_WIDTH 117 | 118 | .whileHasPixels: 119 | # Go through VRAM... 120 | # Massage the colours to n64 16bit colours. 121 | 122 | addi $PIXEL_COUNTER, $PIXEL_COUNTER, -1 123 | 124 | # end loop 125 | bgtz $PIXEL_COUNTER, .whileHasPixels 126 | 127 | # Ask the RDP to render the line we just built. 128 | 129 | ## Sync before loading a new texture. 130 | syncPipe 131 | 132 | ## DMA the data back to DRAM so that the RDP can pick it up. 133 | dmaOut DMEM_GB_BASE, $OUT_BUFFER_ADDRESS, BUFFER_SIZE 134 | 135 | ## Tell RDP where to look for texture data. 136 | setTextureImage RGBA_FORMAT, COLOUR_DEPTH_16, GB_LCD_WIDTH, $OUT_BUFFER_ADDRESS 137 | 138 | ## Set up a tile now that the RDP has the data. 139 | setTile 1, RGBA_FORMAT, COLOUR_DEPTH_16, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 140 | 141 | ## Give tile a size & shape 142 | loadTile 1, 0, 0, GB_LCD_WIDTH, 1 143 | 144 | # Calculate where to draw the line. 145 | add $a0, $START_POSITION, $0 146 | srl $a0, $a0, 16 147 | andi $a1, $START_POSITION, 0x0000FFFF 148 | 149 | add $a2, $RECTANGLE_SIZE, $0 150 | srl $a2, $a2, 16 151 | 152 | add $a2, $a2, $a0 153 | add $a3, $RECTANGLE_HEIGHT, $a1 154 | 155 | ## Draw a rectangle with the texture we have set up 156 | textureRectangle 1, $a0, $a1, $a2, $a3, 0, 0, 2048, 512 157 | 158 | 159 | # Clean-up now that we're done. 160 | 161 | ## Reset DRAM flag that says we're done with this work. 162 | sw $0, Input_IS_BUSY($0) 163 | 164 | liw $A, RSP_INTERFACE_ADDRESS 165 | dmaOut DMEM_GB_BASE, $A, INTERFACE_SIZE 166 | 167 | # job done 168 | stall: 169 | nop 170 | j stall 171 | 172 | break 173 | # Fill remainder with break instruction 174 | .org 2048, 0x0000000D -------------------------------------------------------------------------------- /rsp/ppuSGB.rsp: -------------------------------------------------------------------------------- 1 | nop 2 | break -------------------------------------------------------------------------------- /rsp/rdp.rsp: -------------------------------------------------------------------------------- 1 | #include "rspIncludes.rsp" 2 | 3 | .set noreorder 4 | 5 | .eqv RGBA_FORMAT, 0 6 | .eqv COLOUR_DEPTH_16, 2 7 | 8 | .eqv CLAMP_DISABLAED, 0 9 | .eqv MIRROR_DISABLED, 0 10 | .eqv MASK_T, 0 11 | .eqv MASK_S, 0 12 | .eqv SHIFT_T, 0 13 | .eqv SHIFT_S, 0 14 | 15 | ### 16 | # Sends an RDP command by writing to the RDP registers. 17 | # Assumes command starts at DMEM address 0 18 | # @param {address} $a0 address after last word to send. 19 | ### 20 | _execRdp: 21 | .set noat 22 | 1: 23 | mfc0 $at, $RDP_CMD_STATUS 24 | # bit 8 of $RDP_CMD_STATUS = RDP DMA is busy 25 | andi $at, $at, RDP_DMA_BUSY 26 | bne $at, $0, 1b 27 | nop 28 | 29 | # send 2 to RDP_CMD_STATUS 30 | # in order to DMA from RSP memory (DMEM) 31 | addi $at, $0, DMEM_RDP_DMA 32 | mtc0 $at, $RDP_CMD_STATUS 33 | 34 | # Kick off the DMA by setting both start and end. 35 | mtc0 $0, $RDP_CMD_START 36 | jr $ra 37 | mtc0 $a0, $RDP_CMD_END # delay slot 38 | .set at 39 | ### 40 | # Sends an RDP command by writing to the RDP registers. 41 | # Assumes command starts at DMEM address 0 42 | # @input words The number of words to send 43 | ### 44 | .macro execRdp words 45 | stackPush 1 46 | # + 4 because end expects the next address after data ends. 47 | addi $a0, $0, (\words * 4) + 4 48 | nop # TODO - figure out why the code breaks and jumps to some random address without this NOP 49 | jal _execRdp 50 | nop 51 | stackPop 1 52 | .endm 53 | 54 | ### 55 | # Executes the No Op RDP command. 56 | # Used to pad command buffers. 57 | # Command 0x00 58 | ### 59 | .macro rdpNoOp 60 | sw $0, 0($0) 61 | execRdp 1 62 | .endm 63 | 64 | ### 65 | # Executes the Sync Pipe RDP command 66 | # Command 0x27 67 | ### 68 | .macro syncPipe offset 69 | lui $t0, 0xE700 70 | sw $t0, \offset($0) 71 | .endm 72 | 73 | ### 74 | # Executes the syncTile RDP command 75 | # Command 0x28 76 | ### 77 | .macro syncTile 78 | lui $t0, 0xE800 79 | sw $t0, 0($0) 80 | execRdp 1 81 | .endm 82 | 83 | ### 84 | # Executes the syncFull RDP command 85 | # Command 0x29 86 | ### 87 | .macro syncFull 88 | lui $t0, 0xE900 89 | sw $t0, 0x000($0) 90 | execRdp 1 91 | .endm 92 | 93 | ### 94 | # Execute the syncLoad RDP command 95 | # Command 0x31 96 | ### 97 | .macro syncLoad 98 | lui $t0, 0xF100 99 | sw $t0, 0($0) 100 | execRdp 1 101 | .endm 102 | 103 | 104 | ### 105 | # Executes the Set Primitive Depth command 106 | # Command 0x2E 107 | # @input z Primitive Z, whatever that means. 108 | # @input dz Primitive Delta Z...presumably change in Z? 109 | .macro setPrimDepth z, dz 110 | lui $t0 0x2E00 111 | sw $t0, 0($0) 112 | lui $t0, \z 113 | or $t0, $t0, \dz 114 | sw $t0, 4($0) 115 | 116 | execRdp 2 117 | .endm 118 | 119 | ### 120 | # Executes the setFillColour RDP command 121 | # Command 0x37 122 | # @input upper The colour to set. Upper 16b for 32b colours. 123 | # @input? lower Lower 16b for 32b colours. 124 | ### 125 | .macro setFillColour upper, lower 126 | # "0x37 Set Fill Color" 127 | lui $t0, 0xF700 128 | sw $t0, 0x000($0) 129 | 130 | ## Second Byte - Packed Color 131 | lui $t0, \upper 132 | 133 | .ifb lower 134 | ori $t0, \lower 135 | .else 136 | ori $t0, \upper 137 | .endif 138 | sw $t0, 0x004($0) 139 | 140 | execRdp 2 141 | .endm 142 | 143 | ### 144 | # Fills specified rectangle with the colour set in setFillColour command (0x37) 145 | # Command 0x36 146 | # @input x1 x co-ord of top left corner. 147 | # @input y1 y co-ord of top left corner. 148 | # @input x2 x co-ord of bottom right corner 149 | # @input y2 y co-ord of bottom right corner 150 | .macro fillRectangle x1, y1, x2, y2 151 | # Command 0x36 (shifted right twice) 152 | # (yes for some reason the numbers we set 153 | # are divided by 4 to get the numbers that 154 | # are displayed. ?) 155 | lui $t0, 0x3D80 156 | 157 | add $t1, $0, \x2 158 | sll $t1, $t1, 12 159 | or $t0, $t0, $t1 160 | or $t0, $t0, \y2 161 | 162 | sll $t0, $t0, 2 163 | 164 | sw $t0, 0x000($0) 165 | 166 | add $t0, \x1, $0 167 | sll $t0, $t0, 12 168 | or $t0, $t0, \y1 169 | sll $t0, $t0, 2 170 | 171 | sw $t0, 0x004($0) 172 | 173 | execRdp 2 174 | .endm 175 | 176 | ### 177 | # 178 | # Command 0x24 Texture Rectangle 179 | # @input tileIndex 180 | # @input x1 x co-ord of bottom right 181 | # @input y1 y co-ord of bottom right 182 | # @input x2 x co-ord of top left 183 | # @input y2 y co-ord of top left 184 | # @input s s co-ord of texture top left 185 | # @input t t co-ord of texture top left 186 | # @input ds change in s / x 187 | # @input dt change in t / y 188 | ### 189 | .macro textureRectangle offset tileIndex x1 y1 x2 y2 s t ds dt 190 | lui $t0, 0xE400 191 | 192 | # We multiply these by four. why I don't know at this point. 193 | sll \x1, \x1, 2 194 | sll \x2, \x2, 2 195 | sll \y1, \y1, 2 196 | sll \y2, \y2, 2 197 | 198 | add $t1, $0, \x2 199 | sll $t1, $t1, 12 200 | or $t1, $t1, \y2 201 | or $t0, $t0, $t1 202 | 203 | sw $t0, \offset($0) 204 | 205 | lui $t0, \tileIndex 206 | sll $t0, $t0, 8 207 | 208 | add $t1, $0, \x1 209 | sll $t1, $t1, 12 210 | or $t1, $t1, \y1 211 | or $t0, $t0, $t1 212 | 213 | sw $t0, (\offset + 4)($0) 214 | 215 | lui $t0, \s 216 | ori $t0, $t0, \t 217 | 218 | sw $t0, (\offset + 8)($0) 219 | 220 | lui $t0, \ds 221 | ori $t0, $t0, \dt 222 | 223 | sw $t0, (\offset + 12)($0) 224 | .endm 225 | 226 | ### 227 | # 228 | # Command 0x34 Load Tile 229 | # @input tileIndex 230 | # @input SL low S co-ord 231 | # @input TL low T co-ord 232 | # @input SH high S co-ord 233 | # @input TH high T co-ord 234 | ### 235 | .macro loadTile offset tile, sl, tl, sh, th 236 | # Command 0x34 237 | lui $t0, 0xF400 238 | 239 | addi $t1, $0, \sl 240 | # multiply by 4, then shift 12. 241 | sll $t1, $t1, 12 + 2 242 | 243 | or $t0, $t0, $t1 244 | 245 | addi $t1, $0, \tl 246 | # Multiply by 4 because the RDP says so. 247 | sll $t1, $t1, 2 248 | or $t0, $t0, $t1 249 | 250 | sw $t0, \offset($0) 251 | 252 | lui $t0, \tile 253 | sll $t0, $t0, 8 254 | # subtract 1 for the high values (again, because RDP says so) 255 | addi $t1, $0, \sh - 1 256 | sll $t1, $t1, 12 + 2 257 | or $t0, $t0, $t1 258 | addi $t1, $0, \th -1 259 | sll $t1, $t1, 2 260 | or $t0, $t0, $t1 261 | 262 | sw $t0, (\offset + 4)($0) 263 | .endm 264 | 265 | ### 266 | # 267 | # Command 0x35 Set Tile 268 | # @input format 269 | # @input depth colour depth 270 | # @input size size of tile line in 64b words 271 | # @input tmem address in tmem (in 64b words so 2 = address 0x008) 272 | # @input palette 273 | # @input {boolean} clampT on/off 274 | # @input {boolean} mirrorT on/off 275 | # etc 276 | ### 277 | .macro setTile offset \ 278 | tileIndex format depth size tmem \ 279 | palette clampT mirrorT maskT \ 280 | shiftT clampS mirrorS maskS shiftS 281 | 282 | # Command 0x35 283 | lui $t0, 0xF500 284 | 285 | lui $t1, \format 286 | lui $t2, \depth 287 | sll $t1, $t1, 5 288 | sll $t2, $t2, 3 289 | or $t1, $t1, $t2 290 | or $t0, $t0, $t1 291 | 292 | addi $t1, $0, \size 293 | sll $t1, $t1, 9 294 | ori $t1, $t1, \tmem 295 | 296 | or $t0, $t0, $t1 297 | 298 | sw $t0, \offset($0) 299 | 300 | lui $t0, \tileIndex 301 | sll $t0, $t0, 8 302 | 303 | # All this is a waste of time & space for now since I'm setting it all to zero. 304 | # But can stay until it becomes a problem or I learn mips macros a bit better. 305 | lui $t1, \palette 306 | sll $t1, $t1, 4 307 | or $t0, $t0, $t1 308 | 309 | lui $t1, \clampT 310 | sll $t1, $t1, 3 311 | or $t0, $t0, $t1 312 | 313 | lui $t1, \mirrorT 314 | sll $t1, $t1, 2 315 | or $t0, $t0, $t1 316 | 317 | addi $t1, $0, \maskT 318 | sll $t1, $t1, 14 319 | or $t0, $t0, $t1 320 | 321 | addi $t1, $0, \shiftT 322 | sll $t1, $t1, 10 323 | or $t0, $t0, $t1 324 | 325 | addi $t1, $0, \clampS 326 | sll $t1, $t1, 9 327 | or $t0, $t0, $t1 328 | 329 | addi $t1, \mirrorS 330 | sll $t1, $t1, 8 331 | or $t0, $t0, $t1 332 | 333 | addi $t1, $0, \maskS 334 | sll $t1, $t1, 4 335 | or $t0, $t0, $t1 336 | 337 | ori $t0, $t0, \shiftS 338 | 339 | sw $t0, (\offset + 4)($0) 340 | .endm 341 | 342 | ### 343 | # Executes the Set Z Image RDP command 344 | # aka set mask image. 345 | # Command 0x3e 346 | # @input address in DRAM of texture. 347 | ### 348 | .macro setZImage address 349 | lui $t0, 0xFE00 350 | sw $t0, 0($0) 351 | sw \address, 4($0) 352 | 353 | execRdp 2 354 | .endm 355 | 356 | ### 357 | # Executes the Set Texture Image RDP command 358 | # Used to point the RDP to an image in DRAM. The next set tile will use this image data. 359 | # 360 | # Command 0x3D 361 | # @input format 362 | # @input depth colour depth 363 | # @input width 364 | # @input address 365 | # @sideAffect registers $2, $t1, $t2 affected. 366 | # @sideAffect DMEM 0x000 - 0x008 affected. 367 | ### 368 | .macro setTextureImage offset format depth width address 369 | # Command 0x3D 370 | lui $t0, 0xFD00 371 | 372 | lui $t1, \format 373 | lui $t2, \depth 374 | sll $t1, $t1, 5 375 | sll $t2, $t2, 3 376 | or $t1, $t1, $t2 377 | or $t0, $t0, $t1 378 | 379 | ori $t0, $t0, (\width - 1) 380 | sw $t0, \offset($0) 381 | 382 | ##if it's a register 383 | sw \address, (\offset + 0x004)($0) 384 | ##else it's a constant... 385 | #liw $t0, \address 386 | ##endif 387 | .endm 388 | 389 | ### 390 | # Executes the RDP Set Color Image command 391 | # Used to point the RDP to the frame buffer where it will draw pixels to. 392 | # 393 | ### 394 | .macro setColorImage offset format size, width address 395 | # Command 0x3F 396 | lui $t0, 0xFF00 397 | 398 | lui $t1, \format 399 | lui $t2, \size 400 | sll $t1, $t1, 5 401 | sll $t2, $t2, 3 402 | or $t1, $t1, $t2 403 | or $t0, $t0, $t1 404 | 405 | ori $t0, $t0, (\width - 1) 406 | sw $t0, \offset($0) 407 | 408 | sw \address, (\offset + 0x004)($0) 409 | .endm 410 | 411 | ### 412 | # Alias for setColorImage with a name that makes sense. 413 | ### 414 | .macro setFrameBuffer offset format size, width address 415 | setColorImage \offset \format \size \width \address 416 | .endm 417 | .set reorder # TODO - YTF is THIS necessary??? -------------------------------------------------------------------------------- /rsp/renderer.rsp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../global.h" 3 | #include "init.rsp" 4 | #include "old_rdp.rsp" 5 | 6 | .set noat 7 | 8 | ### 9 | # Constants 10 | ### 11 | .eqv RspIn_IN_ADDRESS, 0x00 12 | .eqv RspIn_OUT_ADDRESS, 0x04 13 | .eqv RspIn_SCREEN_POSITION, 0x08 14 | .eqv RspIn_RECTANGLE_SIZE, 0x0C 15 | .eqv RspIn_IS_COLOUR, 0x10 16 | .eqv RspIn_IS_BUSY, 0x14 17 | 18 | .eqv INTERFACE_SIZE, 0x20 19 | 20 | .eqv DMEM_SIZE, 0x1000 21 | .eqv BUFFER_SIZE, 0x0F00 22 | # Input pixels for the current rectangle go here. 23 | .eqv DMEM_GB_BASE, 0 24 | 25 | .eqv MONOCHROME_PALETTE, 0x0FF0 26 | .eqv LIGHTEST,0x0000FFFF 27 | .eqv LIGHTER, 0x0000A529 28 | .eqv DARKER, 0x00005295 29 | .eqv DARKEST, 0x00000001 30 | 31 | .eqv BATCH_HEIGHT, 6 32 | .eqv BATCH_COUNT, GB_LCD_HEIGHT / BATCH_HEIGHT 33 | .eqv PIXEL_SIZE, 2 34 | .eqv GB_BUFFER_SIZE, GB_LCD_WIDTH * GB_LCD_HEIGHT * PIXEL_SIZE 35 | 36 | ### 37 | # Register aliases 38 | ### 39 | 40 | # address of next batch of gameboy pixels to be processed in DRAM 41 | .set $GB_BUFFER_ADDRESS, $s0 42 | 43 | # points to current gameboy pixel to be processed in DMEM 44 | .set $PIXEL_ADDRESS, $s1 45 | 46 | # Number of rectangles left to render in current iteration. 47 | .set $RECTANGLE_COUNTER, $s2 48 | 49 | # Number of pixels left to render in current iteration. 50 | .set $PIXEL_COUNTER, $s3 51 | 52 | # Address to place next processed gameboy pixel in DMEM 53 | .set $N64_BUFFER_POINTER, $s4 54 | 55 | # Set if pixels are in colour 56 | .set $IS_COLOUR, $s5 57 | 58 | # Address to store massaged pixels where they will be picked up by the RDP 59 | .set $OUT_BUFFER_ADDRESS, $s6 60 | 61 | # Left and Top of screen 62 | .set $SCREEN_POSITION, $s7 63 | 64 | # Length and Height of each rectangle to render. 65 | .set $RECTANGLE_SIZE, $s8 66 | 67 | # Stashed height of rectangle to save a few cycles. 68 | .set $RECTANGLE_HEIGHT, $s9 69 | 70 | ### 71 | # Load Immediate Word 72 | # loads a 32bit immediate value into register. 73 | # @input \reg Register to load. 74 | # @input \word Value to load. 75 | ### 76 | .macro liw reg word 77 | lui \reg, \word >> 16 78 | ori \reg, \reg, \word & 0xFFFF 79 | .endm 80 | 81 | 82 | ### 83 | # See dmaIn macro below 84 | ### 85 | _dmaIn: 86 | # Don't do anything until DMA is no longer busy 87 | 1: 88 | mfc0 $t0, $RSP_RESERVED 89 | bne $t0, $0, 1b 90 | 91 | 2: 92 | mfc0 $t0, $DMA_FULL 93 | bne $t0, $0, 2b 94 | 95 | # Set source. 96 | mtc0 $a0, $DMA_DRAM 97 | 98 | # Set destination 99 | mtc0 $a1, $DMA_DMEM 100 | 101 | # set length (which kicks off DMA) 102 | addi $a2, $a2, -1 103 | mtc0 $a2, $DMA_IN_LENGTH 104 | 105 | # wait for DMA to complete 106 | 3: 107 | mfc0 $t0, $DMA_BUSY 108 | bne $t0, $0, 3b 109 | 110 | # release the flag 111 | mtc0 $0, $RSP_RESERVED 112 | jr $ra 113 | 114 | 115 | ### 116 | # Writes to the three DMA registers to kick off a transfer 117 | # from DRAM to DMEM then waits for it to complete. 118 | # 119 | # @input source Holds the source address in DRAM 120 | # @input dest Holds the destination adress in DMEM 121 | # @input length Holds the length of data to transfer. 122 | ### 123 | .macro dmaIn source, dest, length 124 | add $a0, $0, \source 125 | addi $a1, $0, \dest 126 | addi $a2, $0, \length 127 | jal _dmaIn 128 | .endm 129 | 130 | ### 131 | # See dmaOut macro below 132 | ### 133 | _dmaOut: 134 | # Don't do anything until DMA is not busy. 135 | 1: 136 | mfc0 $t0, $RSP_RESERVED 137 | bne $t0, $0, 1b 138 | 139 | 2: 140 | mfc0 $t0, $DMA_FULL 141 | bne $t0, $0, 2b 142 | 143 | # Set source. 144 | mtc0 $a0, $DMA_DMEM 145 | 146 | # Set destination 147 | mtc0 $a1, $DMA_DRAM 148 | 149 | # set length (which kicks off DMA) 150 | addi $a2, $a2, -1 151 | mtc0 $a2, $DMA_OUT_LENGTH 152 | 153 | # wait for DMA to complete 154 | 3: 155 | mfc0 $t0, $DMA_BUSY 156 | bne $t0, $0, 3b 157 | 158 | # release the flag 159 | mtc0 $0, $RSP_RESERVED 160 | jr $ra 161 | 162 | ### 163 | # Writes to the three DMA registers to kick off a transfer 164 | # from DMEM to DRAM then then waits for it to complete. 165 | # 166 | # @input source The source address in DMEM 167 | # @input dest The destination adress in DRAM 168 | # @input length The length of data to transfer. 169 | ### 170 | .macro dmaOut source, dest, length 171 | addi $a0, $0, \source 172 | add $a1, $0, \dest 173 | addi $a2, $0, \length 174 | jal _dmaOut 175 | .endm 176 | 177 | ### 178 | # Takes a 2bit DMG colour index and converts to a 16bit n64 colour. 179 | # 180 | # @input reg the dmg colour index (0 - 3) 181 | # @output reg the 16bit dmg colour. 182 | ### 183 | massageMonochrome: 184 | # zero out irrelevant bits 185 | andi $t0, $t0, 3 186 | # and multiply by four since we deal with 32bit addresses 187 | sll $t0, $t0, 2 188 | lw $t0, MONOCHROME_PALETTE($t0) 189 | jr $ra 190 | 191 | ### 192 | # Takes the 16bit blue-endian GBC colour and converts 193 | # it in a red-endian n64 colour. 194 | # 195 | # @input reg contains the GBC colour. 196 | # @ouput reg contains the N64 colour. 197 | ### 198 | massageColour: 199 | # it's sign extended, so... 200 | andi $t0, $t0, 0x0000FFFF 201 | 202 | # blue 203 | andi $t1, $t0, 0x7C00 204 | srl $t1, $t1, 0x09 205 | 206 | # green 207 | andi $t2, $t0, 0x03E0 208 | sll $t2, $t2, 0x01 209 | 210 | # red 211 | andi $t3, $t0, 0x001F 212 | sll $t3, $t3, 0x0B 213 | 214 | # smoosh the three colours together 215 | or $t1, $t1, $t2 216 | or $t1, $t1, $t3 217 | 218 | # set the transparency bit. There are no transparent pixels at this stage. 219 | ori $t1, $t1, 0x01 220 | 221 | ori $t0, $t1, 0 222 | jr $ra 223 | 224 | ### 225 | # Sets the RDP to draw red rectangles. 226 | main: 227 | liw $t0, RSP_INTERFACE_ADDRESS 228 | 229 | # Bring in the configuration. 230 | dmaIn $t0, DMEM_GB_BASE, INTERFACE_SIZE 231 | 232 | # Stash config in some registers so whole memory space is available. 233 | lw $GB_BUFFER_ADDRESS, RspIn_IN_ADDRESS($0) 234 | lw $OUT_BUFFER_ADDRESS, RspIn_OUT_ADDRESS($0) 235 | lw $SCREEN_POSITION, RspIn_SCREEN_POSITION($0) 236 | lw $RECTANGLE_SIZE, RspIn_RECTANGLE_SIZE($0) 237 | andi $RECTANGLE_HEIGHT, $RECTANGLE_SIZE, 0x0000FFFF 238 | lw $IS_COLOUR, RspIn_IS_COLOUR($0) 239 | 240 | bne $IS_COLOUR, $0, 1f 241 | # If monochrome, set up palette in memory 242 | liw $t0, LIGHTEST 243 | sw $t0, MONOCHROME_PALETTE($0) 244 | liw $t0, LIGHTER 245 | sw $t0, (MONOCHROME_PALETTE + 4)($0) 246 | liw $t0, DARKER 247 | sw $t0, (MONOCHROME_PALETTE + 8)($0) 248 | liw $t0, DARKEST 249 | sw $t0, (MONOCHROME_PALETTE + 0xC)($0) 250 | 1: 251 | 252 | addi $RECTANGLE_COUNTER, $0, BATCH_COUNT 253 | 254 | # while address < end of buffer 255 | .whileHasRectangles: 256 | ## Sync before loading a new texture. 257 | syncPipe 258 | 259 | # DMA in the next 2048 pixels 260 | dmaIn $GB_BUFFER_ADDRESS, DMEM_GB_BASE + (DMEM_SIZE / 2), BUFFER_SIZE / 2 261 | addi $PIXEL_ADDRESS, $0, DMEM_GB_BASE + (DMEM_SIZE / 2) 262 | 263 | # Starts at 0 and fills up DMEM as we get through GB pixels. 264 | add $N64_BUFFER_POINTER, $0, $0 265 | 266 | addi $PIXEL_COUNTER, $0, GB_LCD_WIDTH * BATCH_HEIGHT 267 | 268 | # for those pixels 269 | .whileHasPixels: 270 | # put a pixel into a register 271 | lw $t0, 0($PIXEL_ADDRESS) 272 | 273 | # Pixels are 16 bit, not 32, so get the uppper bit first. 274 | srl $t0, $t0, 16 275 | 276 | # massage it from gameboy format to n64 format. 277 | beq $IS_COLOUR, $0, 1f 278 | jal massageColour 279 | j 2f 280 | 1: 281 | jal massageMonochrome 282 | 2: 283 | nop 284 | 285 | # push massaged colour on to the out-buffer 286 | sw $t0, 0($N64_BUFFER_POINTER) 287 | 288 | # Repeat for the lower bit. 289 | lw $t0, 0($PIXEL_ADDRESS) 290 | andi $t0, $t0, 0xFFFF 291 | 292 | # massage it from gameboy format to n64 format. 293 | beq $IS_COLOUR, $0, 1f 294 | jal massageColour 295 | j 2f 296 | 1: 297 | jal massageMonochrome 298 | 2: 299 | nop 300 | 301 | # smoosh the two 16bit colours together into a 32bit word. 302 | lw $t1, 0($N64_BUFFER_POINTER) 303 | sll $t1, $t1, 16 304 | or $t0, $t0, $t1 305 | 306 | # and push on to the buffer. 307 | sw $t0, 0($N64_BUFFER_POINTER) 308 | 309 | # increment all the things. 310 | addi $PIXEL_ADDRESS, $PIXEL_ADDRESS, 0x04 311 | addi $N64_BUFFER_POINTER, $N64_BUFFER_POINTER, 0x04 312 | addi $PIXEL_COUNTER, $PIXEL_COUNTER, -2 313 | 314 | # end loop 315 | bgtz $PIXEL_COUNTER, .whileHasPixels 316 | 317 | # DMA the data back to DRAM so we have our limited memory back. 318 | dmaOut DMEM_GB_BASE, $OUT_BUFFER_ADDRESS, BUFFER_SIZE / 2 319 | 320 | # Ask the RDP to render the textured line we just built. 321 | 322 | ## Tell RDP where to look for texture data. 323 | setTextureImage RGBA_FORMAT, COLOUR_DEPTH_16, GB_LCD_WIDTH, $OUT_BUFFER_ADDRESS 324 | 325 | ## Set up a tile now that the RDP has the data. 326 | setTile 1, RGBA_FORMAT, COLOUR_DEPTH_16, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 327 | 328 | ## Give tile a size & shape 329 | loadTile 1, 0, 0, GB_LCD_WIDTH, BATCH_HEIGHT 330 | 331 | # TODO Cache this somewhere. May need to use some less 332 | # than conventional registers. 333 | # Once there, can be used above as well. 334 | 335 | add $a0, $SCREEN_POSITION, $0 336 | srl $a0, $a0, 16 337 | andi $a1, $SCREEN_POSITION, 0x0000FFFF 338 | 339 | add $a2, $RECTANGLE_SIZE, $0 340 | srl $a2, $a2, 16 341 | 342 | add $a2, $a2, $a0 343 | add $a3, $RECTANGLE_HEIGHT, $a1 344 | 345 | ## Draw a rectangle with the texture we have set up 346 | ## tile = 1 x1,y1 = {0, 0} x2,y2 = {160, 6} 347 | ### not doing anything fancy with the s & ts 348 | textureRectangle 1, $a0, $a1, $a2, $a3, 0, 0, 2048, 512 349 | 350 | # increment address pointer. 351 | # TODO - Magic numbers. 352 | addi $GB_BUFFER_ADDRESS, $GB_BUFFER_ADDRESS, GB_LCD_WIDTH * BATCH_HEIGHT * 2 353 | 354 | # increment draw top 355 | add $SCREEN_POSITION, $SCREEN_POSITION, $RECTANGLE_HEIGHT 356 | addi $RECTANGLE_COUNTER, $RECTANGLE_COUNTER, -1 357 | 358 | # loop until there are no more rectangles to draw. 359 | bgtz $RECTANGLE_COUNTER, .whileHasRectangles 360 | 361 | # Reset DRAM flag that says we're done with this frame. 362 | sw $0, RspIn_IS_BUSY($0) 363 | # Gotta zero out IS_COLOUR too because of the way values are packed. 364 | sw $0, RspIn_IS_COLOUR 365 | 366 | liw $t0, RSP_INTERFACE_ADDRESS 367 | dmaOut DMEM_GB_BASE, $t0, INTERFACE_SIZE 368 | 369 | # job done 370 | stall: 371 | nop 372 | j stall 373 | 374 | break 375 | # Fill remainder with break instruction 376 | .org 2048, 0x0000000D -------------------------------------------------------------------------------- /rsp/rspIncludes.rsp: -------------------------------------------------------------------------------- 1 | # https://www.zeuthen.desy.de/unix/unixguide/infohtml/binutils/docs/as/Pseudo-Ops.html 2 | 3 | #ifndef RSP_INC_INCLUDED 4 | #define RSP_INC_INCLUDED 5 | 6 | #include "vectorShifts.rsp" 7 | 8 | .set noreorder 9 | 10 | # co-processor 0 registers 11 | .set $c0, $0 12 | .set $c1, $1 13 | .set $c2, $2 14 | .set $c3, $3 15 | .set $c4, $4 16 | .set $c5, $5 17 | .set $c6, $6 18 | .set $c7, $7 19 | .set $c8, $8 20 | .set $c9, $9 21 | .set $c10, $10 22 | .set $c11, $11 23 | .set $c12, $12 24 | .set $c13, $13 25 | .set $c14, $14 26 | .set $c15, $15 27 | 28 | # co-processor 0 register descriptions. 29 | .set $DMA_DMEM, $c0 30 | .set $DMA_DRAM, $c1 31 | .set $DMA_IN_LENGTH, $c2 32 | .set $DMA_OUT_LENGTH, $c3 33 | .set $RSP_STATUS, $c4 34 | .set $DMA_FULL, $c5 35 | .set $DMA_BUSY, $c6 36 | .set $RSP_RESERVED, $c7 37 | .set $RDP_CMD_START, $c8 38 | .set $RDP_CMD_END, $c9 39 | .set $RDP_CMD_CURRENT, $c10 40 | .set $RDP_CMD_STATUS, $c11 41 | .set $RDP_CMD_CLOCK, $c12 42 | .set $RDP_CMD_BUSY, $c13 43 | .set $RDP_CMD_PIPE_BUSY,$c14 44 | .set $RDP_CMD_TMEM_BUSY,$c15 45 | 46 | .equ DRAM_RDP_DMA, 1 47 | .equ DMEM_RDP_DMA, 2 48 | .equ RDP_DMA_BUSY, 0x0100 49 | 50 | # primary accumulator 51 | .set $zero, $0 52 | .set $at, $1 53 | 54 | # function return values 55 | .set $r0, $2 56 | .set $r1, $3 57 | 58 | # function arguments 59 | .set $a0, $4 60 | .set $a1, $5 61 | .set $a2, $6 62 | .set $a3, $7 63 | 64 | .set $t0, $8 65 | .set $t1, $9 66 | .set $t2, $10 67 | .set $t3, $11 68 | .set $t4, $12 69 | .set $t5, $13 70 | .set $t6, $14 71 | .set $t7, $15 72 | 73 | .set $s0, $16 74 | .set $s1, $17 75 | .set $s2, $18 76 | .set $s3, $19 77 | .set $s4, $20 78 | .set $s5, $21 79 | .set $s6, $22 80 | .set $s7, $23 81 | 82 | # We have so little memory, so intead of more temporary regs, a few more saved regs go a long way. 83 | .set $s8, $24 84 | .set $s9, $25 85 | 86 | # There's no kernal, so no need for kernal reigsters 87 | .set $s10, $26 88 | .set $s11, $27 89 | 90 | # We'll keep a stack pointer just in case, but other pointers aren't gonna be used. 91 | .set $t8, $28 92 | .set $t9, $29 93 | .set $sp, $30 94 | .set $ra, $31 95 | 96 | ### 97 | # Push the previous function call details on to the stack. 98 | # @param argCount number of arguments to keep track of. 99 | # @note supports up to 4 arguments. 100 | ### 101 | .macro stackPush argCount 102 | sw $ra, 0($sp) 103 | addi $sp, $sp, -4 104 | .if \argCount > 0 105 | sw $a0, 0($sp) 106 | addi $sp, $sp, -4 107 | .endif 108 | .if \argCount > 1 109 | sw $a1, 0($sp) 110 | addi $sp, $sp, -4 111 | .endif 112 | .if \argCount > 2 113 | sw $a2, 0($sp) 114 | addi $sp, $sp, -4 115 | .endif 116 | .if \argCount > 3 117 | sw $a3, 0($sp) 118 | addi $sp, $sp, -4 119 | .endif 120 | .endm 121 | 122 | ### 123 | # Pop the top function call details off the stack. 124 | # @param argCount number of arguments expected with the call. 125 | # @note supports up to 4 arguments. 126 | ### 127 | .macro stackPop argCount 128 | .if \argCount > 3 129 | addi $sp, $sp, 4 130 | lw $a30, 0($sp) 131 | .endif 132 | .if \argCount > 2 133 | addi $sp, $sp, 4 134 | lw $a2, 0($sp) 135 | .endif 136 | .if \argCount > 1 137 | addi $sp, $sp, 4 138 | lw $a1, 0($sp) 139 | .endif 140 | .if \argCount > 0 141 | addi $sp, $sp, 4 142 | lw $a0, 0($sp) 143 | .endif 144 | addi $sp, $sp, 4 145 | lw $ra, 0($sp) 146 | .endm 147 | 148 | ### 149 | # Load Immediate Word 150 | # loads a 32bit immediate value into register. 151 | # @input \reg Register to load. 152 | # @input \word Value to load. 153 | ### 154 | .macro liw reg word 155 | lui \reg, \word >> 16 156 | ori \reg, \reg, \word & 0xFFFF 157 | .endm 158 | 159 | ### 160 | # Store Immediate Word 161 | ### 162 | .macro siw const addr reg 163 | addi $at, $0, \const 164 | sw $at, \addr(\reg) 165 | .endm 166 | 167 | ### 168 | # See dmaIn macro below 169 | ### 170 | _dmaIn: 171 | # Don't do anything until DMA is no longer busy 172 | 1: 173 | mfc0 $t0, $RSP_RESERVED 174 | bne $t0, $0, 1b 175 | nop 176 | 177 | 2: 178 | mfc0 $t0, $DMA_FULL 179 | bne $t0, $0, 2b 180 | nop 181 | 182 | # Set source. 183 | mtc0 $a0, $DMA_DRAM 184 | 185 | # Set destination 186 | mtc0 $a1, $DMA_DMEM 187 | 188 | # set length (which kicks off DMA) 189 | addi $a2, $a2, -1 190 | mtc0 $a2, $DMA_IN_LENGTH 191 | 192 | # wait for DMA to complete 193 | 3: 194 | mfc0 $t0, $DMA_BUSY 195 | bne $t0, $0, 3b 196 | nop 197 | 198 | # release the flag 199 | jr $ra 200 | mtc0 $0, $RSP_RESERVED # delay slot 201 | 202 | ### 203 | # Writes to the three DMA registers to kick off a transfer 204 | # from DRAM to DMEM then waits for it to complete. 205 | # 206 | # @input source Holds the source address in DRAM 207 | # @input dest Holds the destination adress in DMEM 208 | # @input length Holds the length of data to transfer. 209 | ### 210 | .macro dmaIn source, dest, length 211 | add $a0, $0, \source 212 | addi $a1, $0, \dest 213 | addi $a2, $0, \length 214 | jal _dmaIn 215 | nop 216 | .endm 217 | 218 | .macro dmaIn_destR source, dest, length 219 | add $a0, $0, \source 220 | add $a1, $0, \dest 221 | addi $a2, $0, \length 222 | jal _dmaIn 223 | nop 224 | .endm 225 | 226 | 227 | 228 | ### 229 | # See dmaOut macro below 230 | ### 231 | _dmaOut: 232 | # Don't do anything until DMA is not busy. 233 | 1: 234 | mfc0 $t0, $RSP_RESERVED 235 | bne $t0, $0, 1b 236 | nop 237 | 238 | 2: 239 | mfc0 $t0, $DMA_FULL 240 | bne $t0, $0, 2b 241 | nop 242 | 243 | # Set source. 244 | mtc0 $a0, $DMA_DMEM 245 | 246 | # Set destination 247 | mtc0 $a1, $DMA_DRAM 248 | 249 | # set length (which kicks off DMA) 250 | addi $a2, $a2, -1 251 | mtc0 $a2, $DMA_OUT_LENGTH 252 | 253 | # wait for DMA to complete 254 | 3: 255 | mfc0 $t0, $DMA_BUSY 256 | bne $t0, $0, 3b 257 | nop 258 | 259 | jr $ra 260 | # release the flag 261 | mtc0 $0, $RSP_RESERVED # delay slot. 262 | 263 | 264 | ### 265 | # Writes to the three DMA registers to kick off a transfer 266 | # from DMEM to DRAM then then waits for it to complete. 267 | # 268 | # @input source The source address in DMEM 269 | # @input dest The destination adress in DRAM 270 | # @input length The length of data to transfer. 271 | ### 272 | .macro dmaOut source, dest, length 273 | addi $a0, $0, \source 274 | add $a1, $0, \dest 275 | addi $a2, $0, \length 276 | jal _dmaOut 277 | nop 278 | .endm 279 | #endif -------------------------------------------------------------------------------- /rsp/vectorShifts.rsp: -------------------------------------------------------------------------------- 1 | # Helper macros for vector shifts. 2 | # How to use: 3 | # 1) Call macro "vsll_data"/"vsll8_data" in the data segment to define the required constants 4 | # 2) Call macro "setup_vsll " / "setup_vsll8 " in code, passing in one vreg that will hold shift constants 5 | # 3) You can now use the following macros: 6 | # * "vsll , , " to perform a left shift from 0 to 7 bits. 7 | # * "vsll8 , , " to perform a left shift from 8 to 15 bits. 8 | # * "vsrl , , " to perform a logical (unsigned) right shift from 0 to 7 bits. 9 | # * "vsrl8 , , " to perform a logical (unsigned) right shift from 8 to 15 bits. 10 | # * "vsra , , " to perform an arithmetic (signed) shift from 0 to 7 bits. 11 | # * "vsra8 , , " to perform an arithmetic (signed) from 8 to 15 bits. 12 | # 13 | .macro vsll_data 14 | .align 4 15 | V_SHIFT: .half 0x80 # vumdn [e8] << 7 16 | .half 0x40 # vmudn [e9] << 6 17 | .half 0x20 # vmudn [e10] << 5 18 | .half 0x10 # vmudn [e11] << 4 19 | .half 0x8 # vmudn [e12] << 3 20 | .half 0x4 # vmudn [e13] << 2 21 | .half 0x2 # vmudn [e14] << 1 22 | .half 0x1 # vmudn [e15] << 0 23 | 24 | .macro setup_vsll vshiftreg 25 | .set noat 26 | la $1,%lo(V_SHIFT) 27 | lqv \vshiftreg,0, 0,$1 28 | .set at 29 | 30 | .macro vsll vdstreg, vsrcreg, qty 31 | .if (\qty == 7) 32 | vmudn \vdstreg, \vsrcreg, \vshiftreg,8 33 | .elseif (\qty == 6) 34 | vmudn \vdstreg, \vsrcreg, \vshiftreg,9 35 | .elseif (\qty == 5) 36 | vmudn \vdstreg, \vsrcreg, \vshiftreg,10 37 | .elseif (\qty == 4) 38 | vmudn \vdstreg, \vsrcreg, \vshiftreg,11 39 | .elseif (\qty == 3) 40 | vmudn \vdstreg, \vsrcreg, \vshiftreg,12 41 | .elseif (\qty == 2) 42 | vmudn \vdstreg, \vsrcreg, \vshiftreg,13 43 | .elseif (\qty == 1) 44 | vmudn \vdstreg, \vsrcreg, \vshiftreg,14 45 | .elseif (\qty == 0) 46 | vmudn \vdstreg, \vsrcreg, \vshiftreg,15 47 | .elseif (\qty >= 8 && \qty <= 15) 48 | .error "Use vsll8 for quantities in range 8-15" 49 | .else 50 | .error "Invalid quantity in vsll" 51 | .endif 52 | .endm 53 | .endm 54 | .endm 55 | 56 | .macro vsll8_data 57 | .align 4 58 | V_SHIFT8: .half 0x8000 # vmudn [e8] << 15 59 | .half 0x4000 # vumdn [e9] << 14 60 | .half 0x2000 # vmudn [e10] << 13 61 | .half 0x1000 # vmudn [e11] << 12 62 | .half 0x800 # vmudn [e12] << 11 63 | .half 0x400 # vmudn [e13] << 10 64 | .half 0x200 # vmudn [e14] << 9 65 | .half 0x100 # vmudn [e15] << 8 66 | 67 | .macro setup_vsll8 vshiftreg 68 | .set noat 69 | la $1,%lo(V_SHIFT8) 70 | lqv \vshiftreg,0, 0,$1 71 | .set at 72 | 73 | .macro vsll8 vdstreg, vsrcreg, qty 74 | .if (\qty == 15) 75 | vmudn \vdstreg, \vsrcreg, \vshiftreg,8 76 | .elseif (\qty == 14) 77 | vmudn \vdstreg, \vsrcreg, \vshiftreg,9 78 | .elseif (\qty == 13) 79 | vmudn \vdstreg, \vsrcreg, \vshiftreg,10 80 | .elseif (\qty == 12) 81 | vmudn \vdstreg, \vsrcreg, \vshiftreg,11 82 | .elseif (\qty == 11) 83 | vmudn \vdstreg, \vsrcreg, \vshiftreg,12 84 | .elseif (\qty == 10) 85 | vmudn \vdstreg, \vsrcreg, \vshiftreg,13 86 | .elseif (\qty == 9) 87 | vmudn \vdstreg, \vsrcreg, \vshiftreg,14 88 | .elseif (\qty == 8) 89 | vmudn \vdstreg, \vsrcreg, \vshiftreg,15 90 | .elseif (\qty >= 0 && \qty <= 7) 91 | .error "Use vsll for quantities in range 0-7" 92 | .else 93 | .error "Invalid quantity in vsll8" 94 | .endif 95 | .endm 96 | 97 | .macro vsrl vdstreg, vsrcreg, qty 98 | .if (\qty == 1) 99 | vmudl \vdstreg, \vsrcreg, \vshiftreg,8 100 | .elseif (\qty == 2) 101 | vmudl \vdstreg, \vsrcreg, \vshiftreg,9 102 | .elseif (\qty == 3) 103 | vmudl \vdstreg, \vsrcreg, \vshiftreg,10 104 | .elseif (\qty == 4) 105 | vmudl \vdstreg, \vsrcreg, \vshiftreg,11 106 | .elseif (\qty == 5) 107 | vmudl \vdstreg, \vsrcreg, \vshiftreg,12 108 | .elseif (\qty == 6) 109 | vmudl \vdstreg, \vsrcreg, \vshiftreg,13 110 | .elseif (\qty == 7) 111 | vmudl \vdstreg, \vsrcreg, \vshiftreg,14 112 | .elseif (\qty == 8) 113 | vmudl \vdstreg, \vsrcreg, \vshiftreg,15 114 | .elseif (\qty >= 9 && \qty <= 15) 115 | .error "Use vsrl8 for quantities in range 9-15" 116 | .else 117 | .error "Invalid quantity in vsrl" 118 | .endif 119 | .endm 120 | 121 | .macro vsra vdstreg, vsrcreg, qty 122 | .if (\qty == 1) 123 | vmudm \vdstreg, \vsrcreg, \vshiftreg,8 124 | .elseif (\qty == 2) 125 | vmudm \vdstreg, \vsrcreg, \vshiftreg,9 126 | .elseif (\qty == 3) 127 | vmudm \vdstreg, \vsrcreg, \vshiftreg,10 128 | .elseif (\qty == 4) 129 | vmudm \vdstreg, \vsrcreg, \vshiftreg,11 130 | .elseif (\qty == 5) 131 | vmudm \vdstreg, \vsrcreg, \vshiftreg,12 132 | .elseif (\qty == 6) 133 | vmudm \vdstreg, \vsrcreg, \vshiftreg,13 134 | .elseif (\qty == 7) 135 | vmudm \vdstreg, \vsrcreg, \vshiftreg,14 136 | .elseif (\qty == 8) 137 | vmudm \vdstreg, \vsrcreg, \vshiftreg,15 138 | .elseif (\qty >= 9 && \qty <= 15) 139 | .error "Use vsra8 for quantities in range 9-15" 140 | .else 141 | .error "Invalid quantity in vsra" 142 | .endif 143 | .endm 144 | .endm 145 | .endm -------------------------------------------------------------------------------- /rtc.c: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | #include "hwdefs.h" 3 | #include "gbc_state.h" 4 | #include "rtc.h" 5 | 6 | /** 7 | * Starts or stops the RTC when the halt flag is updated. 8 | * Assumes only called on transition. 9 | * @param s Full GameBoy state. 10 | * @param isStopped true if Halting the timer, 0 is turning it on again. 11 | */ 12 | void toggleRealTimeClock(GbState* s, bool isStopped) { 13 | if (isStopped) { 14 | // Keep track of when we turn off the timer. 15 | s->Cartridge.RTCStopTime = get_ticks_ms(); 16 | } else { 17 | // When we switch the timer back on, find out how much time elapsed since we turned it off. 18 | s->Cartridge.RTCTimeStopped += (get_ticks_ms() - s->Cartridge.RTCStopTime); 19 | } 20 | } 21 | 22 | /** 23 | * Updates the current RTC value based on the initial value and get_ticks_ms() 24 | * @returns Error Code 25 | */ 26 | sByte updateRealTimeClock(GbState* s) { 27 | if (s->Cartridge.Ram.Size < SRAM_BANK_SIZE * 0xD) { 28 | return LOAD_ERR_RTC_UNAVAILABLE; 29 | } 30 | 31 | RealTimeClockData rtcData; 32 | rtcData.Seconds = s->Cartridge.Ram.Data[SRAM_BANK_SIZE * 0x08]; 33 | rtcData.Minutes = s->Cartridge.Ram.Data[SRAM_BANK_SIZE * 0x09]; 34 | rtcData.Hours = s->Cartridge.Ram.Data[SRAM_BANK_SIZE * 0x0A]; 35 | rtcData.DaysLow = s->Cartridge.Ram.Data[SRAM_BANK_SIZE * 0x0B]; 36 | rtcData.StatusByte = s->Cartridge.Ram.Data[SRAM_BANK_SIZE * 0x0C]; 37 | 38 | long now = get_ticks_ms(); 39 | long diff = now - s->Cartridge.LastRTCTicks + s->Cartridge.RTCTimeStopped; 40 | diff /= 1000; 41 | long seconds = (rtcData.Seconds + diff); 42 | rtcData.Seconds = seconds % 60; 43 | long minutes = rtcData.Minutes + (seconds / 60); 44 | rtcData.Minutes = minutes % 60; 45 | long hours = rtcData.Hours + (rtcData.Minutes / 60) + (rtcData.Seconds / 3600); 46 | rtcData.Hours = hours % 24; 47 | long days = rtcData.DaysLow + (rtcData.Hours / 24) + (rtcData.Minutes / 1440) + (rtcData.Seconds / 86400); 48 | rtcData.DaysLow = ((byte) days) & 0x7F; 49 | 50 | // TODO - These two sets are wrong, but I can't concentrate right now. 51 | if (days > 0xFF) { 52 | if (rtcData.Status.DaysHigh == 1) { 53 | // Carry flag stays set until reset directly. 54 | rtcData.Status.HasDayCarried = 1; 55 | // DaysHigh overflowed back to 0. 56 | rtcData.Status.DaysHigh = 0; 57 | } else { 58 | rtcData.Status.DaysHigh = 1; 59 | } 60 | } 61 | 62 | memset(s->SRAM + (SRAM_BANK_SIZE * 0x08), rtcData.Seconds, SRAM_BANK_SIZE); 63 | memset(s->SRAM + (SRAM_BANK_SIZE * 0x09), rtcData.Minutes, SRAM_BANK_SIZE); 64 | memset(s->SRAM + (SRAM_BANK_SIZE * 0x0A), rtcData.Hours, SRAM_BANK_SIZE); 65 | memset(s->SRAM + (SRAM_BANK_SIZE * 0x0B), rtcData.DaysLow, SRAM_BANK_SIZE); 66 | memset(s->SRAM + (SRAM_BANK_SIZE * 0x0B), rtcData.StatusByte, SRAM_BANK_SIZE); 67 | 68 | s->Cartridge.LastRTCTicks = now; 69 | s->Cartridge.RTCTimeStopped = 0; 70 | 71 | return 0; 72 | } -------------------------------------------------------------------------------- /rtc.h: -------------------------------------------------------------------------------- 1 | #ifndef RTC_INCLUDED 2 | #define RTC_INCLUDED 1 3 | 4 | #include "types.h" 5 | 6 | typedef struct { 7 | byte HasDayCarried:1; 8 | byte IsTimerStopped:1; 9 | byte pad:5; 10 | byte DaysHigh:1; 11 | } RealTimeClockStatus; 12 | 13 | typedef struct { 14 | byte Seconds; 15 | byte Minutes; 16 | byte Hours; 17 | byte DaysLow; 18 | union { 19 | byte StatusByte; 20 | RealTimeClockStatus Status; 21 | }; 22 | } RealTimeClockData; 23 | 24 | /** 25 | * Initialises memory used by real time clock. 26 | * @param s Full GameBoy state. 27 | * @returns Error Code 28 | */ 29 | sByte updateRealTimeClock(GbState* s); 30 | 31 | /** 32 | * Starts or stops the RTC when the halt flag is updated. 33 | * @param s Full GameBoy state. 34 | * @param isStopped true if Halting the timer, 0 is turning it on again. 35 | */ 36 | void toggleRealTimeClock(GbState* s, bool isStopped); 37 | 38 | #endif -------------------------------------------------------------------------------- /screen.c: -------------------------------------------------------------------------------- 1 | #include "core.h" 2 | #include "state.h" 3 | #include "text.h" 4 | #include "resources.h" 5 | #include "screen.h" 6 | #include "logger.h" 7 | 8 | #include 9 | 10 | /* 11 | // Idealish size, but part pixels 12 | const unsigned short SINGLE_PLAYER_SCREEN_TOP = 50; 13 | const unsigned short SINGLE_PLAYER_SCREEN_LEFT = 140; 14 | const unsigned short SINGLE_PLAYER_SCREEN_WIDTH = 360; 15 | const unsigned short SINGLE_PLAYER_SCREEN_HEIGHT = 324; 16 | */ 17 | 18 | // A little too small, but crisp screen. 19 | const natural SINGLE_PLAYER_SCREEN_TOP = 50; 20 | const natural SINGLE_PLAYER_SCREEN_LEFT = 160; 21 | const natural SINGLE_PLAYER_SCREEN_WIDTH = 320; 22 | const natural SINGLE_PLAYER_SCREEN_HEIGHT = 288; 23 | 24 | const natural PLAYER_1_SCREEN_TOP = 50; 25 | const natural PLAYER_1_SCREEN_LEFT = 27; 26 | const natural PLAYER_1_SCREEN_WIDTH = 267; 27 | const natural PLAYER_1_SCREEN_HEIGHT = 240; 28 | 29 | const natural PLAYER_2_SCREEN_TOP = 50; 30 | const natural PLAYER_2_SCREEN_LEFT = 347; 31 | const natural PLAYER_2_SCREEN_WIDTH = 267; 32 | const natural PLAYER_2_SCREEN_HEIGHT = 240; 33 | 34 | /** 35 | * Draws permanent screen artifacts like the logo and borders 36 | * @param state program state. 37 | * @param frame identifies the frame to draw on. 38 | * @private 39 | */ 40 | static void hudDraw(const display_context_t frame) { 41 | string text = ""; 42 | getText(TextSplash, text); 43 | drawText(frame, text, 170, 10, 1); 44 | } 45 | 46 | /** 47 | * Gets the RDP module ready to render a new texture. 48 | * @param frame identifies frame to render to. 49 | */ 50 | void prepareRdpForSprite(const display_context_t frame) { 51 | // Assure RDP is ready for new commands 52 | rdp_sync(SYNC_PIPE); 53 | // Remove any clipping windows 54 | rdp_set_default_clipping(); 55 | // Enable sprite display instead of solid color fill 56 | rdp_enable_texture_copy(); 57 | // Attach RDP to display 58 | rdp_attach_display(frame); 59 | } 60 | 61 | /** 62 | * Loads a single sprite sheet in to the primary texture slot 63 | * @param spriteSheet sheet the sprite is on. 64 | * @param spriteCode position of the sprite in the sheet. 65 | * @param mirrorSetting how the sprite should behave when mirroring. 66 | */ 67 | void loadSprite(sprite_t* spriteSheet, const byte spriteCode, const mirror_t mirrorSetting) { 68 | // The other text slots don't seem to work for whatever reason, so let's abstract them away. 69 | rdp_load_texture_stride(0, 0, mirrorSetting, spriteSheet, spriteCode); 70 | } 71 | 72 | /** 73 | * Rerenders the background over both display buffers to cover whatever junk was there previously. 74 | * @param state program state 75 | */ 76 | void flushScreen() { 77 | // Assure RDP is ready for new commands 78 | rdp_sync(SYNC_PIPE); 79 | // Remove any clipping windows 80 | rdp_set_default_clipping(); 81 | // Enable sprite display instead of solid color fill 82 | rdp_enable_texture_copy(); 83 | 84 | // Attach RDP to display 85 | rdp_attach_display(1); 86 | loadSprite(getSpriteSheet(), BLUE_BG_TEXTURE, true); 87 | rdp_draw_textured_rectangle(0, 0, 0, RESOLUTION_X, RESOLUTION_Y, MIRROR_DISABLED); 88 | hudDraw(1); 89 | rdp_detach_display(); 90 | 91 | rdp_attach_display(2); 92 | loadSprite(getSpriteSheet(), BLUE_BG_TEXTURE, true); 93 | rdp_draw_textured_rectangle(0, 0, 0, RESOLUTION_X, RESOLUTION_Y, MIRROR_DISABLED); 94 | hudDraw(2); 95 | rdp_detach_display(); 96 | } 97 | 98 | /** 99 | * Draws an unfilled rectangle of a certain thickness. 100 | * @param frame frame to draw the border on. 101 | * @param position Where to draw the border on the screeen. 102 | * @param thickness How thick the border is. 103 | * @param colour The colour of the border. 104 | */ 105 | void drawSolidBorder(const display_context_t frame, const Rectangle* position, const natural thickness, const natural colour) { 106 | rdp_set_default_clipping(); 107 | rdp_attach_display(frame); 108 | rdp_enable_primitive_fill(); 109 | rdp_set_primitive_color(colour); 110 | 111 | rdp_draw_filled_rectangle(position->Left, position->Top, position->Left + thickness, position->Top + position->Height); 112 | rdp_draw_filled_rectangle(position->Left, position->Top + position->Height -thickness, position->Left + position->Width, position->Top + position->Height); 113 | rdp_draw_filled_rectangle(position->Left, position->Top, position->Left + position->Width, position->Top + thickness); 114 | rdp_draw_filled_rectangle(position->Left + position->Width - thickness, position->Top, position->Left + position->Width, position->Top + position->Height); 115 | 116 | rdp_detach_display(); 117 | } 118 | 119 | /** 120 | * Get the gameboy screen rectangle based on player number. 121 | * @param state program state including number of players. 122 | * @param playerNumber number of a given player. 123 | * @out output The calculated screen size & position. 124 | */ 125 | void getScreenPosition(const byte playerNumber, Rectangle* output) { 126 | if (rootState.PlayerCount == 1) { 127 | output->Top = SINGLE_PLAYER_SCREEN_TOP; 128 | output->Left = SINGLE_PLAYER_SCREEN_LEFT; 129 | output->Width = SINGLE_PLAYER_SCREEN_WIDTH; 130 | output->Height = SINGLE_PLAYER_SCREEN_HEIGHT; 131 | } else if (rootState.PlayerCount == 2) { 132 | if (playerNumber == 0) { 133 | output->Top = PLAYER_1_SCREEN_TOP; 134 | output->Left = PLAYER_1_SCREEN_LEFT; 135 | output->Width = PLAYER_1_SCREEN_WIDTH; 136 | output->Height = PLAYER_1_SCREEN_HEIGHT; 137 | } else if (playerNumber == 1) { 138 | output->Top = PLAYER_2_SCREEN_TOP; 139 | output->Left = PLAYER_2_SCREEN_LEFT; 140 | output->Width = PLAYER_2_SCREEN_WIDTH; 141 | output->Height = PLAYER_2_SCREEN_HEIGHT; 142 | } else { 143 | logAndPause("Unimplemented getScreenPosition: %d", rootState.PlayerCount); 144 | } 145 | } else { 146 | logAndPause("Unimplemented getScreenPosition: %d", rootState.PlayerCount); 147 | } 148 | } 149 | 150 | /** 151 | * Fade/Brighten a colour by a given amount. 152 | * @param colour The 16bit colour 153 | * @param fadeAmount This amount will be added to each component. 154 | * @returns The new faded colour. 155 | */ 156 | u16 fadeColour(const natural colour, const byte fadeAmount) { 157 | byte red = colour >> 11; 158 | byte green = (colour >> 6) & 0x1F; 159 | byte blue = (colour >> 1) & 0x1F; 160 | byte t = colour & 1; 161 | 162 | // Find how much space we have to fade. 163 | byte room[3] = { 164 | 0x1F - red, 165 | 0x1F - green, 166 | 0x1F - blue 167 | }; 168 | 169 | byte factor = fadeAmount; 170 | for (byte i = 0; i < 3; i++) { 171 | factor = factor < room[i] ? factor : room[i]; 172 | } 173 | 174 | red = red + factor; 175 | green = green + factor; 176 | blue = blue + factor; 177 | 178 | return (red << 11) | (green << 6) | (blue << 1) | t; 179 | } 180 | 181 | /** 182 | * Takes a 16bit gameboy colour and returns a 32 bit libdragon colour 183 | * @param colour The colour from the gameboy 184 | * @return The colour that libdragon can render. 185 | */ 186 | uInt massageColour(const natural colour) { 187 | // Colours are in tBbbbbGggggRrrrr order, but we need to flip them to RrrrrGggggBbbbbt 188 | natural b = colour & 0x7C00; 189 | natural g = colour & 0x03E0; 190 | natural r = colour & 0x001F; 191 | natural reversed = (r << 11 | (g << 1) | (b >> 9) | 1); 192 | 193 | return (reversed << 16) | reversed; 194 | } 195 | 196 | 197 | -------------------------------------------------------------------------------- /screen.h: -------------------------------------------------------------------------------- 1 | #ifndef SCREEN_INCLUDED 2 | #define SCREEN_INCLUDED 3 | 4 | #include "core.h" 5 | #include "state.h" 6 | 7 | static const short RESOLUTION_X = 640; 8 | static const short RESOLUTION_Y = 480; 9 | 10 | static const char TEXT_HEIGHT = 100; 11 | static const char TEXT_WIDTH = 10; 12 | 13 | static const float TEXT_SCALE_FACTOR = 0.210 / 100.0; 14 | 15 | typedef struct { 16 | u16 Left; 17 | u16 Top; 18 | u16 Width; 19 | u16 Height; 20 | } Rectangle; 21 | 22 | // 16 bit colours are are 5 bits per colour and a transparency flag 23 | static const uInt MONOCHROME_PALETTE[] = { 24 | 0xffffffff, 0xA529A529, 0x52955295, 0x00010001 25 | }; 26 | 27 | /** 28 | * Resets all screen buffers to a known state. 29 | * @param state program state. 30 | */ 31 | void flushScreen(); 32 | 33 | /** 34 | * Gets the RDP module ready to render a new texture. 35 | * @param frame identifies frame to render to. 36 | */ 37 | void prepareRdpForSprite(const display_context_t frame); 38 | 39 | /** 40 | * Loads a single sprite sheet in to the primary texture slot 41 | * @param spriteSheet sheet the sprite is on. 42 | * @param spriteCode position of the sprite in the sheet. 43 | * @param mirrorSetting how the sprite should behave when mirroring. 44 | */ 45 | void loadSprite(sprite_t* spriteSheet, const byte spriteCode, const mirror_t mirrorSetting); 46 | 47 | /** 48 | * Draws an unfilled rectangle of a certain thickness. 49 | * @param frame frame to draw the border on. 50 | * @param position Where to draw the border on the screeen. 51 | * @param thickness How thick the border is. 52 | * @param colour The colour of the border. 53 | */ 54 | void drawSolidBorder(const display_context_t frame, const Rectangle* position, const natural width, const natural colour); 55 | 56 | /** 57 | * Get the gameboy screen rectangle based on player number. 58 | * @param state program state including number of players. 59 | * @param playerNumber number of a given player. 60 | * @out output The calculated screen size & position. 61 | */ 62 | void getScreenPosition(const byte playerNumber, Rectangle* output); 63 | 64 | /** 65 | * Takes a 16bit gameboy colour and returns a 32 bit libdragon colour 66 | * @param colour The colour from the gameboy 67 | * @return The colour that libdragon can render. 68 | */ 69 | uInt massageColour(const natural colour); 70 | 71 | /** 72 | * Fade/Brighten a colour by a given amount. 73 | * @param colour The 16bit colour 74 | * @param fadeAmount This amount will be added to each component. 75 | * @returns The new faded colour. 76 | */ 77 | u16 fadeColour(const natural colour, const byte fadeAmount); 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /sgbDefs.h: -------------------------------------------------------------------------------- 1 | #ifndef SGBDEFS_INCLUDED 2 | #define SGBDEFS_INCLUDED 3 | 4 | typedef enum { 5 | SGBSetPalette01 = 0x00, // PAL01 6 | SGBSetPalette23 = 0x01, // PAL23 7 | SGBSetPalette03 = 0x02, // PAL03 8 | SGBSetPalette12 = 0x03, // PAL12 9 | SGBApplyPaletteToBlocks = 0x04, // ATTR_BLK 10 | SGBApplyPaletteToLines = 0x05, //ATTR_LIN 11 | SGBDividePalettes = 0x06, // ATTR_DIV 12 | SGBApplyPaletteToTiles = 0x07, //ATTR_CHR, 13 | SGBGenerateSound = 0x08, // SOUND 14 | SGBTransferSound = 0x09, // SOU_TRN 15 | SGBSetSystemPalette = 0x0A, // PAL_SET 16 | SGBTransferPalette = 0x0B, //PAL_TRAN 17 | SGBEnableAttraction = 0x0C, //ATRN_EN 18 | SGBEnableIcon = 0x0E, // ICON_ENT 19 | SGBTransferPacketToSgb = 0x0F, // DATA_SND 20 | SGBTransferScreenToSGB = 0x10, //DATA_TRN 21 | SGBRequestMultiplayer = 0x11, //MLT_REQ 22 | SGBSNESJump = 0x12, //JUMP 23 | SGBTransferCharacter = 0x13, //CHR_TRN 24 | SGBTransferOverlay = 0x14, // PCT_TRN 25 | SGBTransferAttributes = 0x15, // ATTR_TRN 26 | SGBSetAttribute = 0x16, // ATTR_SET 27 | SGBMaskWindow = 0x17, // MASK_EN 28 | SGBSetPalettePriority = 0x19, // PAL_PRI 29 | SGBNoop = 0x1A // TransferBoy noop 30 | } SuperGameboyCommand; 31 | 32 | typedef enum { 33 | SGBNoMask, 34 | SGBFrzMask, 35 | SGBBlkMask, 36 | SGBColMask 37 | } SGBMaskState; 38 | 39 | typedef natural Palette[4]; 40 | typedef natural SgbPalette[16]; 41 | 42 | typedef struct { 43 | natural Address; 44 | byte Bank; 45 | byte ByteCount; 46 | byte Data[11]; 47 | } SnesRamBlock; 48 | 49 | typedef byte GbSprite[32]; 50 | 51 | typedef struct { 52 | byte SpriteId; 53 | bool IsYFlipped:1; 54 | bool IsXFlipped:1; 55 | byte pad0:1; 56 | byte PaletteId:3; 57 | byte pad1:2; 58 | } SgbTile; 59 | 60 | typedef struct { 61 | bool HasBuffer:1; 62 | bool HasPendingBit:1; 63 | bool PendingBit:1; 64 | bool AwaitingStopBit:1; // After 16 bytes per packet are transferred, one more 0 bit is sent to singal the end of the packet. 65 | bool JoypadRequestResolved:1; 66 | byte NumberOfPackets:3; 67 | 68 | byte PlayersMode:2; //0-3 69 | byte CurrentController:2; 70 | SGBMaskState MaskState:2; 71 | bool HasPriority:1; 72 | bool HasRamData:1; 73 | 74 | byte BitBuffer; 75 | byte BitPointer; 76 | byte PacketPointer; 77 | byte BytePointer; 78 | bool IsTransferring; 79 | SuperGameboyCommand CurrentCommand; 80 | Palette Palettes[4]; 81 | byte SnesRamBlockCount; 82 | GbSprite SpriteData[256]; 83 | SgbTile OverlayData[1024]; 84 | SgbPalette OverlayPalettes[3]; 85 | byte TilePalettes[360]; // 20 x 18 tiles. 86 | SnesRamBlock* RamBlocks; 87 | byte* Buffer; 88 | natural* PreviousPixels; 89 | } SuperGameboyState; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /sound.c: -------------------------------------------------------------------------------- 1 | #include "sound.h" 2 | 3 | #include 4 | #include 5 | #include "core.h" 6 | #include "state.h" 7 | #include 8 | 9 | 10 | static const byte WaveDuty[4][8] = { 11 | { 1, 0, 0, 0, 0, 0, 0, 0 }, 12 | { 1, 1, 0, 0, 0, 0, 0, 0 }, 13 | { 1, 1, 1, 1, 0, 0, 0, 0 }, 14 | { 1, 1, 1, 1, 1, 1, 0, 0 } 15 | }; 16 | 17 | /** 18 | * @return Duration in milliseconds. 19 | */ 20 | natural getSoundDuration(const GbSoundChannel* channel) { 21 | return (64 - channel->AllChannels.SoundLength) * 4; 22 | } 23 | 24 | Frequency getFrequencyData(const GbSoundChannel* channel) { 25 | if (channel->ChannelNumber >= 4) { 26 | return 0; 27 | } 28 | natural result = 29 | 0x0000 | 30 | ((natural)channel->Frequency.Frequency2 & 0x0007) << 8 | // Only the lowest 3 bits 31 | (channel->Frequency.Frequency1); 32 | return result; 33 | } 34 | 35 | natural getHertz(const GbSoundChannel* channel) { 36 | if (channel->ChannelNumber >= 4) { 37 | return 0; 38 | } 39 | 40 | Frequency data = getFrequencyData(channel); 41 | return 131072 / (2048 - data); 42 | } 43 | 44 | float getNextSweepFrequency(const GbSoundChannel* channel, float currentHz) { 45 | if (channel->ChannelNumber != 1) { 46 | return 0; 47 | } 48 | sByte dir = 1; 49 | if (channel->Channel1.IsFrequencyDecrease) { 50 | dir = -1; 51 | } 52 | 53 | return currentHz + dir * (currentHz/(2^channel->Channel1.SweepShift)); 54 | } 55 | 56 | /** 57 | * Take the data from the SWEEP channel registers and tranform it into a format that consumed by the N64's audio system. 58 | * @param channel The channel data. 59 | * @out buffer The transformed audui buffer. 60 | */ 61 | void prepareChannel1(const GbSoundControl* control, const GbSoundChannel* channel, sShort* buffer) { 62 | if (channel->ChannelNumber != 1) { 63 | return; 64 | } 65 | 66 | for(natural i = 0; i < control->BufferLength; i += 2) { 67 | sShort sample = 0; 68 | 69 | sByte sign = channel->Envelope.IsVolumeIncreasing ? 1 : -1; 70 | sample = channel->Envelope.InitialVolume + (sign * channel->Envelope.EnvelopeSteps); 71 | 72 | // Follows the square wave pattern. 73 | if (!WaveDuty[channel->Channel1And2.WavePatternDuty][i % 8]) { 74 | sample *= -1; 75 | } 76 | 77 | // Don't know why we do this, but gnuboy does it and it makes sense. 78 | sample <<= 2; 79 | 80 | if (control->Bits.IsLeftTerminalEnabled && control->Bits.IsChannel1OnLeftTerminal) { 81 | buffer[i] += sample; 82 | } 83 | if (control->Bits.IsRightTerminalEnabled && control->Bits.IsChannel1OnRightTerminal) { 84 | buffer[i+1] += sample; 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * Take the data from the PULSE channel registers and tranform it into a format that consumed by the N64's audio system. 91 | * @param channel The channel data. 92 | * @out buffer The transformed audui buffer. 93 | */ 94 | void prepareChannel2(const GbSoundControl* control, const GbSoundChannel* channel, sShort* buffer) { 95 | if (channel->ChannelNumber != 2) { 96 | return; 97 | } 98 | 99 | for(natural i = 0; i < control->BufferLength; i += 2) { 100 | sShort sample = 0; 101 | 102 | sByte sign = channel->Envelope.IsVolumeIncreasing ? 1 : -1; 103 | sample = channel->Envelope.InitialVolume + (sign * channel->Envelope.EnvelopeSteps); 104 | 105 | // Follows the square wave pattern. 106 | if (!WaveDuty[channel->Channel1And2.WavePatternDuty][i % 8]) { 107 | sample *= -1; 108 | } 109 | 110 | // Don't know why we do this, but gnuboy does it and it makes sense. 111 | sample <<= 2; 112 | 113 | if (control->Bits.IsLeftTerminalEnabled && control->Bits.IsChannel2OnLeftTerminal) { 114 | buffer[i] += sample; 115 | } 116 | if (control->Bits.IsRightTerminalEnabled && control->Bits.IsChannel2OnRightTerminal) { 117 | buffer[i+1] += sample; 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Take the data from the WAVE channel registers and tranform it into a format that consumed by the N64's audio system. 124 | * @param channel The channel data. 125 | * @out buffer The transformed audui buffer. 126 | */ 127 | void prepareChannel3(const GbSoundControl* control, const GbSoundChannel* channel, sShort* buffer) { 128 | if (channel->ChannelNumber != 3) { 129 | return; 130 | } 131 | 132 | for (natural i = 0; i < control->BufferLength; i+=4) { 133 | byte byteNumber = control->BufferLength % WAVEDATA_LENGTH; 134 | byte data = channel->WaveData[byteNumber]; 135 | 136 | // Four bits per sample. We interleave between the left and right speaker. 137 | byte nibble1 = data >> 4; 138 | byte nibble2 = data & 0x0F; 139 | 140 | switch(channel->Channel3.PatternShift) { 141 | case SoundNoShift: break; 142 | case SoundHalfShift: 143 | nibble1 >>= 1; 144 | nibble2 >>= 1; 145 | case SoundQuarterShift: 146 | nibble1 >>= 2; 147 | nibble2 >>= 2; 148 | case SoundMute: 149 | default: 150 | continue; 151 | } 152 | 153 | if (control->Bits.IsLeftTerminalEnabled && control->Bits.IsChannel3OnLeftTerminal) { 154 | buffer[i] += nibble1; 155 | buffer[i+2] += nibble2; 156 | } 157 | if (control->Bits.IsRightTerminalEnabled && control->Bits.IsChannel3OnRightTerminal) { 158 | buffer[i+1] += nibble1; 159 | buffer[i+3] += nibble2; 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Take the data from the NOISE channel registers and tranform it into a format that consumed by the N64's audio system. 166 | * @param channel The channel data. 167 | * @out buffer The transformed audui buffer. 168 | */ 169 | void prepareChannel4(const GbSoundControl* control, const GbSoundChannel* channel, sShort* buffer) { 170 | if (channel->ChannelNumber != 4) { 171 | return; 172 | } 173 | 174 | for(natural i = 0; i < control->BufferLength; i += 2) { 175 | 176 | // TODO - It seems there's a really complicated way of calculating this, but rand will do for now. 177 | byte sample = rand() % 2; 178 | if (sample == 0) { 179 | sample = -1; 180 | } 181 | 182 | sByte sign = channel->Envelope.IsVolumeIncreasing ? 1 : -1; 183 | sample = sample * channel->Envelope.InitialVolume + (sample * sign * channel->Envelope.EnvelopeSteps); 184 | 185 | // Don't know why we do this, but gnuboy does it and it makes sense. 186 | sample += sample << 1; 187 | 188 | if (control->Bits.IsLeftTerminalEnabled && control->Bits.IsChannel4OnLeftTerminal) { 189 | buffer[i] += sample; 190 | } 191 | if (control->Bits.IsRightTerminalEnabled && control->Bits.IsChannel4OnRightTerminal) { 192 | buffer[i+1] += sample; 193 | } 194 | } 195 | } 196 | 197 | /** 198 | * Adjusts the buffer with data from the given channel. 199 | * @param control General sound control information. 200 | * @param channel Sound channel data. 201 | * @out buffer N64 Audio buffer. 202 | */ 203 | void prepareSoundBuffer(const GbSoundControl* control, const GbSoundChannel* channel, sShort* buffer) { 204 | switch(channel->ChannelNumber) { 205 | case 1: prepareChannel1(control, channel, buffer); 206 | case 2: prepareChannel2(control, channel, buffer); 207 | case 3: prepareChannel3(control, channel, buffer); 208 | case 4: prepareChannel4(control, channel, buffer); 209 | default: return; 210 | } 211 | } 212 | 213 | /** 214 | * Gets sound data for a specific channel from gameboy registers. 215 | * @param gameboy state with sound registers 216 | * @out result The sound control data. 217 | */ 218 | void getSoundControl(const GbState* gbState, GbSoundControl* result) { 219 | /* 220 | result->BufferLength = audio_get_buffer_length(); 221 | result->Bytes.Volume = gbState->io_sound_terminal_control; 222 | result->Bytes.StereoControl = gbState->io_sound_out_terminal; 223 | result->Bytes.ChannelControl = gbState->AudioChannelSwitch; 224 | */ 225 | } 226 | 227 | /** 228 | * Gets general sound control info from gameboy registers. 229 | * @param gameboy state with sound registers 230 | * @param channelNumber channel to get data for. 231 | * @out result The channel data. 232 | */ 233 | void getSoundChannel(const GbState* gbState, const byte channelNumber, GbSoundChannel* result) { 234 | /* 235 | result->ChannelNumber = channelNumber; 236 | switch(channelNumber) { 237 | case 1: 238 | result->Bytes.SweepRegister = gbState->AudioChannel1Sweep; 239 | result->Bytes.SoundLength = gbState->AudioChannel1PatternAndLength; 240 | result->Bytes.Envelope = gbState->AudioChannel1Envelope; 241 | result->Bytes.Frequency1 = gbState->AudioChannel1Frequency; 242 | result->Bytes.FrequencyAndTiming = gbState->AudioChannel1Flags; 243 | break; 244 | case 2: 245 | result->Bytes.SweepRegister = 0; 246 | result->Bytes.SoundLength = gbState->AudioChannel2PatternAndLength; 247 | result->Bytes.Envelope = gbState->AudioChannel2Envelope; 248 | result->Bytes.Frequency1 = gbState->AudioChannel2FrequencyLow; 249 | result->Bytes.FrequencyAndTiming = gbState->AudioChannel2Flags; 250 | break; 251 | case 3: 252 | result->Bytes.SweepRegister = gbState->AudioChannel3Control; 253 | result->Bytes.SoundLength = gbState->AudioChannel3Length; 254 | result->Bytes.Envelope = gbState->AudioChannel3Level; 255 | result->Bytes.Frequency1 = gbState->AudioChannel3FrequencyLow; 256 | result->Bytes.FrequencyAndTiming = gbState->AudioChannel3Flags; 257 | 258 | memcpy(result->WaveData, gbState->SoundWaveData, sizeof(gbState->SoundWaveData)); 259 | break; 260 | case 4: 261 | result->Bytes.SweepRegister = 0; 262 | result->Bytes.SoundLength = gbState->AudioChannel4Length; 263 | result->Bytes.Envelope = 0; 264 | result->Bytes.Frequency1 = gbState->AudioChannel4RNGParameters; 265 | result->Bytes.FrequencyAndTiming = gbState->AudioChannel4Flags; 266 | break; 267 | default: return; 268 | } 269 | */ 270 | } 271 | -------------------------------------------------------------------------------- /sound.h: -------------------------------------------------------------------------------- 1 | #ifndef SOUND_INCLUDED 2 | #define SOUND_INCLUDED 3 | 4 | #include "core.h" 5 | #include "types.h" 6 | 7 | typedef natural Frequency; 8 | 9 | typedef struct { 10 | natural BufferLength; 11 | union { 12 | struct { 13 | byte Volume; 14 | byte StereoControl; 15 | byte ChannelControl; 16 | } Bytes; 17 | struct { 18 | bool IsLeftTerminalEnabled:1; // S02 19 | byte LeftTerminalVolume:3; 20 | bool IsRightTerminalEnabled:1; //S01 21 | byte RightTerminalVolume:3; 22 | 23 | bool IsChannel4OnLeftTerminal:1; 24 | bool IsChannel3OnLeftTerminal:1; 25 | bool IsChannel2OnLeftTerminal:1; 26 | bool IsChannel1OnLeftTerminal:1; 27 | bool IsChannel4OnRightTerminal:1; 28 | bool IsChannel3OnRightTerminal:1; 29 | bool IsChannel2OnRightTerminal:1; 30 | bool IsChannel1OnRightTerminal:1; 31 | 32 | bool IsSoundEnabled:1; 33 | byte pad:3; 34 | bool IsChannel4Enabled:1; 35 | bool IsChannel3Enabled:1; 36 | bool IsChannel2Enabled:1; 37 | bool IsChannel1Enabled:1; 38 | } Bits; 39 | }; 40 | } GbSoundControl; 41 | 42 | typedef struct { 43 | byte ChannelNumber; 44 | union { 45 | struct { 46 | byte SweepRegister; 47 | byte SoundLength; 48 | byte Envelope; 49 | byte Frequency1; 50 | byte FrequencyAndTiming; 51 | } Bytes; 52 | 53 | struct { 54 | byte pad1:1; 55 | byte SweepTime:3; 56 | bool IsFrequencyDecrease:1; 57 | byte SweepShift:3; 58 | byte pad2:8; 59 | byte pad3:8; 60 | byte pad4:8; 61 | byte pad5:8; 62 | } Channel1; 63 | 64 | struct { 65 | byte pad1:8; 66 | byte WavePatternDuty:2; 67 | byte pad2:6; 68 | byte pad3:8; 69 | byte pad4:8; 70 | byte pad5:8; 71 | } Channel1And2; 72 | 73 | struct { 74 | byte pad1:8; 75 | byte pad2:8; 76 | byte InitialVolume:4; 77 | bool IsVolumeIncreasing:1; 78 | byte EnvelopeSteps:3; 79 | byte pad3:8; 80 | byte pad4:8; 81 | } Envelope; 82 | 83 | struct { 84 | byte pad1:8; 85 | byte pad2:8; 86 | byte pad3:8; 87 | byte Frequency1:8; 88 | bool IsSoundReset:1; 89 | byte pad4:4; 90 | byte Frequency2:3; 91 | } Frequency; 92 | 93 | struct { 94 | byte pad1:8; 95 | byte pad2:2; 96 | byte SoundLength:6; 97 | byte pad3:8; 98 | byte pad4:8; 99 | byte pad5:8; 100 | SoundTimingMode TimingMode:1; 101 | byte pad6:7; 102 | } AllChannels; 103 | 104 | struct { 105 | bool IsEnabled:1; 106 | byte pad1:7; 107 | byte pad2:8; 108 | byte pad3:1; 109 | WavePatternShift PatternShift:2; 110 | byte pad5:5; 111 | byte pad6:8; 112 | byte pad7:8; 113 | } Channel3; 114 | 115 | struct { 116 | byte pad7:8; 117 | byte pad8:8; 118 | byte pad9:8; 119 | byte NoiseRatioShift:4; 120 | byte NoiseStepsCode: 1; 121 | byte NoiseRatioFactor:3; 122 | byte pad10: 8; 123 | } Channel4; 124 | }; 125 | 126 | byte WaveData[WAVEDATA_LENGTH]; 127 | 128 | } GbSoundChannel; 129 | 130 | void prepareSoundBuffer(const GbSoundControl* control, const GbSoundChannel* channel, sShort* buffer); 131 | void getSoundControl(const GbState* gbState, GbSoundControl* result); 132 | void getSoundChannel(const GbState* gbState, const byte channelNumber, GbSoundChannel* result); 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /state.c: -------------------------------------------------------------------------------- 1 | #include "state.h" 2 | #include "global.h" 3 | #include 4 | 5 | RootState rootState; 6 | 7 | /** 8 | * Sets up initial configuration for N64 - Gameboy button map. 9 | * @out buttonMap map of N64 to Gameboy buttons. 10 | * @private 11 | */ 12 | static void initialiseButtonMap(GbButton* map) { 13 | GbButton buttons[N64_BUTTON_COUNT]; 14 | memset(buttons, 0x00, sizeof(GbButton) * N64_BUTTON_COUNT); 15 | buttons[NoButton] = GbNoButton; 16 | buttons[A] = GbA; 17 | buttons[B] = GbB; 18 | buttons[Up] = GbUp; 19 | buttons[Down] = GbDown; 20 | buttons[Left] = GbLeft; 21 | buttons[Right] = GbRight; 22 | 23 | // Start, Select and SystemMenu should be configurable 24 | buttons[Start] = GbStart; 25 | buttons[R] = GbSelect; 26 | buttons[L] = GbSystemMenu; 27 | 28 | memcpy(map, &buttons, sizeof(GbButton) * N64_BUTTON_COUNT); 29 | } 30 | 31 | /** 32 | * Sets a Gameboy button to a particular N64 button, unsetting it from the previous mapping. 33 | * @param playerState state containing controller mapping to update. 34 | * @param gbButton gameboy button to set. 35 | * @param n64Button n64 button to set gb button to. 36 | */ 37 | void setButtonToMap(PlayerState* playerState, const GbButton gbButton, const N64Button n64Button) { 38 | // Unset old mapping 39 | for (byte i = 0; i < N64_BUTTON_COUNT; i++) { 40 | if (playerState->ButtonMap[i] == gbButton) { 41 | playerState->ButtonMap[i] = GbNoButton; 42 | } 43 | } 44 | 45 | // Set new mapping. 46 | playerState->ButtonMap[n64Button] = gbButton; 47 | if (gbButton == GbSystemMenu) { 48 | playerState->SystemMenuButton = n64Button; 49 | } 50 | } 51 | 52 | /** 53 | * Initialises a new player state struct with default values. 54 | * @out playerState PlayerState struct to initialise. 55 | */ 56 | void generatePlayerState(PlayerState* playerState) { 57 | playerState->SelectedBorder = BorderNone; 58 | playerState->AudioEnabled = true; 59 | playerState->ActiveMode = Init; 60 | playerState->MenuCursorRow = -1; 61 | playerState->MenuCursorColumn = 0; 62 | initialiseButtonMap(playerState->ButtonMap); 63 | 64 | for (byte i = 0; i < N64_BUTTON_COUNT; i++) { 65 | if (playerState->ButtonMap[i] == GbSystemMenu) { 66 | playerState->SystemMenuButton = i; 67 | } else if (playerState->ButtonMap[i] == GbStart) { 68 | playerState->GbStartButton = i; 69 | } else if (playerState->ButtonMap[i] == GbSelect) { 70 | playerState->GbSelectButton = i; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /state.h: -------------------------------------------------------------------------------- 1 | #ifndef STATE_INCLUDED 2 | #define STATE_INCLUDED 3 | 4 | #include "core.h" 5 | #include "config.h" 6 | #include "sgbDefs.h" 7 | #include "controller.h" 8 | #include "tpakio.h" 9 | #include "types.h" 10 | 11 | typedef struct { 12 | uLong FrameCount; 13 | } MetaState; 14 | 15 | typedef struct { 16 | Mode ActiveMode; 17 | Border SelectedBorder; 18 | GbButton ButtonMap[N64_BUTTON_COUNT]; 19 | N64Button SystemMenuButton; 20 | N64Button GbStartButton; 21 | N64Button GbSelectButton; 22 | bool AudioEnabled; 23 | GbState EmulationState; 24 | SuperGameboyState SGBState; 25 | sByte InitState; 26 | sByte OptionsCursorRow; 27 | sByte MenuCursorRow; 28 | sByte MenuCursorColumn; 29 | byte MenuLayout[2]; 30 | bool WasFrameSkipped; 31 | MetaState Meta; 32 | string ErrorMessage; 33 | } PlayerState; 34 | 35 | typedef struct { 36 | sByte ErrorCode; 37 | byte pad0:2; 38 | bool RequiresRepaint:1; 39 | bool RequiresControllerRead:1; 40 | byte PlayerCount:4; 41 | natural ControllersPresent; 42 | N64ControllerState KeysPressed; 43 | N64ControllerState KeysReleased; 44 | float PixelSize; 45 | display_context_t Frame; 46 | PlayerState Players[MAX_PLAYERS]; // Really not aiming for anything other than 2, but you never know. 47 | } RootState; 48 | 49 | // Global program state. 50 | extern RootState rootState; 51 | 52 | /** 53 | * Sets a Gameboy button to a particular N64 button, unsetting it from the previous mapping. 54 | * @param playerState state containing controller mapping to update. 55 | * @param gbButton gameboy button to set. 56 | * @param n64Button n64 button to set gb button to. 57 | */ 58 | void setButtonToMap(PlayerState* playerState, const GbButton gbButton, const N64Button n64Button); 59 | 60 | /** 61 | * Initialise the state struct for a new player. 62 | * @param playerState struct to initialise. 63 | */ 64 | void generatePlayerState(PlayerState* playerState); 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /superGameboy.h: -------------------------------------------------------------------------------- 1 | #ifndef SUPER_GAMEBOY_INCLUDED 2 | #define SUPER_GAMEBOY_INCLUDED 3 | 4 | #include "state.h" 5 | #include "sgbDefs.h" 6 | 7 | /** 8 | * Puts SuperGameboy data back to their initial state. 9 | * @param state The state to reset. 10 | */ 11 | void resetSGBState(SuperGameboyState* state); 12 | 13 | /** 14 | * Checks whether there is new Supergameboy data to process, and carries out any sgb commands. 15 | * @param state The state of the player we're processing. 16 | */ 17 | void processSGBData(PlayerState* state); 18 | 19 | /** 20 | * Adjusts emulation to perform addition super gameboy functions. 21 | * @param state The state of the player we're processing. 22 | */ 23 | void performSGBFunctions(PlayerState* state); 24 | 25 | /** 26 | * Applies Supergameboy colourisation to the pixels in the buffer. 27 | * @param stateCurrent Super Gameboy data state. 28 | * @param pixelBuffer Greyscale Gameboy pixel buffer that will be overwritten. 29 | * @return Error code 30 | ** 0 Success. 31 | ** -1 Unknown mask type. 32 | */ 33 | sByte applySGBPalettes(const SuperGameboyState* state, natural* pixelBuffer); 34 | #endif 35 | -------------------------------------------------------------------------------- /text.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "core.h" 6 | #include "text.h" 7 | #include "resources.h" 8 | #include "screen.h" 9 | 10 | #include 11 | 12 | static string _strings[TextEnd] = {"", "", "", ""}; 13 | static bool textInitted = false; 14 | 15 | static const byte CHARACTER_SIZE = 24; 16 | static const byte SPRITE_SIZE = 32; 17 | 18 | /** 19 | * Initialises text subsystem by loading sprites etc. 20 | * @return 0 result code 21 | ** 0 success 22 | ** -1 expected file not present 23 | */ 24 | sByte initText() { 25 | if (textInitted) { 26 | return 0; 27 | } 28 | 29 | // Set up string resources 30 | strcpy(_strings[TextEmpty], ""); 31 | strcpy(_strings[TextLoadCartridge], "Press $02 to load cartridge."); 32 | strcpy(_strings[TextNoTpak], "Please insert a Transfer Pak."); 33 | strcpy(_strings[TextNoCartridge], "Please insert a Game Boy cartridge."); 34 | strcpy(_strings[TextLoadingCartridge], "Loading cartridge, please wait..."); 35 | strcpy(_strings[TextExpansionPakRequired], "This cartridge cannot be loaded without an Expansion Pak."); 36 | strcpy(_strings[TextChecksumFailed], "The cartridge could not be read correctly. Error %d."); 37 | strcpy(_strings[TextUnsupportedCartridge], "This cartridge is not yet supported"); 38 | strcpy(_strings[TextRetryCartridgePrompt], "Press A to retry."); 39 | strcpy(_strings[TextMenuResume], "Resume"); 40 | strcpy(_strings[TextMenuReset], "Reset"); 41 | strcpy(_strings[TextMenuChangeCart], "Switch"); 42 | strcpy(_strings[TextMenuOptions], "Options"); 43 | strcpy(_strings[TextMenuAddPlayer], "Add Player"); 44 | strcpy(_strings[TextMenuAddGame], "Add Game"); 45 | strcpy(_strings[TextSplash], "\\~TRANSFER BOY~"); 46 | strcpy(_strings[TextAudioOff], "Audio : Off"); 47 | strcpy(_strings[TextAudioOn], "Audio : On"); 48 | 49 | sByte result = initResources(); 50 | if (result) { 51 | return result; 52 | } else { 53 | textInitted = true; 54 | return result; 55 | } 56 | } 57 | 58 | /** 59 | * Frees resources used by the text subsystem when done. 60 | */ 61 | void freeText() { 62 | //free(_strings); 63 | textInitted = false; 64 | } 65 | 66 | /** 67 | * Gets the text string with the given id code, and loads it into output. 68 | * @param textId The text id code. 69 | * @out output the destination string. 70 | */ 71 | void getText(const TextId textId, string output) { 72 | if (!textInitted) { 73 | initText(); 74 | } 75 | strcpy(output, _strings[textId]); 76 | } 77 | 78 | /** 79 | * Draws a sprite from a sheet at the given location & scale. 80 | * @param spriteCode id of the sprite on the sheet. 81 | * @param spriteSheet the sheet of sprites. 82 | * @param x The x screen position. 83 | * @param y The y screen position. 84 | * @param scale Scale the sprite. 85 | * @private 86 | */ 87 | static void drawSprite(const byte spriteCode, sprite_t* spriteSheet, const natural x, const natural y, const float scale) { 88 | rdp_load_texture_stride(0, 1, MIRROR_DISABLED, spriteSheet, spriteCode); 89 | rdp_draw_sprite_scaled(0, x, y, scale, scale, MIRROR_DISABLED); 90 | } 91 | 92 | /** 93 | * Draws a text character from the sprite sheet at a given location with the given transformation. 94 | * @param character the ASCII character to draw 95 | * @param x The x co-ordinate to draw at. 96 | * @param y The y co-ordinate to draw at. 97 | * @param scale size of the image 98 | * @param transformation Flip/Fade/Shift etc the character. 99 | * @private 100 | */ 101 | static void drawTransformedCharacter(const char character, const natural x, const natural y, const float scale, const Transformation transformation) { 102 | // Avoid printing any control characters, we don't at this point know what 103 | // wackiness will ensue. 104 | if (character <= 0x20) { 105 | return; 106 | } 107 | 108 | sprite_t* sheet = getCharacterSheet(); 109 | 110 | byte offset = character - 0x20; 111 | if (transformation) { 112 | sheet = transformSprite(sheet, offset, transformation); 113 | offset = 0; 114 | } 115 | 116 | drawSprite(offset, sheet, x, y, scale); 117 | } 118 | 119 | /** 120 | * Draws a text character from the sprite sheet at a given location. 121 | * @param character the ASCII character to draw 122 | * @param x The x co-ordinate to draw at. 123 | * @param y The y co-ordinate to draw at. 124 | * @param scale size of the image 125 | * @private 126 | */ 127 | static void drawCharacter(const char character, const natural x, const natural y, const float scale) { 128 | drawTransformedCharacter(character, x, y, scale, 0); 129 | } 130 | 131 | /** 132 | * Draws an image from a sprite sheet, as specified by a token in the string, rotating if requested. 133 | * @param text the string containing the image to draw. 134 | * @param textIndex the position in text that of the token. 135 | * @param length Length of the string to avoid recalculating each iteration. 136 | * @param x The x co-ordinate to draw at. 137 | * @param y The y co-ordinate to draw at. 138 | * @param scale size of the image 139 | * @return 140 | ** next index of the string after the token if positive. 141 | ** error code if negative 142 | *** -1 token is not complete. 143 | *** -2 badly formatted token. 144 | * @private 145 | */ 146 | static sShort drawImage(const string text, const byte textIndex, const byte length, const natural x, const natural y, const float scale) { 147 | byte i = textIndex; 148 | char transformation = text[i+1]; 149 | if (length <= i + 2) { 150 | return -1; 151 | } else if ( 152 | transformation == ROTATE_90 153 | || transformation == ROTATE_180 154 | || transformation == ROTATE_270 155 | || transformation == FLIP_HORIZONTAL 156 | || transformation == FLIP_VERTICAL 157 | ) { 158 | // Flip/Rotation specifier optionally follows the $ sign. 159 | if (length <= i + 3) { 160 | return -1; 161 | } 162 | i++; 163 | } else { 164 | transformation = 0; 165 | } 166 | 167 | // sprite is 2 digit hex, we need to parse it from the string 168 | byte spriteCode = parseByte(&text[i+1], 2, 16); 169 | 170 | sprite_t* sheet = getSpriteSheet(); 171 | if (transformation) { 172 | sheet = transformSprite(sheet, spriteCode, transformation); 173 | spriteCode = 0; 174 | } 175 | 176 | drawSprite(spriteCode, sheet, x, y, ceil(scale)); 177 | i += 2; 178 | return i; 179 | } 180 | 181 | /** 182 | * Draws characters in a line. 183 | * @param x The x co-ordinate to start the string at. 184 | * @param y The y co-ordinate to start the string at. 185 | * @param scale size of the text sprites. 186 | * @return result code 187 | ** 0 success 188 | ** -1 token is not complete. 189 | * @private 190 | */ 191 | static sByte drawTextLine(const string text, const natural x, const natural y, const float scale) { 192 | byte length = strlen(text); 193 | natural left = x; 194 | Transformation transform = 0; 195 | 196 | byte i = 0; 197 | 198 | // If the first character is a ~, fade the whole line. 199 | if (text[0] == '~') { 200 | transform = FADE; 201 | i++; 202 | } 203 | 204 | for (; i < length; i++) { 205 | // $ token means draw a sprite instead of a text character. 206 | if (text[i] == '$') { 207 | sByte result = drawImage(text, i, length, left, y, scale); 208 | if (result < 0) { 209 | return result; 210 | } else { 211 | i = result; 212 | } 213 | left += SPRITE_SIZE * ceil(scale); 214 | continue; 215 | } 216 | // Do literal draw of whatever follows slash. 217 | if (text[i] == '\\') { 218 | i++; 219 | } 220 | drawTransformedCharacter(text[i], left, y, scale, transform); 221 | left += CHARACTER_SIZE * scale; 222 | } 223 | return 0; 224 | } 225 | 226 | 227 | /** 228 | * Draws a horizontal string of text starting at the given location. 229 | * @param frame The id of the frame to draw on. 230 | * @param x The x co-ordinate to start the string at. 231 | * @param y The y co-ordinate to start the string at. 232 | * @param scale size of the text sprites. 233 | */ 234 | void drawText(const display_context_t frame, const string text, const natural x, const natural y, const float scale) { 235 | if (!textInitted) { 236 | initText(); 237 | } 238 | prepareRdpForSprite(frame); 239 | drawTextLine(text, x, y, scale); 240 | 241 | rdp_detach_display(); 242 | } 243 | 244 | /** 245 | * Draws a horizontal string of text starting at the given location. 246 | * @param frame The id of the frame to draw on. 247 | * @param x The x co-ordinate to start the string at. 248 | * @param y The y co-ordinate to start the string at. 249 | * @param scale size of the text sprites. 250 | * @param width width of the area available for text. 251 | */ 252 | void drawTextParagraph( 253 | const display_context_t frame, 254 | const string text, 255 | const natural x, 256 | const natural y, 257 | const float scale, 258 | const natural width 259 | ) { 260 | prepareRdpForSprite(frame); 261 | 262 | natural top = y; 263 | 264 | byte stringLength = strlen(text); 265 | byte maxLength = width / (CHARACTER_SIZE * scale); 266 | 267 | byte i = 0; 268 | while(i < stringLength) { 269 | byte lineAvailable = 0; 270 | lineAvailable = (stringLength - i < maxLength) ? stringLength - i : maxLength; 271 | 272 | // Split on spaces. 273 | byte lineBreak = lineAvailable; 274 | for(byte j = 0; j < lineAvailable; j++) { 275 | if (text[i + j] == '$') { 276 | // 3 characters, but only takes up 1 space. 277 | j += 2; 278 | lineAvailable += 2; 279 | } else if (text[i + j] == '\\') { 280 | // 2 characters, 1 space. 281 | j += 1; 282 | lineAvailable += 1; 283 | } 284 | if (0x20 == *(text + i + j)) { 285 | lineBreak = j; 286 | } 287 | } 288 | 289 | // if we're don't need any more lines, just run to the end. 290 | if (i + lineAvailable >= stringLength) { 291 | lineBreak = lineAvailable; 292 | } else { 293 | // make sure if we end on one of these, we keep the whole token. 294 | if (text[lineBreak] == '$') { 295 | lineBreak += 2; 296 | } else if (text[lineBreak-1] == '$') { 297 | lineBreak += 1; 298 | } else if (text[lineBreak] == '\\') { 299 | lineBreak += 1; 300 | } 301 | 302 | // absorb spaces into the newline 303 | while(lineBreak < stringLength && *(text + i + lineBreak) == 0x20) { 304 | lineBreak++; 305 | } 306 | } 307 | 308 | string line = ""; 309 | memcpy(line, text + i, lineBreak); 310 | 311 | drawTextLine(line, x, top, scale); 312 | 313 | i += lineBreak; 314 | top += CHARACTER_SIZE; 315 | } 316 | } 317 | 318 | /** 319 | * Calculates the length in pixels of a given string, including any sprites. 320 | * @param text the string. 321 | * @return the length in pixels. 322 | */ 323 | natural getStringWidth(const string text) { 324 | // TODO Sprites. 325 | return strlen(text) * CHARACTER_SIZE; 326 | } 327 | -------------------------------------------------------------------------------- /text.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXT_INCLUDED 2 | #define TEXT_INCLUDED 3 | 4 | #include "core.h" 5 | #include 6 | 7 | typedef enum { 8 | TextEmpty, 9 | TextNoTpak, 10 | TextNoCartridge, 11 | TextLoadCartridge, 12 | TextLoadingCartridge, 13 | TextExpansionPakRequired, 14 | TextRetryCartridgePrompt, 15 | TextChecksumFailed, 16 | TextUnsupportedCartridge, 17 | TextMenuResume, 18 | TextMenuReset, 19 | TextMenuChangeCart, 20 | TextMenuOptions, 21 | TextMenuAddPlayer, 22 | TextMenuAddGame, 23 | TextSplash, 24 | TextAudioOn, 25 | TextAudioOff, 26 | TextEnd 27 | } TextId; 28 | 29 | /** 30 | * Gets the text string with the given id code, and loads it into output. 31 | * @param textId The text id code. 32 | * @out output the destination string. 33 | */ 34 | void getText(TextId textId, string output); 35 | 36 | /** 37 | * Initialises text subsystem by loading sprites etc. 38 | */ 39 | sByte initText(); 40 | 41 | /** 42 | * Frees resources used by the text subsystem when done. 43 | */ 44 | void freeText(); 45 | 46 | /** 47 | * Draws a horizontal string of text starting at the given location. 48 | * @param frame The id of the frame to draw on. 49 | * @param x The x co-ordinate to start the string at. 50 | * @param y The y co-ordinate to start the string at. 51 | * @param scale size of the text sprites. 52 | */ 53 | void drawText(const display_context_t frame, const string text, const natural x, const natural y, const float scale); 54 | 55 | /** 56 | * Draws a horizontal string of text starting at the given location. 57 | * @param frame The id of the frame to draw on. 58 | * @param x The x co-ordinate to start the string at. 59 | * @param y The y co-ordinate to start the string at. 60 | * @param scale size of the text sprites. 61 | * @param width width of the area available for text. 62 | */ 63 | void drawTextParagraph( 64 | const display_context_t frame, 65 | const string text, 66 | const natural x, 67 | const natural y, 68 | const float scale, 69 | const natural width 70 | ); 71 | 72 | /** 73 | * Calculates the length in pixels of a given string, including any sprites. 74 | * @param text the string. 75 | * @return the length in pixels. 76 | */ 77 | natural getStringWidth(const string text); 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /tpakio.h: -------------------------------------------------------------------------------- 1 | #ifndef TPAKIO_INCLUDED 2 | #define TPAKIO_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | #include "core.h" 8 | 9 | 10 | /********************************************************************************************* 11 | * Thanks to Saturnu's (translated) writeup from here: 12 | * https://circuit-board.de/forum/index.php/Thread/13481-N64-Transfer-Pak-PIF-commands-Reverse-Engineering/ 13 | * *******************************************************************************************/ 14 | 15 | typedef enum { 16 | TPAK_SUCCESS = 0, 17 | // Invalid controller slot (must be 0-3) 18 | TPAK_ERR_NO_SLOT = -127, 19 | // Cartridge header contains invalid/unknown values. 20 | TPAK_ERR_INVALID_HEADER, 21 | // Header failed checksum 22 | TPAK_ERR_CORRUPT_HEADER, 23 | // Cartridge failed global checksum 24 | TPAK_ERR_CORRUPT_DATA, 25 | // If we loaded this cart we'd probably run out of memory. An expansion pak may help here. 26 | TPAK_ERR_INSUFFICIENT_MEMORY, 27 | // Controller not plugged in. 28 | TPAK_ERR_NO_CONTROLLER, 29 | // Controller detected but no transfer pak. 30 | TPAK_ERR_NO_TPAK, 31 | // Transfer pak detected, but no cartridge. 32 | TPAK_ERR_NO_CARTRIDGE, 33 | // Transfer pak isn't giving us the initialisation values we expect it to. 34 | TPAK_ERR_UNKNOWN_BEHAVIOUR, 35 | // We haven't been able to implement how to read the banks of this memory bank controller yet. 36 | TPAK_ERR_UNSUPPORTED_CARTRIDGE, 37 | // Tried to switch to a bank that is not available in this context. 38 | TPAK_ERR_INVALID_ROM_BANK, 39 | // libdragon read_mempak_address returned an error code 40 | // todo - break these down into useful errors we can respond to. 41 | TPAK_ERR_SYSTEM_ERROR, 42 | 43 | } TpakError; 44 | 45 | // Must be packed into a single byte to fit in the header 46 | typedef enum __attribute__ ((packed)) { 47 | // Change this if we do find a use for 0x04 48 | UNKNOWN_CARTRIDGE_TYPE = 0x04, 49 | 50 | // 32kB ROM 51 | ROM_ONLY = 0x00, 52 | 53 | // MBC1 - max 2MB ROM and/or 32kB RAM) 54 | MBC1 = 0x01, 55 | MBC1_RAM = 0x02, 56 | MBC1_BATTERY = 0x03, 57 | 58 | // MBC2 - max 256kB ROM and 256B RAM 59 | MBC2 = 0x05, 60 | MBC2_BATTERY = 0x06, 61 | MBC2_RAM = 0x08, 62 | MBC2_RAM_BATTERY = 0x09, 63 | 64 | // MMO1 - max 8MB ROM and 128kB RAM 65 | MMM01 = 0x0B, 66 | MMM01_RAM = 0x0C, 67 | MMM01_RAM_BATTERY = 0x0D, 68 | 69 | // MBC3 - max 2MB ROM and/or 32kB RAM and Timer 70 | MBC3_TIMER_BATTERY = 0x0f, 71 | MBC3_TIMER_RAM_BATTERY = 0x10, 72 | MBC3 = 0x11, 73 | MBC3_RAM = 0x012, 74 | MBC3_RAM_BATTERY = 0x013, 75 | 76 | // MBC5 - max 8MB ROM and/or 128kB RAM 77 | MBC5 = 0x19, 78 | MBC5_RAM = 0x1A, 79 | MBC5_RAM_BATTERY = 0x1B, 80 | MBC5_RUMBLE = 0x1C, 81 | MBC5_RUMBLE_RAM = 0x1D, 82 | MBC5_RUMBLE_RAM_BATTERY = 0x1E, 83 | 84 | // MBC6 - who knows? 85 | MBC6 = 0x20, 86 | MBC6_RAM_BATTERY = 0x20, 87 | 88 | // MBC7 - max 8MB ROM or 256kB RAM and Accelerometer 89 | MBC7_SENSOR_RUMBLE_RAM_BATTERY = 0x22, 90 | 91 | HUC3 = 0xFE, 92 | HUC1 = 0xFF, 93 | HUC1_RAM_BATTERY = 0xFF 94 | } CartridgeType; 95 | 96 | typedef struct gameboy_cartridge_header CartridgeHeader; 97 | 98 | typedef struct { 99 | ByteArray Rom; 100 | ByteArray Ram; 101 | long LastRTCTicks; 102 | long RTCStopTime; 103 | long RTCTimeStopped; 104 | CartridgeHeader Header; 105 | CartridgeType Type; 106 | natural RomBankCount; 107 | byte RamBankCount; 108 | natural RamBankSize; 109 | bool IsGbcSupported; 110 | bool IsRumbling; 111 | char* Title; 112 | } GameBoyCartridge; 113 | 114 | /** 115 | * Gets the percentage complete that a given TPak cartridge load is. 116 | * @param controllerNumber controller port we are loading from. 117 | */ 118 | byte getLoadProgress(const byte controllerNumber); 119 | 120 | /** 121 | * Starts or stops the rumble motor for cartridges that have one. 122 | * @param controllerNumber Slot that rumble-capable cartridge is using. 123 | * @param isRumbleStart true to start the motor, false to stop it. 124 | * @returns Error Code 125 | * @note For now this will switch the transfer pak bank to romx and the ram bank to bank 0. 126 | * I think this will be ok because I usually switch to whatever bank I need before any transfer. 127 | */ 128 | sByte toggleRumble(const byte controllerNumber, const bool isRumbleStart); 129 | 130 | /** 131 | * Imports the entire cartridge in to RAM as CartridgeData 132 | * @param controllerNumber get from T-Pak plugged in to this controller slot. 133 | * @out cartridge GB/GBC cartridge rom/ram 134 | * @returns Error Code 135 | */ 136 | sByte importCartridge(const byte controllerNumber, GameBoyCartridge* cartridge); 137 | 138 | /** 139 | * Imports a cartridge header from the TPAK as well as some derived metadata values. 140 | * @param controllerNumber number of controller slot cartridge is plugged in to. 141 | * @out cartridge metadata will be set on the object. 142 | * @returns Error Code 143 | */ 144 | sByte getCartridgeMetadata(const byte controllerNumber, GameBoyCartridge* cartridge); 145 | 146 | /** 147 | * Sets the cartridge RAM with the data in ramData. 148 | * @param controllerNumber T-Pak plugged in to this controller slot. 149 | * @param ramData RAM to copy in to the cartridge. 150 | * @returns Error code 151 | */ 152 | sByte exportCartridgeRam(const byte controllerNumber, GameBoyCartridge* cartridge); 153 | 154 | /** 155 | * Gets the complete ROM data from the cartridge in a transfer pak. 156 | * @param controllerNumber T-Pak plugged in to this controller slot. 157 | * @param cartridge Structure to copy RAM to. 158 | * @returns Error code 159 | */ 160 | sByte importCartridgeRam(const byte controllerNumber, GameBoyCartridge* cartridge); 161 | 162 | #endif -------------------------------------------------------------------------------- /transferboy.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "core.h" 3 | #include "play.h" 4 | #include "menu.h" 5 | #include "options.h" 6 | #include "text.h" 7 | #include "init.h" 8 | #include "controller.h" 9 | #include "state.h" 10 | #include "screen.h" 11 | #include "resources.h" 12 | #include "hwdefs.h" 13 | #include "rsp.h" 14 | #include "fps.h" 15 | #include "debug.h" 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "rsp.h" 22 | 23 | /** 24 | * Sets oft-use colours since we can't assign graphics_make_color to a constant. 25 | * @private 26 | */ 27 | static void setGlobalConstants() { 28 | GLOBAL_BACKGROUND_COLOUR = graphics_make_color(220, 220, 150, 255); 29 | GLOBAL_TEXT_COLOUR = graphics_make_color(255, 0, 0, 255); 30 | SELECTED_MENU_ITEM_COLOUR = graphics_make_color(0x28, 0x6E, 0x31, 255); 31 | SELECTED_OPTIONS_ITEM_COLOUR = graphics_make_color(0xCF, 0x48, 0x2C, 255); 32 | } 33 | 34 | /** 35 | * Inititalises all required libdragon subsystems. 36 | */ 37 | static void initialiseSubsystems() { 38 | getMemoryLimit(); 39 | init_interrupts(); 40 | timer_init(); 41 | 42 | #ifdef SHOW_FRAME_COUNT 43 | new_timer(TIMER_TICKS(1000000), TF_CONTINUOUS, fps_timer); 44 | #endif 45 | 46 | controller_init(); 47 | dfs_init(DFS_DEFAULT_LOCATION); 48 | 49 | if (USE_ANTIALIASING) { 50 | // Slightly better picture, but frame-rate drop on my N64 51 | display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, ANTIALIAS_RESAMPLE); 52 | } else { 53 | display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, ANTIALIAS_OFF); 54 | } 55 | rdp_init(); 56 | graphics_set_color(GLOBAL_TEXT_COLOUR, 0x0); 57 | audio_init(AUDIO_SAMPLE_RATE, 4); 58 | initResources(); 59 | initText(); 60 | } 61 | 62 | /** 63 | * Sets up initial state object that will be used throughout the program. 64 | * @out state the state object. 65 | */ 66 | static void generateState() { 67 | rootState.PlayerCount = 1; 68 | generatePlayerState(&rootState.Players[0]); 69 | } 70 | 71 | /** 72 | * The mainloop of the program. Has three phases and executes for all players at each phase 73 | * phase 1 read input 74 | * phase 2 carry out program logic 75 | * phase 3 paint the screen if required. 76 | * todo - audio. 77 | */ 78 | static void mainLoop() { 79 | rootState.RequiresRepaint = true; 80 | rootState.RequiresControllerRead = true; 81 | uLong iterations = 0; 82 | 83 | Mode modes[MAX_PLAYERS]; 84 | for(byte i = 0; i < MAX_PLAYERS; i++) { 85 | modes[i] = 0; 86 | } 87 | 88 | while(!(rootState.Frame = display_lock())); 89 | 90 | while (true) { 91 | // Read controller about once per frame. 92 | if (rootState.RequiresControllerRead || iterations > GB_LCD_FRAME_CLKS / 2) { 93 | controller_scan(); 94 | natural previous = rootState.ControllersPresent; 95 | rootState.ControllersPresent = get_controllers_present(); 96 | 97 | // If a new controller is plugged in, we may need to indicate that. 98 | if (previous != rootState.ControllersPresent) { 99 | rootState.RequiresRepaint = true; 100 | } 101 | 102 | rootState.KeysPressed = get_keys_pressed(); 103 | rootState.KeysReleased = get_keys_up(); 104 | rootState.RequiresControllerRead = false; 105 | iterations = 0; 106 | } else { 107 | iterations++; 108 | } 109 | 110 | UPDATE_PROFILE(PROFILE_C_SCAN); 111 | 112 | #if MAX_PLAYERS >= 2 113 | for (byte i = 0; i < rootState.PlayerCount; i++) { 114 | modes[i] = rootState.Players[i].ActiveMode; 115 | 116 | switch(modes[i]) { 117 | case Init: 118 | initLogic(i); 119 | break; 120 | case Play: 121 | playLogic(i); 122 | break; 123 | case Menu: 124 | menuLogic(i); 125 | break; 126 | case Options: 127 | optionsLogic(i); 128 | break; 129 | default: break; 130 | } 131 | } 132 | 133 | if (rootState.RequiresRepaint) { 134 | rootState.RequiresRepaint = false; 135 | 136 | for (byte i = 0; i < rootState.PlayerCount; i++) { 137 | switch(modes[i]) { 138 | case Init: 139 | initDraw(i); 140 | break; 141 | case Play: 142 | playDraw(i); 143 | break; 144 | case Menu: 145 | menuDraw(i); 146 | break; 147 | case Options: 148 | optionsDraw(i); 149 | break; 150 | default: break; 151 | } 152 | } 153 | 154 | display_show(rootState.Frame); 155 | while(!(rootState.Frame = display_lock())); 156 | setFrameBufferId(rootState.Frame); 157 | } 158 | #else 159 | modes[0] = rootState.Players[0].ActiveMode; 160 | 161 | switch(modes[0]) { 162 | case Init: 163 | initLogic(0); 164 | break; 165 | case Play: 166 | playLogic(0); 167 | break; 168 | case Menu: 169 | menuLogic(0); 170 | break; 171 | case Options: 172 | optionsLogic(0); 173 | break; 174 | default: break; 175 | } 176 | 177 | if (rootState.RequiresRepaint) { 178 | rootState.RequiresRepaint = false; 179 | 180 | switch(modes[0]) { 181 | case Init: 182 | initDraw(0); 183 | break; 184 | case Play: 185 | playDraw(0); 186 | break; 187 | case Menu: 188 | menuDraw(0); 189 | break; 190 | case Options: 191 | optionsDraw(0); 192 | break; 193 | default: break; 194 | } 195 | 196 | display_show(rootState.Frame); 197 | #ifdef IS_DOUBLE_BUFFERED 198 | while(!(rootState.Frame = display_lock())); 199 | setFrameBufferId(rootState.Frame); 200 | #endif 201 | } 202 | 203 | 204 | UPDATE_PROFILE(PROFILE_DRAW); 205 | 206 | #ifdef IS_PROFILING 207 | if (rootState.Players[0].Meta.FrameCount >= 50) { 208 | displayProfile(); 209 | } 210 | #endif 211 | 212 | #endif 213 | } 214 | } 215 | 216 | /** 217 | * Initialise all then enter main loop. 218 | */ 219 | int main(void) { 220 | initialiseSubsystems(); 221 | setGlobalConstants(); 222 | generateState(); 223 | flushScreen(); 224 | 225 | rsp_init(); 226 | 227 | mainLoop(); 228 | 229 | freeText(); 230 | freeResources(); 231 | display_close(); 232 | rdp_close(); 233 | audio_close(); 234 | return 0; 235 | } 236 | -------------------------------------------------------------------------------- /transferboy.ld: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Custom linker script for TransferBoy 3 | * Adapted from "n64ld.x" as described below 4 | * 5 | * GNU Linker script for building an image that is set up for the N64 6 | * but still has the data factored into sections. It is not directly 7 | * runnable, and it contains the debug info if available. It will need 8 | * a 'loader' to perform the final stage of transformation to produce 9 | * a raw image. 10 | * 11 | * Copyright (c) 1999 Ground Zero Development, All rights reserved. 12 | * Developed by Frank Somers 13 | * Modifications by hcs (halleyscometsoftware@hotmail.com) 14 | * 15 | * $Header: /cvsroot/n64dev/n64dev/lib/alt-libn64/n64ld.x,v 1.2 2006/08/11 15:54:11 halleyscometsw Exp $ 16 | * 17 | * ======================================================================== 18 | */ 19 | 20 | /* 21 | This file must be run through the cpp pre-compiler before use. 22 | Like so: 23 | cpp $(PRE_LD_FILE) | grep -v '^#' >>$(LD_FILE) 24 | */ 25 | 26 | #include "global.h" 27 | 28 | OUTPUT_FORMAT ("elf32-bigmips", "elf32-bigmips", "elf32-littlemips") 29 | OUTPUT_ARCH (mips) 30 | EXTERN (_start) 31 | ENTRY (_start) 32 | 33 | SECTIONS { 34 | /** 35 | * Start address of code is 1K up in uncached, unmapped RAM. We have 36 | * to be at least this far up in order to not interfere with the cart 37 | * boot code which is copying it down from the cart 38 | */ 39 | 40 | . = 0x80000400 ; 41 | 42 | /** 43 | * The text section carries the app code and its relocation addr is 44 | * the first byte of the cart domain in cached, unmapped memory 45 | */ 46 | .text : { 47 | FILL (0) 48 | 49 | *(.boot) 50 | . = ALIGN(16); 51 | __text_start = . ; 52 | *(.text) 53 | *(.text.*) 54 | *(.ctors) 55 | *(.dtors) 56 | *(.rodata) 57 | *(.rodata.*) 58 | *(.init) 59 | *(.fini) 60 | __text_end = . ; 61 | } 62 | 63 | /** 64 | * Data section has relocation address at start of RAM in cached, 65 | * unmapped memory, but is loaded just at the end of the text segment, 66 | * and must be copied to the correct location at startup 67 | */ 68 | .data : { 69 | /** 70 | * Gather all initialised data together. The memory layout 71 | * will place the global initialised data at the lowest addrs. 72 | * The lit8, lit4, sdata and sbss sections have to be placed 73 | * together in that order from low to high addrs with the _gp symbol 74 | * positioned (aligned) at the start of the sdata section. 75 | * We then finish off with the standard bss section 76 | */ 77 | 78 | FILL (0xaa) 79 | 80 | . = ALIGN(16); 81 | __data_start = . ; 82 | *(.data) 83 | *(.irsp) 84 | *(.lit8) 85 | *(.lit4) ; 86 | . = ALIGN(16); 87 | _gp = . ; 88 | *(.sdata) 89 | . = ALIGN(4); 90 | __data_end = . ; 91 | } 92 | 93 | . = ALIGN(8); 94 | 95 | .ctors : { 96 | __CTOR_LIST_SIZE__ = .; 97 | LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 1) 98 | __CTOR_LIST__ = .; 99 | *(.ctors) 100 | LONG(0) 101 | __CTOR_END__ = .; 102 | } 103 | 104 | .bss (NOLOAD) : { 105 | __bss_start = . ; 106 | *(.scommon) 107 | *(.sbss) 108 | *(COMMON) 109 | *(.bss) 110 | . = ALIGN(4); 111 | __bss_end = . ; 112 | end = . ; 113 | } 114 | 115 | /* Places the structure that is shared between the CPU and RSP at a known address so it can be DMAd */ 116 | .rspInterface RSP_INTERFACE_ADDRESS : { 117 | *(.rspInterface) 118 | } 119 | } -------------------------------------------------------------------------------- /transferboy.z64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeldipops/TransferBoy/7c74c645c98fa8df8bd9e43f56e3c6b893cd8e59/transferboy.z64 --------------------------------------------------------------------------------