├── .gitattributes ├── src ├── cas │ ├── bootstrap.h │ ├── cpu │ │ ├── stack.h │ │ ├── stack.s │ │ ├── oc_mem.h │ │ ├── mmu.h │ │ ├── power.h │ │ ├── tmu.h │ │ ├── cmt.h │ │ ├── cpg.h │ │ └── dmac.h │ ├── display.h │ └── bootstrap.cpp ├── emu_ui │ ├── effects.h │ ├── menu │ │ ├── tabs │ │ │ ├── load.h │ │ │ ├── saves.h │ │ │ ├── current.h │ │ │ ├── settings.h │ │ │ ├── saves.cpp │ │ │ ├── load.cpp │ │ │ ├── current.cpp │ │ │ └── settings.cpp │ │ ├── menu.h │ │ └── menu.cpp │ ├── colors.h │ ├── input.h │ ├── components.h │ ├── effects.cpp │ ├── font.h │ ├── font.cpp │ ├── input.cpp │ └── components.cpp ├── start.s ├── core │ ├── cart_ram.h │ ├── frametimes.h │ ├── palettes.h │ ├── emulator.h │ ├── preferences.h │ ├── frametimes.cpp │ ├── error.cpp │ ├── error.h │ ├── controls.h │ ├── cart_ram.cpp │ ├── controls.cpp │ ├── preferences.cpp │ ├── emulator.cpp │ └── palettes.cpp ├── helpers │ ├── functions.h │ ├── macros.h │ ├── functions.cpp │ ├── ini.h │ ├── fileio.h │ ├── fileio.cpp │ └── ini.cpp └── main.cpp ├── .gitignore ├── LICENSE.md ├── linker.ld ├── README.md └── Makefile /.gitattributes: -------------------------------------------------------------------------------- 1 | src/core/peanut_gb.h eol=lf -------------------------------------------------------------------------------- /src/cas/bootstrap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint8_t setup_cas(); 6 | void restore_cas(); 7 | -------------------------------------------------------------------------------- /src/cas/cpu/stack.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" 4 | void *set_stack_ptr(void *ptr); 5 | 6 | extern "C" 7 | void *get_stack_ptr(); 8 | -------------------------------------------------------------------------------- /src/emu_ui/effects.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void darken_screen_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height); 6 | -------------------------------------------------------------------------------- /src/start.s: -------------------------------------------------------------------------------- 1 | .section .init 2 | mov.l main_addr, r0 3 | jmp @r0 4 | nop 5 | .align 2 6 | main_addr: 7 | .long _main 8 | load_addr: 9 | .long 0x8cfe6000 10 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/load.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../menu.h" 4 | #include "../../../core/preferences.h" 5 | 6 | menu_tab *prepare_tab_load(menu_tab *tab); 7 | -------------------------------------------------------------------------------- /src/core/cart_ram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | uint8_t load_cart_ram(struct gb_s *gb); 7 | 8 | uint8_t save_cart_ram(struct gb_s *gb); 9 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/saves.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../menu.h" 4 | #include "../../../core/preferences.h" 5 | 6 | menu_tab *prepare_tab_saves(menu_tab *tab, emu_preferences *preferences); 7 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/current.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../menu.h" 4 | #include "../../../core/preferences.h" 5 | 6 | menu_tab *prepare_tab_current(menu_tab *tab, emu_preferences *preferences); 7 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../menu.h" 4 | #include "../../../core/preferences.h" 5 | 6 | menu_tab *prepare_tab_settings(menu_tab *tab, emu_preferences *preferences); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build items 2 | *.o 3 | *.a 4 | *.exe 5 | peanut-sdl 6 | peanut-debug 7 | tags 8 | .vscode 9 | obj 10 | dist 11 | *.hhk 12 | *.bin 13 | 14 | # OS items 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /src/cas/cpu/stack.s: -------------------------------------------------------------------------------- 1 | .global _set_stack_ptr 2 | .global _get_stack_ptr 3 | 4 | .align 2 5 | _set_stack_ptr: 6 | mov r4, r15 7 | rts 8 | mov r4, r0 9 | 10 | _get_stack_ptr: 11 | mov r15, r0 12 | rts 13 | nop 14 | -------------------------------------------------------------------------------- /src/core/frametimes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "peanut_gb_header.h" 5 | 6 | void frametime_counter_set(struct gb_s *gb); 7 | 8 | void frametime_counter_start(); 9 | 10 | void frametime_counter_wait(struct gb_s *gb); 11 | -------------------------------------------------------------------------------- /src/cas/cpu/oc_mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // On-Chip Memory addresses 4 | #define X_MEMORY_0 ((void *)0xE5007000) 5 | #define X_MEMORY_1 ((void *)0xE5008000) 6 | #define Y_MEMORY_0 ((void *)0xE5017000) 7 | #define Y_MEMORY_1 ((void *)0xE5018000) 8 | #define IL_MEMORY ((void *)0xE5200000) 9 | -------------------------------------------------------------------------------- /src/cas/display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define SCREEN_DATA_REGISTER ((volatile uint32_t *)0xB4000000) 6 | 7 | inline void prepare_gb_lcd() 8 | { 9 | ((void(*)(int, int, int, int))0x80038068)(0, CAS_LCD_WIDTH - 1, 0, (LCD_HEIGHT * 2) - 1); 10 | ((void(*)(int))0x80038040)(0x2c); 11 | } 12 | -------------------------------------------------------------------------------- /src/emu_ui/colors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define COLOR_WHITE 0xFFFF 4 | #define COLOR_BLACK 0x0000 5 | #define COLOR_SUCCESS 0x07E0 6 | #define COLOR_DANGER 0xF800 7 | #define COLOR_DISABLED 0xB5B6 8 | #define COLOR_PRIMARY 0x04A0 9 | #define COLOR_SECONDARY 0x39E7 10 | #define COLOR_MENU_BG 0x2104 11 | #define COLOR_SELECTED 0x8410 12 | -------------------------------------------------------------------------------- /src/emu_ui/input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "menu/menu.h" 5 | 6 | #define INPUT_PROC_NONE 0 7 | #define INPUT_PROC_EXECUTE 1 8 | #define INPUT_PROC_CLOSE 2 9 | 10 | uint8_t process_input(uint8_t **selected_h_item, uint8_t *selected_v_item, 11 | const uint8_t *h_item_count, uint8_t v_item_count, const menu_item *items, bool reset_v_item); 12 | 13 | void wait_input_release(); 14 | -------------------------------------------------------------------------------- /src/cas/cpu/mmu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MMU_AREA_P0 ((void *)0x00000000) 6 | #define MMU_AREA_P1 ((void *)0x80000000) 7 | #define MMU_AREA_P2 ((void *)0xA0000000) 8 | #define MMU_AREA_P3 ((void *)0xC0000000) 9 | #define MMU_AREA_P4 ((void *)0xE0000000) 10 | 11 | inline void *virt_to_phys_addr(void *addr) 12 | { 13 | uint32_t mask = (addr < MMU_AREA_P4)? 0x1FFFFFFF : 0xFFFFFFFF; 14 | 15 | return (void *)((uint32_t)addr & mask); 16 | } 17 | -------------------------------------------------------------------------------- /src/helpers/functions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | T clamp(T val, T min, T max) 7 | { 8 | T tmp = (val < min)? min : val; 9 | return (tmp > max)? max : tmp; 10 | } 11 | 12 | template 13 | T hash_string(const char *str, T range) 14 | { 15 | T h = 0; 16 | 17 | const char *p; 18 | 19 | for (p = str; *p != '\0'; p++) 20 | { 21 | h = ((h << 7) ^ (h >> 25)) ^ *p; 22 | } 23 | 24 | return h % range; 25 | } 26 | 27 | char *itoa_leading_zeros(uint32_t val, char *str, uint8_t base, uint8_t digits); 28 | wchar_t *char_to_wchar(wchar_t *wstr, const char *str); 29 | char *wchar_to_char(char *str, const wchar_t *wstr); 30 | uint32_t align_val(uint32_t val, uint32_t at); 31 | -------------------------------------------------------------------------------- /src/core/palettes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MAX_PALETTE_NAME_LEN 30 6 | #define MAX_PALETTE_COUNT 10 7 | 8 | #define ERR_MAX_PALETTE_REACHED 2 9 | 10 | #define DEFAULT_PALETTE \ 11 | { \ 12 | { 0x7FFF, 0x5294, 0x294A, 0x0000 }, \ 13 | { 0x7FFF, 0x5294, 0x294A, 0x0000 }, \ 14 | { 0x7FFF, 0x5294, 0x294A, 0x0000 } \ 15 | } 16 | 17 | typedef struct 18 | { 19 | char name[MAX_PALETTE_NAME_LEN] __attribute__((aligned)); 20 | 21 | // The actual content of the palette 22 | uint16_t data[3][4] __attribute__((aligned)); 23 | } palette; 24 | 25 | uint8_t create_palette(struct gb_s *gb); 26 | 27 | uint8_t delete_palette(struct gb_s *gb, uint8_t index); 28 | 29 | uint8_t load_palettes(struct gb_s *gb); 30 | 31 | uint8_t save_palette(palette *pal, uint8_t index); 32 | 33 | uint8_t get_user_palettes(palette **pal, struct gb_s *gb); -------------------------------------------------------------------------------- /src/helpers/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../core/preferences.h" 4 | 5 | #define XSTR(x) STR(x) 6 | #define STR(x) #x 7 | 8 | #define CPBOY_VERSION "v0.3.0" 9 | 10 | #define CAS_LCD_WIDTH 320 11 | #define CAS_LCD_HEIGHT 528 12 | 13 | #define MAX_FILENAME_LEN 200 14 | 15 | #define MCS_DIRECTORY "CPBoy" 16 | 17 | #define DIRECTORY_MAIN "\\fls0\\CPBoy\\" 18 | #define DIRECTORY_ROM DIRECTORY_MAIN "roms" 19 | #define DIRECTORY_BIN DIRECTORY_MAIN "bin" 20 | 21 | #define EXTENSION_ROM ".gb" 22 | 23 | #define TOGGLE(value) ((value) = !(value)) 24 | 25 | #define RGB555_TO_RGB565(rgb555) ( \ 26 | 0 | \ 27 | ((rgb555 & 0b0111110000000000) <<1) | \ 28 | ((rgb555 & 0b0000001111100000) <<1) | \ 29 | (rgb555 & 0b0000000000011111) \ 30 | ) 31 | 32 | #define likely(x) __builtin_expect(!!(x), 1) 33 | #define unlikely(x) __builtin_expect(!!(x), 0) 34 | -------------------------------------------------------------------------------- /src/core/emulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "controls.h" 5 | #include "preferences.h" 6 | #include "palettes.h" 7 | 8 | #define FRAMESKIP_MIN 1 9 | #define FRAMESKIP_MAX 100 10 | 11 | #define EMU_SPEED_MIN 50 12 | #define EMU_SPEED_MAX 500 13 | #define EMU_SPEED_STEP 50 14 | 15 | void set_frameskip(struct gb_s *gb, bool enabled, uint8_t amount); 16 | 17 | void set_interlacing(struct gb_s *gb, bool enabled); 18 | 19 | void set_emu_speed(struct gb_s *gb, uint16_t percentage); 20 | 21 | void set_overclock(struct gb_s *gb, bool enabled); 22 | 23 | uint8_t execute_rom(struct gb_s *gb); 24 | 25 | uint8_t prepare_emulator(struct gb_s *gb, emu_preferences *preferences); 26 | 27 | uint8_t close_rom(struct gb_s *gb); 28 | 29 | void free_emulator(struct gb_s *gb); 30 | 31 | uint8_t run_emulator(struct gb_s *gb, emu_preferences *prefs); 32 | 33 | uint8_t load_rom(emu_preferences *prefs); 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Sidney Krombholz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/emu_ui/components.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "colors.h" 5 | 6 | #define STD_CONTENT_OFFSET 6 7 | #define ALERT_CONTENT_OFFSET_X STD_CONTENT_OFFSET 8 | #define ALERT_CONTENT_OFFSET_Y (STD_CONTENT_OFFSET * 2) + (DEBUG_LINE_HEIGHT * 2) 9 | 10 | #define SLIDER_STD_TRACK_COLOR COLOR_DISABLED 11 | #define SLIDER_HANDLE_HEIGHT 12 12 | 13 | #define ALERT_GET_X(val) (val & 0xFFFF) 14 | #define ALERT_GET_Y(val) (val >> 16) 15 | 16 | void draw_rectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height, 17 | uint16_t color, uint16_t border_width, uint16_t border_color); 18 | 19 | void draw_slider(uint16_t x, uint16_t y, uint16_t width, uint16_t track_color, 20 | uint16_t handle_color, uint16_t min_value, uint16_t max_value, uint16_t value); 21 | 22 | uint32_t draw_alert_box(const char *title, const char *subtitle, uint16_t width, 23 | uint16_t height, uint16_t background, uint16_t border); 24 | 25 | void ok_alert(const char *title, const char *subtitle, const char *text, uint16_t foreground, 26 | uint16_t background, uint16_t border); -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_main); 2 | 3 | SECTIONS { 4 | start_address = 0x8CFE6000; 5 | .init start_address : AT(start_address) { 6 | *(.init) 7 | } 8 | info_address = 0x8CFE6010; 9 | . = info_address; 10 | .hollyhock_name : { 11 | *(.hollyhock_name) 12 | } 13 | .hollyhock_description : { 14 | *(.hollyhock_description) 15 | } 16 | .hollyhock_author : { 17 | *(.hollyhock_author) 18 | } 19 | .hollyhock_version : { 20 | *(.hollyhock_version) 21 | } 22 | 23 | .text : { 24 | *(.text) 25 | *(.rodata*) 26 | } 27 | 28 | .data : { 29 | *(.data) 30 | } 31 | 32 | .bss : { 33 | *(.bss) 34 | *(COMMON) 35 | } 36 | 37 | x_mem_addr = 0xE5007000; 38 | . = x_mem_addr; 39 | 40 | .oc_mem.x : { 41 | *(.oc_mem.x) 42 | } 43 | 44 | y_mem_addr = 0xE5017000; 45 | . = y_mem_addr; 46 | 47 | .oc_mem.y : { 48 | *(.oc_mem.y.text) 49 | *(.oc_mem.y.data) 50 | } 51 | 52 | il_mem_addr = 0xE5200000; 53 | . = il_mem_addr; 54 | 55 | .oc_mem.il : { 56 | *(.oc_mem.il.data) 57 | *(.oc_mem.il.text) 58 | } 59 | } -------------------------------------------------------------------------------- /src/emu_ui/effects.cpp: -------------------------------------------------------------------------------- 1 | #include "effects.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define CAS_LCD_WIDTH 320 8 | #define CAS_LCD_HEIGHT 528 9 | 10 | #define EFFECT_DARKEN 13108 11 | 12 | void darken_screen_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height) 13 | { 14 | uint16_t max_x = x + width; 15 | uint16_t max_y = y + height; 16 | 17 | // go through every pixel of the area and darken it 18 | for(uint16_t iy = y; iy < max_y; iy++) 19 | { 20 | for(uint16_t ix = x; ix < max_x; ix++) 21 | { 22 | uint16_t pixel = vram[(iy * CAS_LCD_WIDTH) + ix]; 23 | 24 | // calculate new rgb values through fixed point arithmetic 25 | uint8_t red = ((RGB565_TO_R(pixel) * EFFECT_DARKEN) >>16); 26 | uint8_t green = ((RGB565_TO_G(pixel) * EFFECT_DARKEN) >>16); 27 | uint8_t blue = ((RGB565_TO_B(pixel) * EFFECT_DARKEN) >>16); 28 | 29 | pixel = RGB_TO_RGB565(red, green, blue); 30 | 31 | vram[(iy * CAS_LCD_WIDTH) + ix] = pixel; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/cas/cpu/power.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | union power_mstpcr0 6 | { 7 | struct 8 | { 9 | uint32_t TLB : 1; 10 | uint32_t IC : 1; 11 | uint32_t OC : 1; 12 | uint32_t _reserved0 : 1; 13 | uint32_t IL : 1; 14 | uint32_t _reserved1 : 2; 15 | uint32_t FPU : 1; 16 | uint32_t _reserved2 : 1; 17 | uint32_t INTC : 1; 18 | uint32_t DMAC : 1; 19 | uint32_t _reserved3 : 1; 20 | uint32_t HUDI : 1; 21 | uint32_t DBG : 1; 22 | uint32_t UBC : 1; 23 | uint32_t SUBC : 1; 24 | uint32_t TMU : 1; 25 | uint32_t CMT : 1; 26 | uint32_t RWDT : 1; 27 | uint32_t _reserved4 : 3; 28 | uint32_t SCIF4 : 1; 29 | uint32_t SCIF5 : 1; 30 | uint32_t SCIF0 : 1; 31 | uint32_t SCIF1 : 1; 32 | uint32_t SCIF2 : 1; 33 | uint32_t SCIF3 : 1; 34 | uint32_t _reserved5 : 1; 35 | uint32_t SIOF : 1; 36 | uint32_t _reserved6 : 2; 37 | }; 38 | 39 | uint32_t raw; 40 | }; 41 | 42 | #define POWER_MSTPCR0 ((volatile power_mstpcr0 *)0xA4150030) 43 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/saves.cpp: -------------------------------------------------------------------------------- 1 | #include "saves.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../menu.h" 7 | #include "../../components.h" 8 | #include "../../colors.h" 9 | #include "../../font.h" 10 | #include "../../effects.h" 11 | #include "../../input.h" 12 | #include "../../../core/error.h" 13 | #include "../../../helpers/macros.h" 14 | 15 | namespace hhk 16 | { 17 | #include 18 | } 19 | 20 | #define TAB_SAVES_TITLE "Saves" 21 | 22 | int32_t dummy_function(menu_item *item, gb_s *gb) { return 0; } 23 | 24 | menu_tab *prepare_tab_saves(menu_tab *tab, emu_preferences *preferences) 25 | { 26 | // Description for "Saves" tab 27 | strcpy(tab->title, TAB_SAVES_TITLE); 28 | strcpy(tab->description, "Description"); 29 | 30 | tab->item_count = 1; 31 | tab->items = (menu_item *)hhk::malloc(1 * sizeof(menu_item)); 32 | 33 | if (!tab->items) 34 | { 35 | set_error(EMALLOC); 36 | return nullptr; 37 | } 38 | 39 | tab->items[0].disabled = false; 40 | strcpy(tab->items[0].title, "DUMMY"); 41 | tab->items[0].value[0] = '\0'; 42 | tab->items[0].action = dummy_function; 43 | 44 | return tab; 45 | } 46 | -------------------------------------------------------------------------------- /src/helpers/functions.cpp: -------------------------------------------------------------------------------- 1 | #include "functions.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | wchar_t *char_to_wchar(wchar_t *wstr, const char *str) 8 | { 9 | wchar_t *dest = wstr; 10 | 11 | for (const char *c = str; *c; c++, dest++) 12 | { 13 | *dest = *c; 14 | } 15 | 16 | *dest = '\0'; 17 | 18 | return wstr; 19 | } 20 | 21 | char *wchar_to_char(char *str, const wchar_t *wstr) 22 | { 23 | char *dest = str; 24 | 25 | for (const wchar_t *c = wstr; *c; c++, dest++) 26 | { 27 | *dest = *c; 28 | } 29 | 30 | *dest = '\0'; 31 | 32 | return str; 33 | } 34 | 35 | uint32_t align_val(uint32_t val, uint32_t at) 36 | { 37 | if ((val % at) == 0) 38 | { 39 | return val; 40 | } 41 | 42 | return val + (at - (val % at)); 43 | } 44 | 45 | char *itoa_leading_zeros(uint32_t val, char *str, uint8_t base, uint8_t digits) 46 | { 47 | uint32_t tmp = val / base; 48 | uint8_t digit_count = 1; 49 | 50 | char *p = str; 51 | 52 | while (tmp != 0) 53 | { 54 | tmp /= base; 55 | digit_count++; 56 | } 57 | 58 | for (uint8_t i = 0; i < (digits - digit_count); i++, p++) 59 | { 60 | *p = '0'; 61 | } 62 | 63 | itoa(val, p, base); 64 | 65 | return str; 66 | } 67 | -------------------------------------------------------------------------------- /src/core/preferences.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "controls.h" 5 | #include "palettes.h" 6 | #include "../helpers/macros.h" 7 | 8 | #define DEFAULT_INTERLACE_ENABLE false 9 | #define DEFAULT_FRAMESKIP_ENABLE false 10 | #define DEFAULT_FRAMESKIP_AMOUNT 1 11 | #define DEFAULT_EMU_SPEED 100 12 | #define DEFAULT_OVERCLOCK_ENABLE false 13 | #define DEFAULT_SELECTED_PALETTE 0 14 | 15 | typedef struct 16 | { 17 | bool interlacing_enabled; 18 | 19 | bool frameskip_enabled; 20 | uint8_t frameskip_amount; 21 | 22 | uint16_t emulation_speed; 23 | 24 | bool overclock_enabled; 25 | 26 | uint8_t selected_palette; 27 | } rom_config; 28 | 29 | typedef struct 30 | { 31 | /* Pointer to allocated memory holding GB file. */ 32 | uint8_t *rom; 33 | /* Pointer to allocated memory holding save file. */ 34 | uint8_t *cart_ram; 35 | 36 | char current_filename[200]; 37 | char current_rom_name[16]; 38 | 39 | bool emulator_paused; 40 | 41 | palette *palettes; 42 | uint8_t palette_count; 43 | 44 | emu_controls controls; 45 | rom_config config; 46 | 47 | struct 48 | { 49 | bool controls_changed; 50 | bool rom_config_changed; 51 | } file_states; 52 | } emu_preferences; 53 | 54 | uint8_t load_rom_config(struct gb_s *gb); 55 | 56 | uint8_t save_rom_config(struct gb_s *gb); 57 | -------------------------------------------------------------------------------- /src/core/frametimes.cpp: -------------------------------------------------------------------------------- 1 | #include "frametimes.h" 2 | 3 | #include "emulator.h" 4 | #include "peanut_gb_header.h" 5 | #include "preferences.h" 6 | #include "../cas/cpu/cmt.h" 7 | #include "../cas/cpu/cpg.h" 8 | 9 | #define FRAME_TARGET 60 10 | 11 | void frametime_counter_set(struct gb_s *gb) 12 | { 13 | emu_preferences *pref = (emu_preferences *)gb->direct.priv; 14 | uint8_t frameskip = (pref->config.frameskip_enabled)? 15 | pref->config.frameskip_amount + 1 : 1; 16 | 17 | uint32_t ticks = (CMT_TICKS_PER_SEC / FRAME_TARGET) * frameskip; 18 | 19 | const uint16_t speed_perc = (pref->config.emulation_speed == 0)? 100 : pref->config.emulation_speed; 20 | const uint32_t default_pll = CPG_PLL_MUL_DEFAULT + 1; 21 | const uint32_t current_pll = CPG_FRQCRA->STC + 1; 22 | 23 | // Modify ticks per frame based on currently selected PLL multiplier 24 | ticks = (ticks * current_pll) / default_pll; 25 | 26 | cmt_set((ticks * 100) / speed_perc, MODE_ONE_SHOT, REQUEST_DISABLE); 27 | } 28 | 29 | void frametime_counter_start() 30 | { 31 | cmt_start(); 32 | } 33 | 34 | void frametime_counter_wait(struct gb_s *gb) 35 | { 36 | emu_preferences *pref = (emu_preferences *)gb->direct.priv; 37 | 38 | if (!gb->direct.frame_drawn) 39 | { 40 | return; 41 | } 42 | 43 | if (pref->config.emulation_speed == (EMU_SPEED_MAX + EMU_SPEED_STEP)) 44 | { 45 | return; 46 | } 47 | 48 | cmt_wait(); 49 | } -------------------------------------------------------------------------------- /src/helpers/ini.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "macros.h" 5 | 6 | #define INI_MAX_SECTION_LEN 20 7 | #define INI_MAX_KEY_LEN 20 8 | #define INI_MAX_VALUE_LEN MAX_FILENAME_LEN 9 | #define INI_MAX_CONTENT_LEN 1024 10 | 11 | #define INI_TYPE_INT 0 12 | #define INI_TYPE_STRING 1 13 | 14 | 15 | struct ini_key 16 | { 17 | char name[INI_MAX_KEY_LEN]; 18 | char value_str[INI_MAX_VALUE_LEN]; 19 | uint32_t value_int; 20 | uint8_t value_type; 21 | }; 22 | 23 | struct ini_section 24 | { 25 | char name[INI_MAX_SECTION_LEN]; 26 | ini_key *keys; 27 | uint32_t key_count; 28 | uint32_t keys_size; 29 | }; 30 | 31 | struct ini_file 32 | { 33 | ini_section *sections; 34 | uint32_t section_count; 35 | uint32_t sections_size; 36 | }; 37 | 38 | 39 | // TODO: MUST BE FREED 40 | // ini_file *ini_parse(const char *ini_string, uint32_t len, ini_file *file); 41 | ini_file *ini_parse(const char *ini_string, uint32_t len, ini_file *file); 42 | 43 | char *ini_write(ini_file *file, char *ini_string, uint32_t len); 44 | 45 | ini_section *find_section(const ini_file *file, const char *name); 46 | 47 | ini_key *find_key(const ini_section *section, const char *name); 48 | 49 | void free_ini_file(ini_file *file); 50 | 51 | ini_key *add_key(ini_section *section, const char *name, uint8_t value_type = INI_TYPE_INT, 52 | uint32_t value_int = 0, const char *value_str = nullptr); 53 | 54 | ini_section *add_section(ini_file *file, const char *name); 55 | -------------------------------------------------------------------------------- /src/cas/cpu/tmu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define TMU_TICKS_PER_SEC 7000000 6 | 7 | enum tmu_tcr_tpsc 8 | { 9 | PHI_DIV_4 = 0x0, 10 | PHI_DIV_16 = 0x1, 11 | PHI_DIV_64 = 0x2, 12 | PHI_DIV_256 = 0x3, 13 | PHI_DIV_1024 = 0x4 14 | }; 15 | 16 | union tmu_tstr 17 | { 18 | struct 19 | { 20 | uint8_t _reserved0 : 5; 21 | uint8_t STR2 : 1; 22 | uint8_t STR1 : 1; 23 | uint8_t STR0 : 1; 24 | }; 25 | 26 | uint8_t raw; 27 | }; 28 | 29 | union tmu_tcr 30 | { 31 | struct 32 | { 33 | uint16_t _reserved0 : 7; 34 | uint16_t UNF : 1; // Underflow Flag 35 | uint16_t _reserved1 : 2; 36 | uint16_t UNIE : 1; // Underflow Interrupt Enable 37 | uint16_t _reserved2 : 2; 38 | tmu_tcr_tpsc TPSC : 3; // Timer Prescaler 39 | }; 40 | 41 | uint16_t raw; 42 | }; 43 | 44 | // General register 45 | #define TMU_TSTR ((volatile tmu_tstr *) 0xA4490004) 46 | 47 | // Channel 0 48 | #define TMU_TCOR_0 ((volatile uint32_t *) 0xA4490008) 49 | #define TMU_TCNT_0 ((volatile uint32_t *) 0xA449000C) 50 | #define TMU_TCR_0 ((volatile tmu_tcr *) 0xA4490010) 51 | 52 | // Channel 1 53 | #define TMU_TCOR_1 ((volatile uint32_t *) 0xA4490014) 54 | #define TMU_TCNT_1 ((volatile uint32_t *) 0xA4490018) 55 | #define TMU_TCR_1 ((volatile tmu_tcr *) 0xA449001C) 56 | 57 | // Channel 2 58 | #define TMU_TCOR_2 ((volatile uint32_t *) 0xA4490020) 59 | #define TMU_TCNT_2 ((volatile uint32_t *) 0xA4490024) 60 | #define TMU_TCR_2 ((volatile tmu_tcr *) 0xA4490028) -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/load.cpp: -------------------------------------------------------------------------------- 1 | #include "load.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../menu.h" 7 | #include "../../components.h" 8 | #include "../../colors.h" 9 | #include "../../font.h" 10 | #include "../../effects.h" 11 | #include "../../input.h" 12 | #include "../../../core/error.h" 13 | #include "../../../helpers/macros.h" 14 | #include "../../../helpers/fileio.h" 15 | #include "../../../helpers/functions.h" 16 | 17 | namespace hhk 18 | { 19 | #include 20 | } 21 | 22 | #define TAB_LOAD_TITLE "Load" 23 | #define TAB_LOAD_ITEM_COUNT 20 24 | 25 | int32_t action_set_romfile(menu_item *item, gb_s *gb) 26 | { 27 | emu_preferences *prefs = (emu_preferences *)gb->direct.priv; 28 | 29 | strlcpy(prefs->current_filename, item->title, sizeof(prefs->current_filename)); 30 | 31 | return MENU_LOAD_NEW; 32 | } 33 | 34 | menu_tab *prepare_tab_load(menu_tab *tab) 35 | { 36 | strlcpy(tab->title, TAB_LOAD_TITLE, sizeof(tab->title)); 37 | 38 | char files[TAB_LOAD_ITEM_COUNT][MAX_FILENAME_LEN]; 39 | 40 | tab->item_count = find_files(DIRECTORY_ROM "\\*" EXTENSION_ROM, files, TAB_LOAD_ITEM_COUNT); 41 | tab->items = (menu_item *)hhk::malloc(tab->item_count * sizeof(menu_item)); 42 | 43 | if (!(tab->items)) 44 | { 45 | set_error(EMALLOC); 46 | return nullptr; 47 | } 48 | 49 | for (uint8_t i = 0; i < tab->item_count; i++) 50 | { 51 | tab->items[i].disabled = false; 52 | strlcpy(tab->items[i].title, files[i], sizeof(tab->items[i].title)); 53 | tab->items[i].value[0] = '\0'; 54 | tab->items[i].action = action_set_romfile; 55 | } 56 | 57 | char tmp[5]; 58 | 59 | strlcpy(tab->description, "Detected ", sizeof(tab->description)); 60 | strlcat(tab->description, itoa(tab->item_count, tmp, 10), sizeof(tab->description)); 61 | strlcat(tab->description, " ROMs in " DIRECTORY_ROM "\\", sizeof(tab->description)); 62 | 63 | return tab; 64 | } 65 | -------------------------------------------------------------------------------- /src/cas/bootstrap.cpp: -------------------------------------------------------------------------------- 1 | #include "bootstrap.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "cpu/cpg.h" 7 | #include "cpu/cmt.h" 8 | #include "cpu/dmac.h" 9 | #include "cpu/oc_mem.h" 10 | #include "cpu/power.h" 11 | #include "../core/error.h" 12 | #include "../helpers/fileio.h" 13 | 14 | // All external binaries have to be defined here 15 | const char *bin_files[] = { 16 | "il.bin", 17 | "y.bin" 18 | }; 19 | 20 | void *load_addresses[] = { 21 | IL_MEMORY, 22 | Y_MEMORY_0 23 | }; 24 | 25 | uint8_t load_bins(const char **bin_files, void **load_addresses, size_t bin_count); 26 | 27 | uint8_t setup_cas() 28 | { 29 | // Load external binaries 30 | if (load_bins(bin_files, load_addresses, sizeof(load_addresses) / sizeof(void *))) 31 | { 32 | return 1; 33 | } 34 | 35 | // Enable DMA Controller 36 | POWER_MSTPCR0->DMAC = 0; 37 | DMAC_DMAOR->raw = 0; 38 | DMAC_DMAOR->DME = 1; 39 | 40 | // Enable Timers 41 | POWER_MSTPCR0->TMU = 0; 42 | POWER_MSTPCR0->CMT = 0; 43 | 44 | // Create main folder for mcs vars 45 | MCS_CreateFolder("CPBoy", nullptr); 46 | 47 | return 0; 48 | } 49 | 50 | void restore_cas() 51 | { 52 | // Disable DMA Controller 53 | DMAC_DMAOR->DME = 0; 54 | POWER_MSTPCR0->DMAC = 1; 55 | 56 | // Disable Timers 57 | cmt_stop(); 58 | 59 | POWER_MSTPCR0->CMT = 1; 60 | POWER_MSTPCR0->TMU = 1; 61 | 62 | // Restore clock speed 63 | cpg_set_pll_mul(CPG_PLL_MUL_DEFAULT); 64 | } 65 | 66 | uint8_t load_bins(const char **bin_files, void **load_addresses, size_t bin_count) 67 | { 68 | for (size_t i = 0; i < bin_count; i++) 69 | { 70 | char bin_path[MAX_FILENAME_LEN] = DIRECTORY_BIN "\\" ; 71 | strlcat(bin_path, bin_files[i], sizeof(bin_path)); 72 | 73 | size_t bin_size; 74 | 75 | if (get_file_size(bin_path, &bin_size)) 76 | { 77 | return 1; 78 | } 79 | 80 | if (read_file(bin_path, load_addresses[i], bin_size)) 81 | { 82 | return 1; 83 | } 84 | } 85 | 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Sidney Krombholz 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "cas/bootstrap.h" 28 | #include "core/emulator.h" 29 | #include "core/error.h" 30 | 31 | APP_NAME("CPBoy") 32 | APP_DESCRIPTION("A Gameboy (DMG) emulator. Forked from PeanutGB by deltabeard.") 33 | APP_AUTHOR("diddyholz") 34 | APP_VERSION(CPBOY_VERSION) 35 | 36 | gb_s main_gb __attribute__((section(".oc_mem.y.data"))); 37 | emu_preferences main_preferences __attribute__((section(".oc_mem.y.data"))); 38 | 39 | extern "C" 40 | int32_t main() 41 | { 42 | calcInit(); 43 | 44 | if (setup_cas()) 45 | { 46 | error_crash_alert(get_error_string(errno)); 47 | goto end; 48 | } 49 | 50 | if (run_emulator(&main_gb, &main_preferences) != 0) 51 | { 52 | // Error handling 53 | error_crash_alert(get_error_string(errno)); 54 | } 55 | 56 | end: 57 | restore_cas(); 58 | calcEnd(); 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/emu_ui/menu/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../../core/emulator.h" 5 | #include "../../helpers/macros.h" 6 | 7 | #define MENU_CLOSED 0 8 | #define MENU_LOAD_NEW 1 9 | #define MENU_CRASH 2 10 | #define MENU_EMU_QUIT 3 11 | 12 | #define TAB_DESCR_MAX_FILENAME_LENGTH 42 13 | 14 | typedef struct menu_item 15 | { 16 | bool disabled; // Disabled state of the item 17 | char title[MAX_FILENAME_LEN]; // The title of the menu item 18 | char value[MAX_FILENAME_LEN]; // The secondary value of the item (e.g. Enabled/Disabled) 19 | uint16_t value_color; // The color of the value text 20 | int32_t (*action)(menu_item *, gb_s*); // The function that runs, when the item was clicked 21 | } menu_item; 22 | 23 | typedef struct 24 | { 25 | char title[10]; // The title of the menu tab 26 | char description[100]; // The description of the menu tab 27 | uint8_t item_count; // Amount of menu items 28 | menu_item *items; // Array of menu items in this tab 29 | } menu_tab; 30 | 31 | typedef struct 32 | { 33 | uint16_t x_pos; 34 | uint16_t y_pos; 35 | uint16_t width; 36 | uint16_t height; 37 | uint16_t background; // The background color of the menu 38 | uint8_t selected_tab; // The currently selected tab 39 | uint8_t selected_item; // The currently selected item in the selected tab 40 | uint8_t tab_count; // Amount of menu tabs 41 | menu_tab *tabs; // Array of menu tabs 42 | } menu; 43 | 44 | /*! 45 | @brief Draws the pause overlay 46 | */ 47 | void draw_pause_overlay(); 48 | 49 | /*! 50 | @brief Draws the menu overlay 51 | */ 52 | void draw_menu_overlay(); 53 | 54 | /*! 55 | @brief Draws a menu 56 | @param menu A pointer to the menu to be drawn 57 | */ 58 | void draw_menu(menu *menu); 59 | 60 | 61 | uint8_t load_menu(emu_preferences *prefs); 62 | 63 | /*! 64 | @brief Switches context to the emulation menu, 65 | returns when either the menu is closed or the emulator is quit 66 | @return Returns MENU_EMU_QUIT, MENU_CLOSED or MENU_LOAD_NEW 67 | */ 68 | uint8_t emulation_menu(struct gb_s *gb, bool preview_only); 69 | 70 | void draw_load_alert(void); 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPBoy 2 | 3 | CPBoy is a work in progress DMG GameBoy emulator for the fx-CP400. It is based on the [Peanut-GB emulator by deltabeard](https://github.com/deltabeard/Peanut-GB). 4 | 5 | Many games run with fullspeed with only one frame being skipped. Unfortunately, to make the emulator as fast as it is on such a low performing hardware, some accuracy had to be sacrificied which may cause some unexpected bugs with some roms. 6 | 7 | ## Getting Started 8 | 9 | You will need to have the [Hollyhock-2 CFW](https://github.com/SnailMath/hollyhock-2/) installed. After that, extract the CPBoy.zip file from the [latest release](https://github.com/diddyholz/CPBoy/releases) to the root of your calculator. You should then have the following file structure on your calculator: 10 | ``` 11 | ├── run.bin (Hollyhock launcher) 12 | ├── CPBoy.bin (Main CPBoy executable) 13 | └── CPBoy/ 14 | ├── bin/ 15 | | ├── il.bin 16 | | └── y.bin 17 | └── roms/ 18 | └── ** Put your roms in here ** 19 | ``` 20 | 21 | To load your Gameboy ROMs, put them into the `/CPBoy/roms/` directory. They should have the file ending `.gb`. CPBoy should then automatically detect the roms. 22 | 23 | 24 | ## Controls 25 | 26 | The controls can be changed in the "Settings" tab in CPBoy. 27 | 28 | These are the default controls: 29 | 30 | | GB Action | Calculator key | 31 | | --------- | -------------- | 32 | | A | EXE | 33 | | B | + | 34 | | SELECT | SHIFT | 35 | | START | CLEAR | 36 | | UP | UP | 37 | | DOWN | DOWN | 38 | | LEFT | LEFT | 39 | | RIGHT | RIGHT | 40 | | Open Menu | (-) | 41 | 42 | 43 | ## Games That Don't Work 44 | 45 | - Gameboy Colour games that are not backwards compatible with the Gameboy 46 | - Pokemon Pinball 47 | - Metroid 2 (Samus goes invisible in some areas) 48 | - Turok 2 (Crashes upon death) 49 | 50 | If you encounter any issues with games, I will add them to this list. 51 | 52 | 53 | ## Building 54 | 55 | If you want to build the emulator from source you will need the [Hollyhock-2 SDK + Newlib](https://github.com/SnailMath/hollyhock-2/). Then, run ´make´ in your terminal. 56 | 57 | 58 | ## License 59 | 60 | This project is licensed under the MIT License. 61 | -------------------------------------------------------------------------------- /src/core/error.cpp: -------------------------------------------------------------------------------- 1 | #include "error.h" 2 | 3 | #include 4 | #include 5 | #include "../emu_ui/components.h" 6 | #include "../emu_ui/colors.h" 7 | #include "../helpers/functions.h" 8 | 9 | #define ERROR_ALERT_MSG_MAX_LEN 400 10 | 11 | const char *error_messages[] = { 12 | ERROR_MSG_EMALLOC, 13 | ERROR_MSG_EFOPEN, 14 | ERROR_MSG_EFREAD, 15 | ERROR_MSG_EFWRITE, 16 | ERROR_MSG_EFCLOSE, 17 | ERROR_MSG_EMKDIR, 18 | ERROR_MSG_EEMUCARTRIDGE, 19 | ERROR_MSG_EEMUCHECKSUM, 20 | ERROR_MSG_EEMUGEN, 21 | ERROR_MSG_ESTRBUFEMPTY, 22 | }; 23 | 24 | uint8_t errno; 25 | 26 | char error_file[ERROR_MAX_FILE_LEN]; 27 | char error_info[ERROR_MAX_INFO_LEN]; 28 | 29 | uint32_t error_line; 30 | 31 | void _set_error(uint8_t error, const char *file, uint32_t line, const char *info) 32 | { 33 | errno = error; 34 | 35 | strlcpy(error_file, file, ERROR_MAX_FILE_LEN - 1); 36 | strlcpy(error_info, info, ERROR_MAX_INFO_LEN - 1); 37 | 38 | error_line = line; 39 | 40 | // Make sure string are null terminated 41 | error_file[ERROR_MAX_FILE_LEN - 1] = '\0'; 42 | error_info[ERROR_MAX_INFO_LEN - 1] = '\0'; 43 | } 44 | 45 | const char *get_error_string(uint8_t error) 46 | { 47 | static char error_string[100]; 48 | 49 | strlcpy(error_string, error_messages[error], sizeof(error_string)); 50 | 51 | return error_string; 52 | } 53 | 54 | void error_crash_alert(const char *error) 55 | { 56 | char error_msg[ERROR_ALERT_MSG_MAX_LEN]; 57 | char tmp[11]; 58 | 59 | strlcpy(error_msg, error, sizeof(error_msg)); 60 | 61 | if (error_file[0] != '\0') 62 | { 63 | // Print error info if it exists 64 | if (error_info[0] != '\0') 65 | { 66 | strlcat(error_msg, " (", sizeof(error_msg)); 67 | strlcat(error_msg, error_info, sizeof(error_msg)); 68 | strlcat(error_msg, ")", sizeof(error_msg)); 69 | } 70 | 71 | strlcat(error_msg, "\n(", sizeof(error_msg)); 72 | strlcat(error_msg, error_file, sizeof(error_msg)); 73 | strlcat(error_msg, ":", sizeof(error_msg)); 74 | strlcat(error_msg, itoa(error_line, tmp, 10), sizeof(error_msg)); 75 | strlcat(error_msg, ")\n\n", sizeof(error_msg)); 76 | } 77 | 78 | strlcat(error_msg, ERROR_MSG_GEN_EMULATOR_QUIT, sizeof(error_msg)); 79 | 80 | error_gen_alert(error_msg); 81 | } 82 | 83 | void error_gen_alert(const char *error) 84 | { 85 | ok_alert("ERROR!", nullptr, error, COLOR_DANGER, COLOR_BLACK, COLOR_DANGER); 86 | } -------------------------------------------------------------------------------- /src/cas/cpu/cmt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define CMT_TICKS_PER_SEC 3600000 6 | 7 | enum cmt_cmcsr_cms 8 | { 9 | SIZE_32_BIT = 0x0, 10 | SIZE_16_BIT = 0x1, 11 | }; 12 | 13 | enum cmt_cmcsr_cmm 14 | { 15 | MODE_ONE_SHOT = 0x0, // Timer will only run once 16 | MODE_FREE_RUNNING = 0x1, // Timer will restart after clearing flags 17 | }; 18 | 19 | enum cmt_cmcsr_cmr 20 | { 21 | REQUEST_DISABLE = 0x0, // Disable DMA transfer request and internal interrupt request 22 | REQUEST_DMA = 0x1, // Enable DMA transfer request 23 | REQUEST_IIR = 0x2, // Enable internal interrupt request 24 | }; 25 | 26 | enum cmt_cmcsr_cks 27 | { 28 | CLOCK_RCLK_DIV_8 = 0x4, 29 | CLOCK_RCLK_DIV_32 = 0x5, 30 | CLOCK_RCLK_DIV_128 = 0x6, 31 | }; 32 | 33 | // Compare match timer start register 34 | union cmt_cmstr 35 | { 36 | struct 37 | { 38 | uint16_t _reserved0 : 10; 39 | uint16_t STR5 : 1; // Count start 40 | uint16_t _reserved1 : 5; 41 | }; 42 | 43 | uint16_t raw; 44 | }; 45 | 46 | // Compare match timer control/status register 47 | union cmt_cmcsr 48 | { 49 | struct 50 | { 51 | uint16_t CMF : 1; // Compare Match Flag 52 | uint16_t OVF : 1; // Overflow Flag 53 | uint16_t WRFLG : 1; // Write State Flag 54 | uint16_t _reserved0 : 3; 55 | cmt_cmcsr_cms CMS : 1; // Compare Match Timer Counter Size 56 | cmt_cmcsr_cmm CMM : 1; // Compare Match Mode 57 | uint16_t CMTOUT_IE : 1; 58 | uint16_t _reserved1 : 1; 59 | cmt_cmcsr_cmr CMR : 2; // Compare Match Request 60 | uint16_t _reserved2 : 1; 61 | cmt_cmcsr_cks CKS : 3; // Clock Select 62 | }; 63 | 64 | uint16_t raw; 65 | }; 66 | 67 | #define CMT_CMSTR ((volatile cmt_cmstr *)0xA44A0000) 68 | #define CMT_CMCSR ((volatile cmt_cmcsr *)0xA44A0060) 69 | #define CMT_CMCNT ((volatile uint32_t *) 0xA44A0064) 70 | #define CMT_CMCOR ((volatile uint32_t *) 0xA44A0068) 71 | 72 | inline void cmt_set(uint32_t constant, cmt_cmcsr_cmm cmm, cmt_cmcsr_cmr cmr) 73 | { 74 | CMT_CMSTR->STR5 = 0; 75 | *CMT_CMCOR = constant; 76 | *CMT_CMCNT = 0; 77 | 78 | cmt_cmcsr temp_cmcsr = { .raw = 0 }; 79 | temp_cmcsr.CMS = SIZE_32_BIT; 80 | temp_cmcsr.CMM = cmm; 81 | temp_cmcsr.CMR = cmr; 82 | temp_cmcsr.CKS = CLOCK_RCLK_DIV_8; 83 | 84 | CMT_CMCSR->raw = temp_cmcsr.raw; 85 | } 86 | 87 | inline void cmt_start() 88 | { 89 | CMT_CMCSR->CMF = 0; 90 | CMT_CMSTR->STR5 = 1; 91 | } 92 | 93 | inline void cmt_stop() 94 | { 95 | CMT_CMSTR->STR5 = 0; 96 | *CMT_CMCNT = 0; 97 | CMT_CMCSR->raw = 0; 98 | } 99 | 100 | inline void cmt_wait() 101 | { 102 | while (!CMT_CMCSR->CMF) { } 103 | } 104 | -------------------------------------------------------------------------------- /src/core/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define EMALLOC 0 6 | #define EFOPEN 1 7 | #define EFREAD 2 8 | #define EFWRITE 3 9 | #define EFCLOSE 4 10 | #define EMKDIR 5 11 | #define EEMUCARTRIDGE 6 12 | #define EEMUCHECKSUM 7 13 | #define EEMUGEN 8 14 | #define ESTRBUFEMPTY 9 15 | 16 | #define ERROR_MSG_EMALLOC "Failed to allocate memory" 17 | #define ERROR_MSG_EFOPEN "Failed to open file" 18 | #define ERROR_MSG_EFREAD "Failed to read file" 19 | #define ERROR_MSG_EFWRITE "Failed to write to file" 20 | #define ERROR_MSG_EFCLOSE "Failed to close file" 21 | #define ERROR_MSG_EMKDIR "Failed to make directory" 22 | #define ERROR_MSG_EEMUCARTRIDGE "Unsupported cardridge format" 23 | #define ERROR_MSG_EEMUCHECKSUM "ROM Checksum failure" 24 | #define ERROR_MSG_EEMUGEN "Unknown error on emulator context initialization" 25 | #define ERROR_MSG_ESTRBUFEMPTY "The string buffer ran out of space" 26 | #define ERROR_MSG_GEN_EMULATOR_RETRY "Please try a different ROM" 27 | #define ERROR_MSG_GEN_EMULATOR_QUIT "The emulator will now quit" 28 | 29 | #define ERROR_MAX_FILE_LEN 100 30 | #define ERROR_MAX_INFO_LEN 100 31 | 32 | #define set_error(error) _set_error(error, __FILE__, __LINE__, "") 33 | #define set_error_i(error, info) _set_error(error, __FILE__, __LINE__, info) 34 | 35 | /*! 36 | @brief Stores information about an error 37 | */ 38 | extern uint8_t errno; 39 | 40 | /*! 41 | @brief The file in which an error occured 42 | */ 43 | extern char error_file[ERROR_MAX_FILE_LEN]; 44 | 45 | /*! 46 | @brief The file in which an error occured 47 | */ 48 | extern char error_info[ERROR_MAX_INFO_LEN]; 49 | 50 | /*! 51 | @brief The line at which an error occured 52 | */ 53 | extern uint32_t error_line; 54 | 55 | /*! 56 | @brief Sets error parameters when an error occured 57 | @param error The errno to be set 58 | @param file The file where the error occured 59 | @param line The line where the error occured 60 | @param info An info about the error (e.g. file name for file errors) 61 | */ 62 | void _set_error(uint8_t error, const char *file, uint32_t line, const char *info); 63 | 64 | /*! 65 | @brief Gets a user readable string to the error message 66 | @param error The error message to be printed 67 | */ 68 | const char *get_error_string(uint8_t error); 69 | 70 | /*! 71 | @brief Prints an error message and an emulator is quitting message. 72 | Will automatically add file and line number. 73 | @param error The error message to be printed 74 | */ 75 | void error_crash_alert(const char *error); 76 | 77 | /*! 78 | @brief Prints a generic error message 79 | @param error The error message to be printed 80 | */ 81 | void error_gen_alert(const char *error); -------------------------------------------------------------------------------- /src/helpers/fileio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "macros.h" 6 | 7 | #define write_mcs(dir, name, buf, size) _write_mcs(dir, name, buf, size, __FILE__, __LINE__) 8 | 9 | #define read_mcs(dir, name, buf, size) _read_mcs(dir, name, buf, size, __FILE__, __LINE__) 10 | 11 | /** 12 | * Writes a file and does error handling. The file will be created 13 | * if it does not exist 14 | * This should be used whenever a file is written. 15 | * 16 | * @param file The filename to write to 17 | * @param buf The buffer to be written 18 | * @param len The size of the buffer 19 | * 20 | * @return Returns 0 on success else an error occured 21 | */ 22 | #define write_file(file, buf, len) _write_file(file, buf, len, __FILE__, __LINE__) 23 | 24 | /** 25 | * Read a file and does error handling. 26 | * This should be used whenever a file is read. 27 | * 28 | * @param file The filename to be read 29 | * @param buf The buffer in which to read 30 | * @param len The size of the buffer 31 | * 32 | * @return Returns 0 on success else an error occured 33 | */ 34 | #define read_file(file, buf, len) _read_file(file, buf, len, __FILE__, __LINE__) 35 | 36 | /** 37 | * Read a file and does error handling. 38 | * This should be used whenever a file is read. 39 | * 40 | * @param file The file to be deleted 41 | * 42 | * @return Returns 0 on success else an error occured 43 | */ 44 | #define delete_file(file) _delete_file(file, __FILE__, __LINE__) 45 | 46 | /** 47 | * Gets the size of a file in bytes and does error handling. 48 | * 49 | * @param file The file to be checked 50 | * @param size A pointer to where the size will be written to 51 | * 52 | * @return Returns 0 on success else an error occured 53 | */ 54 | #define get_file_size(file, size) _get_file_size(file, size, __FILE__, __LINE__) 55 | 56 | /** 57 | * Find files matching the given path 58 | * 59 | * @param path The path to be searched (Can contain wildcards) 60 | * @param buf The buffer in which the found files should be written to 61 | * @param len The maximum amount of files to find (The size of your buffer) 62 | * 63 | * @return Returns the amount of files found 64 | */ 65 | uint8_t find_files(const char *path, char (*buf)[MAX_FILENAME_LEN], uint8_t max); 66 | 67 | uint8_t _write_mcs(const char *dir, const char *name, void *buf, size_t len, 68 | const char *err_file, uint32_t err_line); 69 | uint8_t _read_mcs(const char *dir, const char *name, void **buf, uint32_t *len, 70 | const char *err_file, uint32_t err_line); 71 | uint8_t _write_file(const char *file, void *buf, size_t len, const char *err_file, uint32_t err_line); 72 | uint8_t _read_file(const char *file, void *buf, size_t len, const char *err_file, uint32_t err_line); 73 | uint8_t _delete_file(const char *file, const char *err_file, uint32_t err_line); 74 | uint8_t _get_file_size(const char *file, size_t *size, const char *err_file, uint32_t err_line); 75 | -------------------------------------------------------------------------------- /src/cas/cpu/cpg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // SH7724 Clock Pulse Generator 6 | 7 | enum cpg_frqcra_stc 8 | { 9 | PLL_MUL_12 = 0x05, 10 | PLL_MUL_16 = 0x07, 11 | PLL_MUL_24 = 0x0B, 12 | PLL_MUL_30 = 0x0E, 13 | PLL_MUL_32 = 0x0F, 14 | PLL_MUL_36 = 0x11, 15 | PLL_MUL_48 = 0x17 16 | }; 17 | 18 | enum cpg_frqcra_ifc 19 | { 20 | IFC_DIV_2 = 0x0, 21 | IFC_DIV_3 = 0x1, 22 | IFC_DIV_4 = 0x2, 23 | IFC_DIV_6 = 0x3, 24 | IFC_DIV_8 = 0x4, 25 | IFC_DIV_12 = 0x5, 26 | IFC_DIV_16 = 0x6, 27 | IFC_DIV_24 = 0x8, 28 | IFC_DIV_32 = 0x9, 29 | IFC_DIV_36 = 0xA, 30 | IFC_DIV_48 = 0xB, 31 | IFC_DIV_72 = 0xD 32 | }; 33 | 34 | enum cpg_frqcra_sfc 35 | { 36 | SFC_DIV_4 = 0x2, 37 | SFC_DIV_6 = 0x3, 38 | SFC_DIV_8 = 0x4, 39 | SFC_DIV_12 = 0x5, 40 | SFC_DIV_16 = 0x6, 41 | SFC_DIV_24 = 0x8, 42 | SFC_DIV_32 = 0x9, 43 | SFC_DIV_36 = 0xA, 44 | SFC_DIV_48 = 0xB, 45 | SFC_DIV_72 = 0xD 46 | }; 47 | 48 | enum cpg_frqcra_bfc 49 | { 50 | BFC_DIV_4 = 0x2, 51 | BFC_DIV_6 = 0x3, 52 | BFC_DIV_8 = 0x4, 53 | BFC_DIV_12 = 0x5, 54 | BFC_DIV_16 = 0x6, 55 | BFC_DIV_24 = 0x8, 56 | BFC_DIV_32 = 0x9, 57 | BFC_DIV_36 = 0xA, 58 | BFC_DIV_48 = 0xB, 59 | BFC_DIV_72 = 0xD 60 | }; 61 | 62 | enum cpg_frqcra_p1fc 63 | { 64 | P1FC_DIV_4 = 0x2, 65 | P1FC_DIV_6 = 0x3, 66 | P1FC_DIV_8 = 0x4, 67 | P1FC_DIV_12 = 0x5, 68 | P1FC_DIV_16 = 0x6, 69 | P1FC_DIV_24 = 0x8, 70 | P1FC_DIV_32 = 0x9, 71 | P1FC_DIV_36 = 0xA, 72 | P1FC_DIV_48 = 0xB, 73 | P1FC_DIV_72 = 0xD 74 | }; 75 | 76 | // Frequency control register A 77 | union cpg_frqcra 78 | { 79 | struct 80 | { 81 | uint32_t KICK : 1; 82 | uint32_t _reserved0 : 1; 83 | cpg_frqcra_stc STC : 6; // PLL Circuit Multiplication Ratio 84 | cpg_frqcra_ifc IFC : 4; // CPU Clock (I-Phi) Frequency Division Ratio 85 | uint32_t _reserved1 : 4; 86 | cpg_frqcra_sfc SFC : 4; // SuperHy Clock (S-Phi) Frequency Division Ratio 87 | cpg_frqcra_bfc BFC : 4; // Bus Clock (B-Phi) Frequency Division Ratio 88 | uint32_t _reserved2 : 4; 89 | cpg_frqcra_p1fc P1FC : 4; // Peripheral Clock (P-Phi) Frequency Division Ratio 90 | }; 91 | 92 | uint32_t raw; 93 | }; 94 | 95 | // Frequency change status register 96 | union cpg_lstatus 97 | { 98 | struct 99 | { 100 | uint32_t _reserved0 : 31; 101 | uint32_t FRQF : 1; // Frequency changing status flag 102 | }; 103 | 104 | uint32_t raw; 105 | }; 106 | 107 | #define CPG_FRQCRA ((volatile cpg_frqcra *) 0xA4150000) 108 | #define CPG_LSTATUS ((volatile cpg_lstatus *)0xA4150060) 109 | 110 | #define CPG_PLL_MUL_DEFAULT PLL_MUL_32 111 | 112 | inline void cpg_frqf_wait() 113 | { 114 | while (CPG_LSTATUS->FRQF == 1) {} 115 | } 116 | 117 | inline void cpg_set_pll_mul(cpg_frqcra_stc multiplier) 118 | { 119 | // Set new PLL multiplier and activate it 120 | CPG_FRQCRA->STC = multiplier; 121 | CPG_FRQCRA->KICK = 1; 122 | 123 | // Wait until frequency change is completed 124 | cpg_frqf_wait(); 125 | } 126 | -------------------------------------------------------------------------------- /src/core/controls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../core/peanut_gb_header.h" 6 | 7 | #define DEFAULT_GB_KEY_A_0 KEY_EXE 8 | #define DEFAULT_GB_KEY_A_1 0 9 | #define DEFAULT_GB_KEY_B_0 KEY_ADD 10 | #define DEFAULT_GB_KEY_B_1 0 11 | #define DEFAULT_GB_KEY_START_0 KEY_CLEAR 12 | #define DEFAULT_GB_KEY_START_1 0 13 | #define DEFAULT_GB_KEY_SELECT_0 KEY_SHIFT 14 | #define DEFAULT_GB_KEY_SELECT_1 0 15 | #define DEFAULT_GB_KEY_UP_0 0 16 | #define DEFAULT_GB_KEY_UP_1 KEY_UP 17 | #define DEFAULT_GB_KEY_DOWN_0 0 18 | #define DEFAULT_GB_KEY_DOWN_1 KEY_DOWN 19 | #define DEFAULT_GB_KEY_LEFT_0 KEY_LEFT 20 | #define DEFAULT_GB_KEY_LEFT_1 0 21 | #define DEFAULT_GB_KEY_RIGHT_0 KEY_RIGHT 22 | #define DEFAULT_GB_KEY_RIGHT_1 0 23 | 24 | #define CAS_KEY_TEXT_SHIFT "[SHIFT]" 25 | #define CAS_KEY_TEXT_CLEAR "[CLEAR]" 26 | #define CAS_KEY_TEXT_BACKSPACE "[<--]" 27 | #define CAS_KEY_TEXT_LEFT "[LEFT]" 28 | #define CAS_KEY_TEXT_RIGHT "[RIGHT]" 29 | #define CAS_KEY_TEXT_Z "[Z]" 30 | #define CAS_KEY_TEXT_POWER "[^]" 31 | #define CAS_KEY_TEXT_DIVIDE "[/]" 32 | #define CAS_KEY_TEXT_MULTIPLY "[*]" 33 | #define CAS_KEY_TEXT_SUBSTRACT "[-]" 34 | #define CAS_KEY_TEXT_ADD "[+]" 35 | #define CAS_KEY_TEXT_EXE "[EXE]" 36 | #define CAS_KEY_TEXT_EXP "[EXP]" 37 | #define CAS_KEY_TEXT_3 "[3]" 38 | #define CAS_KEY_TEXT_6 "[6]" 39 | #define CAS_KEY_TEXT_9 "[9]" 40 | #define CAS_KEY_TEXT_KEYBOARD "[KEYBOARD]" 41 | #define CAS_KEY_TEXT_UP "[UP]" 42 | #define CAS_KEY_TEXT_DOWN "[DOWN]" 43 | #define CAS_KEY_TEXT_EQUALS "[=]" 44 | #define CAS_KEY_TEXT_X "[X]" 45 | #define CAS_KEY_TEXT_Y "[Y]" 46 | #define CAS_KEY_TEXT_LEFT_BRACKET "[(]" 47 | #define CAS_KEY_TEXT_RIGHT_BRACKET "[)]" 48 | #define CAS_KEY_TEXT_COMMA "[,]" 49 | #define CAS_KEY_TEXT_NEGATIVE "[-]" 50 | #define CAS_KEY_TEXT_0 "[0]" 51 | #define CAS_KEY_TEXT_DOT "[.]" 52 | #define CAS_KEY_TEXT_1 "[1]" 53 | #define CAS_KEY_TEXT_2 "[2]" 54 | #define CAS_KEY_TEXT_4 "[4]" 55 | #define CAS_KEY_TEXT_5 "[5]" 56 | #define CAS_KEY_TEXT_7 "[7]" 57 | #define CAS_KEY_TEXT_8 "[8]" 58 | #define CAS_KEY_TEXT_NONE "NONE" 59 | 60 | #define GB_KEY_TEXT_A "A" 61 | #define GB_KEY_TEXT_B "B" 62 | #define GB_KEY_TEXT_START "START" 63 | #define GB_KEY_TEXT_SELECT "SELECT" 64 | #define GB_KEY_TEXT_UP "UP" 65 | #define GB_KEY_TEXT_DOWN "DOWN" 66 | #define GB_KEY_TEXT_LEFT "LEFT" 67 | #define GB_KEY_TEXT_RIGHT "RIGHT" 68 | 69 | #define GB_KEY_COUNT 8 70 | 71 | #define GB_KEY_A 0 72 | #define GB_KEY_B 1 73 | #define GB_KEY_START 2 74 | #define GB_KEY_SELECT 3 75 | #define GB_KEY_UP 4 76 | #define GB_KEY_DOWN 5 77 | #define GB_KEY_LEFT 6 78 | #define GB_KEY_RIGHT 7 79 | 80 | typedef uint32_t emu_controls[GB_KEY_COUNT][2]; 81 | 82 | uint8_t load_controls(struct gb_s *gb); 83 | uint8_t save_controls(struct gb_s *gb); 84 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # run `make all` to compile the .hhk and .bin file, use `make` to compile only the .bin file. 2 | # The .hhk file is the original format, the bin file is a newer format. 3 | APP_NAME:=CPBoy 4 | 5 | ifndef SDK_DIR 6 | $(error You need to define the SDK_DIR environment variable, and point it to the sdk/ folder) 7 | endif 8 | 9 | AS:=sh4-elf-as 10 | AS_FLAGS:= 11 | 12 | COMMON_FLAGS:=-ffreestanding -fshort-wchar -O2 -m4a-nofpu 13 | INCLUDES:=-I $(SDK_DIR)/include/ -I $(SDK_DIR)/newlib/sh-elf/include 14 | WARNINGS:=-Wall -Wextra 15 | 16 | CC:=sh4-elf-gcc 17 | CC_FLAGS:=$(COMMON_FLAGS) $(INCLUDES) $(WARNINGS) 18 | 19 | CXX:=sh4-elf-g++ 20 | CXX_FLAGS:=-fno-exceptions -fno-rtti -Wno-write-strings $(COMMON_FLAGS) $(INCLUDES) $(WARNINGS) 21 | 22 | LD:=sh4-elf-gcc 23 | LD_FLAGS:=-nostartfiles -m4a-nofpu -Wno-undef -L$(SDK_DIR)/newlib/sh-elf/lib 24 | 25 | READELF:=sh4-elf-readelf 26 | OBJCOPY:=sh4-elf-objcopy 27 | 28 | SOURCEDIR = src 29 | BUILDDIR = obj 30 | OUTDIR = dist 31 | BINDIR = $(OUTDIR)/CPBoy/bin 32 | 33 | AS_SOURCES:=$(shell find $(SOURCEDIR) -name '*.s') 34 | CC_SOURCES:=$(shell find $(SOURCEDIR) -name '*.c') 35 | CXX_SOURCES:=$(shell find $(SOURCEDIR) -name '*.cpp') 36 | OBJECTS := $(addprefix $(BUILDDIR)/,$(AS_SOURCES:.s=.o)) \ 37 | $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.c=.o)) \ 38 | $(addprefix $(BUILDDIR)/,$(CXX_SOURCES:.cpp=.o)) 39 | 40 | APP_ELF:=$(OUTDIR)/$(APP_NAME).elf 41 | APP_BIN:=$(OUTDIR)/$(APP_NAME).bin 42 | IL_BIN:=$(BINDIR)/il.bin 43 | Y_BIN:=$(BINDIR)/y.bin 44 | 45 | bin: $(APP_BIN) $(IL_BIN) $(Y_BIN) Makefile 46 | 47 | hhk: $(APP_ELF) Makefile 48 | 49 | all: $(APP_ELF) $(APP_BIN) $(IL_BIN) $(Y_BIN) Makefile 50 | 51 | clean: 52 | rm -rf $(BUILDDIR) $(OUTDIR) 53 | 54 | $(APP_BIN): $(APP_ELF) 55 | $(OBJCOPY) --remove-section=.oc_mem* --output-target=binary $(APP_ELF) $@ 56 | 57 | $(IL_BIN): $(APP_ELF) 58 | mkdir -p $(dir $@) 59 | $(OBJCOPY) --only-section=.oc_mem.il* --output-target=binary $(APP_ELF) $@ 60 | 61 | $(Y_BIN): $(APP_ELF) 62 | mkdir -p $(dir $@) 63 | $(OBJCOPY) --only-section=.oc_mem.y* --output-target=binary $(APP_ELF) $@ 64 | 65 | $(APP_ELF): $(OBJECTS) $(SDK_DIR)/sdk.o linker.ld 66 | mkdir -p $(dir $@) 67 | $(LD) -T linker.ld -o $@ $(LD_FLAGS) $(OBJECTS) $(SDK_DIR)/sdk.o 68 | 69 | # We're not actually building sdk.o, just telling the user they need to do it 70 | # themselves. Just using the target to trigger an error when the file is 71 | # required but does not exist. 72 | $(SDK_DIR)/sdk.o: 73 | $(error You need to build the SDK before using it. Run make in the SDK directory, and check the README.md in the SDK directory for more information) 74 | 75 | $(BUILDDIR)/%.o: %.s 76 | mkdir -p $(dir $@) 77 | $(AS) $< -o $@ $(AS_FLAGS) 78 | 79 | $(BUILDDIR)/%.o: %.c 80 | mkdir -p $(dir $@) 81 | $(CC) -c $< -o $@ $(CC_FLAGS) 82 | 83 | # Break the build if global constructors are present: 84 | # Read the sections from the object file (with readelf -S) and look for any 85 | # called .ctors - if they exist, give the user an error message, delete the 86 | # object file (so that on subsequent runs of make the build will still fail) 87 | # and exit with an error code to halt the build. 88 | $(BUILDDIR)/%.o: %.cpp 89 | mkdir -p $(dir $@) 90 | $(CXX) -c $< -o $@ $(CXX_FLAGS) 91 | @$(READELF) $@ -S | grep ".ctors" > /dev/null && echo "ERROR: Global constructors aren't supported." && rm $@ && exit 1 || exit 0 92 | 93 | .PHONY: bin hhk all clean -------------------------------------------------------------------------------- /src/core/cart_ram.cpp: -------------------------------------------------------------------------------- 1 | #include "cart_ram.h" 2 | 3 | #include 4 | #include 5 | #include "error.h" 6 | #include "emulator.h" 7 | #include "../helpers/macros.h" 8 | #include "../helpers/fileio.h" 9 | #include "../helpers/functions.h" 10 | 11 | #define RAM_VAR_CONSTANT "R_" 12 | 13 | // Gets the mcs varname of the current roms cart ram save 14 | // Make sure the buffer is big enough 15 | char *get_cart_ram_var_name(emu_preferences *preferences, char *name_buffer) 16 | { 17 | char *str = preferences->current_rom_name; 18 | 19 | // Check if this rom has a name 20 | if (strcmp(preferences->current_rom_name, "") == 0) 21 | { 22 | str = preferences->current_filename; 23 | } 24 | 25 | strcpy(name_buffer, RAM_VAR_CONSTANT); 26 | itoa_leading_zeros( 27 | hash_string(str, 0xFFFFFF), 28 | name_buffer + (sizeof(RAM_VAR_CONSTANT) - 1), 29 | 16, 30 | 6 31 | ); 32 | 33 | return name_buffer; 34 | } 35 | 36 | uint8_t load_cart_ram(struct gb_s *gb) 37 | { 38 | emu_preferences *preferences = (emu_preferences *)(gb->direct.priv); 39 | size_t len = gb_get_save_size(gb); 40 | 41 | // If save file not required. 42 | if (len == 0) 43 | { 44 | preferences->cart_ram = nullptr; 45 | return 0; 46 | } 47 | 48 | // Allocate enough memory to hold save file. 49 | preferences->cart_ram = (uint8_t *)malloc(len); 50 | 51 | if(!preferences->cart_ram) 52 | { 53 | char err_info[ERROR_MAX_INFO_LEN]; 54 | char tmp[20]; 55 | 56 | strlcpy(err_info, "Cart RAM: ", sizeof(err_info)); 57 | strlcat(err_info, itoa(len, tmp, 10), sizeof(err_info)); 58 | strlcat(err_info, "B", sizeof(err_info)); 59 | 60 | set_error_i(EMALLOC, err_info); 61 | return 1; 62 | } 63 | 64 | memset(preferences->cart_ram, 0xFF, len); 65 | 66 | // Load cart ram 67 | char var_name[MAX_FILENAME_LEN]; 68 | char *mcs_data = nullptr; 69 | uint32_t mcs_size = 0; 70 | 71 | get_cart_ram_var_name(preferences, var_name); 72 | 73 | // If succesfully read data, convert copy it to cart ram 74 | if (read_mcs(MCS_DIRECTORY, var_name, (void **)&mcs_data, &mcs_size) || !mcs_data) 75 | { 76 | return 1; 77 | } 78 | 79 | // Convert to byte array 80 | for (uint32_t i = 0; i < mcs_size; i += 2) 81 | { 82 | char hex_byte[3] = { mcs_data[i], mcs_data[i + 1], '\0'}; 83 | preferences->cart_ram[i / 2] = strtol(hex_byte, nullptr, 16); 84 | } 85 | 86 | return 0; 87 | } 88 | 89 | uint8_t save_cart_ram(struct gb_s *gb) 90 | { 91 | emu_preferences *preferences = (emu_preferences *)(gb->direct.priv); 92 | size_t len = gb_get_save_size(gb); 93 | 94 | // Current ROM does not have a cart ram 95 | if(len == 0 || !(preferences->cart_ram)) 96 | { 97 | return 0; 98 | } 99 | 100 | // Convert byte array to hex string array 101 | const uint32_t str_size = (len * 2) + 1; 102 | const uint32_t var_size = align_val(str_size + 1, 4); 103 | char var_contents[var_size]; 104 | 105 | memset(var_contents, 0, var_size); 106 | 107 | for (size_t i = 0; i < len; i++) 108 | { 109 | itoa_leading_zeros(preferences->cart_ram[i], &(var_contents[(i * 2)]), 16, 2); 110 | } 111 | 112 | // Add trailing 0 just to make sure 113 | var_contents[str_size - 1] = '\0'; 114 | 115 | // Write cart rom 116 | char var_name[MAX_FILENAME_LEN]; 117 | get_cart_ram_var_name(preferences, var_name); 118 | 119 | return write_mcs(MCS_DIRECTORY, var_name, var_contents, var_size); 120 | } 121 | -------------------------------------------------------------------------------- /src/emu_ui/font.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /// @brief The width of a character in pixels 6 | #define DEBUG_CHAR_WIDTH 8 7 | 8 | /// @brief The height of a character in pixels 9 | #define DEBUG_CHAR_HEIGHT 12 10 | 11 | /// @brief The height of a line in pixels 12 | #define DEBUG_LINE_HEIGHT 14 13 | 14 | /*! 15 | @brief Converts a 16-bit unsigned int to a hex string 16 | @param string The string buffer 17 | @param word The 16-bit unsigned int to be converted 18 | */ 19 | void word_to_string(char *string, uint16_t word); 20 | 21 | /*! 22 | @brief Print a character at an absolute position on the screen 23 | @param character The character to print 24 | @param x The x-position where the character should be printed in pixels 25 | @param y The y-position where the character should be printed in pixels 26 | @param size Pass 0 to print in normal size and 1 to print double sized 27 | @param foreground The color in which the text should be printed 28 | @param background The background of the text 29 | @param enable_transparency Pass 0 to disable the use of black (0x0000) background as transparent 30 | */ 31 | void print_char(char character, uint16_t x, uint16_t y, uint8_t size, 32 | uint16_t foreground, uint16_t background, bool enable_transparency); 33 | 34 | /*! 35 | @brief Print a string at an absolute position on the screen with automatic newline 36 | @param string A pointer to the string to be printed 37 | @param x The x-position where the character should be printed in pixels 38 | @param y The y-position where the character should be printed in pixels 39 | @param size Pass 0 to print in normal size and 1 to print double sized 40 | @param foreground The color in which the text should be printed 41 | @param background The background of the text 42 | @param enable_transparency Pass 0 to disable the use of black (0x0000) background as transparent 43 | */ 44 | void print_string(const char *string, uint16_t x, uint16_t y, uint8_t size, 45 | uint16_t foreground, uint16_t background, bool enable_transparency); 46 | 47 | /*! 48 | @brief Prints a string horizontally centered at an absolute position on the screen with automatic newline 49 | @param string A pointer to the string to be printed 50 | @param x The x-position where the character should be printed in pixels 51 | @param y The y-position where the character should be printed in pixels 52 | @param width The width of the textarea in which the string should be printed 53 | @param size Pass 0 to print in normal size and 1 to print double sized 54 | @param foreground The color in which the text should be printed 55 | @param background The background of the text 56 | @param enable_transparency Pass 0 to disable the use of black (0x0000) background as transparent 57 | */ 58 | void print_string_centered(const char *string, uint16_t x, uint16_t y, uint16_t width, 59 | uint8_t size, uint16_t foreground, uint16_t background, bool enable_transparency); 60 | 61 | /*! 62 | @brief Print a string at an absolute position on the screen with automatic newline (Max n chars) 63 | @param string A pointer to the string to be printed 64 | @param n The max number of characters to be printed (pass 0 to ignore) 65 | @param x The x-position where the character should be printed in pixels 66 | @param y The y-position where the character should be printed in pixels 67 | @param x_max The x-position at which a newline should be printed 68 | @param size Pass 0 to print in normal size and 1 to print double sized 69 | @param foreground The color in which the text should be printed 70 | @param background The background of the text 71 | @param enable_transparency Pass 0 to disable the use of black (0x0000) background as transparent 72 | @return Returns the number of characters printed 73 | */ 74 | uint16_t print_n_string(const char *string, uint16_t n, uint16_t x, uint16_t y, uint16_t x_max, 75 | uint8_t size, uint16_t foreground, uint16_t background, bool enable_transparency); 76 | -------------------------------------------------------------------------------- /src/emu_ui/font.cpp: -------------------------------------------------------------------------------- 1 | #include "font.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "components.h" 7 | 8 | #define FONTBASE 0x8062F4C8 9 | #define DISPLAY_WIDTH 320 10 | #define DISPLAY_HEIGHT 528 11 | 12 | char hex_chars[17] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 13 | 'A', 'B', 'C', 'D', 'E', 'F' }; 14 | 15 | void word_to_string(char *string, uint16_t word) 16 | { 17 | string[0] = '0'; 18 | string[1] = 'x'; 19 | string[6] = 0; 20 | 21 | for(uint8_t x = 0; x < 4; x++) 22 | { 23 | string[x] = hex_chars[word % 16]; 24 | word /= 16; 25 | } 26 | } 27 | 28 | uint16_t print_n_string(const char *string, uint16_t n, uint16_t x, uint16_t y, uint16_t x_max, 29 | uint8_t size, uint16_t foreground, uint16_t background, bool enable_transparency) 30 | { 31 | uint16_t char_counter = 0; 32 | uint16_t cur_line_counter = 0; 33 | 34 | for (const char *p = string; *p != '\0'; p++, char_counter++) 35 | { 36 | if (n != 0 && char_counter == n) return char_counter; 37 | 38 | uint16_t x_pos = x + (cur_line_counter * ((DEBUG_CHAR_WIDTH - 2) * (size + 1))); 39 | 40 | // Check if newline 41 | if (x_pos > x_max || *p == '\n') 42 | { 43 | y += DEBUG_LINE_HEIGHT; 44 | cur_line_counter = 0; 45 | continue; 46 | } 47 | 48 | print_char( 49 | *p, 50 | x_pos, 51 | y, 52 | size, 53 | foreground, 54 | background, 55 | enable_transparency 56 | ); 57 | 58 | cur_line_counter++; 59 | } 60 | 61 | return char_counter; 62 | } 63 | 64 | void print_string(const char *string, uint16_t x, uint16_t y, uint8_t size, 65 | uint16_t foreground, uint16_t background, bool enable_transparency) 66 | { 67 | print_n_string( 68 | string, 69 | 0, 70 | x, 71 | y, 72 | DISPLAY_WIDTH, 73 | size, 74 | foreground, 75 | background, 76 | enable_transparency 77 | ); 78 | } 79 | 80 | void print_string_centered(const char *string, uint16_t x, uint16_t y, uint16_t width, 81 | uint8_t size, uint16_t foreground, uint16_t background, bool enable_transparency) 82 | { 83 | const uint16_t actual_x = x + ((width - (strlen(string) * (DEBUG_CHAR_WIDTH - 2))) / 2); 84 | 85 | // Print background accross entire width 86 | if(!enable_transparency || background) 87 | { 88 | draw_rectangle( 89 | x, 90 | y, 91 | width, 92 | DEBUG_LINE_HEIGHT * size, 93 | background, 94 | 0, 95 | 0 96 | ); 97 | } 98 | 99 | print_n_string( 100 | string, 101 | 0, 102 | actual_x, 103 | y, 104 | DISPLAY_WIDTH, 105 | 0, 106 | foreground, 107 | background, 108 | enable_transparency 109 | ); 110 | } 111 | 112 | void print_char(char character, uint16_t x, uint16_t y, uint8_t size, 113 | uint16_t foreground, uint16_t background, bool enable_transparency) 114 | { 115 | uint8_t charIndex = character - ' '; 116 | 117 | size = (size != 0); 118 | size++; 119 | 120 | // fill background with background color if it is not black 121 | if(!enable_transparency || background) 122 | { 123 | draw_rectangle( 124 | x, 125 | y, 126 | DEBUG_CHAR_WIDTH * size, 127 | (DEBUG_CHAR_HEIGHT + 2) * size, 128 | background, 129 | 0, 130 | 0 131 | ); 132 | } 133 | 134 | // now draw the character 135 | uint16_t *pixel = (uint16_t *)(FONTBASE + (0xC0 * charIndex)); 136 | 137 | uint16_t tempXPos = x; 138 | uint16_t tempYPos = y + 1; 139 | 140 | for(uint8_t iy = 0; iy < DEBUG_CHAR_HEIGHT; iy++) 141 | { 142 | tempXPos = x; 143 | 144 | for(uint8_t ix = 0; ix < DEBUG_CHAR_WIDTH; ix++) 145 | { 146 | if(*pixel == 0) 147 | { 148 | if(tempYPos >= DISPLAY_HEIGHT || tempXPos >= DISPLAY_WIDTH) 149 | continue; 150 | 151 | vram[(tempYPos * DISPLAY_WIDTH) + tempXPos] = foreground; 152 | 153 | if(size == 2) 154 | { 155 | vram[(tempYPos * DISPLAY_WIDTH) + tempXPos + 1] = foreground; 156 | vram[((tempYPos + 1) * DISPLAY_WIDTH) + tempXPos] = foreground; 157 | vram[((tempYPos + 1) * DISPLAY_WIDTH) + tempXPos + 1] = foreground; 158 | } 159 | } 160 | 161 | pixel++; 162 | tempXPos += size; 163 | } 164 | 165 | tempYPos += size; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/emu_ui/input.cpp: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "menu/menu.h" 7 | 8 | uint32_t key_streak; // Holds the streak amount 9 | uint32_t streak_key1; // Holds key1 for the current streak 10 | uint32_t streak_key2; // Holds key2 for the current streak 11 | 12 | inline void reset_key_streak() 13 | { 14 | key_streak = 0; 15 | streak_key1 = 0; 16 | streak_key2 = 0; 17 | } 18 | 19 | inline void set_key_streak(uint32_t key1, uint32_t key2) 20 | { 21 | key_streak = 1; 22 | streak_key1 = key1; 23 | streak_key2 = key2; 24 | } 25 | 26 | void wait_input_release() 27 | { 28 | uint32_t key1; 29 | uint32_t key2; 30 | 31 | do 32 | { 33 | getKey(&key1, &key2); 34 | } 35 | while (key1 | key2); 36 | 37 | reset_key_streak(); 38 | } 39 | 40 | uint8_t process_input(uint8_t **selected_h_item, uint8_t *selected_v_item, 41 | const uint8_t *h_item_count, uint8_t v_item_count, const menu_item *items, bool reset_v_item) 42 | { 43 | uint32_t key1; 44 | uint32_t key2; 45 | 46 | // Check if keys are pressed 47 | if (!Input_IsAnyKeyDown()) 48 | { 49 | reset_key_streak(); 50 | 51 | return INPUT_PROC_NONE; 52 | } 53 | 54 | getKey(&key1, &key2); 55 | 56 | // Check if keystreak should be increased 57 | if (key1 == streak_key1 && key2 == streak_key2) 58 | { 59 | key_streak++; 60 | } 61 | else 62 | { 63 | // Set key streak to current button 64 | set_key_streak(key1, key2); 65 | } 66 | 67 | // Check if currently in key streak 68 | if (key_streak > 1) 69 | { 70 | // Default delay of 4 except for first button press 71 | uint8_t delay = (key_streak == 2)? 30 : 4; 72 | 73 | for (uint8_t i = 0; i < delay; i++) 74 | { 75 | LCD_Refresh(); // Refresh to create a delay 76 | 77 | // Check if key is still pressed 78 | getKey(&key1, &key2); 79 | 80 | if (key1 != streak_key1 || key2 != streak_key2) 81 | { 82 | set_key_streak(key1, key2); 83 | break; 84 | } 85 | } 86 | } 87 | 88 | // Process input 89 | if (testKey(key1, key2, KEY_DOWN)) 90 | { 91 | uint8_t last_active = *selected_v_item; 92 | 93 | // Skip disabled items 94 | // This will cause an endless loop if every item is disabled 95 | do 96 | { 97 | if (items && !items[*selected_v_item].disabled) 98 | { 99 | last_active = *selected_v_item; 100 | } 101 | 102 | if (*selected_v_item != v_item_count - 1) 103 | { 104 | (*selected_v_item)++; 105 | } 106 | else 107 | { 108 | *selected_v_item = last_active; 109 | } 110 | } while (items && items[*selected_v_item].disabled); 111 | 112 | return INPUT_PROC_NONE; 113 | } 114 | 115 | if (testKey(key1, key2, KEY_UP)) 116 | { 117 | uint8_t last_active = *selected_v_item; 118 | 119 | // Skip disabled items 120 | // This will cause an endless loop if every item is disabled 121 | do 122 | { 123 | if (items && !items[*selected_v_item].disabled) 124 | { 125 | last_active = *selected_v_item; 126 | } 127 | 128 | if (*selected_v_item != 0) 129 | { 130 | (*selected_v_item)--; 131 | } 132 | else 133 | { 134 | *selected_v_item = last_active; 135 | } 136 | } while (items && items[*selected_v_item].disabled); 137 | 138 | return INPUT_PROC_NONE; 139 | } 140 | 141 | if (testKey(key1, key2, KEY_LEFT)) 142 | { 143 | if (*(selected_h_item[*selected_v_item]) != 0) 144 | { 145 | (*(selected_h_item[*selected_v_item]))--; 146 | 147 | // Select first item on tab 148 | if (reset_v_item) 149 | { 150 | *selected_v_item = 0; 151 | } 152 | } 153 | 154 | return INPUT_PROC_NONE; 155 | } 156 | 157 | if (testKey(key1, key2, KEY_RIGHT)) 158 | { 159 | if (selected_h_item[*selected_v_item] && *(selected_h_item[*selected_v_item]) != (h_item_count[*selected_v_item] - 1)) 160 | { 161 | (*(selected_h_item[*selected_v_item]))++; 162 | 163 | // Select first item on tab 164 | if (reset_v_item) 165 | { 166 | *selected_v_item = 0; 167 | } 168 | } 169 | 170 | return INPUT_PROC_NONE; 171 | } 172 | 173 | // Execute item action 174 | if (testKey(key1, key2, KEY_EXE)) 175 | { 176 | return INPUT_PROC_EXECUTE; 177 | } 178 | 179 | if (testKey(key1, key2, KEY_NEGATIVE)) 180 | { 181 | return INPUT_PROC_CLOSE; 182 | } 183 | 184 | return INPUT_PROC_NONE; 185 | } 186 | -------------------------------------------------------------------------------- /src/core/controls.cpp: -------------------------------------------------------------------------------- 1 | #include "controls.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "preferences.h" 7 | #include "emulator.h" 8 | #include "error.h" 9 | #include "../helpers/fileio.h" 10 | #include "../helpers/functions.h" 11 | #include "../helpers/ini.h" 12 | #include "../helpers/macros.h" 13 | 14 | #define CONTROLS_INI_VAR_NAME "Controls" 15 | 16 | #define CONTROLS_INI_SECTION_NAME "Ctrls" 17 | #define CONTROLS_INI_KEY_SEPERATOR '_' 18 | 19 | void set_controls_defaults(emu_controls *controls) 20 | { 21 | (*controls)[GB_KEY_A][0] = DEFAULT_GB_KEY_A_0; 22 | (*controls)[GB_KEY_A][1] = DEFAULT_GB_KEY_A_1; 23 | (*controls)[GB_KEY_B][0] = DEFAULT_GB_KEY_B_0; 24 | (*controls)[GB_KEY_B][1] = DEFAULT_GB_KEY_B_1; 25 | (*controls)[GB_KEY_START][0] = DEFAULT_GB_KEY_START_0; 26 | (*controls)[GB_KEY_START][1] = DEFAULT_GB_KEY_START_1; 27 | (*controls)[GB_KEY_SELECT][0] = DEFAULT_GB_KEY_SELECT_0; 28 | (*controls)[GB_KEY_SELECT][1] = DEFAULT_GB_KEY_SELECT_1; 29 | (*controls)[GB_KEY_UP][0] = DEFAULT_GB_KEY_UP_0; 30 | (*controls)[GB_KEY_UP][1] = DEFAULT_GB_KEY_UP_1; 31 | (*controls)[GB_KEY_DOWN][0] = DEFAULT_GB_KEY_DOWN_0; 32 | (*controls)[GB_KEY_DOWN][1] = DEFAULT_GB_KEY_DOWN_1; 33 | (*controls)[GB_KEY_LEFT][0] = DEFAULT_GB_KEY_LEFT_0; 34 | (*controls)[GB_KEY_LEFT][1] = DEFAULT_GB_KEY_LEFT_1; 35 | (*controls)[GB_KEY_RIGHT][0] = DEFAULT_GB_KEY_RIGHT_0; 36 | (*controls)[GB_KEY_RIGHT][1] = DEFAULT_GB_KEY_RIGHT_1; 37 | } 38 | 39 | uint8_t process_controls_ini(char *ini_string, uint32_t len, emu_controls *controls) 40 | { 41 | ini_file file; 42 | ini_parse(ini_string, len, &file); 43 | 44 | ini_section *section = find_section(&file, CONTROLS_INI_SECTION_NAME); 45 | 46 | if (!section) 47 | { 48 | free_ini_file(&file); 49 | return 1; 50 | } 51 | 52 | for (uint8_t i = 0; i < GB_KEY_COUNT; i++) 53 | { 54 | char a = '0' + i; 55 | char key_name[4] = { a, CONTROLS_INI_KEY_SEPERATOR, '0', '\0' }; 56 | 57 | // Find the key for the first int of the controls array 58 | ini_key *key_0 = find_key(section, key_name); 59 | 60 | if (!key_0) 61 | { 62 | free_ini_file(&file); 63 | return 1; 64 | } 65 | 66 | (*controls)[i][0] = key_0->value_int; 67 | 68 | key_name[2]++; 69 | 70 | // Find the key for the second int of the controls array 71 | ini_key *key_1 = find_key(section, key_name); 72 | 73 | if (!key_1) 74 | { 75 | free_ini_file(&file); 76 | return 1; 77 | } 78 | 79 | (*controls)[i][1] = key_1->value_int; 80 | } 81 | 82 | free_ini_file(&file); 83 | 84 | return 0; 85 | } 86 | 87 | char *create_controls_ini(emu_controls *controls, char *ini_string, uint32_t len) 88 | { 89 | ini_file file; 90 | file.section_count = 0; 91 | file.sections_size = 0; 92 | 93 | ini_section *controls_section = add_section(&file, CONTROLS_INI_SECTION_NAME); 94 | 95 | if (!controls_section) 96 | { 97 | free_ini_file(&file); 98 | return nullptr; 99 | } 100 | 101 | for (uint8_t i = 0; i < GB_KEY_COUNT; i++) 102 | { 103 | char a = '0' + i; 104 | char key_name[] = { a, CONTROLS_INI_KEY_SEPERATOR, '0', '\0' }; 105 | 106 | if (!add_key( 107 | controls_section, 108 | key_name, 109 | INI_TYPE_INT, 110 | (*controls)[i][0] 111 | )) 112 | { 113 | free_ini_file(&file); 114 | return nullptr; 115 | } 116 | 117 | key_name[2]++; 118 | 119 | if (!add_key( 120 | controls_section, 121 | key_name, 122 | INI_TYPE_INT, 123 | (*controls)[i][1] 124 | )) 125 | { 126 | free_ini_file(&file); 127 | return nullptr; 128 | } 129 | } 130 | 131 | ini_write(&file, ini_string, len); 132 | free_ini_file(&file); 133 | 134 | return ini_string; 135 | } 136 | 137 | uint8_t load_controls(struct gb_s *gb) 138 | { 139 | emu_controls *controls = &(((emu_preferences *)(gb->direct.priv))->controls); 140 | uint32_t size; 141 | char *ini_string; 142 | 143 | set_controls_defaults(controls); 144 | 145 | if (read_mcs(MCS_DIRECTORY, CONTROLS_INI_VAR_NAME, (void **)&ini_string, &size) == 0) 146 | { 147 | if (process_controls_ini(ini_string, size, controls) == 0) 148 | { 149 | return 0; 150 | } 151 | } 152 | 153 | // Restore defaults when read failed 154 | set_controls_defaults(controls); 155 | return 1; 156 | } 157 | 158 | uint8_t save_controls(struct gb_s *gb) 159 | { 160 | emu_controls *controls = &(((emu_preferences *)(gb->direct.priv))->controls); 161 | char ini_string[INI_MAX_CONTENT_LEN]; 162 | 163 | create_controls_ini(controls, ini_string, sizeof(ini_string)); 164 | 165 | uint32_t size = align_val(strlen(ini_string), 4); 166 | 167 | return write_mcs(MCS_DIRECTORY, CONTROLS_INI_VAR_NAME, ini_string, size); 168 | } 169 | -------------------------------------------------------------------------------- /src/helpers/fileio.cpp: -------------------------------------------------------------------------------- 1 | #include "fileio.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../core/error.h" 8 | #include "functions.h" 9 | 10 | uint8_t _write_mcs(const char *dir, const char *name, void *buf, size_t size, 11 | const char *err_file, uint32_t err_line) 12 | { 13 | int32_t ret = MCS_SetVariable(dir, name, VARTYPE_STR, size, buf); 14 | 15 | if (ret != 0) 16 | { 17 | char err_info[100] = "mcs\\"; 18 | char tmp[10]; 19 | 20 | itoa(ret, tmp, 16); 21 | 22 | strlcat(err_info, dir, sizeof(err_info)); 23 | strlcat(err_info, "\\", sizeof(err_info)); 24 | strlcat(err_info, name, sizeof(err_info)); 25 | strlcat(err_info, ": 0x", sizeof(err_info)); 26 | strlcat(err_info, tmp, sizeof(err_info)); 27 | 28 | _set_error(EFWRITE, err_file, err_line, err_info); 29 | return 1; 30 | } 31 | 32 | return 0; 33 | } 34 | 35 | uint8_t _read_mcs(const char *dir, const char *name, void **buf, uint32_t *size, 36 | const char *err_file, uint32_t err_line) 37 | { 38 | char *name2; 39 | uint8_t var_type; 40 | 41 | int32_t ret = MCS_GetVariable(dir, name, &var_type, &name2, buf, size); 42 | 43 | if (ret != 0) 44 | { 45 | char err_info[100] = "mcs\\"; 46 | char tmp[10]; 47 | 48 | itoa(ret, tmp, 16); 49 | 50 | strlcat(err_info, dir, sizeof(err_info)); 51 | strlcat(err_info, "\\", sizeof(err_info)); 52 | strlcat(err_info, name, sizeof(err_info)); 53 | strlcat(err_info, ": 0x", sizeof(err_info)); 54 | strlcat(err_info, tmp, sizeof(err_info)); 55 | 56 | _set_error(EFREAD, err_file, err_line, err_info); 57 | return 1; 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | uint8_t _write_file(const char *file, void *buf, size_t len, const char *err_file, uint32_t err_line) 64 | { 65 | int32_t fd = open(file, OPEN_WRITE | OPEN_CREATE); 66 | 67 | char err_info[ERROR_MAX_INFO_LEN]; 68 | strlcpy(err_info, "w: ", sizeof(err_info)); 69 | strlcat(err_info, file, sizeof(err_info)); 70 | 71 | if (fd < 0) 72 | { 73 | _set_error(EFOPEN, err_file, err_line, err_info); 74 | return 1; 75 | } 76 | 77 | if (write(fd, buf, len) < 0) 78 | { 79 | close(fd); 80 | 81 | _set_error(EFWRITE, err_file, err_line, err_info); 82 | return 1; 83 | } 84 | 85 | if (close(fd) < 0) 86 | { 87 | _set_error(EFCLOSE, err_file, err_line, err_info); 88 | return 1; 89 | } 90 | 91 | return 0; 92 | } 93 | 94 | uint8_t _read_file(const char *file, void *buf, size_t len, const char *err_file, uint32_t err_line) 95 | { 96 | int32_t fd = open(file, OPEN_READ); 97 | 98 | char err_info[ERROR_MAX_INFO_LEN]; 99 | strlcpy(err_info, "r: ", sizeof(err_info)); 100 | strlcat(err_info, file, sizeof(err_info)); 101 | 102 | if (fd < 0) 103 | { 104 | _set_error(EFOPEN, err_file, err_line, err_info); 105 | return 1; 106 | } 107 | 108 | if (read(fd, buf, len) < 0) 109 | { 110 | close(fd); 111 | 112 | _set_error(EFREAD, err_file, err_line, err_info); 113 | return 1; 114 | } 115 | 116 | if (close(fd) < 0) 117 | { 118 | _set_error(EFCLOSE, err_file, err_line, err_info); 119 | return 1; 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | uint8_t _delete_file(const char *file, const char *err_file, uint32_t err_line) 126 | { 127 | if (remove(file) < 0) 128 | { 129 | _set_error(EFCLOSE, err_file, err_line, file); 130 | return 1; 131 | } 132 | 133 | return 0; 134 | } 135 | 136 | uint8_t _get_file_size(const char *file, size_t *size, const char *err_file, uint32_t err_line) 137 | { 138 | int32_t fd = open(file, OPEN_READ); 139 | 140 | char err_info[ERROR_MAX_INFO_LEN]; 141 | strlcpy(err_info, "r: ", sizeof(err_info)); 142 | strlcat(err_info, file, sizeof(err_info)); 143 | 144 | if (fd < 0) 145 | { 146 | _set_error(EFOPEN, err_file, err_line, err_info); 147 | return 1; 148 | } 149 | 150 | struct stat file_stat; 151 | 152 | if (fstat(fd, &file_stat) < 0) 153 | { 154 | _set_error(EFREAD, err_file, err_line, err_info); 155 | return 1; 156 | } 157 | 158 | if (close(fd) < 0) 159 | { 160 | _set_error(EFCLOSE, err_file, err_line, err_info); 161 | return 1; 162 | } 163 | 164 | *size = file_stat.fileSize; 165 | 166 | return 0; 167 | } 168 | 169 | uint8_t find_files(const char *path, char (*buf)[MAX_FILENAME_LEN], uint8_t max) 170 | { 171 | if (max == 0) 172 | { 173 | return 0; 174 | } 175 | 176 | wchar_t wpath[MAX_FILENAME_LEN]; 177 | wchar_t filename[MAX_FILENAME_LEN]; 178 | struct findInfo info; 179 | int handle; 180 | int32_t ret; 181 | uint8_t file_count = 0; 182 | 183 | char_to_wchar(wpath, path); 184 | 185 | ret = findFirst(wpath, &handle, filename, &info); 186 | 187 | while(ret >= 0) 188 | { 189 | // Check if this is a file 190 | if (info.type == info.EntryTypeFile) 191 | { 192 | // Check if filename begins with . (is hidden) 193 | if (filename[0] != '.') 194 | { 195 | // Copy file name 196 | wchar_to_char(buf[file_count], filename); 197 | file_count++; 198 | } 199 | } 200 | 201 | //serch the next 202 | ret = findNext(handle, filename, &info); 203 | } 204 | 205 | findClose(handle); 206 | 207 | return file_count; 208 | } 209 | -------------------------------------------------------------------------------- /src/cas/cpu/dmac.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../../helpers/macros.h" 5 | 6 | // Configuration Enums 7 | enum dmac_dmaor_cms 8 | { 9 | CMS_NORMAL = 0x0, 10 | INTERMITTENT_16 = 0x2, 11 | INTERMITTENT_64 = 0x3, 12 | INTERMITTENT_256 = 0x4, 13 | }; 14 | 15 | enum dmac_dmaor_pr 16 | { 17 | PRIORITY_0 = 0x0, // CH0 > CH1 > CH2 > CH3 > CH4 > CH5 18 | PRIORITY_1 = 0x1, // CH0 > CH2 > CH3 > CH1 > CH4 > CH5 19 | ROUND_ROBIN = 0x3, 20 | }; 21 | 22 | // CHCR 23 | enum dmac_chcr_rpt 24 | { 25 | REPEAT_NORMAL = 0x0, 26 | REPEAT_SAR_DAR_TCR = 0x1, 27 | REPEAT_DAR_TCR = 0x2, 28 | REPEAT_SAR_TCR = 0x3, 29 | RELOAD_SAR_DAR_TCR = 0x5, 30 | RELOAD_DAR_TCR = 0x6, 31 | RELOAD_SAR_TCR = 0x7, 32 | }; 33 | 34 | enum dmac_chcr_am 35 | { 36 | READ_CYCLE = 0x0, 37 | WRITE_CYCLE = 0x1, 38 | }; 39 | 40 | enum dmac_chcr_al 41 | { 42 | ACTIVE_LOW = 0x0, 43 | ACTIVE_HIGH = 0x1, 44 | }; 45 | 46 | enum dmac_chcr_ts_0 47 | { 48 | SIZE_1_0 = 0x0, 49 | SIZE_2_0 = 0x1, 50 | SIZE_4_0 = 0x2, 51 | SIZE_16_0 = 0x3, 52 | SIZE_32_0 = 0x0, 53 | SIZE_8_0 = 0x3, 54 | SIZE_8x2_0 = 0x3, 55 | SIZE_16x2_0 = 0x0, 56 | }; 57 | 58 | enum dmac_chcr_ts_1 59 | { 60 | SIZE_1_1 = 0x0, 61 | SIZE_2_1 = 0x0, 62 | SIZE_4_1 = 0x0, 63 | SIZE_16_1 = 0x0, 64 | SIZE_32_1 = 0x1, 65 | SIZE_8_1 = 0x1, 66 | SIZE_8x2_1 = 0x2, 67 | SIZE_16x2_1 = 0x3, 68 | }; 69 | 70 | enum dmac_chcr_dm 71 | { 72 | DAR_FIXED_SOFT = 0x0, // Address is fixed, but will be incremented in 16/32-byte division 73 | // transfer mode. Check SH7730 User's Manual Figure 12.3.7 74 | DAR_INCREMENT = 0x1, 75 | DAR_DECREMENT = 0x2, // Prohibited in 8/16/32-byte transfer mode 76 | DAR_FIXED_HARD = 0x3, // Address is fixed, even in 16/32-byte division transfer mode. 77 | }; 78 | 79 | enum dmac_chcr_sm 80 | { 81 | SAR_FIXED_SOFT = 0x0, // Address is fixed, but will be incremented in 16/32-byte division 82 | // transfer mode. Check SH7730 User's Manual Figure 12.3.7 83 | SAR_INCREMENT = 0x1, 84 | SAR_DECREMENT = 0x2, // Prohibited in 8/16/32-byte transfer mode 85 | SAR_FIXED_HARD = 0x3, // Address is fixed, even in 16/32-byte division transfer mode. 86 | }; 87 | 88 | enum dmac_chcr_rs 89 | { 90 | EXTERNAL = 0x0, 91 | AUTO = 0x4, 92 | DMARS = 0x8, 93 | }; 94 | 95 | enum dmac_chcr_dlds 96 | { 97 | LOW_LEVEL = 0x0, 98 | FALLING_EDGE = 0x1, 99 | HIGH_LEVEL = 0x2, 100 | RISING_EDGE = 0x3, 101 | }; 102 | 103 | enum dmac_chcr_tb 104 | { 105 | CYCLE_STEAL = 0x0, 106 | BURST = 0x1, 107 | }; 108 | 109 | union dmac_chcr 110 | { 111 | struct 112 | { 113 | uint32_t _reserved0 : 1; 114 | uint32_t LCKN : 1; // Bus Release Enable in Cycle Steal Mode 115 | uint32_t _reserved1 : 2; 116 | dmac_chcr_rpt RPT : 3; // DMA Settings Renewal Specify 117 | uint32_t _reserved2 : 1; 118 | uint32_t DO : 1; // DMA Overrun 119 | uint32_t _reserved3 : 1; 120 | dmac_chcr_ts_1 TS_1 : 2; // DMA Transfer Size Specify (MS 2 bits) 121 | uint32_t HE : 1; // Half End Flag 122 | uint32_t HIE : 1; // Half End Interrupt Enable 123 | dmac_chcr_am AM : 1; // Acknowledge Mode 124 | dmac_chcr_al AL : 1; // Acknowledge Level 125 | dmac_chcr_dm DM : 2; // Destination Address Mode 126 | dmac_chcr_sm SM : 2; // Source Address Mode 127 | dmac_chcr_rs RS : 4; // Resource Select 128 | dmac_chcr_dlds DLDS : 2; // DREQ Level and Edge Select 129 | dmac_chcr_tb TB : 1; // Transfer Bus Mode 130 | dmac_chcr_ts_0 TS_0 : 2; // DMA Transfer Size Specify (LS 2 bits) 131 | uint32_t IE : 1; // Interrupt Enable 132 | uint32_t TE : 1; // Transfer End Flag 133 | uint32_t DE : 1; // DMA Enable 134 | }; 135 | uint32_t raw; 136 | }; 137 | 138 | union dmac_dmaor 139 | { 140 | struct 141 | { 142 | dmac_dmaor_cms CMS : 4; // Cycle Steal Mode 143 | uint16_t _reserved0 : 2; 144 | dmac_dmaor_pr PR : 2; // Priority Mode 145 | uint16_t _reserved1 : 5; 146 | uint16_t AE : 1; // Address Error Flag 147 | uint16_t NMIF : 1; // NMI Flag 148 | uint16_t DME : 1; // DMA Master Enable 149 | }; 150 | uint16_t raw; 151 | }; 152 | 153 | // Channel 0 154 | #define DMAC_SAR_0 ((volatile uint32_t *)0xFE008020) 155 | #define DMAC_DAR_0 ((volatile uint32_t *)0xFE008024) 156 | #define DMAC_TCR_0 ((volatile uint32_t *)0xFE008028) 157 | #define DMAC_CHCR_0 ((volatile dmac_chcr *)0xFE00802C) 158 | 159 | // Channel 1 160 | #define DMAC_SAR_1 ((volatile uint32_t *)0xFE008030) 161 | #define DMAC_DAR_1 ((volatile uint32_t *)0xFE008034) 162 | #define DMAC_TCR_1 ((volatile uint32_t *)0xFE008038) 163 | #define DMAC_CHCR_1 ((volatile dmac_chcr *)0xFE00803C) 164 | 165 | // Common 166 | #define DMAC_DMAOR ((volatile dmac_dmaor *)0xFE008060) 167 | 168 | // Channel 0 B-Registers 169 | #define DMAC_SARB_0 ((volatile uint32_t *)0xFE008120) 170 | #define DMAC_DARB_0 ((volatile uint32_t *)0xFE008124) 171 | #define DMAC_TCRB_0 ((volatile uint32_t *)0xFE008128) 172 | 173 | // Channel 1 B-Registers 174 | #define DMAC_SARB_1 ((volatile uint32_t *)0xFE008130) 175 | #define DMAC_DARB_1 ((volatile uint32_t *)0xFE008134) 176 | #define DMAC_TCRB_1 ((volatile uint32_t *)0xFE008138) 177 | 178 | /** 179 | * Waits for a DMA operation to complete on a channel. 180 | * 181 | * @param chcr The control register of the channel to wait for. 182 | * 183 | * @return Returns true on successful operation and false for an address error. 184 | */ 185 | inline bool dma_wait(volatile dmac_chcr *chcr) 186 | { 187 | // Check if DMA was never running 188 | if (unlikely(!chcr->DE || !DMAC_DMAOR->DME)) 189 | { 190 | return true; 191 | } 192 | 193 | for (;;) 194 | { 195 | if (DMAC_DMAOR->AE) 196 | { 197 | // Address error 198 | DMAC_DMAOR->AE = 0; 199 | return false; 200 | } 201 | 202 | if (chcr->TE) 203 | { 204 | // Success 205 | return true; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/core/preferences.cpp: -------------------------------------------------------------------------------- 1 | #include "preferences.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../helpers/fileio.h" 8 | #include "../helpers/ini.h" 9 | #include "../helpers/functions.h" 10 | #include "../helpers/macros.h" 11 | #include "error.h" 12 | #include "emulator.h" 13 | 14 | #define CONFIG_VAR_CONSTANT "C_" 15 | 16 | #define CONFIG_INI_SECTION_NAME "Cfg" 17 | #define CONFIG_INI_INTERLACE_ENABLE_KEY "interl_en" 18 | #define CONFIG_INI_FRAMESKIP_ENABLE_KEY "fs_en" 19 | #define CONFIG_INI_FRAMESKIP_AMOUNT_KEY "fs_am" 20 | #define CONFIG_INI_EMU_SPEED_KEY "emu_spd" 21 | #define CONFIG_INI_OVERCLOCK_ENABLE_KEY "oclk_en" 22 | #define CONFIG_INI_SELECTED_PALETTE_KEY "sel_pal" 23 | 24 | char *get_rom_config_var_name(emu_preferences *preferences, char *name_buffer) 25 | { 26 | char *str = preferences->current_rom_name; 27 | 28 | // Check if this rom has a name 29 | if (strcmp(preferences->current_rom_name, "") == 0) 30 | { 31 | str = preferences->current_filename; 32 | } 33 | 34 | strcpy(name_buffer, CONFIG_VAR_CONSTANT); 35 | itoa_leading_zeros( 36 | hash_string(str, 0xFFFFFF), 37 | name_buffer + (sizeof(CONFIG_VAR_CONSTANT) - 1), 38 | 16, 39 | 6 40 | ); 41 | 42 | return name_buffer; 43 | } 44 | 45 | struct gb_s *set_config_defaults(struct gb_s *gb) 46 | { 47 | emu_preferences *prefs = (emu_preferences *)(gb->direct.priv); 48 | 49 | set_interlacing(gb, DEFAULT_INTERLACE_ENABLE); 50 | set_frameskip(gb, DEFAULT_FRAMESKIP_ENABLE, DEFAULT_FRAMESKIP_AMOUNT); 51 | set_emu_speed(gb, DEFAULT_EMU_SPEED); 52 | set_overclock(gb, DEFAULT_OVERCLOCK_ENABLE); 53 | 54 | prefs->config.selected_palette = DEFAULT_SELECTED_PALETTE; 55 | 56 | return gb; 57 | } 58 | 59 | uint8_t process_config_ini(char *ini_string, uint32_t len, struct gb_s *gb) 60 | { 61 | emu_preferences *prefs = (emu_preferences *)(gb->direct.priv); 62 | 63 | ini_file file; 64 | ini_parse(ini_string, len, &file); 65 | 66 | ini_section *section = find_section(&file, CONFIG_INI_SECTION_NAME); 67 | 68 | if (!section) 69 | { 70 | free_ini_file(&file); 71 | return 1; 72 | } 73 | 74 | ini_key *interl_en = find_key(section, CONFIG_INI_INTERLACE_ENABLE_KEY); 75 | ini_key *fs_en = find_key(section, CONFIG_INI_FRAMESKIP_ENABLE_KEY); 76 | ini_key *fs_amount = find_key(section, CONFIG_INI_FRAMESKIP_AMOUNT_KEY); 77 | ini_key *emu_speed = find_key(section, CONFIG_INI_EMU_SPEED_KEY); 78 | ini_key *oclk_en = find_key(section, CONFIG_INI_OVERCLOCK_ENABLE_KEY); 79 | ini_key *sel_pal = find_key(section, CONFIG_INI_SELECTED_PALETTE_KEY); 80 | 81 | if (fs_en && fs_amount) 82 | { 83 | set_frameskip(gb, fs_en->value_int, fs_amount->value_int); 84 | } 85 | 86 | if (interl_en) 87 | { 88 | set_interlacing(gb, false); // Interlacing is currently not working and therefore defaults to disabled 89 | // set_interlacing(gb, interl_en->value_int); 90 | } 91 | 92 | if (emu_speed) 93 | { 94 | set_emu_speed(gb, emu_speed->value_int); 95 | } 96 | 97 | if (oclk_en) 98 | { 99 | set_overclock(gb, oclk_en->value_int); 100 | } 101 | 102 | if (sel_pal) 103 | { 104 | prefs->config.selected_palette = sel_pal->value_int; 105 | } 106 | 107 | free_ini_file(&file); 108 | 109 | return 0; 110 | } 111 | 112 | char *create_config_ini(rom_config *config, char *ini_string, uint32_t len) 113 | { 114 | ini_file file; 115 | file.section_count = 0; 116 | file.sections_size = 0; 117 | file.sections = nullptr; 118 | 119 | ini_section *config_section = add_section(&file, CONFIG_INI_SECTION_NAME); 120 | 121 | if (!config_section) 122 | { 123 | free_ini_file(&file); 124 | return nullptr; 125 | } 126 | 127 | if (!add_key( 128 | config_section, 129 | CONFIG_INI_INTERLACE_ENABLE_KEY, 130 | INI_TYPE_INT, 131 | config->interlacing_enabled 132 | )) 133 | { 134 | free_ini_file(&file); 135 | return nullptr; 136 | } 137 | 138 | if (!add_key( 139 | config_section, 140 | CONFIG_INI_FRAMESKIP_ENABLE_KEY, 141 | INI_TYPE_INT, 142 | config->frameskip_enabled 143 | )) 144 | { 145 | free_ini_file(&file); 146 | return nullptr; 147 | } 148 | 149 | if (!add_key( 150 | config_section, 151 | CONFIG_INI_FRAMESKIP_AMOUNT_KEY, 152 | INI_TYPE_INT, 153 | config->frameskip_amount 154 | )) 155 | { 156 | free_ini_file(&file); 157 | return nullptr; 158 | } 159 | 160 | if (!add_key( 161 | config_section, 162 | CONFIG_INI_EMU_SPEED_KEY, 163 | INI_TYPE_INT, 164 | config->emulation_speed 165 | )) 166 | { 167 | free_ini_file(&file); 168 | return nullptr; 169 | } 170 | 171 | if (!add_key( 172 | config_section, 173 | CONFIG_INI_OVERCLOCK_ENABLE_KEY, 174 | INI_TYPE_INT, 175 | config->overclock_enabled 176 | )) 177 | { 178 | free_ini_file(&file); 179 | return nullptr; 180 | } 181 | 182 | if (!add_key( 183 | config_section, 184 | CONFIG_INI_SELECTED_PALETTE_KEY, 185 | INI_TYPE_INT, 186 | config->selected_palette 187 | )) 188 | { 189 | free_ini_file(&file); 190 | return nullptr; 191 | } 192 | 193 | ini_write(&file, ini_string, len); 194 | free_ini_file(&file); 195 | 196 | return ini_string; 197 | } 198 | 199 | uint8_t load_rom_config(struct gb_s *gb) 200 | { 201 | emu_preferences *preferences = (emu_preferences *)(gb->direct.priv); 202 | 203 | uint32_t size; 204 | char *ini_string; 205 | char var_name[MAX_FILENAME_LEN]; 206 | 207 | get_rom_config_var_name(preferences, var_name); 208 | set_config_defaults(gb); 209 | 210 | if (read_mcs(MCS_DIRECTORY, var_name, (void **)&ini_string, &size) == 0) 211 | { 212 | if (process_config_ini(ini_string, size, gb) == 0) 213 | { 214 | return 0; 215 | } 216 | } 217 | 218 | // Restore defaults if read failed 219 | set_config_defaults(gb); 220 | return 1; 221 | } 222 | 223 | uint8_t save_rom_config(struct gb_s *gb) 224 | { 225 | emu_preferences *preferences = (emu_preferences *)(gb->direct.priv); 226 | 227 | char var_name[MAX_FILENAME_LEN]; 228 | char ini_string[INI_MAX_CONTENT_LEN]; 229 | 230 | get_rom_config_var_name(preferences, var_name); 231 | 232 | if (!create_config_ini(&(preferences->config), ini_string, INI_MAX_CONTENT_LEN)) 233 | { 234 | return 1; 235 | } 236 | 237 | uint32_t size = align_val(strlen(ini_string), 4); 238 | 239 | return write_mcs(MCS_DIRECTORY, var_name, ini_string, size); 240 | } 241 | -------------------------------------------------------------------------------- /src/emu_ui/components.cpp: -------------------------------------------------------------------------------- 1 | #include "components.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../emu_ui/input.h" 8 | #include "../emu_ui/font.h" 9 | #include "../emu_ui/effects.h" 10 | #include "../emu_ui/colors.h" 11 | #include "../helpers/macros.h" 12 | 13 | // Use namespace because of conflicting function declarations in mem.hpp and string.h 14 | namespace hhk 15 | { 16 | #include 17 | } 18 | 19 | #define CAS_LCD_WIDTH 320 20 | #define CAS_LCD_HEIGHT 528 21 | 22 | #define SLIDER_HANDLE_WIDTH 4 23 | #define SLIDER_TRACK_HEIGHT 2 24 | #define SLIDER_Y_OFFSET 1 25 | 26 | #define TEXT_ALERT_MIN_WIDTH 100 27 | #define TEXT_ALERT_MAX_CHAR 40 28 | #define TEXT_ALERT_MAX_WIDTH ((2 * ALERT_CONTENT_OFFSET_X) + ((DEBUG_CHAR_WIDTH - 2) * TEXT_ALERT_MAX_CHAR)) 29 | #define TEXT_ALERT_MIN_HEIGTH (STD_CONTENT_OFFSET * 4) + (DEBUG_LINE_HEIGHT * 2) 30 | #define TEXT_ALERT_MAX_HEIGHT (CAS_LCD_HEIGHT - 60) 31 | 32 | #define MAX_ALERT_MESSAGE_LEN 300 33 | 34 | int string_split_newline(char *dest, size_t len, const char *src, uint16_t max_chars) 35 | { 36 | const char *last_space = nullptr; 37 | const char *prev = src; 38 | 39 | uint16_t line = 1; 40 | 41 | // Clear destination buffer 42 | memset(dest, 0, len); 43 | 44 | for (const char *p = src; *p != '\0'; p++) 45 | { 46 | // Check if there is still enough space to hold current line 47 | if (len < (size_t)(p - prev)) { 48 | return line; 49 | } 50 | 51 | // Check if line has reached max_chars 52 | if ((p - prev) == (max_chars - 1)) 53 | { 54 | // Check if space was found 55 | if (!last_space) { 56 | last_space = p; 57 | } 58 | 59 | last_space++; 60 | 61 | // Write line to destination buffer when max chars is reached 62 | strncat(dest, prev, last_space - prev); 63 | strcat(dest, "\n"); 64 | 65 | len -= (last_space - prev); 66 | prev = last_space; 67 | last_space = nullptr; 68 | line++; 69 | continue; 70 | } 71 | 72 | if (*p == ' ') 73 | { 74 | last_space = p; 75 | } 76 | else if (*p == '\n') 77 | { 78 | last_space = p + 1; 79 | strncat(dest, prev, last_space - prev); 80 | 81 | len -= (last_space - prev); 82 | prev = last_space; 83 | last_space = nullptr; 84 | line++; 85 | continue; 86 | } 87 | } 88 | 89 | // Handle last line 90 | strlcat(dest, prev, len); 91 | 92 | return line; 93 | } 94 | 95 | void draw_rectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height, 96 | uint16_t color, uint16_t border, uint16_t border_color) 97 | { 98 | uint16_t max_x = x + width; 99 | uint16_t max_y = y + height; 100 | uint16_t border_left = x + border; 101 | uint16_t border_right = max_x - border; 102 | uint16_t border_top = y + border; 103 | uint16_t border_bottom = max_y - border; 104 | 105 | for (uint16_t iy = y; iy < max_y; iy++) 106 | { 107 | for (uint16_t ix = x; ix < max_x; ix++) 108 | { 109 | if(iy >= CAS_LCD_HEIGHT || ix >= CAS_LCD_WIDTH) 110 | continue; 111 | 112 | uint16_t pixel_color = color; 113 | 114 | // Check if on border 115 | if ( 116 | ix < border_left || 117 | ix >= border_right || 118 | iy < border_top || 119 | iy >= border_bottom 120 | ) 121 | { 122 | pixel_color = border_color; 123 | } 124 | 125 | vram[(iy * CAS_LCD_WIDTH) + ix] = pixel_color; 126 | } 127 | } 128 | } 129 | 130 | void draw_slider(uint16_t x, uint16_t y, uint16_t width, uint16_t track_color, 131 | uint16_t handle_color, uint16_t min_value, uint16_t max_value, uint16_t value) 132 | { 133 | const uint16_t track_width = width - SLIDER_HANDLE_WIDTH; 134 | 135 | // Calculate handle position (Fixed point arithmetic) 136 | const uint16_t handle_offset = ((((value - min_value) * 0x10000) / (max_value - min_value)) * track_width) / 0x10000; 137 | 138 | // Draw track 139 | draw_rectangle( 140 | x + (SLIDER_HANDLE_WIDTH / 2), 141 | y + (SLIDER_HANDLE_HEIGHT / 2) - (SLIDER_TRACK_HEIGHT / 2) + SLIDER_Y_OFFSET, 142 | track_width, 143 | SLIDER_TRACK_HEIGHT, 144 | track_color, 145 | 0, 146 | 0 147 | ); 148 | 149 | // Draw handle 150 | draw_rectangle( 151 | x + handle_offset, 152 | y + SLIDER_Y_OFFSET, 153 | SLIDER_HANDLE_WIDTH, 154 | SLIDER_HANDLE_HEIGHT, 155 | handle_color, 156 | 0, 157 | 0 158 | ); 159 | } 160 | 161 | uint32_t draw_alert_box(const char *title, const char *subtitle, uint16_t width, 162 | uint16_t height, uint16_t background, uint16_t border) 163 | { 164 | const uint16_t x_pos = (CAS_LCD_WIDTH - width) / 2; 165 | const uint16_t y_pos = (CAS_LCD_HEIGHT - height) / 2; 166 | 167 | draw_rectangle( 168 | x_pos, 169 | y_pos, 170 | width, 171 | height, 172 | background, 173 | 1, 174 | border 175 | ); 176 | 177 | // Print title 178 | if (title) 179 | { 180 | print_string_centered( 181 | title, 182 | x_pos, 183 | y_pos + STD_CONTENT_OFFSET, 184 | width, 185 | 0, 186 | border, 187 | COLOR_BLACK, 188 | true 189 | ); 190 | } 191 | 192 | // Print subtitle 193 | if (subtitle) 194 | { 195 | print_string_centered( 196 | subtitle, 197 | x_pos, 198 | y_pos + STD_CONTENT_OFFSET + DEBUG_LINE_HEIGHT, 199 | width, 200 | 0, 201 | COLOR_DISABLED, 202 | COLOR_BLACK, 203 | true 204 | ); 205 | } 206 | 207 | // Return x and y pos as uint32_t 208 | return ((uint32_t)y_pos <<16) | ((uint32_t)x_pos & 0xFFFF); 209 | } 210 | 211 | void ok_alert(const char *title, const char *subtitle, const char *text, uint16_t foreground, 212 | uint16_t background, uint16_t border) 213 | { 214 | // Backup LCD and darken background 215 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 216 | 217 | if (lcd_backup) 218 | { 219 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 220 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 221 | } 222 | 223 | const uint16_t text_len = strlen(text); 224 | 225 | // Calculate alert size 226 | uint16_t width = text_len * (DEBUG_CHAR_WIDTH - 2) + (ALERT_CONTENT_OFFSET_X * 2); 227 | uint16_t height = TEXT_ALERT_MIN_HEIGTH + ((subtitle)? DEBUG_LINE_HEIGHT : 0); 228 | 229 | uint8_t lines = 1; 230 | 231 | if (width < TEXT_ALERT_MIN_WIDTH) 232 | { 233 | width = TEXT_ALERT_MIN_WIDTH; 234 | } 235 | 236 | char message[MAX_ALERT_MESSAGE_LEN]; 237 | lines = string_split_newline(message, sizeof(message), text, TEXT_ALERT_MAX_CHAR); 238 | 239 | // Calculate height when above max characters per line 240 | if (width > TEXT_ALERT_MAX_WIDTH) 241 | { 242 | width = TEXT_ALERT_MAX_WIDTH; 243 | height += (lines) * DEBUG_LINE_HEIGHT; 244 | } 245 | 246 | uint32_t position = draw_alert_box(title, subtitle, width, height, background, border); 247 | 248 | uint16_t x = ALERT_GET_X(position); 249 | uint16_t y = ALERT_GET_Y(position); 250 | 251 | print_n_string( 252 | message, 253 | 0, 254 | x + ALERT_CONTENT_OFFSET_X, 255 | y + ALERT_CONTENT_OFFSET_Y - ((subtitle)? 0 : DEBUG_LINE_HEIGHT), 256 | x + width - ALERT_CONTENT_OFFSET_X, 257 | 0, 258 | foreground, 259 | background, 260 | true 261 | ); 262 | 263 | // Print ok button 264 | print_string_centered( 265 | " OK ", 266 | x + ALERT_CONTENT_OFFSET_X, 267 | y + ALERT_CONTENT_OFFSET_Y + ((lines - ((subtitle)? 0 : 1)) * DEBUG_LINE_HEIGHT) + STD_CONTENT_OFFSET, 268 | width - (2 * ALERT_CONTENT_OFFSET_X), 269 | 0, 270 | foreground, 271 | COLOR_SELECTED, 272 | false 273 | ); 274 | 275 | LCD_Refresh(); 276 | 277 | // Wait for ok loop 278 | uint32_t key1; 279 | uint32_t key2; 280 | 281 | // handle controls 282 | wait_input_release(); 283 | 284 | do 285 | { 286 | getKey(&key1, &key2); 287 | } while (!testKey(key1, key2, KEY_EXE)); 288 | 289 | // Wait until EXE button is released 290 | wait_input_release(); 291 | 292 | // Close alert 293 | if (lcd_backup) 294 | { 295 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 296 | hhk::free(lcd_backup); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/helpers/ini.cpp: -------------------------------------------------------------------------------- 1 | #include "ini.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../core/error.h" 7 | #include "../emu_ui/font.h" 8 | #include "../helpers/functions.h" 9 | 10 | // Use namespace because of conflicting function declarations in mem.hpp and string.h 11 | namespace hhk 12 | { 13 | #include 14 | } 15 | 16 | #define TMP_STRING_LEN INI_MAX_VALUE_LEN 17 | #define INI_LINE_TYPE_SECTION 0 18 | #define INI_LINE_TYPE_KEY 1 19 | #define INI_LINE_TYPE_COMMENT 2 20 | 21 | void *double_space(void *src, uint32_t *size, size_t elem_size) 22 | { 23 | uint32_t old_size = *size; 24 | 25 | // Allocate double current size 26 | *size = (*size == 0)? 1 : *size * 2; 27 | 28 | void *new_space = hhk::malloc((*size) * elem_size); 29 | 30 | if (!new_space) 31 | { 32 | char err_info[ERROR_MAX_INFO_LEN]; 33 | char tmp[20]; 34 | 35 | strlcpy(err_info, ".ini Parsing: ", sizeof(err_info)); 36 | strlcat(err_info, itoa((*size) * elem_size, tmp, 10), sizeof(err_info)); 37 | strlcat(err_info, "B", sizeof(err_info)); 38 | 39 | set_error_i(EMALLOC, err_info); 40 | 41 | return nullptr; 42 | } 43 | 44 | // Copy old data to new space 45 | if (old_size != 0) 46 | { 47 | memcpy(new_space, src, old_size * elem_size); 48 | hhk::free(src); 49 | } 50 | 51 | return new_space; 52 | } 53 | 54 | ini_file *ini_parse(const char *ini_string, uint32_t len, ini_file *file) 55 | { 56 | uint8_t char_counter = 0; 57 | 58 | const char *p = ini_string; 59 | char tmp[TMP_STRING_LEN]; 60 | 61 | bool quotes_opened = false; 62 | 63 | uint8_t line_type = INI_LINE_TYPE_KEY; 64 | 65 | ini_section *tmp_section = nullptr; 66 | ini_key *tmp_key = nullptr; 67 | 68 | // init ini_file 69 | file->section_count = 0; 70 | file->sections_size = 0; 71 | file->sections = nullptr; 72 | 73 | // Go through each character and parse 74 | for (uint32_t i = 0; *p && i < len; i++, p++) 75 | { 76 | switch (*p) 77 | { 78 | case ';': 79 | case '#': 80 | line_type = INI_LINE_TYPE_COMMENT; 81 | break; 82 | 83 | case '[': 84 | char_counter = 0; 85 | break; 86 | 87 | case ']': 88 | if (line_type == INI_LINE_TYPE_COMMENT) 89 | { 90 | break; 91 | } 92 | 93 | tmp[char_counter] = '\0'; 94 | char_counter = 0; 95 | 96 | tmp_section = add_section(file, tmp); 97 | 98 | if (!tmp_section) 99 | { 100 | free_ini_file(file); 101 | return nullptr; 102 | } 103 | 104 | break; 105 | 106 | case '=': 107 | if (line_type == INI_LINE_TYPE_COMMENT || !tmp_section) 108 | { 109 | break; 110 | } 111 | 112 | tmp[char_counter] = '\0'; 113 | char_counter = 0; 114 | 115 | tmp_key = add_key(tmp_section, tmp); 116 | 117 | if (!tmp_key) 118 | { 119 | free_ini_file(file); 120 | return nullptr; 121 | } 122 | break; 123 | 124 | case '"': 125 | if (!tmp_key) 126 | { 127 | break; 128 | } 129 | 130 | tmp[char_counter] = '\0'; 131 | 132 | if (quotes_opened) 133 | { 134 | tmp_key->value_type = INI_TYPE_STRING; 135 | tmp_key->value_int = 0; 136 | 137 | strlcpy(tmp_key->value_str, tmp, sizeof(tmp_key->value_str)); 138 | } 139 | else 140 | { 141 | quotes_opened = true; 142 | char_counter = 0; 143 | } 144 | 145 | char_counter = 0; 146 | break; 147 | 148 | case '\n': 149 | // If reached end of line and tmp string is not empty and has key, add the value of the 150 | // tmp string as integer value to key. 151 | if (tmp_key && char_counter != 0 && !quotes_opened) 152 | { 153 | tmp[char_counter] = '\0'; 154 | tmp_key->value_type = INI_TYPE_INT; 155 | tmp_key->value_int = atoi(tmp); 156 | tmp_key->value_str[0] = '\0'; 157 | } 158 | 159 | tmp_key = nullptr; 160 | char_counter = 0; 161 | line_type = INI_LINE_TYPE_KEY; 162 | quotes_opened = false; 163 | break; 164 | 165 | case ' ': 166 | // Only process space when in quotes 167 | if (!quotes_opened) 168 | { 169 | break; 170 | } 171 | // Intentional fallthrough 172 | 173 | default: 174 | if (char_counter < TMP_STRING_LEN) 175 | { 176 | tmp[char_counter] = *p; 177 | char_counter++; 178 | } 179 | break; 180 | } 181 | } 182 | 183 | return file; 184 | } 185 | 186 | char *ini_write(ini_file *file, char *ini_string, uint32_t len) 187 | { 188 | uint32_t remaining_len = len; 189 | 190 | ini_string[0] = '\0'; 191 | 192 | // Go through each section, write the corresponding header and its keys 193 | for (uint32_t i = 0; i < file->section_count; i++) 194 | { 195 | ini_section *section = &(file->sections[i]); 196 | 197 | // Write header 198 | strncat(ini_string, "[", remaining_len); 199 | ini_string[len - 1] = '\0'; 200 | 201 | if (remaining_len < 1) 202 | { 203 | break; 204 | } 205 | 206 | remaining_len -= 1; 207 | 208 | strncat(ini_string, section->name, remaining_len); 209 | ini_string[len - 1] = '\0'; 210 | 211 | if (remaining_len < strlen(section->name)) 212 | { 213 | break; 214 | } 215 | 216 | remaining_len -= strlen(section->name); 217 | 218 | strncat(ini_string, "]\n", remaining_len); 219 | ini_string[len - 1] = '\0'; 220 | 221 | if (remaining_len < 2) 222 | { 223 | break; 224 | } 225 | 226 | remaining_len -= 2; 227 | 228 | // Write keys 229 | for (uint32_t j = 0; j < section->key_count; j++) 230 | { 231 | 232 | ini_key *key = &(section->keys[j]); 233 | 234 | strncat(ini_string, key->name, remaining_len); 235 | ini_string[len - 1] = '\0'; 236 | 237 | if (remaining_len < strlen(key->name)) 238 | { 239 | break; 240 | } 241 | 242 | remaining_len -= strlen(key->name); 243 | 244 | strncat(ini_string, "=", remaining_len); 245 | ini_string[len - 1] = '\0'; 246 | 247 | if (remaining_len < 1) 248 | { 249 | break; 250 | } 251 | 252 | remaining_len--; 253 | 254 | // Write actual value 255 | if (key->value_type == INI_TYPE_INT) 256 | { 257 | char tmp[12]; 258 | itoa(key->value_int, tmp, 10); 259 | 260 | strncat(ini_string, tmp, remaining_len); 261 | ini_string[len - 1] = '\0'; 262 | 263 | if (remaining_len < strlen(tmp)) 264 | { 265 | break; 266 | } 267 | 268 | remaining_len -= strlen(tmp); 269 | } 270 | else 271 | { 272 | strncat(ini_string, "\"", remaining_len); 273 | ini_string[len - 1] = '\0'; 274 | 275 | if (remaining_len < 1) 276 | { 277 | break; 278 | } 279 | 280 | remaining_len -= 1; 281 | 282 | strncat(ini_string, key->value_str, remaining_len); 283 | ini_string[len - 1] = '\0'; 284 | 285 | if (remaining_len < strlen(key->value_str)) 286 | { 287 | break; 288 | } 289 | 290 | remaining_len -= strlen(key->value_str); 291 | 292 | strncat(ini_string, "\"", remaining_len); 293 | ini_string[len - 1] = '\0'; 294 | 295 | if (remaining_len < 1) 296 | { 297 | break; 298 | } 299 | 300 | remaining_len -= 1; 301 | } 302 | 303 | strncat(ini_string, "\n", remaining_len); 304 | ini_string[len - 1] = '\0'; 305 | 306 | if (remaining_len < 1) 307 | { 308 | break; 309 | } 310 | 311 | remaining_len -= 1; 312 | } 313 | } 314 | 315 | return ini_string; 316 | } 317 | 318 | // Searches a section by name in a section array and returns a pointer if found 319 | ini_section *find_section(const ini_file *file, const char *name) 320 | { 321 | if (!file) 322 | { 323 | return nullptr; 324 | } 325 | 326 | for (uint32_t i = 0; i < file->section_count; i++) 327 | { 328 | if (strncmp(file->sections[i].name, name, TMP_STRING_LEN) == 0) 329 | { 330 | return &(file->sections[i]); 331 | } 332 | } 333 | 334 | return nullptr; 335 | } 336 | 337 | // Searches a key by name in a keys array and returns a pointer if found 338 | ini_key *find_key(const ini_section *section, const char *name) 339 | { 340 | if (!section) 341 | { 342 | return nullptr; 343 | } 344 | 345 | for (uint32_t i = 0; i < section->key_count; i++) 346 | { 347 | if (strncmp(section->keys[i].name, name, TMP_STRING_LEN) == 0) 348 | { 349 | return &(section->keys[i]); 350 | } 351 | } 352 | 353 | return nullptr; 354 | } 355 | 356 | void free_ini_file(ini_file *file) 357 | { 358 | for (uint32_t i = 0; i < file->section_count; i++) 359 | { 360 | hhk::free(file->sections[i].keys); 361 | file->sections->keys = nullptr; 362 | } 363 | 364 | hhk::free(file->sections); 365 | file->sections = nullptr; 366 | } 367 | 368 | ini_key *add_key(ini_section *section, const char *name, uint8_t value_type, 369 | uint32_t value_int, const char *value_str) 370 | { 371 | if (!section) 372 | { 373 | return nullptr; 374 | } 375 | 376 | ini_key *tmp_key = find_key(section, name); 377 | 378 | // If key could not be found, create a new key in the keys array. 379 | // We first check if new space needs to be allocated. If so, we will double 380 | // the available space. 381 | if (!tmp_key) 382 | { 383 | if (section->key_count == section->keys_size) 384 | { 385 | section->keys = (ini_key *)double_space(section->keys, &(section->keys_size), sizeof(ini_key)); 386 | 387 | if (!section->keys) 388 | { 389 | return nullptr; 390 | } 391 | } 392 | 393 | tmp_key = &(section->keys[section->key_count]); 394 | section->key_count++; 395 | 396 | // Set values for newly created key 397 | tmp_key->value_type = value_type; 398 | tmp_key->value_int = value_int; 399 | 400 | // Use valuestr if it was specified, else fill it with 0s 401 | if (value_str) 402 | { 403 | strlcpy(tmp_key->value_str, value_str, sizeof(tmp_key->value_str)); 404 | } 405 | else 406 | { 407 | memset(tmp_key->value_str, 0, sizeof(tmp_key->value_str)); 408 | } 409 | 410 | // Set name of key 411 | strlcpy(tmp_key->name, name, sizeof(tmp_key->name)); 412 | } 413 | 414 | return tmp_key; 415 | } 416 | 417 | ini_section *add_section(ini_file *file, const char *name) 418 | { 419 | if (!file) 420 | { 421 | return nullptr; 422 | } 423 | 424 | ini_section *tmp_section = find_section(file, name); 425 | 426 | // If section could not be found, create a new section in the sections array. 427 | // We first check if new space needs to be allocated. If so, we will double 428 | // the available space. 429 | if (!tmp_section) 430 | { 431 | if (file->section_count == file->sections_size) 432 | { 433 | file->sections = (ini_section *)double_space(file->sections, &(file->sections_size), sizeof(ini_section)); 434 | 435 | if (!file->sections) 436 | { 437 | return nullptr; 438 | } 439 | } 440 | 441 | tmp_section = &(file->sections[file->section_count]); 442 | file->section_count++; 443 | 444 | // Set default values for newly created section 445 | tmp_section->key_count = 0; 446 | tmp_section->keys_size = 0; 447 | tmp_section->keys = nullptr; 448 | strlcpy(tmp_section->name, name, sizeof(tmp_section->name)); 449 | } 450 | 451 | return tmp_section; 452 | } 453 | -------------------------------------------------------------------------------- /src/core/emulator.cpp: -------------------------------------------------------------------------------- 1 | #include "emulator.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "controls.h" 11 | #include "cart_ram.h" 12 | #include "error.h" 13 | #include "frametimes.h" 14 | #include "peanut_gb.h" 15 | #include "../cas/display.h" 16 | #include "../cas/cpu/cmt.h" 17 | #include "../cas/cpu/cpg.h" 18 | #include "../cas/cpu/dmac.h" 19 | #include "../cas/cpu/oc_mem.h" 20 | #include "../cas/cpu/stack.h" 21 | #include "../emu_ui/menu/menu.h" 22 | #include "../helpers/macros.h" 23 | #include "../helpers/functions.h" 24 | #include "../helpers/fileio.h" 25 | 26 | #define INPUT_NONE 0 27 | #define INPUT_OPEN_MENU 1 28 | 29 | #define STACK_PTR_ADDR (void *)((uint32_t)Y_MEMORY_1 + (0x1000 - 4)) 30 | 31 | /* Global arrays in OC-Memory */ 32 | uint8_t gb_wram[WRAM_SIZE]; 33 | uint8_t gb_vram[VRAM_SIZE] __attribute__((section(".oc_mem.x"))); 34 | uint8_t gb_oam[OAM_SIZE] __attribute__((section(".oc_mem.y.data"))); 35 | uint8_t gb_hram_io[HRAM_IO_SIZE] __attribute__((section(".oc_mem.y.data"))); 36 | 37 | uint8_t execution_handle_input(struct gb_s *gb) 38 | { 39 | uint32_t key1; 40 | uint32_t key2; 41 | 42 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 43 | 44 | // Handle Key Input 45 | getKey(&key1, &key2); 46 | 47 | // Skip this function if no keys are pressed 48 | if (!Input_IsAnyKeyDown()) 49 | { 50 | key1 = 0; 51 | key2 = 0; 52 | } 53 | 54 | gb->direct.joypad_bits.a = !((key1 & preferences->controls[GB_KEY_A][0]) | (key2 & preferences->controls[GB_KEY_A][1])); 55 | gb->direct.joypad_bits.b = !((key1 & preferences->controls[GB_KEY_B][0]) | (key2 & preferences->controls[GB_KEY_B][1])); 56 | gb->direct.joypad_bits.start = !((key1 & preferences->controls[GB_KEY_START][0]) | (key2 & preferences->controls[GB_KEY_START][1])); 57 | gb->direct.joypad_bits.select = !((key1 & preferences->controls[GB_KEY_SELECT][0]) | (key2 & preferences->controls[GB_KEY_SELECT][1])); 58 | gb->direct.joypad_bits.up = !((key1 & preferences->controls[GB_KEY_UP][0]) | (key2 & preferences->controls[GB_KEY_UP][1])); 59 | gb->direct.joypad_bits.down = !((key1 & preferences->controls[GB_KEY_DOWN][0]) | (key2 & preferences->controls[GB_KEY_DOWN][1])); 60 | gb->direct.joypad_bits.left = !((key1 & preferences->controls[GB_KEY_LEFT][0]) | (key2 & preferences->controls[GB_KEY_LEFT][1])); 61 | gb->direct.joypad_bits.right = !((key1 & preferences->controls[GB_KEY_RIGHT][0]) | (key2 & preferences->controls[GB_KEY_RIGHT][1])); 62 | 63 | // Check if menu should be opened 64 | if (testKey(key1, key2, KEY_NEGATIVE)) 65 | { 66 | return INPUT_OPEN_MENU; 67 | } 68 | 69 | return INPUT_NONE; 70 | } 71 | 72 | void set_frameskip(struct gb_s *gb, bool enabled, uint8_t amount) 73 | { 74 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 75 | uint8_t new_amount = clamp(amount, (uint8_t)FRAMESKIP_MIN, (uint8_t)FRAMESKIP_MAX); 76 | 77 | // Check if anything should be changed 78 | if ( 79 | enabled == preferences->config.frameskip_enabled 80 | && new_amount == preferences->config.frameskip_amount 81 | && enabled == gb->direct.frame_skip 82 | && new_amount == (gb->direct.frame_skip_amount + 1) 83 | ) 84 | { 85 | return; 86 | } 87 | 88 | gb->direct.frame_skip = enabled; 89 | gb->direct.frame_skip_amount = (new_amount + 1); 90 | preferences->config.frameskip_enabled = enabled; 91 | preferences->config.frameskip_amount = new_amount; 92 | 93 | preferences->file_states.rom_config_changed = true; 94 | 95 | frametime_counter_set(gb); 96 | } 97 | 98 | void set_interlacing(struct gb_s *gb, bool enabled) 99 | { 100 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 101 | 102 | // Check if anything should be changed 103 | if ( 104 | enabled == preferences->config.interlacing_enabled 105 | && enabled == gb->direct.interlace 106 | ) 107 | { 108 | return; 109 | } 110 | 111 | gb->direct.interlace = enabled; 112 | preferences->config.interlacing_enabled = enabled; 113 | 114 | preferences->file_states.rom_config_changed = true; 115 | } 116 | 117 | void set_emu_speed(struct gb_s *gb, uint16_t percentage){ 118 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 119 | 120 | uint16_t new_percentage = clamp(percentage, (uint16_t)EMU_SPEED_MIN, (uint16_t)(EMU_SPEED_MAX + EMU_SPEED_STEP)); 121 | 122 | preferences->config.emulation_speed = new_percentage; 123 | preferences->file_states.rom_config_changed = true; 124 | 125 | frametime_counter_set(gb); 126 | } 127 | 128 | void set_overclock(struct gb_s *gb, bool enabled) 129 | { 130 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 131 | 132 | // Check if anything should be changed 133 | if (preferences->config.overclock_enabled == enabled) 134 | { 135 | return; 136 | } 137 | 138 | preferences->config.overclock_enabled = enabled; 139 | preferences->file_states.rom_config_changed = true; 140 | 141 | cpg_set_pll_mul((enabled)? PLL_MUL_48 : CPG_PLL_MUL_DEFAULT); 142 | frametime_counter_set(gb); 143 | } 144 | 145 | // Draws scanline into framebuffer. 146 | void lcd_draw_line(struct gb_s *gb, const uint32_t pixels[160], 147 | const uint_fast8_t line) 148 | { 149 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 150 | 151 | // Wait for previous DMA to complete 152 | dma_wait(DMAC_CHCR_0); 153 | 154 | if (unlikely(line == 0)) 155 | { 156 | prepare_gb_lcd(); 157 | } 158 | 159 | // When emulator will be paused, render a full frame in vram 160 | if (unlikely(preferences->emulator_paused)) 161 | { 162 | for (uint16_t i = 0; i < LCD_WIDTH; i++) 163 | { 164 | *(uint32_t *)&vram[(i * 2) + ((line * 2) * CAS_LCD_WIDTH)] = pixels[i]; 165 | *(uint32_t *)&vram[(i * 2) + (((line * 2) + 1) * CAS_LCD_WIDTH)] = pixels[i]; 166 | } 167 | 168 | return; 169 | } 170 | 171 | // Initialize DMA settings 172 | dmac_chcr tmp_chcr = { .raw = 0 }; 173 | tmp_chcr.TS_0 = SIZE_32_0; 174 | tmp_chcr.TS_1 = SIZE_32_1; 175 | tmp_chcr.DM = DAR_FIXED_SOFT; 176 | tmp_chcr.SM = SAR_INCREMENT; 177 | tmp_chcr.RS = AUTO; 178 | tmp_chcr.TB = CYCLE_STEAL; 179 | tmp_chcr.RPT = RELOAD_SAR_TCR; 180 | tmp_chcr.DE = 1; 181 | 182 | DMAC_CHCR_0->raw = 0; 183 | 184 | *DMAC_SAR_0 = (uint32_t)pixels; // P4 Area (OC-Memory) => Physical address is same as virtual 185 | *DMAC_DAR_0 = (uint32_t)SCREEN_DATA_REGISTER & 0x1FFFFFFF; // P2 Area => Physical address is virtual with 3 ms bits cleared 186 | *DMAC_TCR_0 = (CAS_LCD_WIDTH * 2) / 32 * 2; // (Pixels per line * bytes per pixel) / dmac operation bytes * 2 lines 187 | *DMAC_TCRB_0 = ((CAS_LCD_WIDTH * 2 / 32) << 16) 188 | | (CAS_LCD_WIDTH * 2 / 32); 189 | 190 | // Start Channel 0 191 | DMAC_CHCR_0->raw = tmp_chcr.raw; 192 | } 193 | 194 | // Handles an error reported by the emulator. The emulator context may be used 195 | // to better understand why the error given in gb_err was reported. 196 | // TODO: Correctly implement this 197 | void gb_error(struct gb_s *gb, const enum gb_error_e gb_err, const uint16_t val) 198 | { 199 | switch(gb_err) 200 | { 201 | case GB_INVALID_OPCODE: 202 | Debug_Printf(0, 0, false, 0, "Invalid opcode %#04x at PC: %#06x, SP: %#06x\n", 203 | val, 204 | gb->cpu_reg.pc.reg - 1, 205 | gb->cpu_reg.sp); 206 | break; 207 | 208 | // Ignoring non fatal errors. 209 | case GB_INVALID_WRITE: 210 | case GB_INVALID_READ: 211 | Debug_Printf(0, 0, false, 0, "IO-Operation failed"); 212 | return; 213 | 214 | default: 215 | Debug_Printf(0, 0, false, 0, "Unknown error while executing"); 216 | break; 217 | } 218 | 219 | for (uint8_t i = 0; i < 100; i++) 220 | { 221 | LCD_Refresh(); 222 | } 223 | 224 | return; 225 | } 226 | 227 | uint8_t prepare_emulator(struct gb_s *gb, emu_preferences *preferences) 228 | { 229 | enum gb_init_error_e gb_ret; 230 | 231 | // Initialise emulator context 232 | gb_ret = gb_init(gb, &gb_error, preferences, gb_wram, gb_vram, gb_oam, gb_hram_io, preferences->rom); 233 | 234 | // Add ROM name to preference struct 235 | gb_get_rom_name(gb, preferences->current_rom_name); 236 | 237 | switch(gb_ret) 238 | { 239 | case GB_INIT_NO_ERROR: 240 | break; 241 | 242 | case GB_INIT_CARTRIDGE_UNSUPPORTED: 243 | set_error(EEMUCARTRIDGE); 244 | return 1; 245 | 246 | case GB_INIT_INVALID_CHECKSUM: 247 | // Ignore invalid checksum 248 | break; 249 | 250 | default: 251 | set_error(EEMUGEN); 252 | return 1; 253 | } 254 | 255 | // Init gameboy rtc (Just zero everything) 256 | struct tm time; 257 | time.tm_sec = 0; 258 | time.tm_min = 0; 259 | time.tm_hour = 0; 260 | time.tm_yday = 0; 261 | 262 | gb_set_rtc(gb, &time); 263 | 264 | // Initialise lcd stuff 265 | gb_init_lcd(gb, &lcd_draw_line); 266 | 267 | // Load cart save 268 | load_cart_ram(gb); 269 | gb_set_cram(gb, preferences->cart_ram); 270 | 271 | // Load user configs 272 | load_rom_config(gb); 273 | load_controls(gb); 274 | 275 | preferences->file_states.controls_changed = false; 276 | preferences->file_states.rom_config_changed = false; 277 | 278 | if (load_palettes(gb)) 279 | { 280 | return 1; 281 | } 282 | 283 | // load_savestates(); 284 | 285 | // check if loaded palette is out of range 286 | if (preferences->config.selected_palette >= preferences->palette_count) 287 | { 288 | // Reset to default palette 289 | preferences->config.selected_palette = 0; 290 | } 291 | 292 | // Set default flags 293 | preferences->emulator_paused = false; 294 | 295 | return 0; 296 | } 297 | 298 | void free_emulator(struct gb_s *gb) 299 | { 300 | emu_preferences *prefs = (emu_preferences *)gb->direct.priv; 301 | 302 | free(prefs->rom); 303 | free(prefs->cart_ram); 304 | free(prefs->palettes); 305 | } 306 | 307 | uint8_t close_rom(struct gb_s *gb) 308 | { 309 | emu_preferences *prefs = (emu_preferences *)gb->direct.priv; 310 | uint8_t return_code = save_cart_ram(gb); 311 | 312 | if (prefs->file_states.rom_config_changed) 313 | { 314 | return_code |= save_rom_config(gb); 315 | } 316 | 317 | free_emulator(gb); 318 | 319 | return return_code; 320 | } 321 | 322 | uint8_t execute_rom(struct gb_s *gb) 323 | { 324 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 325 | frametime_counter_set(gb); 326 | 327 | for (;;) 328 | { 329 | frametime_counter_start(); 330 | 331 | // Handle rtc 332 | if (unlikely(gb->display.frame_count % 24 == 0)) 333 | { 334 | gb_tick_rtc(gb); 335 | } 336 | 337 | void *tmp_stack_ptr_bak = get_stack_ptr(); 338 | set_stack_ptr(STACK_PTR_ADDR); 339 | 340 | // Run CPU until next frame 341 | gb_run_frame(gb); 342 | 343 | set_stack_ptr(tmp_stack_ptr_bak); 344 | 345 | frametime_counter_wait(gb); 346 | 347 | // Check if pause menu should be displayed 348 | if (unlikely(preferences->emulator_paused && gb->direct.frame_drawn)) 349 | { 350 | uint8_t menu_code = emulation_menu(gb, false); 351 | 352 | if (menu_code != MENU_CLOSED) 353 | { 354 | return menu_code; 355 | } 356 | 357 | preferences->emulator_paused = false; 358 | 359 | LCD_Refresh(); 360 | } 361 | 362 | // Handle input 363 | if (unlikely(execution_handle_input(gb) == INPUT_OPEN_MENU)) 364 | { 365 | // Do not actually open the menu, but render another frame for gb preview first 366 | preferences->emulator_paused = true; 367 | } 368 | } 369 | } 370 | 371 | uint8_t run_emulator(struct gb_s *gb, emu_preferences *prefs) 372 | { 373 | bool exit_emulator = false; 374 | 375 | // Show load menu 376 | switch (load_menu(prefs)) 377 | { 378 | case MENU_EMU_QUIT: 379 | return 0; 380 | 381 | case MENU_CRASH: 382 | return 1; 383 | 384 | default: 385 | break; 386 | } 387 | 388 | // Emulator rom load, execute and unload loop 389 | while (!exit_emulator) 390 | { 391 | draw_load_alert(); 392 | LCD_Refresh(); 393 | 394 | if (load_rom(prefs) != 0) 395 | { 396 | return 1; 397 | } 398 | 399 | if (prepare_emulator(gb, prefs) != 0) 400 | { 401 | return 1; 402 | } 403 | 404 | // Render preview of menu 405 | fillScreen(0x0000); 406 | emulation_menu(gb, true); 407 | LCD_Refresh(); 408 | 409 | switch (execute_rom(gb)) 410 | { 411 | case MENU_CRASH: 412 | // Try to normaly close the rom, but this may fail 413 | close_rom(gb); 414 | return 1; 415 | 416 | case MENU_EMU_QUIT: 417 | exit_emulator = true; 418 | 419 | default: 420 | break; 421 | } 422 | 423 | if (close_rom(gb) != 0) 424 | { 425 | return 1; 426 | } 427 | } 428 | 429 | if (prefs->file_states.controls_changed) 430 | { 431 | if (save_controls(gb) != 0) 432 | { 433 | return 1; 434 | } 435 | } 436 | 437 | return 0; 438 | } 439 | 440 | uint8_t load_rom(emu_preferences *prefs) 441 | { 442 | char rom_filename[MAX_FILENAME_LEN] = DIRECTORY_ROM "\\"; 443 | strncat(rom_filename, prefs->current_filename, MAX_FILENAME_LEN); 444 | rom_filename[MAX_FILENAME_LEN - 1] = '\0'; 445 | 446 | size_t rom_size; 447 | 448 | if (get_file_size(rom_filename, &rom_size)) 449 | { 450 | return 1; 451 | } 452 | 453 | // dynamically allocate space for rom in heap 454 | prefs->rom = (uint8_t *)malloc(rom_size); 455 | 456 | // check if pointer to rom is no nullptr 457 | if (!prefs->rom) 458 | { 459 | char err_info[ERROR_MAX_INFO_LEN]; 460 | char tmp[20]; 461 | 462 | strlcpy(err_info, "GB ROM: ", sizeof(err_info)); 463 | strlcat(err_info, itoa(rom_size, tmp, 10), sizeof(err_info)); 464 | strlcat(err_info, "B", sizeof(err_info)); 465 | 466 | set_error_i(EMALLOC, err_info); 467 | return 1; 468 | } 469 | 470 | if(read_file(rom_filename, prefs->rom, rom_size) != 0) 471 | { 472 | return 1; 473 | } 474 | 475 | return 0; 476 | } 477 | -------------------------------------------------------------------------------- /src/emu_ui/menu/menu.cpp: -------------------------------------------------------------------------------- 1 | #include "menu.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "tabs/current.h" 10 | #include "tabs/load.h" 11 | #include "tabs/saves.h" 12 | #include "tabs/settings.h" 13 | #include "../components.h" 14 | #include "../effects.h" 15 | #include "../colors.h" 16 | #include "../input.h" 17 | #include "../font.h" 18 | #include "../../core/emulator.h" 19 | #include "../../core/controls.h" 20 | #include "../../core/peanut_gb_header.h" 21 | #include "../../core/error.h" 22 | #include "../../helpers/macros.h" 23 | #include "../../helpers/functions.h" 24 | #include "../../helpers/fileio.h" 25 | 26 | // Use namespace because of conflicting function declarations in mem.hpp and string.h 27 | namespace hhk 28 | { 29 | #include 30 | } 31 | 32 | #define MENU_DESCRIPTION_HEIGHT 37 33 | #define MENU_DESCRIPTION_X_OFFSET STD_CONTENT_OFFSET 34 | #define MENU_TAB_HEIGHT 18 35 | #define MENU_TAB_TITLE_OFFSET 2 36 | #define MENU_ITEM_X_OFFSET STD_CONTENT_OFFSET 37 | #define MENU_ITEM_Y_OFFSET STD_CONTENT_OFFSET 38 | 39 | #define LOAD_ALERT_WIDTH 150 40 | #define LOAD_ALERT_HEIGHT (DEBUG_LINE_HEIGHT * 3) 41 | 42 | #define TAB_COUNT 4 43 | 44 | void draw_pause_overlay() 45 | { 46 | darken_screen_area(0, 0, CAS_LCD_WIDTH, LCD_HEIGHT * 2); 47 | 48 | // print game paused text 49 | print_string_centered("Emulation paused", 0, 128, CAS_LCD_WIDTH, 0, COLOR_WHITE, COLOR_BLACK, 1); 50 | print_string_centered("Press [(-)] to continue", 0, 148, CAS_LCD_WIDTH, 0, COLOR_WHITE, COLOR_BLACK, 1); 51 | } 52 | 53 | void draw_menu_overlay() 54 | { 55 | darken_screen_area(0, LCD_HEIGHT * 2, CAS_LCD_WIDTH, CAS_LCD_HEIGHT - (LCD_HEIGHT * 2)); 56 | 57 | // print open menu text 58 | print_string_centered("Press [(-)] to open menu", 0, 402, CAS_LCD_WIDTH, 0, COLOR_WHITE, COLOR_BLACK, 1); 59 | } 60 | 61 | void draw_tab_bar(uint16_t x, uint16_t y, uint16_t width, 62 | uint8_t selected_tab, uint8_t tab_count, menu_tab *tabs) 63 | { 64 | const uint16_t tab_width = width / tab_count; 65 | 66 | // Draw background 67 | draw_rectangle( 68 | x, 69 | y, 70 | width, 71 | MENU_TAB_HEIGHT, 72 | COLOR_SECONDARY, 73 | 0, 74 | 0 75 | ); 76 | 77 | // Draw background for selected tab 78 | draw_rectangle( 79 | x + (selected_tab * tab_width), 80 | y, 81 | tab_width, 82 | MENU_TAB_HEIGHT, 83 | COLOR_PRIMARY, 84 | 0, 85 | 0 86 | ); 87 | 88 | // Draw tab labels 89 | for (uint8_t i = 0; i < tab_count; i++) 90 | { 91 | const uint8_t tab_title_len = strlen(tabs[i].title); 92 | const uint16_t tab_offset = x + (i * tab_width); 93 | const uint16_t tab_title_offset = (tab_width - (tab_title_len * (DEBUG_CHAR_WIDTH - 2))) / 2; 94 | 95 | print_string( 96 | tabs[i].title, 97 | tab_offset + tab_title_offset, 98 | y + MENU_TAB_TITLE_OFFSET, 99 | 0, 100 | COLOR_WHITE, 101 | COLOR_BLACK, 102 | true 103 | ); 104 | } 105 | } 106 | 107 | void draw_tab_description(uint16_t x, uint16_t y, uint16_t width, menu_tab *tab) 108 | { 109 | // Fill description background 110 | draw_rectangle( 111 | x, 112 | y, 113 | width, 114 | MENU_DESCRIPTION_HEIGHT, 115 | COLOR_PRIMARY, 116 | 0, 117 | 0 118 | ); 119 | 120 | // Search for newline in description 121 | const char *p; 122 | for (p = tab->description; *p != '\0' && *p != '\n'; p++) { } 123 | 124 | uint8_t y_offset; 125 | 126 | if (*p == '\n') 127 | { 128 | y_offset = (MENU_DESCRIPTION_HEIGHT - (2 * DEBUG_LINE_HEIGHT)) / 2; 129 | } 130 | else 131 | { 132 | y_offset = (MENU_DESCRIPTION_HEIGHT - DEBUG_LINE_HEIGHT) / 2; 133 | } 134 | 135 | print_string( 136 | tab->description, 137 | x + MENU_DESCRIPTION_X_OFFSET, 138 | y + y_offset, 139 | 0, 140 | COLOR_BLACK, 141 | COLOR_BLACK, 142 | true 143 | ); 144 | } 145 | 146 | void draw_menu_items(uint16_t x, uint16_t y, uint16_t width, 147 | uint8_t selected_item, menu_tab *tab) 148 | { 149 | for (uint8_t i = 0; i < tab->item_count; i++) 150 | { 151 | menu_item *current_item = &(tab->items[i]); 152 | 153 | // Draw background for selected item 154 | if (selected_item == i) 155 | { 156 | draw_rectangle( 157 | x, 158 | y + (i * DEBUG_LINE_HEIGHT), 159 | width, 160 | DEBUG_LINE_HEIGHT, 161 | COLOR_SELECTED, 162 | 0, 163 | 0 164 | ); 165 | } 166 | 167 | // Draw title 168 | print_string( 169 | current_item->title, 170 | x + MENU_ITEM_X_OFFSET, 171 | y + (i * DEBUG_LINE_HEIGHT), 172 | 0, 173 | (current_item->disabled)? COLOR_DISABLED : COLOR_WHITE, 174 | COLOR_BLACK, 175 | true 176 | ); 177 | 178 | // Draw value if it exists 179 | if (current_item->value[0] != '\0') 180 | { 181 | const uint8_t value_len = strlen(current_item->value); 182 | const uint16_t value_pos = x + width - MENU_ITEM_X_OFFSET - 183 | (value_len * (DEBUG_CHAR_WIDTH - 2)) - 1; 184 | 185 | print_string( 186 | current_item->value, 187 | value_pos, 188 | y + (i * DEBUG_LINE_HEIGHT), 189 | 0, 190 | current_item->value_color, 191 | COLOR_BLACK, 192 | true 193 | ); 194 | } 195 | } 196 | } 197 | 198 | void draw_menu(menu *menu) 199 | { 200 | const uint16_t bottom_bar_pos = menu->y_pos + menu->height - MENU_TAB_HEIGHT - 1; 201 | 202 | // Fill menu area 203 | draw_rectangle( 204 | menu->x_pos, 205 | menu->y_pos, 206 | menu->width, 207 | menu->height, 208 | menu->background, 209 | 0, 210 | 0 211 | ); 212 | 213 | draw_tab_bar( 214 | menu->x_pos, 215 | bottom_bar_pos, 216 | menu->width, 217 | menu->selected_tab, 218 | menu->tab_count, 219 | menu->tabs 220 | ); 221 | 222 | draw_tab_description( 223 | menu->x_pos, 224 | menu->y_pos, 225 | menu->width, 226 | &(menu->tabs[menu->selected_tab]) 227 | ); 228 | 229 | draw_menu_items( 230 | menu->x_pos, 231 | menu->y_pos + MENU_DESCRIPTION_HEIGHT + MENU_ITEM_Y_OFFSET, 232 | menu->width, 233 | menu->selected_item, 234 | &(menu->tabs[menu->selected_tab]) 235 | ); 236 | } 237 | 238 | menu *prepare_load_menu_info(menu *menu) 239 | { 240 | menu->x_pos = 0; 241 | menu->y_pos = 0; 242 | menu->width = CAS_LCD_WIDTH; 243 | menu->height = CAS_LCD_HEIGHT; 244 | menu->background = COLOR_MENU_BG; 245 | menu->selected_tab = 0; 246 | menu->selected_item = 0; 247 | menu->tab_count = 1; 248 | menu->tabs = (menu_tab *)hhk::malloc(sizeof(menu_tab)); 249 | 250 | if (!menu->tabs) 251 | { 252 | set_error(EMALLOC); 253 | return nullptr; 254 | } 255 | 256 | if (!prepare_tab_load(&(menu->tabs[0]))) 257 | { 258 | return nullptr; 259 | } 260 | 261 | return menu; 262 | } 263 | 264 | menu *prepare_menu_info(menu *menu, gb_s *gb) 265 | { 266 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 267 | 268 | menu->x_pos = 0; 269 | menu->y_pos = LCD_HEIGHT * 2; 270 | menu->width = CAS_LCD_WIDTH; 271 | menu->height = CAS_LCD_HEIGHT - (LCD_HEIGHT * 2); 272 | menu->background = COLOR_MENU_BG; 273 | menu->selected_tab = 0; 274 | menu->selected_item = 0; 275 | menu->tab_count = 4; 276 | menu->tabs = (menu_tab *)hhk::malloc(menu->tab_count * sizeof(menu_tab)); 277 | 278 | if (!menu->tabs) 279 | { 280 | set_error(EMALLOC); 281 | return nullptr; 282 | } 283 | 284 | if (!prepare_tab_current(&(menu->tabs[0]), preferences)) 285 | { 286 | return nullptr; 287 | } 288 | 289 | if (!prepare_tab_saves(&(menu->tabs[1]), preferences)) 290 | { 291 | return nullptr; 292 | } 293 | 294 | if (!prepare_tab_load(&(menu->tabs[2]))) 295 | { 296 | return nullptr; 297 | } 298 | 299 | if (!prepare_tab_settings(&(menu->tabs[3]), preferences)) 300 | { 301 | return nullptr; 302 | } 303 | 304 | return menu; 305 | } 306 | 307 | void cleanup_menu_info(menu *menu) 308 | { 309 | // Free the items for each tab 310 | for (uint8_t i = 0; i < menu->tab_count; i++) 311 | { 312 | hhk::free(menu->tabs[i].items); 313 | } 314 | 315 | // Free the tabs array 316 | hhk::free(menu->tabs); 317 | } 318 | 319 | uint8_t load_menu(emu_preferences *prefs) 320 | { 321 | // Prepare the menu 322 | menu load_menu; 323 | 324 | prepare_load_menu_info(&load_menu); 325 | 326 | uint8_t return_code; 327 | 328 | wait_input_release(); 329 | 330 | // Menu loop 331 | for (;;) 332 | { 333 | draw_menu(&load_menu); 334 | LCD_Refresh(); 335 | 336 | // Create horizontal items count array 337 | uint8_t h_items_count[load_menu.tabs[load_menu.selected_tab].item_count]; 338 | 339 | for (uint8_t i = 0; i < load_menu.tabs[load_menu.selected_tab].item_count; i++) 340 | { 341 | h_items_count[i] = 1; 342 | } 343 | 344 | // Create horizontal items pointer array 345 | uint8_t *selected_h_items[load_menu.tabs[load_menu.selected_tab].item_count]; 346 | 347 | for (uint8_t i = 0; i < load_menu.tabs[load_menu.selected_tab].item_count; i++) 348 | { 349 | selected_h_items[i] = &load_menu.selected_tab; 350 | } 351 | 352 | // Process input 353 | // This will move the selected item and selected tab and check if the menu was closed 354 | // or an action should be executed 355 | return_code = process_input( 356 | selected_h_items, 357 | &(load_menu.selected_item), 358 | h_items_count, 359 | load_menu.tabs[load_menu.selected_tab].item_count, 360 | load_menu.tabs[load_menu.selected_tab].items, 361 | true 362 | ); 363 | 364 | // Check if something should be executed or the menu was closed 365 | if (return_code == INPUT_PROC_EXECUTE) 366 | { 367 | // Create dummy gb struct 368 | gb_s dummy_gb; 369 | dummy_gb.direct.priv = prefs; 370 | 371 | // Execute action 372 | return_code = load_menu.tabs[load_menu.selected_tab] 373 | .items[load_menu.selected_item].action( 374 | &(load_menu.tabs[load_menu.selected_tab] 375 | .items[load_menu.selected_item]), 376 | &dummy_gb); 377 | 378 | // Close the menu if something went wrong in the action 379 | if (return_code != 0) 380 | { 381 | break; 382 | } 383 | } 384 | else if (return_code == INPUT_PROC_CLOSE) 385 | { 386 | return_code = MENU_EMU_QUIT; 387 | break; 388 | } 389 | } 390 | 391 | cleanup_menu_info(&load_menu); 392 | 393 | // Wait for input to be released 394 | wait_input_release(); 395 | 396 | return return_code; 397 | } 398 | 399 | uint8_t emulation_menu(struct gb_s *gb, bool preview_only) 400 | { 401 | // Backup gb frame and display pause overlay 402 | uint16_t *gb_frame_backup = (uint16_t *)hhk::malloc(LCD_HEIGHT * LCD_WIDTH * sizeof(uint16_t)); 403 | 404 | if (gb_frame_backup) 405 | { 406 | for(uint16_t y = 0; y < LCD_HEIGHT; y++) 407 | { 408 | for(uint16_t x = 0; x < LCD_WIDTH; x++) 409 | { 410 | gb_frame_backup[(y * LCD_WIDTH) + x] = vram[((y * 2) * CAS_LCD_WIDTH) + (x * 2)]; 411 | } 412 | } 413 | } 414 | 415 | draw_pause_overlay(); 416 | 417 | // Prepare the menu 418 | menu emulation_menu; 419 | 420 | if (prepare_menu_info(&emulation_menu, gb) == nullptr) 421 | { 422 | return MENU_CRASH; 423 | } 424 | 425 | int32_t return_code = MENU_CLOSED; 426 | 427 | if (!preview_only) 428 | { 429 | wait_input_release(); 430 | } 431 | 432 | // Menu loop 433 | for (;;) 434 | { 435 | draw_menu(&emulation_menu); 436 | 437 | if (preview_only) 438 | { 439 | break; 440 | } 441 | 442 | LCD_Refresh(); 443 | 444 | // Create horizontal items count array 445 | uint8_t h_items_count[emulation_menu.tabs[emulation_menu.selected_tab].item_count]; 446 | 447 | for (uint8_t i = 0; i < emulation_menu.tabs[emulation_menu.selected_tab].item_count; i++) 448 | { 449 | h_items_count[i] = TAB_COUNT; 450 | } 451 | 452 | // Create horizontal items pointer array 453 | uint8_t *selected_h_items[emulation_menu.tabs[emulation_menu.selected_tab].item_count]; 454 | 455 | for (uint8_t i = 0; i < emulation_menu.tabs[emulation_menu.selected_tab].item_count; i++) 456 | { 457 | selected_h_items[i] = &(emulation_menu.selected_tab); 458 | } 459 | 460 | // Process input 461 | // This will move the selected item and selected tab and check if the menu was closed 462 | // or an action should be executed 463 | return_code = process_input( 464 | selected_h_items, 465 | &(emulation_menu.selected_item), 466 | h_items_count, 467 | emulation_menu.tabs[emulation_menu.selected_tab].item_count, 468 | emulation_menu.tabs[emulation_menu.selected_tab].items, 469 | true 470 | ); 471 | 472 | // Check if something should be executed or the menu was closed 473 | if (return_code == INPUT_PROC_EXECUTE) 474 | { 475 | // Execute action 476 | return_code = emulation_menu.tabs[emulation_menu.selected_tab] 477 | .items[emulation_menu.selected_item].action( 478 | &(emulation_menu.tabs[emulation_menu.selected_tab] 479 | .items[emulation_menu.selected_item]), 480 | gb 481 | ); 482 | 483 | // Close the menu if something went wrong in the action 484 | if (return_code != 0) 485 | { 486 | break; 487 | } 488 | } 489 | else if (return_code == INPUT_PROC_CLOSE) 490 | { 491 | return_code = MENU_CLOSED; 492 | break; 493 | } 494 | } 495 | 496 | if (!preview_only) 497 | { 498 | wait_input_release(); 499 | } 500 | 501 | cleanup_menu_info(&emulation_menu); 502 | 503 | // restore lcd 504 | if (gb_frame_backup) 505 | { 506 | for(uint16_t y = 0; y < LCD_HEIGHT; y++) 507 | { 508 | for(uint16_t x = 0; x < LCD_WIDTH; x++) 509 | { 510 | vram[((y * 2) * (LCD_WIDTH * 2)) + (x * 2)] = gb_frame_backup[y * LCD_WIDTH + x]; 511 | vram[((y * 2) * (LCD_WIDTH * 2)) + (x * 2) + 1] = gb_frame_backup[y * LCD_WIDTH + x]; 512 | vram[(((y * 2) + 1) * (LCD_WIDTH * 2)) + (x * 2)] = gb_frame_backup[y * LCD_WIDTH + x]; 513 | vram[(((y * 2) + 1) * (LCD_WIDTH * 2)) + (x * 2) + 1] = gb_frame_backup[y * LCD_WIDTH + x]; 514 | } 515 | } 516 | 517 | hhk::free(gb_frame_backup); 518 | } 519 | 520 | draw_menu_overlay(); 521 | 522 | return return_code; 523 | } 524 | 525 | void draw_load_alert(void) 526 | { 527 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 528 | 529 | uint32_t pos = draw_alert_box(nullptr, nullptr, LOAD_ALERT_WIDTH, LOAD_ALERT_HEIGHT, COLOR_MENU_BG, COLOR_PRIMARY); 530 | 531 | print_string_centered( 532 | "Loading ...", 533 | ALERT_GET_X(pos), 534 | ALERT_GET_Y(pos) + DEBUG_LINE_HEIGHT, 535 | LOAD_ALERT_WIDTH, 536 | 0, 537 | COLOR_WHITE, 538 | COLOR_BLACK, 539 | true 540 | ); 541 | } 542 | -------------------------------------------------------------------------------- /src/core/palettes.cpp: -------------------------------------------------------------------------------- 1 | #include "palettes.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "error.h" 9 | #include "emulator.h" 10 | #include "../helpers/fileio.h" 11 | #include "../helpers/functions.h" 12 | #include "../helpers/ini.h" 13 | #include "../helpers/macros.h" 14 | #include "../emu_ui/font.h" 15 | 16 | #define PALETTE_NAME_CONSTANT "Palette " 17 | #define PALETTE_VAR_CONSTANT "Pal" 18 | 19 | #define PALETTE_CONF_VAR_NAME "PalCfg" 20 | #define PALETTE_CONF_INI_SECTION_NAME "PalCfg" 21 | #define PALETTE_CONF_INI_COUNT_KEY "pal_cnt" 22 | 23 | #define PALETTE_INI_SECTION_NAME "Pal" 24 | #define PALETTE_INI_NAME_KEY "name" 25 | #define PALETTE_INI_KEY_SEPERATOR '_' 26 | 27 | bool get_game_palette(uint8_t game_checksum, uint16_t (*game_palette)[4]) 28 | { 29 | /* Palettes by deltabeard from the PeanutGB SDL example */ 30 | switch(game_checksum) 31 | { 32 | /* Balloon Kid and Tetris Blast */ 33 | case 0x71: 34 | case 0xFF: 35 | { 36 | const uint16_t palette[3][4] = 37 | { 38 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E60), RGB555_TO_RGB565(0x7C00), 0x0000 }, /* OBJ0 */ 39 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E60), RGB555_TO_RGB565(0x7C00), 0x0000 }, /* OBJ1 */ 40 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E60), RGB555_TO_RGB565(0x7C00), 0x0000 } /* BG */ 41 | }; 42 | memcpy(game_palette, palette, sizeof(palette)); 43 | return 1; 44 | } 45 | 46 | /* Pokemon Yellow and Tetris */ 47 | case 0x15: 48 | case 0xDB: 49 | case 0x95: /* Not officially */ 50 | { 51 | const uint16_t palette[3][4] = 52 | { 53 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7FE0), RGB555_TO_RGB565(0x7C00), 0x0000 }, /* OBJ0 */ 54 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7FE0), RGB555_TO_RGB565(0x7C00), 0x0000 }, /* OBJ1 */ 55 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7FE0), RGB555_TO_RGB565(0x7C00), 0x0000 } /* BG */ 56 | }; 57 | memcpy(game_palette, palette, sizeof(palette)); 58 | return 1; 59 | } 60 | 61 | /* Donkey Kong */ 62 | case 0x19: 63 | { 64 | const uint16_t palette[3][4] = 65 | { 66 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), 0x0000 }, /* OBJ0 */ 67 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), 0x0000 }, /* OBJ1 */ 68 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E60), RGB555_TO_RGB565(0x7C00), 0x0000 } /* BG */ 69 | }; 70 | memcpy(game_palette, palette, sizeof(palette)); 71 | return 1; 72 | } 73 | 74 | /* Pokemon Blue */ 75 | case 0x61: 76 | case 0x45: 77 | 78 | /* Pokemon Blue Star */ 79 | case 0xD8: 80 | { 81 | const uint16_t palette[3][4] = 82 | { 83 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), 0x0000 }, /* OBJ0 */ 84 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x329F), RGB555_TO_RGB565(0x001F), 0x0000 }, /* OBJ1 */ 85 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x329F), RGB555_TO_RGB565(0x001F), 0x0000 } /* BG */ 86 | }; 87 | memcpy(game_palette, palette, sizeof(palette)); 88 | return 1; 89 | } 90 | 91 | /* Pokemon Red */ 92 | case 0x14: 93 | { 94 | const uint16_t palette[3][4] = 95 | { 96 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x3FE6), RGB555_TO_RGB565(0x0200), 0x0000 }, /* OBJ0 */ 97 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), 0x0000 }, /* OBJ1 */ 98 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), 0x0000 } /* BG */ 99 | }; 100 | memcpy(game_palette, palette, sizeof(palette)); 101 | return 1; 102 | } 103 | 104 | /* Pokemon Red Star */ 105 | case 0x8B: 106 | { 107 | const uint16_t palette[3][4] = 108 | { 109 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), 0x0000 }, /* OBJ0 */ 110 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x329F), RGB555_TO_RGB565(0x001F), 0x0000 }, /* OBJ1 */ 111 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x3FE6), RGB555_TO_RGB565(0x0200), 0x0000 } /* BG */ 112 | }; 113 | memcpy(game_palette, palette, sizeof(palette)); 114 | return 1; 115 | } 116 | 117 | /* Kirby */ 118 | case 0x27: 119 | case 0x49: 120 | case 0x5C: 121 | case 0xB3: 122 | { 123 | const uint16_t palette[3][4] = 124 | { 125 | { RGB555_TO_RGB565(0x7D8A), RGB555_TO_RGB565(0x6800), RGB555_TO_RGB565(0x3000), RGB555_TO_RGB565(0x0000) }, /* OBJ0 */ 126 | { RGB555_TO_RGB565(0x001F), RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7FEF), RGB555_TO_RGB565(0x021F) }, /* OBJ1 */ 127 | { RGB555_TO_RGB565(0x527F), RGB555_TO_RGB565(0x7FE0), RGB555_TO_RGB565(0x0180), RGB555_TO_RGB565(0x0000) } /* BG */ 128 | }; 129 | memcpy(game_palette, palette, sizeof(palette)); 130 | return 1; 131 | } 132 | 133 | /* Donkey Kong Land [1/2/III] */ 134 | case 0x18: 135 | case 0x6A: 136 | case 0x4B: 137 | case 0x6B: 138 | { 139 | const uint16_t palette[3][4] = 140 | { 141 | { RGB555_TO_RGB565(0x7F08), RGB555_TO_RGB565(0x7F40), RGB555_TO_RGB565(0x48E0), RGB555_TO_RGB565(0x2400) }, /* OBJ0 */ 142 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x2EFF), RGB555_TO_RGB565(0x7C00), RGB555_TO_RGB565(0x001F) }, /* OBJ1 */ 143 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x463B), RGB555_TO_RGB565(0x2951), RGB555_TO_RGB565(0x0000) } /* BG */ 144 | }; 145 | memcpy(game_palette, palette, sizeof(palette)); 146 | return 1; 147 | } 148 | 149 | /* Link's Awakening */ 150 | case 0x70: 151 | { 152 | const uint16_t palette[3][4] = 153 | { 154 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x03E0), RGB555_TO_RGB565(0x1A00), RGB555_TO_RGB565(0x0120) }, /* OBJ0 */ 155 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x329F), RGB555_TO_RGB565(0x001F), RGB555_TO_RGB565(0x001F) }, /* OBJ1 */ 156 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7E10), RGB555_TO_RGB565(0x48E7), RGB555_TO_RGB565(0x0000) } /* BG */ 157 | }; 158 | memcpy(game_palette, palette, sizeof(palette)); 159 | return 1; 160 | } 161 | 162 | /* Mega Man [1/2/3] & others I don't care about. */ 163 | case 0x01: 164 | case 0x10: 165 | case 0x29: 166 | case 0x52: 167 | case 0x5D: 168 | case 0x68: 169 | case 0x6D: 170 | case 0xF6: 171 | { 172 | const uint16_t palette[3][4] = 173 | { 174 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x329F), RGB555_TO_RGB565(0x001F), 0x0000 }, /* OBJ0 */ 175 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x3FE6), RGB555_TO_RGB565(0x0200), 0x0000 }, /* OBJ1 */ 176 | { RGB555_TO_RGB565(0x7FFF), RGB555_TO_RGB565(0x7EAC), RGB555_TO_RGB565(0x40C0), 0x0000 } /* BG */ 177 | }; 178 | memcpy(game_palette, palette, sizeof(palette)); 179 | return 1; 180 | } 181 | 182 | default: 183 | return 0; 184 | } 185 | } 186 | 187 | char *generate_palette_name(char *str_buffer, struct gb_s *gb) 188 | { 189 | // Go through all palettes and check which index is free 190 | palette *user_palettes; 191 | uint8_t user_palette_count = get_user_palettes(&user_palettes, gb); 192 | int8_t max_index = -1; 193 | 194 | for (uint8_t i = 0; i < user_palette_count; i++) 195 | { 196 | char *p = strchr(user_palettes[i].name, ' '); 197 | 198 | if (!p) 199 | { 200 | continue; 201 | } 202 | 203 | int tmp = atoi(p + 1); 204 | 205 | if (tmp > max_index) 206 | { 207 | max_index = tmp; 208 | } 209 | } 210 | 211 | strcpy(str_buffer, PALETTE_NAME_CONSTANT); 212 | 213 | char tmp[4]; 214 | 215 | strcat(str_buffer, itoa_leading_zeros(max_index + 1, tmp, 10, 2)); 216 | 217 | return str_buffer; 218 | } 219 | 220 | uint8_t process_palette_ini(char *ini_string, uint32_t len, palette *pal) 221 | { 222 | ini_file file; 223 | ini_parse(ini_string, len, &file); 224 | 225 | ini_section *section = find_section(&file, PALETTE_INI_SECTION_NAME); 226 | 227 | if (!section) 228 | { 229 | free_ini_file(&file); 230 | return 1; 231 | } 232 | 233 | ini_key *name = find_key(section, PALETTE_INI_NAME_KEY); 234 | 235 | if (!name) 236 | { 237 | free_ini_file(&file); 238 | return 1; 239 | } 240 | 241 | strcpy(pal->name, name->value_str); 242 | 243 | // Get all colors 244 | for (uint8_t p = 0; p < 3; p++) 245 | { 246 | for (uint8_t c = 0; c < 4; c++) 247 | { 248 | char a = '0' + p; 249 | char b = '0' + c; 250 | 251 | const char key_name[4] = { a, PALETTE_INI_KEY_SEPERATOR, b, '\0' }; 252 | 253 | ini_key *color = find_key(section, key_name); 254 | 255 | if (!color) 256 | { 257 | free_ini_file(&file); 258 | return 1; 259 | } 260 | 261 | pal->data[p][c] = color->value_int; 262 | } 263 | } 264 | 265 | free_ini_file(&file); 266 | 267 | return 0; 268 | } 269 | 270 | char *create_palette_ini(palette *pal, char *ini_string, uint32_t len) 271 | { 272 | ini_file file; 273 | file.section_count = 0; 274 | file.sections_size = 0; 275 | 276 | ini_section *palette_section = add_section(&file, PALETTE_INI_SECTION_NAME); 277 | 278 | if (!palette_section) 279 | { 280 | return nullptr; 281 | } 282 | 283 | if (!add_key( 284 | palette_section, 285 | PALETTE_INI_NAME_KEY, 286 | INI_TYPE_STRING, 287 | 0, 288 | pal->name 289 | )) 290 | { 291 | free_ini_file(&file); 292 | return nullptr; 293 | } 294 | 295 | // Setup palette data 296 | for (uint8_t p = 0; p < 3; p++) 297 | { 298 | for (uint8_t c = 0; c < 4; c++) 299 | { 300 | char a = '0' + p; 301 | char b = '0' + c; 302 | 303 | const char key_name[] = { a, PALETTE_INI_KEY_SEPERATOR, b, '\0' }; 304 | 305 | if (!add_key( 306 | palette_section, 307 | key_name, 308 | INI_TYPE_INT, 309 | pal->data[p][c] 310 | )) 311 | { 312 | free_ini_file(&file); 313 | return nullptr; 314 | } 315 | } 316 | } 317 | 318 | ini_write(&file, ini_string, len); 319 | free_ini_file(&file); 320 | 321 | return ini_string; 322 | } 323 | 324 | uint8_t process_palette_config_ini(char *ini_string, uint32_t len, uint8_t *count) 325 | { 326 | ini_file file; 327 | ini_parse(ini_string, len, &file); 328 | 329 | ini_section *section = find_section(&file, PALETTE_CONF_INI_SECTION_NAME); 330 | 331 | if (!section) 332 | { 333 | free_ini_file(&file); 334 | return 1; 335 | } 336 | 337 | ini_key *pal_count = find_key(section, PALETTE_CONF_INI_COUNT_KEY); 338 | 339 | if (!pal_count) 340 | { 341 | free_ini_file(&file); 342 | return 1; 343 | } 344 | 345 | *count = pal_count->value_int; 346 | 347 | free_ini_file(&file); 348 | 349 | return 0; 350 | } 351 | 352 | char *create_palette_config_ini(uint8_t count, char *ini_string, uint32_t len) 353 | { 354 | ini_file file; 355 | file.section_count = 0; 356 | file.sections_size = 0; 357 | 358 | ini_section *pal_config_section = add_section(&file, PALETTE_CONF_INI_SECTION_NAME); 359 | 360 | if (!pal_config_section) 361 | { 362 | free_ini_file(&file); 363 | return nullptr; 364 | } 365 | 366 | if (!add_key( 367 | pal_config_section, 368 | PALETTE_CONF_INI_COUNT_KEY, 369 | INI_TYPE_INT, 370 | count 371 | )) 372 | { 373 | free_ini_file(&file); 374 | return nullptr; 375 | } 376 | 377 | ini_write(&file, ini_string, len); 378 | free_ini_file(&file); 379 | 380 | return ini_string; 381 | } 382 | 383 | uint8_t save_palette_config(uint8_t pal_count) 384 | { 385 | char ini_string[INI_MAX_CONTENT_LEN]; 386 | 387 | if (!create_palette_config_ini(pal_count, ini_string, sizeof(ini_string))) 388 | { 389 | return 1; 390 | } 391 | 392 | // Get 4 aligned size for mcs variable 393 | uint32_t size = align_val(strlen(ini_string), 4); 394 | 395 | if (write_mcs(MCS_DIRECTORY, PALETTE_CONF_VAR_NAME, ini_string, size)) 396 | { 397 | return 1; 398 | } 399 | 400 | return 0; 401 | } 402 | 403 | uint8_t create_palette(struct gb_s *gb) 404 | { 405 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 406 | 407 | palette new_pal; 408 | uint16_t default_palette[3][4] = DEFAULT_PALETTE; 409 | uint8_t user_palette_count = get_user_palettes(nullptr, gb); 410 | 411 | // Check if already reached max palette count 412 | if (user_palette_count == MAX_PALETTE_COUNT) 413 | { 414 | return ERR_MAX_PALETTE_REACHED; 415 | } 416 | 417 | generate_palette_name(new_pal.name, gb); 418 | memcpy(new_pal.data, default_palette, sizeof(default_palette)); 419 | 420 | // Save newly created palette 421 | if (save_palette(&new_pal, user_palette_count)) 422 | { 423 | return 1; 424 | } 425 | 426 | // Save new palette config 427 | if (save_palette_config(user_palette_count + 1)) 428 | { 429 | return 1; 430 | } 431 | 432 | // Reload palettes 433 | free(preferences->palettes); 434 | load_palettes(gb); 435 | 436 | return 0; 437 | } 438 | 439 | uint8_t delete_palette(struct gb_s *gb, uint8_t index) 440 | { 441 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 442 | palette *user_palettes = nullptr; 443 | uint8_t user_palette_count = get_user_palettes(&user_palettes, gb); 444 | 445 | // Overwrite palette in palette array and move everything to the front 446 | for (uint8_t i = index; i < (user_palette_count - 1); i++) 447 | { 448 | memcpy(&(user_palettes[i]), &(user_palettes[i + 1]), sizeof(palette)); 449 | save_palette(&(user_palettes[i]), i); 450 | } 451 | 452 | // Reduce total palette count 453 | preferences->palette_count--; 454 | 455 | save_palette_config(user_palette_count - 1); 456 | 457 | return 0; 458 | } 459 | 460 | uint8_t load_palettes(struct gb_s *gb) 461 | { 462 | emu_preferences *preferences = (emu_preferences *)(gb->direct.priv); 463 | 464 | uint32_t var_size; 465 | uint8_t user_palette_count = 0; 466 | char *ini_string; 467 | 468 | // Try to load palette config 469 | if (read_mcs(MCS_DIRECTORY, PALETTE_CONF_VAR_NAME, (void **)&ini_string, &var_size) == 0) 470 | { 471 | if (process_palette_config_ini(ini_string, var_size, &user_palette_count)) 472 | { 473 | return 1; 474 | } 475 | } 476 | 477 | // Get game palette 478 | uint16_t game_palette[3][4]; 479 | 480 | bool has_game_palette = get_game_palette(gb_colour_hash(gb), game_palette); 481 | 482 | preferences->palette_count = 1 + has_game_palette + user_palette_count; 483 | preferences->palettes = (palette *)malloc(preferences->palette_count * sizeof(palette)); 484 | 485 | // Check if malloc failed 486 | if (!(preferences->palettes)) 487 | { 488 | char tmp[10]; 489 | char err_info[ERROR_MAX_INFO_LEN]; 490 | strlcpy(err_info, "Palettes: ", sizeof(err_info)); 491 | strlcat(err_info, itoa(preferences->palette_count, tmp, 10), sizeof(err_info)); 492 | strlcat(err_info, "B", sizeof(err_info)); 493 | 494 | set_error_i(EMALLOC, err_info); 495 | return 1; 496 | } 497 | 498 | // Set default palette 499 | uint16_t default_palette[3][4] = DEFAULT_PALETTE; 500 | strlcpy(preferences->palettes[0].name, "Default", sizeof(preferences->palettes[0].name)); 501 | memcpy(preferences->palettes[0].data, default_palette, sizeof(default_palette)); 502 | 503 | // Set game palette if it exists 504 | if (has_game_palette) 505 | { 506 | strlcpy(preferences->palettes[1].name, preferences->current_rom_name, sizeof(preferences->palettes[1].name)); 507 | memcpy(preferences->palettes[1].data, game_palette, sizeof(game_palette)); 508 | } 509 | 510 | // Set user palettes 511 | for (uint8_t i = 0; i < user_palette_count; i++) 512 | { 513 | char tmp[3]; 514 | char var_name[sizeof(PALETTE_VAR_CONSTANT) + 2] = PALETTE_VAR_CONSTANT; 515 | 516 | strlcat(var_name, itoa(i, tmp, 16), sizeof(var_name)); 517 | 518 | if (read_mcs(MCS_DIRECTORY, var_name, (void **)&ini_string, &var_size)) 519 | { 520 | return 1; 521 | } 522 | 523 | if (process_palette_ini(ini_string, var_size, &(preferences->palettes[1 + has_game_palette + i]))) 524 | { 525 | return 1; 526 | } 527 | } 528 | 529 | return 0; 530 | } 531 | 532 | uint8_t save_palette(palette *pal, uint8_t index) 533 | { 534 | char ini_string[INI_MAX_CONTENT_LEN]; 535 | 536 | if (!create_palette_ini(pal, ini_string, sizeof(ini_string))) 537 | { 538 | return 1; 539 | } 540 | 541 | uint32_t size = align_val(strlen(ini_string), 4); 542 | 543 | char tmp[3]; 544 | char var_name[sizeof(tmp) + sizeof(PALETTE_VAR_CONSTANT) - 1] = PALETTE_VAR_CONSTANT; 545 | 546 | strlcat(var_name, itoa(index, tmp, 16), sizeof(var_name)); 547 | 548 | if (write_mcs(MCS_DIRECTORY, var_name, ini_string, size) != 0) 549 | { 550 | return 1; 551 | } 552 | 553 | return 0; 554 | } 555 | 556 | uint8_t get_user_palettes(palette **pal, struct gb_s *gb) 557 | { 558 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 559 | uint16_t tmp[3][4]; 560 | 561 | bool has_game_palette = get_game_palette(gb_colour_hash(gb), tmp); 562 | 563 | if (pal) 564 | { 565 | *pal = &(preferences->palettes[1 + has_game_palette]); 566 | } 567 | 568 | return preferences->palette_count - 1 - has_game_palette; 569 | } 570 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/current.cpp: -------------------------------------------------------------------------------- 1 | #include "current.h" 2 | 3 | #include 4 | #include 5 | #include "../menu.h" 6 | #include "../../components.h" 7 | #include "../../colors.h" 8 | #include "../../font.h" 9 | #include "../../effects.h" 10 | #include "../../input.h" 11 | #include "../../../core/error.h" 12 | #include "../../../helpers/macros.h" 13 | #include "../../../helpers/functions.h" 14 | 15 | namespace hhk 16 | { 17 | #include 18 | } 19 | 20 | #define TAB_CURRENT_TITLE "Current" 21 | 22 | #define TAB_CUR_ITEM_COUNT 5 23 | 24 | #define TAB_CUR_ITEM_FRAMESKIP_INDEX 0 25 | #define TAB_CUR_ITEM_FRAMESKIP_TITLE "Frameskipping" 26 | #define TAB_CUR_ITEM_FRAMESKIP_SUBTITLE "Skips rendering and LCD-Refresh" 27 | 28 | #define TAB_CUR_ITEM_INTERL_INDEX 0 29 | #define TAB_CUR_ITEM_INTERL_TITLE "Interlacing" 30 | 31 | #define TAB_CUR_ITEM_SPEED_INDEX 1 32 | #define TAB_CUR_ITEM_SPEED_TITLE "Emulation Speed" 33 | #define TAB_CUR_ITEM_SPEED_SUBTITLE "Set the emulation speed target" 34 | 35 | #define TAB_CUR_ITEM_OVERCLOCK_INDEX 2 36 | #define TAB_CUR_ITEM_OVERCLOCK_TITLE "Overclock" 37 | 38 | #define TAB_CUR_ITEM_PALETTE_INDEX 3 39 | #define TAB_CUR_ITEM_PALETTE_TITLE "Color Palette" 40 | #define TAB_CUR_ITEM_PALETTE_SUBTITLE "Select a palette for this ROM" 41 | 42 | #define TAB_CUR_ITEM_QUIT_INDEX 4 43 | #define TAB_CUR_ITEM_QUIT_TITLE "Quit CPBoy" 44 | 45 | #define DIALOG_FRAMESKIP_ITEM_COUNT 3 46 | #define DIALOG_FRAMESKIP_WIDTH 200 47 | #define DIALOG_FRAMESKIP_HEIGHT DEBUG_LINE_HEIGHT * DIALOG_FRAMESKIP_ITEM_COUNT + ALERT_CONTENT_OFFSET_Y + (4 * STD_CONTENT_OFFSET) 48 | 49 | #define DIALOG_PALETTE_WIDTH 200 50 | 51 | #define DIALOG_SPEED_ITEM_COUNT 2 52 | #define DIALOG_SPEED_WIDTH 200 53 | 54 | #define OVERLOCK_WARNING_TEXT "The CAS will be overclocked by 50%.\n" \ 55 | "This will increase performance at the cost of decreased battery life.\n\n" \ 56 | "Use at your own risk" 57 | 58 | void draw_frameskip_alert(emu_preferences *preferences, uint8_t selected_item) 59 | { 60 | uint32_t position = draw_alert_box( 61 | TAB_CUR_ITEM_FRAMESKIP_TITLE, 62 | TAB_CUR_ITEM_FRAMESKIP_SUBTITLE, 63 | DIALOG_FRAMESKIP_WIDTH, 64 | DIALOG_FRAMESKIP_HEIGHT, 65 | COLOR_MENU_BG, 66 | COLOR_PRIMARY 67 | ); 68 | 69 | char tmp[4]; 70 | 71 | const uint16_t dialog_x = ALERT_GET_X(position); 72 | const uint16_t dialog_y = ALERT_GET_Y(position); 73 | 74 | const uint16_t slider_offset = ALERT_CONTENT_OFFSET_X + (7 * DEBUG_CHAR_WIDTH); 75 | const uint16_t slider_width = DIALOG_FRAMESKIP_WIDTH - slider_offset - 76 | (4 * DEBUG_CHAR_WIDTH) - STD_CONTENT_OFFSET; 77 | 78 | // Draw frameskip enabled state 79 | print_string_centered( 80 | (preferences->config.frameskip_enabled)? "Enabled" : "Disabled", 81 | dialog_x, 82 | dialog_y + ALERT_CONTENT_OFFSET_Y, 83 | DIALOG_FRAMESKIP_WIDTH, 84 | 0, 85 | (preferences->config.frameskip_enabled)? COLOR_SUCCESS : COLOR_DANGER, 86 | (selected_item == 0) ? COLOR_SELECTED : COLOR_BLACK, 87 | true 88 | ); 89 | 90 | // Draw amount slider 91 | print_string( 92 | "Frames", 93 | dialog_x + ALERT_CONTENT_OFFSET_X, 94 | dialog_y + ALERT_CONTENT_OFFSET_Y + DEBUG_LINE_HEIGHT + STD_CONTENT_OFFSET, 95 | 0, 96 | COLOR_WHITE, 97 | COLOR_BLACK, 98 | true 99 | ); 100 | 101 | draw_slider( 102 | dialog_x + slider_offset, 103 | dialog_y + ALERT_CONTENT_OFFSET_Y + DEBUG_LINE_HEIGHT + STD_CONTENT_OFFSET, 104 | slider_width, 105 | SLIDER_STD_TRACK_COLOR, 106 | (selected_item == 1) ? COLOR_PRIMARY : COLOR_WHITE, 107 | FRAMESKIP_MIN, 108 | FRAMESKIP_MAX, 109 | preferences->config.frameskip_amount 110 | ); 111 | 112 | print_string_centered( 113 | itoa(preferences->config.frameskip_amount, tmp, 10), 114 | dialog_x + slider_offset + slider_width + STD_CONTENT_OFFSET + STD_CONTENT_OFFSET, 115 | dialog_y + ALERT_CONTENT_OFFSET_Y + DEBUG_LINE_HEIGHT + STD_CONTENT_OFFSET, 116 | (DEBUG_CHAR_WIDTH - 2) * 4, 117 | 0, 118 | COLOR_WHITE, 119 | COLOR_BLACK, 120 | true 121 | ); 122 | 123 | // Print ok button 124 | print_string_centered( 125 | "OK", 126 | dialog_x + ALERT_CONTENT_OFFSET_X, 127 | dialog_y + ALERT_CONTENT_OFFSET_Y + (2 * DEBUG_LINE_HEIGHT) + (3 * STD_CONTENT_OFFSET), 128 | DIALOG_FRAMESKIP_WIDTH - (2 * ALERT_CONTENT_OFFSET_X), 129 | 0, 130 | COLOR_WHITE, 131 | (selected_item == 2)? COLOR_SELECTED : COLOR_BLACK, 132 | true 133 | ); 134 | } 135 | 136 | void draw_speed_alert(emu_preferences *preferences, uint8_t selected_item) 137 | { 138 | uint16_t dialog_height = ALERT_CONTENT_OFFSET_Y + (2 * DEBUG_LINE_HEIGHT) + (3 * STD_CONTENT_OFFSET); 139 | 140 | uint32_t position = draw_alert_box( 141 | TAB_CUR_ITEM_SPEED_TITLE, 142 | TAB_CUR_ITEM_SPEED_SUBTITLE, 143 | DIALOG_SPEED_WIDTH, 144 | dialog_height, 145 | COLOR_MENU_BG, 146 | COLOR_PRIMARY 147 | ); 148 | 149 | char tmp[9]; 150 | 151 | if (preferences->config.emulation_speed == EMU_SPEED_MAX + EMU_SPEED_STEP) 152 | { 153 | strlcpy(tmp, "Unlocked", sizeof(tmp)); 154 | } 155 | else 156 | { 157 | itoa(preferences->config.emulation_speed, tmp, 10); 158 | strlcat(tmp, "%", sizeof(tmp)); 159 | } 160 | 161 | const uint16_t dialog_x = ALERT_GET_X(position); 162 | const uint16_t dialog_y = ALERT_GET_Y(position); 163 | 164 | const uint16_t slider_offset = ALERT_CONTENT_OFFSET_X + (6 * DEBUG_CHAR_WIDTH); 165 | const uint16_t slider_width = DIALOG_FRAMESKIP_WIDTH - slider_offset - 166 | (9 * DEBUG_CHAR_WIDTH) - STD_CONTENT_OFFSET; 167 | 168 | // Draw amount slider 169 | print_string( 170 | "Speed", 171 | dialog_x + ALERT_CONTENT_OFFSET_X, 172 | dialog_y + ALERT_CONTENT_OFFSET_Y, 173 | 0, 174 | COLOR_WHITE, 175 | COLOR_BLACK, 176 | true 177 | ); 178 | 179 | draw_slider( 180 | dialog_x + slider_offset, 181 | dialog_y + ALERT_CONTENT_OFFSET_Y, 182 | slider_width, 183 | SLIDER_STD_TRACK_COLOR, 184 | (selected_item == 0) ? COLOR_PRIMARY : COLOR_WHITE, 185 | EMU_SPEED_MIN, 186 | EMU_SPEED_MAX + EMU_SPEED_STEP, 187 | preferences->config.emulation_speed 188 | ); 189 | 190 | print_string_centered( 191 | tmp, 192 | dialog_x + slider_offset + slider_width + STD_CONTENT_OFFSET + STD_CONTENT_OFFSET, 193 | dialog_y + ALERT_CONTENT_OFFSET_Y, 194 | (DEBUG_CHAR_WIDTH - 2) * 8, 195 | 0, 196 | COLOR_WHITE, 197 | COLOR_BLACK, 198 | true 199 | ); 200 | 201 | // Print ok button 202 | print_string_centered( 203 | "OK", 204 | dialog_x + ALERT_CONTENT_OFFSET_X, 205 | dialog_y + ALERT_CONTENT_OFFSET_Y + DEBUG_LINE_HEIGHT + (2 * STD_CONTENT_OFFSET), 206 | DIALOG_FRAMESKIP_WIDTH - (2 * ALERT_CONTENT_OFFSET_X), 207 | 0, 208 | COLOR_WHITE, 209 | (selected_item == 1)? COLOR_SELECTED : COLOR_BLACK, 210 | true 211 | ); 212 | } 213 | 214 | void draw_palette_selection_alert(emu_preferences *preferences, uint8_t selected_item) 215 | { 216 | uint16_t dialog_height = ALERT_CONTENT_OFFSET_Y + (preferences->palette_count * DEBUG_LINE_HEIGHT) + STD_CONTENT_OFFSET; 217 | 218 | uint32_t position = draw_alert_box( 219 | TAB_CUR_ITEM_PALETTE_TITLE, 220 | TAB_CUR_ITEM_PALETTE_SUBTITLE, 221 | DIALOG_PALETTE_WIDTH, 222 | dialog_height, 223 | COLOR_MENU_BG, 224 | COLOR_PRIMARY 225 | ); 226 | 227 | const uint16_t dialog_x = ALERT_GET_X(position); 228 | const uint16_t dialog_y = ALERT_GET_Y(position); 229 | 230 | // Draw every palette 231 | for (uint8_t i = 0; i < preferences->palette_count; i++) 232 | { 233 | print_string_centered( 234 | preferences->palettes[i].name, 235 | dialog_x + ALERT_CONTENT_OFFSET_X, 236 | dialog_y + ALERT_CONTENT_OFFSET_Y + (i * DEBUG_LINE_HEIGHT), 237 | DIALOG_PALETTE_WIDTH - (2 * ALERT_CONTENT_OFFSET_X), 238 | 0, 239 | COLOR_WHITE, 240 | (selected_item == i)? COLOR_SELECTED : COLOR_BLACK, 241 | true 242 | ); 243 | } 244 | } 245 | 246 | int32_t frameskip_alert(struct gb_s *gb) 247 | { 248 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 249 | 250 | // Backup LCD and darken background 251 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 252 | 253 | if (lcd_backup) 254 | { 255 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 256 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 257 | } 258 | 259 | bool frameskip_enabled = preferences->config.frameskip_enabled; 260 | uint8_t frameskip_amount; 261 | 262 | uint8_t selected_item = 0; 263 | 264 | // Create horizontal items count 265 | static const uint8_t h_items_count[DIALOG_FRAMESKIP_ITEM_COUNT] = { 1, FRAMESKIP_MAX, 1 }; 266 | 267 | // Create horizontal items pointer 268 | uint8_t *selected_h_items[DIALOG_FRAMESKIP_ITEM_COUNT] = { nullptr, &frameskip_amount, nullptr }; 269 | 270 | // Rendering and input handling 271 | for (;;) 272 | { 273 | frameskip_amount = preferences->config.frameskip_amount - FRAMESKIP_MIN; 274 | 275 | draw_frameskip_alert(preferences, selected_item); 276 | 277 | // Check if OK button or toggle was pressed 278 | if ( 279 | process_input( 280 | selected_h_items, 281 | &selected_item, 282 | h_items_count, 283 | DIALOG_FRAMESKIP_ITEM_COUNT, 284 | nullptr, 285 | false 286 | ) == INPUT_PROC_EXECUTE 287 | ) 288 | { 289 | if (selected_item == 0) 290 | { 291 | TOGGLE(frameskip_enabled); 292 | } 293 | else if (selected_item == 2) 294 | { 295 | break; 296 | } 297 | } 298 | 299 | // Apply frameskip to emulator 300 | set_frameskip(gb, frameskip_enabled, frameskip_amount + FRAMESKIP_MIN); 301 | 302 | LCD_Refresh(); 303 | } 304 | 305 | // Close alert 306 | if (lcd_backup) 307 | { 308 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 309 | hhk::free(lcd_backup); 310 | } 311 | 312 | return 0; 313 | } 314 | 315 | int32_t emu_speed_alert(struct gb_s *gb) 316 | { 317 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 318 | 319 | // Backup LCD and darken background 320 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 321 | 322 | if (lcd_backup) 323 | { 324 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 325 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 326 | } 327 | 328 | uint8_t emu_speed; 329 | uint8_t selected_item = 0; 330 | 331 | // Create horizontal items count 332 | static const uint8_t h_items_count[DIALOG_SPEED_ITEM_COUNT] = { (EMU_SPEED_MAX + EMU_SPEED_STEP) / EMU_SPEED_STEP, 1 }; 333 | 334 | // Create horizontal items pointer 335 | uint8_t *selected_h_items[DIALOG_SPEED_ITEM_COUNT] = { &emu_speed, nullptr }; 336 | 337 | // Rendering and input handling 338 | for (;;) 339 | { 340 | emu_speed = (preferences->config.emulation_speed - EMU_SPEED_MIN) / EMU_SPEED_STEP; 341 | 342 | draw_speed_alert(preferences, selected_item); 343 | 344 | // Check if OK button or toggle was pressed 345 | if ( 346 | process_input( 347 | selected_h_items, 348 | &selected_item, 349 | h_items_count, 350 | DIALOG_SPEED_ITEM_COUNT, 351 | nullptr, 352 | false 353 | ) == INPUT_PROC_EXECUTE 354 | ) 355 | { 356 | if (selected_item == 1) 357 | { 358 | break; 359 | } 360 | } 361 | 362 | // Apply speed to emulator 363 | set_emu_speed(gb, (emu_speed * EMU_SPEED_STEP) + EMU_SPEED_MIN); 364 | 365 | LCD_Refresh(); 366 | } 367 | 368 | // Close alert 369 | if (lcd_backup) 370 | { 371 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 372 | hhk::free(lcd_backup); 373 | } 374 | 375 | return 0; 376 | } 377 | 378 | int32_t palette_selection_alert(struct gb_s *gb) 379 | { 380 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 381 | 382 | // Backup LCD and darken background 383 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 384 | 385 | if (lcd_backup) 386 | { 387 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 388 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 389 | } 390 | 391 | uint8_t selected_item = 0; 392 | 393 | // Create horizontal item count array 394 | uint8_t h_items_count[preferences->palette_count]; 395 | 396 | for (uint8_t i = 0; i < preferences->palette_count; i++) 397 | { 398 | h_items_count[i] = 1; 399 | } 400 | 401 | // Create horizontal items pointer array 402 | uint8_t *selected_h_items[preferences->palette_count]; 403 | 404 | for (uint8_t i = 0; i < preferences->palette_count; i++) 405 | { 406 | selected_h_items[i] = nullptr; 407 | } 408 | 409 | // Rendering and input handling 410 | for (;;) 411 | { 412 | draw_palette_selection_alert(preferences, selected_item); 413 | 414 | // Check if palette was selected 415 | if ( 416 | process_input( 417 | selected_h_items, 418 | &selected_item, 419 | h_items_count, 420 | preferences->palette_count, 421 | nullptr, 422 | false 423 | ) == INPUT_PROC_EXECUTE 424 | ) 425 | { 426 | // Do not do anything if the same palette is selected 427 | if (preferences->config.selected_palette == selected_item) 428 | { 429 | break; 430 | } 431 | 432 | preferences->file_states.rom_config_changed = true; 433 | preferences->config.selected_palette = selected_item; 434 | break; 435 | } 436 | 437 | LCD_Refresh(); 438 | } 439 | 440 | // Close alert 441 | if (lcd_backup) 442 | { 443 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 444 | hhk::free(lcd_backup); 445 | } 446 | 447 | return 0; 448 | } 449 | 450 | int32_t action_frameskip_selection(menu_item *item, gb_s *gb) 451 | { 452 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 453 | int32_t return_code = frameskip_alert(gb); 454 | 455 | // Update item value text and color 456 | strlcpy(item->value, (preferences->config.frameskip_enabled)? "Enabled (" : "Disabled", sizeof(item->value)); 457 | item->value_color = (preferences->config.frameskip_enabled)? COLOR_SUCCESS : COLOR_DANGER; 458 | 459 | // Append number of frames to skip when enabled 460 | if (preferences->config.frameskip_enabled) 461 | { 462 | char tmp[4]; 463 | 464 | strlcat(item->value, itoa(preferences->config.frameskip_amount, tmp, 10), sizeof(item->value)); 465 | strlcat(item->value, ")", sizeof(item->value)); 466 | } 467 | 468 | return return_code; 469 | } 470 | 471 | int32_t action_speed_selection(menu_item *item, gb_s *gb) 472 | { 473 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 474 | int32_t return_code = emu_speed_alert(gb); 475 | 476 | // Update item value text and color 477 | item->value_color = COLOR_SUCCESS; 478 | 479 | if (preferences->config.emulation_speed == EMU_SPEED_MAX + EMU_SPEED_STEP) 480 | { 481 | strlcpy(item->value, "Unlocked", sizeof(item->value)); 482 | } 483 | else 484 | { 485 | itoa(preferences->config.emulation_speed, item->value, 10); 486 | strlcat(item->value, "%", sizeof(item->value)); 487 | } 488 | 489 | return return_code; 490 | } 491 | 492 | int32_t action_interlacing_selection(menu_item *item, gb_s *gb) 493 | { 494 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 495 | 496 | // Toggle interlacing 497 | set_interlacing(gb, !preferences->config.interlacing_enabled); 498 | 499 | // Update item value text and color 500 | strlcpy(item->value, (preferences->config.interlacing_enabled)? "Enabled" : "Disabled", sizeof(item->value)); 501 | item->value_color = (preferences->config.interlacing_enabled)? COLOR_SUCCESS : COLOR_DANGER; 502 | 503 | return 0; 504 | } 505 | 506 | int32_t action_overclock_selection(menu_item *item, gb_s *gb) 507 | { 508 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 509 | 510 | // Show warning when enabling overclock 511 | if (!preferences->config.overclock_enabled) 512 | { 513 | ok_alert( 514 | TAB_CUR_ITEM_OVERCLOCK_TITLE, 515 | nullptr, 516 | OVERLOCK_WARNING_TEXT, 517 | COLOR_WHITE, 518 | COLOR_MENU_BG, 519 | COLOR_PRIMARY 520 | ); 521 | } 522 | 523 | // Toggle overclock 524 | set_overclock(gb, !preferences->config.overclock_enabled); 525 | 526 | // Update item value text and color 527 | strlcpy(item->value, (preferences->config.overclock_enabled)? "Enabled" : "Disabled", sizeof(item->value)); 528 | item->value_color = (preferences->config.overclock_enabled)? COLOR_SUCCESS : COLOR_DANGER; 529 | 530 | return 0; 531 | } 532 | 533 | int32_t action_palette_selection(menu_item *item, gb_s *gb) 534 | { 535 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 536 | 537 | uint8_t ret = palette_selection_alert(gb); 538 | 539 | // Update item value 540 | strlcpy(item->value, preferences->palettes[preferences->config.selected_palette].name, sizeof(item->value)); 541 | 542 | return ret; 543 | } 544 | 545 | int32_t action_quit_emulator(menu_item *item, gb_s *gb) 546 | { 547 | return MENU_EMU_QUIT; 548 | } 549 | 550 | menu_tab *prepare_tab_current(menu_tab *tab, emu_preferences *preferences) 551 | { 552 | char filename[TAB_DESCR_MAX_FILENAME_LENGTH + 1]; 553 | 554 | // Description and title for "Settings" tab 555 | strlcpy(tab->title, TAB_CURRENT_TITLE, sizeof(tab->title)); 556 | strlcpy(tab->description, "Current ROM: ", sizeof(tab->description)); 557 | strlcat(tab->description, preferences->current_rom_name, sizeof(tab->description)); 558 | strlcat(tab->description, "\nFilename: ", sizeof(tab->description)); 559 | 560 | // If filename is too long, copy only first TAB_DESCR_MAX_FILENAME_LENGTH chars 561 | if (strlen(preferences->current_filename) > TAB_DESCR_MAX_FILENAME_LENGTH) 562 | { 563 | strlcpy(filename, preferences->current_filename, TAB_DESCR_MAX_FILENAME_LENGTH - 4); 564 | strlcat(filename, " ...", sizeof(filename)); 565 | } 566 | else 567 | { 568 | strlcpy(filename, preferences->current_filename, sizeof(filename)); 569 | } 570 | 571 | strlcat(tab->description, filename, sizeof(tab->description)); 572 | 573 | tab->item_count = TAB_CUR_ITEM_COUNT; 574 | tab->items = (menu_item *)hhk::malloc(TAB_CUR_ITEM_COUNT * sizeof(menu_item)); 575 | 576 | if (!tab->items) 577 | { 578 | set_error(EMALLOC); 579 | return nullptr; 580 | } 581 | 582 | // Disabled state for each item 583 | tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].disabled = false; 584 | tab->items[TAB_CUR_ITEM_SPEED_INDEX].disabled = false; 585 | tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].disabled = false; 586 | // tab->items[TAB_CUR_ITEM_INTERL_INDEX].disabled = false; 587 | tab->items[TAB_CUR_ITEM_PALETTE_INDEX].disabled = false; 588 | tab->items[TAB_CUR_ITEM_QUIT_INDEX].disabled = false; 589 | 590 | // Title for each item 591 | strlcpy(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].title, TAB_CUR_ITEM_FRAMESKIP_TITLE, sizeof(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].title)); 592 | strlcpy(tab->items[TAB_CUR_ITEM_SPEED_INDEX].title, TAB_CUR_ITEM_SPEED_TITLE, sizeof(tab->items[TAB_CUR_ITEM_SPEED_INDEX].title)); 593 | strlcpy(tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].title, TAB_CUR_ITEM_OVERCLOCK_TITLE, sizeof(tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].title)); 594 | // strlcpy(tab->items[TAB_CUR_ITEM_INTERL_INDEX].title, TAB_CUR_ITEM_INTERL_TITLE, sizeof(tab->items[TAB_CUR_ITEM_INTERL_INDEX].title)); 595 | strlcpy(tab->items[TAB_CUR_ITEM_PALETTE_INDEX].title, TAB_CUR_ITEM_PALETTE_TITLE, sizeof(tab->items[TAB_CUR_ITEM_PALETTE_INDEX].title)); 596 | strlcpy(tab->items[TAB_CUR_ITEM_QUIT_INDEX].title, TAB_CUR_ITEM_QUIT_TITLE, sizeof(tab->items[TAB_CUR_ITEM_QUIT_INDEX].title)); 597 | 598 | // Value for each item 599 | strlcpy(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value, 600 | (preferences->config.frameskip_enabled)? "Enabled (" : "Disabled", 601 | sizeof(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value)); 602 | 603 | if (preferences->config.frameskip_enabled) 604 | { 605 | char tmp[4]; 606 | 607 | itoa(preferences->config.frameskip_amount, tmp, 10); 608 | strlcat(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value, tmp, sizeof(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value)); 609 | strlcat(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value, ")", sizeof(tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value)); 610 | } 611 | 612 | if (preferences->config.emulation_speed == EMU_SPEED_MAX + EMU_SPEED_STEP) 613 | { 614 | strlcpy(tab->items[TAB_CUR_ITEM_SPEED_INDEX].value, "Unlocked", sizeof(tab->items[TAB_CUR_ITEM_SPEED_INDEX].value)); 615 | } 616 | else 617 | { 618 | itoa(preferences->config.emulation_speed, tab->items[TAB_CUR_ITEM_SPEED_INDEX].value, 10); 619 | strlcat(tab->items[TAB_CUR_ITEM_SPEED_INDEX].value, "%", sizeof(tab->items[TAB_CUR_ITEM_SPEED_INDEX].value)); 620 | } 621 | 622 | // strlcpy(tab->items[TAB_CUR_ITEM_INTERL_INDEX].value, 623 | // (preferences->config.interlacing_enabled)? "Enabled" : "Disabled", 624 | // sizeof(tab->items[TAB_CUR_ITEM_INTERL_INDEX].value)); 625 | strlcpy(tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].value, 626 | (preferences->config.overclock_enabled)? "Enabled" : "Disabled", 627 | sizeof(tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].value)); 628 | strlcpy(tab->items[TAB_CUR_ITEM_PALETTE_INDEX].value, 629 | preferences->palettes[preferences->config.selected_palette].name, 630 | sizeof(tab->items[TAB_CUR_ITEM_PALETTE_INDEX].value)); 631 | tab->items[TAB_CUR_ITEM_QUIT_INDEX].value[0] = '\0'; 632 | 633 | // Value color for each item 634 | tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].value_color = 635 | (preferences->config.frameskip_enabled)? COLOR_SUCCESS : COLOR_DANGER; 636 | // tab->items[TAB_CUR_ITEM_INTERL_INDEX].value_color = 637 | // (preferences->config.interlacing_enabled)? COLOR_SUCCESS : COLOR_DANGER; 638 | tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].value_color = 639 | (preferences->config.overclock_enabled)? COLOR_SUCCESS : COLOR_DANGER; 640 | tab->items[TAB_CUR_ITEM_SPEED_INDEX].value_color = COLOR_SUCCESS; 641 | tab->items[TAB_CUR_ITEM_PALETTE_INDEX].value_color = COLOR_SUCCESS; 642 | 643 | // Action for each item 644 | tab->items[TAB_CUR_ITEM_FRAMESKIP_INDEX].action = action_frameskip_selection; 645 | tab->items[TAB_CUR_ITEM_SPEED_INDEX].action = action_speed_selection; 646 | // tab->items[TAB_CUR_ITEM_INTERL_INDEX].action = action_interlacing_selection; 647 | tab->items[TAB_CUR_ITEM_OVERCLOCK_INDEX].action = action_overclock_selection; 648 | tab->items[TAB_CUR_ITEM_PALETTE_INDEX].action = action_palette_selection; 649 | tab->items[TAB_CUR_ITEM_QUIT_INDEX].action = action_quit_emulator; 650 | 651 | return tab; 652 | } 653 | -------------------------------------------------------------------------------- /src/emu_ui/menu/tabs/settings.cpp: -------------------------------------------------------------------------------- 1 | #include "current.h" 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../menu.h" 9 | #include "../../components.h" 10 | #include "../../colors.h" 11 | #include "../../font.h" 12 | #include "../../effects.h" 13 | #include "../../input.h" 14 | #include "../../../core/error.h" 15 | #include "../../../helpers/macros.h" 16 | 17 | namespace hhk 18 | { 19 | #include 20 | } 21 | 22 | #define TAB_SETTINGS_TITLE "Settings" 23 | #define TAB_SETTINGS_DESCRIPTION "Settings" 24 | 25 | #define TAB_SETTINGS_ITEM_COUNT 3 26 | #define TAB_SETTINGS_ITEM_PALETTE_INDEX 0 27 | #define TAB_SETTINGS_ITEM_PALETTE_TITLE "Custom Color Palettes" 28 | #define TAB_SETTINGS_ITEM_PALETTE_SUBTITLE "[(-)] to delete" 29 | #define TAB_SETTINGS_ITEM_CONTROLS_INDEX 1 30 | #define TAB_SETTINGS_ITEM_CONTROLS_TITLE "Controls" 31 | #define TAB_SETTINGS_ITEM_CONTROLS_SUBTITLE "Select key to edit" 32 | #define TAB_SETTINGS_ITEM_CREDITS_INDEX 2 33 | #define TAB_SETTINGS_ITEM_CREDITS_TITLE "Credits" 34 | #define TAB_SETTINGS_ITEM_CREDITS_SUBTITLE CPBOY_VERSION 35 | #define TAB_SETTINGS_ITEM_CREDITS_CONTENT \ 36 | "Based on Peanut-GB by deltabeard\n\n" \ 37 | "Source at github.com/diddyholz/CPBoy\n\n" \ 38 | "Contributors:\n" \ 39 | "diddyholz (Sidney Krombholz)\n" \ 40 | "s3ansh33p (Sean McGinty)" \ 41 | 42 | #define DIALOG_CONTROLS_ITEM_COUNT (GB_KEY_COUNT + 1) 43 | #define DIALOG_CONTROLS_WIDTH 150 44 | #define DIALOG_CONTROLS_HEIGHT ALERT_CONTENT_OFFSET_Y + (DIALOG_CONTROLS_ITEM_COUNT * DEBUG_LINE_HEIGHT) + (2 * STD_CONTENT_OFFSET) 45 | 46 | #define DIALOG_SELECT_KEY_WIDTH 200 47 | #define DIALOG_SELECT_KEY_HEIGHT STD_CONTENT_OFFSET * 2 + DEBUG_LINE_HEIGHT 48 | 49 | #define DIALOG_PALETTE_WIDTH 200 50 | #define DIALOG_PALETTE_HEIGHT (ALERT_CONTENT_OFFSET_Y - DEBUG_LINE_HEIGHT) \ 51 | + (3 * ((2 * STD_CONTENT_OFFSET) + DIALOG_PALETTE_COLOR_PANEL_HEIGHT + DEBUG_LINE_HEIGHT)) \ 52 | + STD_CONTENT_OFFSET + DEBUG_LINE_HEIGHT 53 | #define DIALOG_PALETTE_COLOR_RECT_HEIGHT 25 54 | #define DIALOG_PALETTE_COLOR_PANEL_HEIGHT (3 * (SLIDER_HANDLE_HEIGHT + DIALOG_PALETTE_SLIDER_SPACING)) \ 55 | + DIALOG_PALETTE_COLOR_RECT_HEIGHT + STD_CONTENT_OFFSET 56 | #define DIALOG_PALETTE_SLIDER_SPACING 2 57 | #define DIALOG_PALETTE_SELECTED_COLOR COLOR_PRIMARY 58 | #define DIALOG_PALETTE_SELECTED_BORDER_WIDTH 2 59 | #define DIALOG_PALETTE_COLOR_DESC_WIDTH 5 * (DEBUG_CHAR_WIDTH - 2) 60 | 61 | #define SELECTKEY_IDLE "Waiting for key ..." 62 | 63 | void draw_controls_alert(emu_preferences *preferences, uint8_t selected_item) 64 | { 65 | uint32_t position = draw_alert_box( 66 | TAB_SETTINGS_ITEM_CONTROLS_TITLE, 67 | TAB_SETTINGS_ITEM_CONTROLS_SUBTITLE, 68 | DIALOG_CONTROLS_WIDTH, 69 | DIALOG_CONTROLS_HEIGHT, 70 | COLOR_MENU_BG, 71 | COLOR_PRIMARY 72 | ); 73 | 74 | const uint16_t dialog_x = ALERT_GET_X(position); 75 | const uint16_t dialog_y = ALERT_GET_Y(position); 76 | 77 | // Draw every controls option 78 | for (uint8_t i = 0; i < GB_KEY_COUNT; i++) 79 | { 80 | char title[14]; 81 | 82 | // Draw controls title 83 | switch (i) 84 | { 85 | case GB_KEY_A: 86 | strcpy(title, GB_KEY_TEXT_A); 87 | break; 88 | case GB_KEY_B: 89 | strcpy(title, GB_KEY_TEXT_B); 90 | break; 91 | case GB_KEY_START: 92 | strcpy(title, GB_KEY_TEXT_START); 93 | break; 94 | case GB_KEY_SELECT: 95 | strcpy(title, GB_KEY_TEXT_SELECT); 96 | break; 97 | case GB_KEY_UP: 98 | strcpy(title, GB_KEY_TEXT_UP); 99 | break; 100 | case GB_KEY_DOWN: 101 | strcpy(title, GB_KEY_TEXT_DOWN); 102 | break; 103 | case GB_KEY_LEFT: 104 | strcpy(title, GB_KEY_TEXT_LEFT); 105 | break; 106 | case GB_KEY_RIGHT: 107 | strcpy(title, GB_KEY_TEXT_RIGHT); 108 | break; 109 | 110 | default: 111 | break; 112 | } 113 | 114 | print_string( 115 | title, 116 | dialog_x + ALERT_CONTENT_OFFSET_X, 117 | dialog_y + ALERT_CONTENT_OFFSET_Y + (i * DEBUG_LINE_HEIGHT), 118 | 0, 119 | COLOR_WHITE, 120 | COLOR_BLACK, 121 | true 122 | ); 123 | 124 | // Draw button title 125 | switch (preferences->controls[i][0]) 126 | { 127 | case KEY_SHIFT: 128 | strcpy(title, CAS_KEY_TEXT_SHIFT); 129 | break; 130 | case KEY_CLEAR: 131 | strcpy(title, CAS_KEY_TEXT_CLEAR); 132 | break; 133 | case KEY_BACKSPACE: 134 | strcpy(title, CAS_KEY_TEXT_BACKSPACE); 135 | break; 136 | case KEY_LEFT: 137 | strcpy(title, CAS_KEY_TEXT_LEFT); 138 | break; 139 | case KEY_RIGHT: 140 | strcpy(title, CAS_KEY_TEXT_RIGHT); 141 | break; 142 | case KEY_Z: 143 | strcpy(title, CAS_KEY_TEXT_Z); 144 | break; 145 | case KEY_POWER: 146 | strcpy(title, CAS_KEY_TEXT_POWER); 147 | break; 148 | case KEY_DIVIDE: 149 | strcpy(title, CAS_KEY_TEXT_DIVIDE); 150 | break; 151 | case KEY_MULTIPLY: 152 | strcpy(title, CAS_KEY_TEXT_MULTIPLY); 153 | break; 154 | case KEY_SUBTRACT: 155 | strcpy(title, CAS_KEY_TEXT_SUBSTRACT); 156 | break; 157 | case KEY_ADD: 158 | strcpy(title, CAS_KEY_TEXT_ADD); 159 | break; 160 | case KEY_EXE: 161 | strcpy(title, CAS_KEY_TEXT_EXE); 162 | break; 163 | case KEY_EXP: 164 | strcpy(title, CAS_KEY_TEXT_EXP); 165 | break; 166 | case KEY_3: 167 | strcpy(title, CAS_KEY_TEXT_3); 168 | break; 169 | case KEY_6: 170 | strcpy(title, CAS_KEY_TEXT_6); 171 | break; 172 | case KEY_9: 173 | strcpy(title, CAS_KEY_TEXT_9); 174 | break; 175 | 176 | default: 177 | break; 178 | } 179 | 180 | switch (preferences->controls[i][1]) 181 | { 182 | case KEY_KEYBOARD: 183 | strcpy(title, CAS_KEY_TEXT_KEYBOARD); 184 | break; 185 | case KEY_UP: 186 | strcpy(title, CAS_KEY_TEXT_UP); 187 | break; 188 | case KEY_DOWN: 189 | strcpy(title, CAS_KEY_TEXT_DOWN); 190 | break; 191 | case KEY_EQUALS: 192 | strcpy(title, CAS_KEY_TEXT_EQUALS); 193 | break; 194 | case KEY_X: 195 | strcpy(title, CAS_KEY_TEXT_X); 196 | break; 197 | case KEY_Y: 198 | strcpy(title, CAS_KEY_TEXT_Y); 199 | break; 200 | case KEY_LEFT_BRACKET: 201 | strcpy(title, CAS_KEY_TEXT_LEFT_BRACKET); 202 | break; 203 | case KEY_RIGHT_BRACKET: 204 | strcpy(title, CAS_KEY_TEXT_RIGHT_BRACKET); 205 | break; 206 | case KEY_COMMA: 207 | strcpy(title, CAS_KEY_TEXT_COMMA); 208 | break; 209 | case KEY_NEGATIVE: 210 | strcpy(title, CAS_KEY_TEXT_NEGATIVE); 211 | break; 212 | case KEY_0: 213 | strcpy(title, CAS_KEY_TEXT_0); 214 | break; 215 | case KEY_DOT: 216 | strcpy(title, CAS_KEY_TEXT_DOT); 217 | break; 218 | case KEY_1: 219 | strcpy(title, CAS_KEY_TEXT_1); 220 | break; 221 | case KEY_2: 222 | strcpy(title, CAS_KEY_TEXT_2); 223 | break; 224 | case KEY_4: 225 | strcpy(title, CAS_KEY_TEXT_4); 226 | break; 227 | case KEY_5: 228 | strcpy(title, CAS_KEY_TEXT_5); 229 | break; 230 | case KEY_7: 231 | strcpy(title, CAS_KEY_TEXT_7); 232 | break; 233 | case KEY_8: 234 | strcpy(title, CAS_KEY_TEXT_8); 235 | break; 236 | 237 | default: 238 | break; 239 | } 240 | 241 | if (!(preferences->controls[i][0]) && !(preferences->controls[i][1])) 242 | { 243 | strcpy(title, CAS_KEY_TEXT_NONE); 244 | } 245 | 246 | print_string_centered( 247 | title, 248 | dialog_x + (DIALOG_CONTROLS_WIDTH - (ALERT_CONTENT_OFFSET_X + (10 * (DEBUG_CHAR_WIDTH - 2)) + 2)), 249 | dialog_y + ALERT_CONTENT_OFFSET_Y + (i * DEBUG_LINE_HEIGHT), 250 | (DEBUG_CHAR_WIDTH - 2) * 10, 251 | 0, 252 | COLOR_WHITE, 253 | (selected_item == i)? COLOR_SELECTED : COLOR_BLACK, 254 | true 255 | ); 256 | } 257 | 258 | print_string_centered( 259 | " OK ", 260 | dialog_x, 261 | dialog_y + ALERT_CONTENT_OFFSET_Y + (GB_KEY_COUNT * DEBUG_LINE_HEIGHT) + STD_CONTENT_OFFSET, 262 | DIALOG_CONTROLS_WIDTH, 263 | 0, 264 | COLOR_WHITE, 265 | (selected_item == 8)? COLOR_SELECTED : COLOR_BLACK, 266 | true 267 | ); 268 | } 269 | 270 | void draw_select_key_alert() 271 | { 272 | uint32_t position = draw_alert_box( 273 | nullptr, 274 | nullptr, 275 | DIALOG_SELECT_KEY_WIDTH, 276 | DIALOG_SELECT_KEY_HEIGHT, 277 | COLOR_MENU_BG, 278 | COLOR_PRIMARY 279 | ); 280 | 281 | uint16_t dialog_x = ALERT_GET_X(position); 282 | uint16_t dialog_y = ALERT_GET_Y(position); 283 | 284 | print_string_centered( 285 | SELECTKEY_IDLE, 286 | dialog_x, 287 | dialog_y + STD_CONTENT_OFFSET, 288 | DIALOG_SELECT_KEY_WIDTH, 289 | 0, 290 | COLOR_WHITE, 291 | COLOR_BLACK, 292 | true 293 | ); 294 | } 295 | 296 | void draw_edit_color_panel(uint16_t x, uint16_t y, uint16_t width, uint16_t *colors, 297 | uint8_t selected_item, uint8_t selected_color) 298 | { 299 | const uint16_t color_rect_width = width / 4; 300 | 301 | // Draw color selection rectangles 302 | for (uint8_t i = 0; i < 4; i++) 303 | { 304 | draw_rectangle( 305 | x + (i * color_rect_width), 306 | y, 307 | color_rect_width, 308 | DIALOG_PALETTE_COLOR_RECT_HEIGHT, 309 | colors[i], 310 | (selected_color == i) * DIALOG_PALETTE_SELECTED_BORDER_WIDTH, 311 | (selected_item == 0)? DIALOG_PALETTE_SELECTED_COLOR : COLOR_WHITE 312 | ); 313 | } 314 | 315 | const char *color_descriptions[] = { 316 | "Red", 317 | "Green", 318 | "Blue" 319 | }; 320 | 321 | const uint8_t color_max_vals[] = { 322 | 31, // Red (5-bit) 323 | 63, // Green (6-bit) 324 | 31 // Blue (5-bit) 325 | }; 326 | 327 | const uint16_t color_vals[] = { 328 | RGB565_TO_R(colors[selected_color]), 329 | RGB565_TO_G(colors[selected_color]), 330 | RGB565_TO_B(colors[selected_color]) 331 | }; 332 | 333 | // Draw sliders and description 334 | for (uint8_t i = 0; i < 3; i++) 335 | { 336 | print_string( 337 | color_descriptions[i], 338 | x, 339 | y + DIALOG_PALETTE_COLOR_RECT_HEIGHT + STD_CONTENT_OFFSET + (i * (SLIDER_HANDLE_HEIGHT + DIALOG_PALETTE_SLIDER_SPACING)), 340 | 0, 341 | COLOR_WHITE, 342 | COLOR_BLACK, 343 | true 344 | ); 345 | 346 | draw_slider( 347 | x + DIALOG_PALETTE_COLOR_DESC_WIDTH + STD_CONTENT_OFFSET, 348 | y + DIALOG_PALETTE_COLOR_RECT_HEIGHT + STD_CONTENT_OFFSET + (i * (SLIDER_HANDLE_HEIGHT + DIALOG_PALETTE_SLIDER_SPACING)), 349 | width - (DIALOG_PALETTE_COLOR_DESC_WIDTH + STD_CONTENT_OFFSET), 350 | SLIDER_STD_TRACK_COLOR, 351 | (selected_item == (i + 1)) ? COLOR_PRIMARY : COLOR_WHITE, 352 | 0, 353 | color_max_vals[i], 354 | color_vals[i] 355 | ); 356 | } 357 | } 358 | 359 | void draw_edit_palette_alert(palette *pal, uint8_t selected_item, uint8_t *selected_colors) 360 | { 361 | uint32_t position = draw_alert_box( 362 | pal->name, 363 | nullptr, 364 | DIALOG_PALETTE_WIDTH, 365 | DIALOG_PALETTE_HEIGHT, 366 | COLOR_MENU_BG, 367 | COLOR_PRIMARY 368 | ); 369 | 370 | const uint16_t dialog_x = ALERT_GET_X(position); 371 | const uint16_t dialog_y = ALERT_GET_Y(position); 372 | const uint16_t content_y = dialog_y + ALERT_CONTENT_OFFSET_Y - DEBUG_LINE_HEIGHT; 373 | 374 | const char *titles[] = { 375 | "OBJ0", 376 | "OBJ1", 377 | "Background" 378 | }; 379 | 380 | // Print all titles and selection panels 381 | for (uint8_t i = 0; i < 3; i++) 382 | { 383 | print_string( 384 | titles[i], 385 | dialog_x + ALERT_CONTENT_OFFSET_X, 386 | content_y + (i * (DIALOG_PALETTE_COLOR_PANEL_HEIGHT + DEBUG_LINE_HEIGHT + (2 * STD_CONTENT_OFFSET))), 387 | 0, 388 | COLOR_WHITE, 389 | COLOR_BLACK, 390 | true 391 | ); 392 | 393 | draw_edit_color_panel( 394 | dialog_x + ALERT_CONTENT_OFFSET_X, 395 | content_y + DEBUG_LINE_HEIGHT + (i * (DIALOG_PALETTE_COLOR_PANEL_HEIGHT + DEBUG_LINE_HEIGHT + (2 * STD_CONTENT_OFFSET))), 396 | DIALOG_PALETTE_WIDTH - 2 * (STD_CONTENT_OFFSET), 397 | pal->data[i], 398 | selected_item - (i * 4), 399 | selected_colors[i] 400 | ); 401 | } 402 | 403 | print_string_centered( 404 | " OK ", 405 | dialog_x, 406 | content_y + (3 * (DIALOG_PALETTE_COLOR_PANEL_HEIGHT + DEBUG_LINE_HEIGHT + (2 * STD_CONTENT_OFFSET))), 407 | DIALOG_PALETTE_WIDTH, 408 | 0, 409 | COLOR_WHITE, 410 | (selected_item == 12)? COLOR_SELECTED : COLOR_BLACK, 411 | true 412 | ); 413 | } 414 | 415 | void draw_palettes_alert(palette *user_palettes, uint8_t user_palette_count, 416 | uint8_t selected_item) 417 | { 418 | uint16_t dialog_height = ALERT_CONTENT_OFFSET_Y + ((user_palette_count + 2) * DEBUG_LINE_HEIGHT) + (2 * STD_CONTENT_OFFSET); 419 | 420 | uint32_t position = draw_alert_box( 421 | TAB_SETTINGS_ITEM_PALETTE_TITLE, 422 | TAB_SETTINGS_ITEM_PALETTE_SUBTITLE, 423 | DIALOG_PALETTE_WIDTH, 424 | dialog_height, 425 | COLOR_MENU_BG, 426 | COLOR_PRIMARY 427 | ); 428 | 429 | const uint16_t dialog_x = ALERT_GET_X(position); 430 | const uint16_t dialog_y = ALERT_GET_Y(position); 431 | 432 | // Draw every palette. Begin from 1 to skip default 433 | for (uint8_t i = 0; i < user_palette_count; i++) 434 | { 435 | print_string_centered( 436 | user_palettes[i].name, 437 | dialog_x + ALERT_CONTENT_OFFSET_X, 438 | dialog_y + ALERT_CONTENT_OFFSET_Y + (i * DEBUG_LINE_HEIGHT), 439 | DIALOG_PALETTE_WIDTH - (2 * ALERT_CONTENT_OFFSET_X), 440 | 0, 441 | COLOR_WHITE, 442 | (selected_item == i)? COLOR_SELECTED : COLOR_BLACK, 443 | true 444 | ); 445 | } 446 | 447 | // Print Create New button 448 | print_string_centered( 449 | " Create New ", 450 | dialog_x, 451 | dialog_y + ALERT_CONTENT_OFFSET_Y + (user_palette_count * DEBUG_LINE_HEIGHT) + STD_CONTENT_OFFSET, 452 | DIALOG_PALETTE_WIDTH, 453 | 0, 454 | COLOR_WHITE, 455 | (selected_item == user_palette_count)? COLOR_SELECTED : COLOR_BLACK, 456 | true 457 | ); 458 | 459 | print_string_centered( 460 | " OK ", 461 | dialog_x, 462 | dialog_y + ALERT_CONTENT_OFFSET_Y + ((user_palette_count + 1) * DEBUG_LINE_HEIGHT) + STD_CONTENT_OFFSET, 463 | DIALOG_PALETTE_WIDTH, 464 | 0, 465 | COLOR_WHITE, 466 | (selected_item == (user_palette_count + 1))? COLOR_SELECTED : COLOR_BLACK, 467 | true 468 | ); 469 | } 470 | 471 | int32_t select_key_alert(uint32_t *key) 472 | { 473 | // Backup LCD and darken background 474 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 475 | 476 | if (lcd_backup) 477 | { 478 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 479 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 480 | } 481 | 482 | draw_select_key_alert(); 483 | 484 | LCD_Refresh(); 485 | 486 | wait_input_release(); 487 | 488 | while (!Input_IsAnyKeyDown()) { } 489 | 490 | // Save the pressed key to the controls array 491 | // Do this in a loop because the key buffer might 492 | // still be empty even when a key is pressed 493 | do { 494 | getKey(&(key[0]), &(key[1])); 495 | } 496 | while(!(key[0] | key[1])); 497 | 498 | // Close alert 499 | if (lcd_backup) 500 | { 501 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 502 | hhk::free(lcd_backup); 503 | } 504 | 505 | wait_input_release(); 506 | 507 | return 0; 508 | } 509 | 510 | int32_t controls_alert(struct gb_s *gb) 511 | { 512 | emu_preferences *preferences = (emu_preferences *)gb->direct.priv; 513 | 514 | // Backup LCD and darken background 515 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 516 | 517 | if (lcd_backup) 518 | { 519 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 520 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 521 | } 522 | 523 | uint8_t selected_item = 0; 524 | 525 | // Create horizontal items count 526 | uint8_t h_items_count[DIALOG_CONTROLS_ITEM_COUNT]; 527 | 528 | for (uint8_t i = 0; i < DIALOG_CONTROLS_ITEM_COUNT; i++) 529 | { 530 | h_items_count[i] = 1; 531 | } 532 | 533 | // Create horizontal items pointer 534 | uint8_t *selected_h_items[DIALOG_CONTROLS_ITEM_COUNT]; 535 | 536 | for (uint8_t i = 0; i < DIALOG_CONTROLS_ITEM_COUNT; i++) 537 | { 538 | selected_h_items[i] = nullptr; 539 | } 540 | 541 | // Rendering and input handling 542 | for (;;) 543 | { 544 | draw_controls_alert(preferences, selected_item); 545 | 546 | uint8_t ret = process_input( 547 | selected_h_items, 548 | &selected_item, 549 | h_items_count, 550 | DIALOG_CONTROLS_ITEM_COUNT, 551 | nullptr, 552 | false 553 | ); 554 | 555 | // Check which controls key was clicked 556 | if (ret == INPUT_PROC_EXECUTE) 557 | { 558 | // Check if OK button was pressed 559 | if (selected_item == GB_KEY_COUNT) { 560 | break; 561 | } 562 | 563 | select_key_alert(preferences->controls[selected_item]); 564 | 565 | preferences->file_states.controls_changed = true; 566 | } 567 | 568 | LCD_Refresh(); 569 | } 570 | 571 | // Close alert 572 | if (lcd_backup) 573 | { 574 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 575 | hhk::free(lcd_backup); 576 | } 577 | 578 | return 0; 579 | } 580 | 581 | int32_t edit_palette_alert(palette *pal) 582 | { 583 | // Backup LCD and darken background 584 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 585 | 586 | if (lcd_backup) 587 | { 588 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 589 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 590 | } 591 | 592 | uint8_t selected_item = 0; 593 | uint8_t selected_colors[3] = { 0 }; 594 | uint8_t prev_selected_colors[3] = { 0 }; 595 | uint8_t colors[3][3] = { 0 }; 596 | 597 | // Create horizontal item count array 598 | uint8_t h_items_count[] = { 599 | 4, 32, 64, 32, // OBJ0 600 | 4, 32, 64, 32, // OBJ1 601 | 4, 32, 64, 32, // OBJ2 602 | 1, // OK 603 | }; 604 | 605 | // Create horizontal items pointer array 606 | uint8_t *selected_h_items[] = { 607 | &selected_colors[0], &colors[0][0], &colors[0][1], &colors[0][2], // OBJ0 608 | &selected_colors[1], &colors[1][0], &colors[1][1], &colors[1][2], // OBJ1 609 | &selected_colors[2], &colors[2][0], &colors[2][1], &colors[2][2], // OBJ2 610 | nullptr // OK 611 | }; 612 | 613 | // Get colors 614 | for (uint8_t i = 0; i < 3; i++) 615 | { 616 | colors[i][0] = RGB565_TO_R(pal->data[i][selected_colors[i]]); 617 | colors[i][1] = RGB565_TO_G(pal->data[i][selected_colors[i]]); 618 | colors[i][2] = RGB565_TO_B(pal->data[i][selected_colors[i]]); 619 | } 620 | 621 | // Rendering and input handling 622 | for (;;) 623 | { 624 | draw_edit_palette_alert(pal, selected_item, selected_colors); 625 | 626 | // Check if palette or add new was selected 627 | if ( 628 | process_input( 629 | selected_h_items, 630 | &selected_item, 631 | h_items_count, 632 | 13, 633 | nullptr, 634 | false 635 | ) == INPUT_PROC_EXECUTE 636 | ) 637 | { 638 | // Check if OK button was selected 639 | if (selected_item == 12) { 640 | break; 641 | } 642 | } 643 | 644 | // Check if color was switched and needs to be refetched 645 | if (memcmp(prev_selected_colors, selected_colors, sizeof(selected_colors)) != 0) 646 | { 647 | // Get colors 648 | for (uint8_t i = 0; i < 3; i++) 649 | { 650 | colors[i][0] = RGB565_TO_R(pal->data[i][selected_colors[i]]); 651 | colors[i][1] = RGB565_TO_G(pal->data[i][selected_colors[i]]); 652 | colors[i][2] = RGB565_TO_B(pal->data[i][selected_colors[i]]); 653 | } 654 | 655 | memcpy(prev_selected_colors, selected_colors, sizeof(selected_colors)); 656 | } 657 | else 658 | { 659 | // Set colors 660 | for (uint8_t i = 0; i < 3; i++) 661 | { 662 | pal->data[i][selected_colors[i]] = RGB_TO_RGB565((uint16_t)colors[i][0], (uint16_t)colors[i][1], (uint16_t)colors[i][2]); 663 | } 664 | } 665 | 666 | LCD_Refresh(); 667 | } 668 | 669 | // Close alert 670 | if (lcd_backup) 671 | { 672 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 673 | hhk::free(lcd_backup); 674 | } 675 | 676 | return 0; 677 | } 678 | 679 | int32_t palettes_alert(struct gb_s *gb) 680 | { 681 | emu_preferences *preferences = (emu_preferences *)(gb->direct.priv); 682 | palette *user_palettes; 683 | uint8_t user_palette_count = get_user_palettes(&user_palettes, gb); 684 | 685 | // Backup LCD and darken background 686 | uint16_t *lcd_backup = (uint16_t *)hhk::malloc(CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 687 | 688 | if (lcd_backup) 689 | { 690 | memcpy(lcd_backup, vram, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 691 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 692 | } 693 | 694 | uint8_t selected_item = 0; 695 | 696 | // Create horizontal item count array 697 | uint8_t h_items_count[user_palette_count + 2]; 698 | 699 | for (uint8_t i = 0; i < user_palette_count + 2; i++) 700 | { 701 | h_items_count[i] = 1; 702 | } 703 | 704 | // Create horizontal items pointer array 705 | uint8_t *selected_h_items[user_palette_count + 2]; 706 | 707 | for (uint8_t i = 0; i < user_palette_count + 2; i++) 708 | { 709 | selected_h_items[i] = nullptr; 710 | } 711 | 712 | // Rendering and input handling 713 | for (;;) 714 | { 715 | draw_palettes_alert(user_palettes, user_palette_count, selected_item); 716 | 717 | uint8_t ret; 718 | 719 | // Check if palette or add new was selected 720 | if ( 721 | (ret = process_input( 722 | selected_h_items, 723 | &selected_item, 724 | h_items_count, 725 | user_palette_count + 2, 726 | nullptr, 727 | false 728 | )) != INPUT_PROC_NONE 729 | ) 730 | { 731 | if (ret == INPUT_PROC_CLOSE) 732 | { 733 | if (selected_item < user_palette_count) 734 | { 735 | delete_palette(gb, selected_item); 736 | user_palette_count = get_user_palettes(&user_palettes, gb); 737 | 738 | // Restore background as the window gets smaller 739 | if (lcd_backup) 740 | { 741 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 742 | darken_screen_area(0, 0, CAS_LCD_WIDTH, CAS_LCD_HEIGHT); 743 | } 744 | } 745 | } 746 | else if (ret == INPUT_PROC_EXECUTE) 747 | { 748 | // Check if OK was pressed 749 | if (selected_item == user_palette_count + 1) 750 | { 751 | break; 752 | } 753 | 754 | // Check if Create New was pressed 755 | if (selected_item == user_palette_count) 756 | { 757 | uint8_t ret = create_palette(gb); 758 | 759 | if (ret == ERR_MAX_PALETTE_REACHED) 760 | { 761 | ok_alert( 762 | "ERROR", 763 | "Could not create palette", 764 | "You have already created the maximum of " XSTR(MAX_PALETTE_COUNT) " palettes!", 765 | COLOR_WHITE, 766 | COLOR_MENU_BG, 767 | COLOR_PRIMARY 768 | ); 769 | 770 | continue; 771 | } 772 | else if (ret != 0) 773 | { 774 | return MENU_CRASH; 775 | } 776 | 777 | user_palette_count = get_user_palettes(&user_palettes, gb); 778 | } 779 | 780 | edit_palette_alert(&(user_palettes[selected_item])); 781 | save_palette(&(user_palettes[selected_item]), selected_item); 782 | } 783 | } 784 | 785 | LCD_Refresh(); 786 | } 787 | 788 | // Close alert 789 | if (lcd_backup) 790 | { 791 | memcpy(vram, lcd_backup, CAS_LCD_HEIGHT * CAS_LCD_WIDTH * sizeof(uint16_t)); 792 | hhk::free(lcd_backup); 793 | } 794 | 795 | return 0; 796 | } 797 | 798 | int32_t action_edit_palettes(menu_item *item, gb_s *gb) 799 | { 800 | return palettes_alert(gb); 801 | } 802 | 803 | int32_t action_edit_controls(menu_item *item, gb_s *gb) 804 | { 805 | return controls_alert(gb); 806 | } 807 | 808 | int32_t action_show_credits(menu_item *item, gb_s *gb) 809 | { 810 | ok_alert( 811 | TAB_SETTINGS_ITEM_CREDITS_TITLE, 812 | TAB_SETTINGS_ITEM_CREDITS_SUBTITLE, 813 | TAB_SETTINGS_ITEM_CREDITS_CONTENT, 814 | COLOR_WHITE, 815 | COLOR_MENU_BG, 816 | COLOR_PRIMARY 817 | ); 818 | 819 | return 0; 820 | } 821 | 822 | menu_tab *prepare_tab_settings(menu_tab *tab, emu_preferences *preferences) 823 | { 824 | // Description and title for "Settings" tab 825 | strcpy(tab->title, TAB_SETTINGS_TITLE); 826 | strcpy(tab->description, TAB_SETTINGS_DESCRIPTION); 827 | 828 | tab->item_count = TAB_SETTINGS_ITEM_COUNT; 829 | tab->items = (menu_item *)hhk::malloc(TAB_SETTINGS_ITEM_COUNT * sizeof(menu_item)); 830 | 831 | if (!tab->items) 832 | { 833 | set_error(EMALLOC); 834 | return nullptr; 835 | } 836 | 837 | // Disabled state for each item 838 | tab->items[TAB_SETTINGS_ITEM_PALETTE_INDEX].disabled = false; 839 | tab->items[TAB_SETTINGS_ITEM_CONTROLS_INDEX].disabled = false; 840 | tab->items[TAB_SETTINGS_ITEM_CREDITS_INDEX].disabled = false; 841 | 842 | // Title for each item 843 | strcpy(tab->items[TAB_SETTINGS_ITEM_PALETTE_INDEX].title, TAB_SETTINGS_ITEM_PALETTE_TITLE); 844 | strcpy(tab->items[TAB_SETTINGS_ITEM_CONTROLS_INDEX].title, TAB_SETTINGS_ITEM_CONTROLS_TITLE); 845 | strcpy(tab->items[TAB_SETTINGS_ITEM_CREDITS_INDEX].title, TAB_SETTINGS_ITEM_CREDITS_TITLE); 846 | 847 | // Value for each item 848 | tab->items[TAB_SETTINGS_ITEM_PALETTE_INDEX].value[0] = '\0'; 849 | tab->items[TAB_SETTINGS_ITEM_CONTROLS_INDEX].value[0] = '\0'; 850 | tab->items[TAB_SETTINGS_ITEM_CREDITS_INDEX].value[0] = '\0'; 851 | 852 | // Action for each item 853 | tab->items[TAB_SETTINGS_ITEM_PALETTE_INDEX].action = action_edit_palettes; 854 | tab->items[TAB_SETTINGS_ITEM_CONTROLS_INDEX].action = action_edit_controls; 855 | tab->items[TAB_SETTINGS_ITEM_CREDITS_INDEX].action = action_show_credits; 856 | 857 | return tab; 858 | } 859 | --------------------------------------------------------------------------------