├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── common ├── common.mk ├── include │ └── function.inc └── lib │ ├── include │ └── nitro │ │ ├── cache.h │ │ ├── font.h │ │ ├── fs.h │ │ ├── heap.h │ │ ├── pad.h │ │ ├── sdk_ver.h │ │ ├── thread.h │ │ ├── tp.h │ │ └── twl.h │ └── src │ └── nitro │ ├── font.c │ └── import.c ├── docs └── HowToBuild.md ├── example ├── README.md ├── dragon_quest_5 │ ├── assets │ │ └── charmap.txt │ ├── common │ │ ├── config.mk │ │ └── symbols.ld │ ├── overlay │ │ ├── linker.ld │ │ └── src │ │ │ └── keyboard_game_interface.c │ ├── patch.py │ └── rom_nitrofs │ │ └── keyboard │ │ ├── keys.tex │ │ └── pinyin_table.bin ├── layton │ ├── assets │ │ └── charmap.txt │ ├── common │ │ ├── config.mk │ │ └── symbols.ld │ ├── overlay │ │ └── src │ │ │ └── keyboard_game_interface.c │ ├── overlay_ldr │ │ ├── Makefile │ │ ├── patch.S │ │ └── src │ │ │ └── overlay_ldr.c │ ├── patch.py │ └── rom_nitrofs │ │ ├── data │ │ └── script │ │ │ └── qscript │ │ │ └── q64_param.gds │ │ └── keyboard │ │ ├── font.bin │ │ ├── keys.tex │ │ └── pinyin_table.bin ├── pokemon_conquest │ ├── assets │ │ └── charmap.txt │ ├── common │ │ ├── config.mk │ │ └── symbols.ld │ ├── overlay │ │ └── src │ │ │ └── keyboard_game_interface.c │ ├── patch.py │ └── rom_nitrofs │ │ └── keyboard │ │ ├── keys.tex │ │ └── pinyin_table.bin └── pokemon_hg │ ├── assets │ └── CharTable.txt │ ├── common │ ├── config.mk │ └── symbols.ld │ ├── overlay │ └── src │ │ └── keyboard_game_interface.c │ ├── patch.py │ └── rom_nitrofs │ └── keyboard │ ├── keys.tex │ └── pinyin_table.bin ├── overlay ├── Makefile ├── include │ ├── arm_instruction.h │ ├── hook.h │ ├── keyboard.h │ └── touch.h ├── linker.ld └── src │ ├── hook.c │ ├── keyboard.c │ ├── keyboard_glyph.c │ ├── keyboard_heap.c │ ├── keyboard_thread.c │ ├── keyborad_pinyin.c │ ├── misc.S │ ├── overlay_entry.c │ ├── touch.c │ └── videoGL_alt.c ├── overlay_ldr ├── Makefile ├── linker.ld └── src │ └── overlay_ldr.c ├── preview ├── preview_conquest.gif ├── preview_dq5.gif ├── preview_hg.gif └── preview_layton.gif ├── resource └── keys.tex └── script ├── common.py ├── create_font.py ├── create_keycode_conv_table.py ├── create_pinyin_table.py ├── ndspy_hotfix.py ├── patch_util.py ├── pattern ├── nitrosdk_arm.yar ├── nitrosdk_common.yar └── nitrosdk_thumb.yar ├── pinyin.txt ├── requirements.txt └── rom_analyzer.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.c text eol=lf 2 | *.h text eol=lf 3 | *.S text eol=lf 4 | *.ld text eol=lf 5 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | rom 3 | build 4 | /*.bin 5 | *.elf 6 | common/config.mk 7 | common/symbols.ld 8 | __pycache__ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 enler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | all: 4 | make -C overlay 5 | make -C overlay_ldr 6 | 7 | clean: 8 | make -C overlay clean 9 | make -C overlay_ldr clean 10 | rm *.bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nitro Keyboard Plugin是一个为NDS汉化游戏开发的通用的支持中文输入的键盘插件 2 | 接入键盘插件后,可以在游戏内呼出键盘进行输入,以下为效果演示 3 | ![效果演示_心金](preview/preview_hg.gif) 4 | ![效果演示_雷顿](preview/preview_layton.gif) 5 | 像雷顿教授这种使用手写输入的游戏,也可以使用中文来回答了 6 | ![效果演示_宝可梦信长](preview/preview_conquest.gif) 7 | ![效果演示_勇者斗恶龙5](preview/preview_dq5.gif) 8 | 9 | 雷顿教授的键盘插件演示补丁下载, 10 | 链接:[度盘地址](https://pan.baidu.com/s/1CLfQgl8Y-_R0AswGS3-7_Q) 提取码:r7i3 11 | 请使用xdelta工具将补丁打在雷顿教授与不可思议的小镇简体汉化版上 12 | 在第45题“宇宙人之谜”的解答页面中,按下R+X键即可呼出中文键盘进行解答 13 | 14 | 接入文档:[点击查看](docs/HowToBuild.md) 15 | 接入遇到问题时请提issue 16 | 17 | 资料引用: 18 | 最初的灵感来源:[DSTWO的DS游侠](http://chn.supercard.sc/manual/dstwo/dsyx.htm),有一个简易的键盘供用户编辑金手指 19 | 重绘游戏画面的实现:一部分参考了[nds-bootstrap](https://github.com/DS-Homebrew/nds-bootstrap)的inGameMenu的实现 20 | -------------------------------------------------------------------------------- /common/common.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(strip $(DEVKITARM)),) 2 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 3 | endif 4 | 5 | COMMON_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 6 | 7 | include $(COMMON_DIR)/config.mk 8 | 9 | OUTPUT_ELF = $(OUTPUT).elf 10 | OUTPUT_BIN = $(OUTPUT).bin 11 | 12 | SYMBOLS_LD := $(COMMON_DIR)/symbols.ld 13 | 14 | PREFIX := $(DEVKITARM)/bin/arm-none-eabi- 15 | CC := $(PREFIX)gcc 16 | OBJCOPY := $(PREFIX)objcopy 17 | OBJDUMP := $(PREFIX)objdump 18 | AS := $(PREFIX)as 19 | LD := $(PREFIX)gcc 20 | CC1 := $(shell $(CC) --print-prog-name=cc1) -quiet 21 | CPP := $(PREFIX)cpp 22 | AR := $(PREFIX)ar 23 | 24 | OBJ_DIR := $(COMMON_DIR)/../build 25 | 26 | LIBPATH := -L $(DEVKITARM)/../libnds/lib -L $(DEVKITARM)/../calico/lib -L "$(dir $(shell $(CC) -print-file-name=libgcc.a))" -L "$(dir $(shell $(CC) -print-file-name=libnosys.a))" -L "$(dir $(shell $(CC) -print-file-name=libc.a))" 27 | LIBS := $(LIBPATH) -lnds9 -lcalico_ds9 -lc -lgcc 28 | NITRO_LIB:= $(OBJ_DIR)/nitro.a 29 | 30 | COMMON_INC_DIRS:= $(COMMON_DIR)/lib/include $(COMMON_DIR)/include $(DEVKITARM)/../calico/include $(DEVKITARM)/include $(DEVKITARM)/../libnds/include 31 | 32 | NITRO_LIB_SRCS := $(wildcard $(COMMON_DIR)/lib/src/nitro/*.c) 33 | NITRO_LIB_OBJS := $(patsubst $(COMMON_DIR)/lib/%, $(OBJ_DIR)/lib/%, $(NITRO_LIB_SRCS:.c=.o)) 34 | 35 | NITRO_LIB_COMPILE_MODE := -mthumb -mthumb-interwork 36 | NITRO_LIB_C_FLAGS := -Os -mabi=aapcs -march=armv5te -mtune=arm946e-s -DARM9=1 -D__NDS__ -DNITROSDK_VER=$(NITROSDK_VER) 37 | ifeq ($(IS_NITROSDK_THUMB), 0) 38 | $(OBJ_DIR)/lib/src/nitro/import.o: NITRO_LIB_COMPILE_MODE := -marm 39 | endif 40 | 41 | .PHONY: all 42 | 43 | all: 44 | @: 45 | 46 | $(NITRO_LIB_OBJS): $(OBJ_DIR)/%.o: $(COMMON_DIR)/%.c 47 | $(CC) -MMD -MP -MF $(OBJ_DIR)/$*.d $(foreach dir,$(COMMON_INC_DIRS),-I $(dir)) $(NITRO_LIB_C_FLAGS) $(NITRO_LIB_COMPILE_MODE) -o $(OBJ_DIR)/$*.o -c $< 48 | 49 | $(NITRO_LIB): $(NITRO_LIB_OBJS) 50 | $(AR) rcs $@ $^ 51 | -------------------------------------------------------------------------------- /common/include/function.inc: -------------------------------------------------------------------------------- 1 | .macro arm_func_start name:req 2 | .align 2, 0 3 | .global \name 4 | .arm 5 | .type \name, %function 6 | .endm 7 | 8 | .macro arm_func_end name:req 9 | .size \name, .-\name 10 | .endm 11 | 12 | .macro thumb_func_start name:req 13 | .align 2, 0 14 | .global \name 15 | .thumb 16 | .thumb_func 17 | .type \name, %function 18 | .endm 19 | 20 | .macro non_word_aligned_thumb_func_start name:req 21 | .global \name 22 | .thumb 23 | .thumb_func 24 | .type \name, %function 25 | .endm 26 | 27 | .macro thumb_func_end name:req 28 | .size \name, .-\name 29 | .endm 30 | -------------------------------------------------------------------------------- /common/lib/include/nitro/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_CACHE_H 2 | #define NITRO_CACHE_H 3 | 4 | #include 5 | 6 | void DC_InvalidateRange(void *startAddr, u32 nBytes); 7 | void DC_StoreRange(const void *startAddr, u32 nBytes); 8 | void DC_FlushRange(const void *startAddr, u32 nBytes); 9 | 10 | #endif -------------------------------------------------------------------------------- /common/lib/include/nitro/font.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_FONT_H 2 | #define NITRO_FONT_H 3 | 4 | #include 5 | 6 | // reference: https://github.com/hadashisora/NintyFont/tree/master/formats/NFTR 7 | 8 | struct NitroFontCharMappingSection; 9 | 10 | typedef struct { 11 | s8 left; 12 | u8 width; 13 | u8 advance; 14 | } NitroGlyphMetrics; 15 | 16 | typedef struct { 17 | u8 cellWidth; 18 | u8 cellHeight; 19 | u16 cellSize; 20 | s8 baselinePos; 21 | u8 maxGlyphWidth; 22 | u8 bpp; 23 | u8 flags; 24 | u8 glpyhData[]; 25 | } NitroFontGlyphSection; 26 | 27 | typedef struct NitroFontMetricsSection { 28 | u16 indexBegin; 29 | u16 indexEnd; 30 | struct NitroFontMetricsSection *nextSection; 31 | NitroGlyphMetrics glyphMetrics[]; 32 | } NitroFontMetricsSection; 33 | 34 | typedef struct NitroFontCharMappingSection { 35 | u16 charBegin; 36 | u16 charEnd; 37 | u16 mappingMethod; 38 | u16 reserved; 39 | struct NitroFontCharMappingSection *nextSection; 40 | u16 mappingInfo[]; 41 | } NitroFontCharMappingSection; 42 | 43 | typedef struct { 44 | u8 fontType; 45 | u8 lineFeed; 46 | u16 defalutGlyphIndex; 47 | NitroGlyphMetrics defalutGlyphMetrics; 48 | u8 encoding; 49 | NitroFontGlyphSection *glyphSection; 50 | NitroFontMetricsSection *metricsSection; 51 | NitroFontCharMappingSection *charMappingSection; 52 | } NitroFontInfoSection; 53 | 54 | bool LoadGlyphData( 55 | const NitroFontInfoSection *font, 56 | u16 charCode, 57 | const u8 **outGlyphBitmap, 58 | NitroGlyphMetrics *outMetrics 59 | ); 60 | 61 | #endif // NITRO_FONT_H -------------------------------------------------------------------------------- /common/lib/include/nitro/fs.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_FS_H 2 | #define NITRO_FS_H 3 | 4 | #include 5 | #include "nitro/sdk_ver.h" 6 | 7 | typedef struct { 8 | #if NITROSDK_VER >= MAKE_NITROSDK_VER(5, 0) 9 | u8 _[0x48]; 10 | #else 11 | u8 _[0x3c]; 12 | #endif 13 | } FSFile; 14 | 15 | typedef struct { 16 | u8 _[44]; 17 | } FSOverlayInfo; 18 | 19 | 20 | void FS_InitFile(FSFile *p_file); 21 | bool FS_OpenFile(FSFile *p_file, const char *path); 22 | 23 | #if NITROSDK_VER >= MAKE_NITROSDK_VER(5, 0) 24 | u32 FS_GetLength(const FSFile *p_file); 25 | #elif NITROSDK_VER >= MAKE_NITROSDK_VER(3, 0) 26 | static inline u32 FS_GetLength(const FSFile *p_file) { 27 | return *(u32 *)((u8*)p_file + 0x28) - *(u32 *)((u8*)p_file + 0x24); 28 | } 29 | #else 30 | static inline u32 FS_GetLength(const FSFile *p_file) { 31 | return *(u32 *)((u8*)p_file + 0x24) - *(u32 *)((u8*)p_file + 0x20); 32 | } 33 | #endif 34 | 35 | bool FS_SeekFile(FSFile *p_file, s32 offset, s32 origin); 36 | s32 FS_ReadFile(FSFile *p_file, void *dst, s32 len); 37 | bool FS_CloseFile(FSFile *p_file); 38 | 39 | bool FS_LoadOverlay(u32 target, u32 id); 40 | bool FS_LoadOverlayInfo(FSOverlayInfo *p_ovi, u32 target, u32 id); 41 | bool FS_LoadOverlayImage(FSOverlayInfo *p_ovi); 42 | void FS_StartOverlay(FSOverlayInfo *p_ovi); 43 | 44 | #endif -------------------------------------------------------------------------------- /common/lib/include/nitro/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_HEAP_H 2 | #define NITRO_HEAP_H 3 | 4 | #include 5 | 6 | void * OS_AllocFromHeap(s32 id, s32 heap, u32 size); 7 | void OS_FreeToHeap(s32 id, s32 heap, void *ptr); 8 | 9 | void *FndAllocFromExpHeapEx(void *heapHandle, u32 size, s32 flag); 10 | void FndFreeToExpHeap(void *heapHandle, void *ptr); 11 | 12 | #endif -------------------------------------------------------------------------------- /common/lib/include/nitro/pad.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_PAD_H 2 | #define NITRO_PAD_H 3 | 4 | #include 5 | #include 6 | 7 | #define PAD_PLUS_KEY_MASK (KEY_RIGHT | KEY_LEFT | KEY_UP | KEY_DOWN) 8 | #define PAD_BUTTON_MASK (KEY_A | KEY_B | KEY_SELECT | KEY_START | KEY_R |KEY_L | KEY_X | KEY_Y) 9 | 10 | extern u16 HW_BUTTON_XY_BUF; 11 | 12 | static inline u16 PAD_Read(void) { 13 | return (u16)(((REG_KEYINPUT | HW_BUTTON_XY_BUF) ^ 14 | (PAD_PLUS_KEY_MASK | PAD_BUTTON_MASK)) & (PAD_PLUS_KEY_MASK | PAD_BUTTON_MASK)); 15 | } 16 | 17 | #define KEY_PRESSED(mask) ((PAD_Read() & (mask)) == (mask)) 18 | 19 | #endif -------------------------------------------------------------------------------- /common/lib/include/nitro/sdk_ver.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_SDK_VER_H 2 | 3 | #define NITRO_SDK_VER_H 4 | 5 | #define MAKE_NITROSDK_VER(major, minor) (((major) << 8) | (minor)) 6 | 7 | #endif // NITRO_SDK_VER_H -------------------------------------------------------------------------------- /common/lib/include/nitro/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_THREAD_H 2 | #define NITRO_THREAD_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | u64 div_numer; 8 | u64 div_denom; 9 | u64 sqrt; 10 | u16 div_mode; 11 | u16 sqrt_mode; 12 | } 13 | CPContext; 14 | 15 | typedef struct 16 | { 17 | u32 cpsr; 18 | u32 r[13]; 19 | u32 sp; 20 | u32 lr; 21 | u32 pc_plus4; 22 | u32 sp_svc; 23 | CPContext cp_context; 24 | } 25 | OSContext; 26 | 27 | typedef struct { 28 | OSContext context; 29 | u8 _[512 - sizeof(OSContext)]; 30 | } 31 | OSThread; 32 | 33 | void OS_CreateThread(OSThread *thread, void (*func) (void *), void *arg, void *stack, u32 stackSize, u32 prio); 34 | void OS_WakeupThreadDirect(OSThread *thread); 35 | void OS_ExitThread(void); 36 | void OS_SleepThread(void *queue); 37 | 38 | #endif -------------------------------------------------------------------------------- /common/lib/include/nitro/tp.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_TP_H 2 | #define NITRO_TP_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | u16 x; 8 | u16 y; 9 | u16 touch; 10 | u16 validity; 11 | } 12 | TPData; 13 | 14 | void TP_GetCalibratedPoint(TPData *disp, const TPData *raw); 15 | 16 | #endif -------------------------------------------------------------------------------- /common/lib/include/nitro/twl.h: -------------------------------------------------------------------------------- 1 | #ifndef NITRO_TWL_H 2 | #define NITRO_TWL_H 3 | 4 | #include 5 | 6 | bool OS_IsRunOnTwl(); 7 | 8 | #endif -------------------------------------------------------------------------------- /common/lib/src/nitro/font.c: -------------------------------------------------------------------------------- 1 | #include "nitro/font.h" 2 | 3 | typedef struct { 4 | u16 charCode; 5 | u16 index; 6 | } NitroCharToGlyphMapEntry; 7 | 8 | typedef struct { 9 | u16 entryCount; 10 | const NitroCharToGlyphMapEntry entries[]; 11 | } NitroCharToGlyphMap; 12 | 13 | static inline s32 FindGlyphIndex(const NitroFontInfoSection *font, u16 charCode) { 14 | NitroFontCharMappingSection *section = font->charMappingSection; 15 | while (section) { 16 | if (charCode >= section->charBegin && charCode <= section->charEnd) { 17 | if (section->mappingMethod == 0) { 18 | return charCode - section->charBegin + section->mappingInfo[0]; 19 | } 20 | else if (section->mappingMethod == 1) { 21 | u16 index = section->mappingInfo[charCode - section->charBegin]; 22 | if ((s16)index == -1) 23 | break; 24 | return index; 25 | } 26 | else if (section->mappingMethod == 2) { 27 | const NitroCharToGlyphMap *map = (const NitroCharToGlyphMap *)§ion->mappingInfo[0]; 28 | const NitroCharToGlyphMapEntry *begin = &map->entries[0]; 29 | const NitroCharToGlyphMapEntry *end = &map->entries[map->entryCount - 1]; 30 | while (begin <= end) { 31 | const NitroCharToGlyphMapEntry *mid = begin + (end - begin) / 2; 32 | if (mid->charCode < charCode) 33 | begin = mid + 1; 34 | else if (mid->charCode > charCode) 35 | end = mid - 1; 36 | else 37 | return mid->index; 38 | } 39 | } 40 | } 41 | section = section->nextSection; 42 | } 43 | return -1; 44 | } 45 | 46 | static inline const NitroGlyphMetrics *GetGlyphMetrics(const NitroFontInfoSection *font, u16 index) { 47 | NitroFontMetricsSection *section = font->metricsSection; 48 | while (section) { 49 | if (index >= section->indexBegin && index <= section->indexEnd) { 50 | return §ion->glyphMetrics[index - section->indexBegin]; 51 | } 52 | section = section->nextSection; 53 | } 54 | return &font->defalutGlyphMetrics; 55 | } 56 | 57 | bool LoadGlyphData( 58 | const NitroFontInfoSection *font, 59 | u16 charCode, 60 | const u8 **outGlyphBitmap, 61 | NitroGlyphMetrics *outMetrics 62 | ) 63 | { 64 | s32 index = FindGlyphIndex(font, charCode); 65 | if (index < 0) 66 | index = font->defalutGlyphIndex; 67 | const NitroGlyphMetrics *glyphMetrics = GetGlyphMetrics(font, (u16)index); 68 | 69 | outMetrics->left = glyphMetrics->left; 70 | outMetrics->width = glyphMetrics->width; 71 | outMetrics->advance = glyphMetrics->advance; 72 | 73 | *outGlyphBitmap = font->glyphSection->glpyhData + font->glyphSection->cellSize * index; 74 | return true; 75 | } -------------------------------------------------------------------------------- /common/lib/src/nitro/import.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nitro/sdk_ver.h" 3 | #include "nitro/fs.h" 4 | #include "nitro/heap.h" 5 | #include "nitro/thread.h" 6 | #include "nitro/tp.h" 7 | #include "nitro/twl.h" 8 | 9 | #define IMPORT __attribute__((naked)) 10 | 11 | IMPORT void FS_InitFile(FSFile *p_file) {} 12 | 13 | IMPORT bool FS_OpenFile(FSFile *p_file, const char *path) {} 14 | 15 | #if NITROSDK_VER >= MAKE_NITROSDK_VER(5, 0) 16 | IMPORT u32 FS_GetLength(const FSFile *p_file) {} 17 | #endif 18 | 19 | IMPORT bool FS_SeekFile( FSFile *p_file, s32 offset, s32 origin ) {} 20 | 21 | IMPORT s32 FS_ReadFile(FSFile *p_file, void *dst, s32 len) {} 22 | 23 | IMPORT bool FS_CloseFile(FSFile *p_file) {} 24 | 25 | IMPORT bool FS_LoadOverlay(u32 target, u32 id) {} 26 | 27 | IMPORT bool FS_LoadOverlayInfo(FSOverlayInfo *p_ovi, u32 target, u32 id) {} 28 | 29 | IMPORT bool FS_LoadOverlayImage(FSOverlayInfo *p_ovi) {} 30 | 31 | IMPORT void FS_StartOverlay(FSOverlayInfo *p_ovi) {} 32 | 33 | IMPORT void * OS_AllocFromHeap(s32 id, s32 heap, u32 size) {} 34 | 35 | IMPORT void OS_FreeToHeap(s32 id, s32 heap, void *ptr) {} 36 | 37 | IMPORT void *FndAllocFromExpHeapEx(void *heapHandle, u32 size, s32 flag) {} 38 | 39 | IMPORT void FndFreeToExpHeap(void *heapHandle, void *ptr) {} 40 | 41 | IMPORT void OS_CreateThread(OSThread *thread, void (*func) (void *), void *arg, void *stack, u32 stackSize, u32 prio) {} 42 | 43 | IMPORT void OS_WakeupThreadDirect(OSThread *thread) {} 44 | 45 | IMPORT void OS_ExitThread(void) {} 46 | 47 | IMPORT void OS_SleepThread(void *queue) {} 48 | 49 | IMPORT void TP_GetCalibratedPoint(TPData *disp, const TPData *raw) {} 50 | 51 | #if NITROSDK_VER >= MAKE_NITROSDK_VER(5, 0) 52 | IMPORT bool OS_IsRunOnTwl() {} 53 | #endif -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | 本目录用于存放示例工程的相关代码跟资源 2 | 想要编译示例工程,请先确保已经安装了devkitpro,并且确保libnds的版本为2.0.0 3 | 示例工程中的common、overlay目录以及其他的脚本文件,请直接复制到项目的根目录 4 | 示例工程中的assets是汉化编码表等文件,rom_nitrofs是要打包进nds rom的文件 5 | 编译的时候直接cd到项目根目录运行make即可进行编译 6 | 接着再运行示例工程中的脚本,如patch.py,就可以将编译后的二进制文件进行应用, 7 | arm9.bin等文件的路径硬编码在了脚本里,请打开脚本确认或修改 -------------------------------------------------------------------------------- /example/dragon_quest_5/common/config.mk: -------------------------------------------------------------------------------- 1 | OVERLAY_ID := 44 2 | OVERLAY_ADDR := 0x02189C60 3 | OVERLAY_NAME := overlay_0044 4 | OVERLAY_LDR_ADDR := 0x02000100 5 | INJECT_OVERLAY_ID := 4 6 | IS_NITROSDK_THUMB := 1 7 | NITROSDK_VER := 0x0402 8 | -------------------------------------------------------------------------------- /example/dragon_quest_5/common/symbols.ld: -------------------------------------------------------------------------------- 1 | FS_InitFile = 0x02072A4C|1; 2 | FS_OpenFile = 0x02072C20|1; 3 | FS_ReadFile = 0x02072D3C|1; 4 | FS_SeekFile = 0x02072D48|1; 5 | FS_CloseFile = 0x02072C4C|1; 6 | FS_LoadOverlay = 0x0207328C|1; 7 | FS_LoadOverlayInfo = 0x02072FF4|1; 8 | FS_LoadOverlayImage = 0x02073094|1; 9 | FS_StartOverlay = 0x02073148|1; 10 | OS_WakeupThreadDirect = 0x02086A80|1; 11 | OS_SleepThread = 0x02086A04|1; 12 | TP_GetCalibratedPoint = 0x02077C54|1; 13 | OS_AllocFromHeap = 0x020873E8|1; 14 | OS_FreeToHeap = 0x0208748C|1; 15 | FndAllocFromExpHeapEx = 0x0207D9B8|1; 16 | FndFreeToExpHeap = 0x0207DAC0|1; 17 | OS_SaveContext = 0x02086C2C; 18 | SVC_WaitVBlankIntr = 0x0200080E|1; 19 | OS_CreateThread = 0x0208686C|1; 20 | LanucherThreadContext = 0x02125744; 21 | OSi_IrqThreadQueue = 0x027E0060; 22 | HW_BUTTON_XY_BUF = 0x027FFFA8; 23 | HW_TOUCHPANEL_BUF = 0x027FFFAA; 24 | PtrToArenaLo = 0x020872D8; 25 | Orig_OverlayStaticInitBegin = 0x021273BC; 26 | Orig_OverlayStaticInitEnd = 0x021273C4; 27 | TP_GetUserInfo = 0x02077900|1; 28 | TP_SetCalibrateParam = 0x02077978|1; -------------------------------------------------------------------------------- /example/dragon_quest_5/overlay/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 2 | OUTPUT_ARCH(arm) 3 | 4 | ENTRY(_start) 5 | 6 | SECTIONS 7 | { 8 | .text : { 9 | *src/overlay_entry.o(.text) 10 | *src/*.o(.text*) 11 | *nitro.a:import.o(.text) 12 | *nitro.a:font.o(.text) 13 | *libc.a:libc_a-memcmp.o(.text.memcmp) 14 | *libc.a:libc_a-memcpy-stub.o(.text.memcpy) 15 | *libc.a:libc_a-memset.o(.text.memset) 16 | *libgcc.a:_divsi3.o(.text) 17 | *libgcc.a:_dvmd_tls.o(.text) 18 | *libcalico_ds9.a:arm-cache.32.o(.text.armDCacheFlush) 19 | *libcalico_ds9.a:arm-cache.32.o(.text.armDrainWriteBuffer) 20 | *libcalico_ds9.a:arm-shims.32.o(.text.armIrqLockByPsr) 21 | *libcalico_ds9.a:arm-shims.32.o(.text.armIrqUnlockByPsr) 22 | *libcalico_ds9.a:bios.o(.text.svcCpuSet) 23 | *libcalico_ds9.a:bios.o(.text.svcWaitByLoop) 24 | *libnds9.a:dynamicArray.o(.text.DynamicArrayDelete) 25 | *libnds9.a:dynamicArray.o(.text.DynamicArrayGet) 26 | *libnds9.a:dynamicArray.o(.text.DynamicArrayInit) 27 | *libnds9.a:dynamicArray.o(.text.DynamicArraySet) 28 | *libnds9.a:gl2d.o(.text.SetOrtho) 29 | *libnds9.a:gl2d.o(.text.glBegin2D) 30 | *libnds9.a:gl2d.o(.text.glBoxFilled) 31 | *libnds9.a:gl2d.o(.text.glEnd2D) 32 | *libnds9.a:gl2d.o(.text.glLine) 33 | *libnds9.a:gl2d.o(.text.glLoadSpriteSet) 34 | *libnds9.a:gl2d.o(.text.glLoadTileSet) 35 | *libnds9.a:gl2d.o(.text.glSprite) 36 | *libnds9.a:gl2d.o(.text.glTexImage2D) 37 | *libnds9.a:video.o(.text.vramRestoreBanks_EFG) 38 | *libnds9.a:video.o(.text.vramRestorePrimaryBanks) 39 | *libnds9.a:videoGL.o(.text.glAssignColorTable) 40 | *libnds9.a:videoGL.o(.text.glBindTexture) 41 | *libnds9.a:videoGL.o(.text.glColorTableEXT) 42 | *libnds9.a:videoGL.o(.text.glGenTextures) 43 | *libnds9.a:videoGL.o(.text.glGetGlobals) 44 | *libnds9.a:videoGL.o(.text.glGetTexturePointer) 45 | *libnds9.a:videoGL.o(.text.glTexImage2D) 46 | *libnds9.a:videoGL.o(.text.removePaletteFromTexture.part.0) 47 | *libnds9.a:videoGL.o(.text.vramBlock_Construct) 48 | *libnds9.a:videoGL.o(.text.vramBlock__allocateBlock) 49 | *libnds9.a:videoGL.o(.text.vramBlock__deallocateBlock.part.0) 50 | *libnds9.a:videoGL.o(.text.vramBlock_allocateBlock) 51 | *libnds9.a:videoGL.o(.text.vramBlock_allocateSpecial) 52 | *libnds9.a:videoGL.o(.text.vramBlock_deallocateBlock) 53 | *libnds9.a:videoGL.o(.text.vramBlock_examineSpecial) 54 | *libnds9.a:videoGL.o(.text.vramBlock_init) 55 | *libnds9.a:videoGL.o(.text.vramBlock_terminate) 56 | *libnds9.a:videoGL.o(.text.vramGetBank) 57 | } 58 | . = ALIGN(4); 59 | 60 | .rodata : { 61 | *src/*.o(.rodata*) 62 | *libnds9.a:videoGL.o(.rodata) 63 | } 64 | . = ALIGN(4); 65 | 66 | .data : { 67 | *src/*.o(.data) 68 | *libnds9.a:videoGL.o(.data.glGlob) 69 | } 70 | . = ALIGN(4); 71 | 72 | __init_functions_begin__ = .; 73 | .init_functions : {*src/misc.o(.init_functions)} 74 | __init_functions_end__ = .; 75 | . = ALIGN(4); 76 | __ram_size__ = . - ADDR(.text); 77 | 78 | .bss : { 79 | *src/*.o(.bss*) 80 | *libnds9.a:gl2d.o(.bss.gCurrentTexture) 81 | *libnds9.a:gl2d.o(.bss.g_depth) 82 | *libnds9.a:videoGL.o(.bss.diffuse_ambient.1) 83 | *libnds9.a:videoGL.o(.bss.glGlobalData) 84 | *libnds9.a:videoGL.o(.bss.specular_emission.0) 85 | *libnds9.a:videoGL_base.o(.bss.glCurClearColor) 86 | } 87 | . = ALIGN(4); 88 | __bss_size__ = . - ADDR(.bss); 89 | __size__ = . - ADDR(.text); 90 | 91 | /* DWARF debug sections. 92 | Symbols in the DWARF debugging sections are relative to the beginning 93 | of the section so we begin them at 0. */ 94 | 95 | /* DWARF 1 */ 96 | .debug 0 : { *(.debug) } 97 | .line 0 : { *(.line) } 98 | 99 | /* GNU DWARF 1 extensions */ 100 | .debug_srcinfo 0 : { *(.debug_srcinfo) } 101 | .debug_sfnames 0 : { *(.debug_sfnames) } 102 | 103 | /* DWARF 1.1 and DWARF 2 */ 104 | .debug_aranges 0 : { *(.debug_aranges) } 105 | .debug_pubnames 0 : { *(.debug_pubnames) } 106 | 107 | /* DWARF 2 */ 108 | .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } 109 | .debug_abbrev 0 : { *(.debug_abbrev) } 110 | .debug_line 0 : { *(.debug_line) } 111 | .debug_frame 0 : { *(.debug_frame) } 112 | .debug_str 0 : { *(.debug_str) } 113 | .debug_loc 0 : { *(.debug_loc) } 114 | .debug_macinfo 0 : { *(.debug_macinfo) } 115 | 116 | /DISCARD/ : 117 | { 118 | *(*) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /example/dragon_quest_5/overlay/src/keyboard_game_interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "nitro/font.h" 5 | #include "nitro/fs.h" 6 | #include "nitro/heap.h" 7 | #include "nitro/pad.h" 8 | #include "hook.h" 9 | #include "keyboard.h" 10 | 11 | #define IMPORT __attribute__((naked)) 12 | 13 | #define max(a,b) ((a) > (b) ? (a) : (b)) 14 | 15 | IMPORT bool TP_GetUserInfo(void *calibrate) {} 16 | IMPORT void TP_SetCalibrateParam(const void *param) {} 17 | 18 | static void *gNamingContextAlt; 19 | 20 | static u16 gBppConvTable[256]; 21 | 22 | void (*Orig_NamingBegin)(void **); 23 | void (*Orig_NamingQuit)(void **); 24 | 25 | void InitBppConvTable() { 26 | for (int i = 0; i < 256; i++) { 27 | u16 value = 0; 28 | for (int j = 0; j < 8; j++) { 29 | if (i & (1 << j)) 30 | value |= 0x4000 >> (j * 2); 31 | } 32 | gBppConvTable[i] = value; 33 | } 34 | } 35 | 36 | void Hook_NamingBegin(void *namingContext) { 37 | gNamingContextAlt = namingContext; 38 | Orig_NamingBegin(namingContext); 39 | } 40 | 41 | void Hook_NamingQuit(void *namingContext) { 42 | gNamingContextAlt = NULL; 43 | Orig_NamingQuit(namingContext); 44 | } 45 | 46 | static void* Alloc(u32 size) { 47 | return FndAllocFromExpHeapEx(*(void**)0x020FADD4, size, -4); 48 | } 49 | 50 | static void Free(void *ptr) { 51 | FndFreeToExpHeap(*(void**)0x020FADD4, ptr); 52 | } 53 | 54 | static void OnOverlayLoaded() { 55 | static bool initialized = false; 56 | if (initialized) 57 | return; 58 | initialized = true; 59 | InitBppConvTable(); 60 | 61 | Orig_NamingBegin = *(void **)0x02094B14; 62 | Orig_NamingQuit = *(void **)0x02094B24; 63 | 64 | *(void **)0x02094B14 = Hook_NamingBegin; 65 | *(void **)0x02094B24 = Hook_NamingQuit; 66 | 67 | u32 calibrate[2]; 68 | TP_GetUserInfo(&calibrate); 69 | TP_SetCalibrateParam(calibrate); 70 | } 71 | 72 | static bool ShouldShowKeyboard() { 73 | return KEY_PRESSED(KEY_R | KEY_X) && gNamingContextAlt; 74 | } 75 | 76 | static int GetMaxInputLength() { 77 | return *(u32*)0x020C18F4; 78 | } 79 | 80 | static void ConvertGlyph(const u8 *glyphCell, u8 *output, s32 cellWidth, s32 cellHeight, const NitroGlyphMetrics* metrics) { 81 | int i = 0; 82 | int remainBitCount = 8; 83 | int bitOffset = 0; 84 | u32 line = 0; 85 | u32 mask = ((1 << metrics->width) - 1) << (16 - metrics->width - metrics->left); 86 | 87 | while (i < cellHeight) { 88 | int rightShift = max(remainBitCount - (cellWidth - bitOffset), 0); 89 | int bitsLength = (remainBitCount - rightShift); 90 | int leftShift = 16 - bitOffset - bitsLength; 91 | line = (*glyphCell >> rightShift << leftShift) | line; 92 | bitOffset += bitsLength; 93 | remainBitCount -= bitsLength; 94 | if (remainBitCount == 0) { 95 | remainBitCount = 8; 96 | glyphCell++; 97 | } 98 | if (bitOffset == cellWidth) { 99 | if (metrics->left >= 0) 100 | line >>= metrics->left; 101 | else 102 | line <<= metrics->left; 103 | line = line & mask; 104 | u16 lineLeft = gBppConvTable[line >> 8]; 105 | u16 lineRight = gBppConvTable[line & 0xFF]; 106 | output[0] = lineLeft & 0xFF; 107 | output[1] = lineLeft >> 8; 108 | output[2] = lineRight & 0xFF; 109 | output[3] = lineRight >> 8; 110 | output += 4; 111 | line = 0; 112 | bitOffset = 0; 113 | i++; 114 | } 115 | } 116 | } 117 | 118 | static bool LoadGlyph(u16 charCode, u8 *output, int *advance) { 119 | const u8 *glyphCell; 120 | NitroGlyphMetrics metrics; 121 | const NitroFontInfoSection *font = *(NitroFontInfoSection **)0x20fed74; 122 | 123 | memset(output, 0, 64); 124 | if (LoadGlyphData(font, charCode, &glyphCell, &metrics)) { 125 | int top = (16 - font->glyphSection->cellHeight) / 2; 126 | ConvertGlyph(glyphCell, output + top * 4, font->glyphSection->cellWidth, font->glyphSection->cellHeight, &metrics); 127 | *advance = metrics.advance; 128 | return true; 129 | } 130 | return false; 131 | } 132 | 133 | static bool KeycodeToChar(u16 keycode, u16 *output) { 134 | if (keycode == KEYCODE_AMPERSAND || 135 | keycode == KEYCODE_LESS || 136 | keycode == KEYCODE_FULLWIDTH_QUOTE || 137 | keycode == KEYCODE_FULLWIDTH_DOUBLE_QUOTE ) 138 | return false; 139 | *output = keycode; 140 | return true; 141 | } 142 | 143 | static bool CanContinueInput(u16 *inputText, int length, u16 nextChar) { 144 | return true; 145 | } 146 | 147 | static void OnInputFinished(u16 *inputText, int length, bool isCanceled) { 148 | u32 *currentPos = (u32 *)0x020C18F8; 149 | u32 *nameCharsUtf8 = (u32 *)0x020C1A68; 150 | u32 *needsScreenUpdate = ((u32 *)gNamingContextAlt + 1); 151 | if (!isCanceled) { 152 | for (int i = 0; i < length; i++) { 153 | u16 charCode = inputText[i]; 154 | if (charCode < 0x80) 155 | nameCharsUtf8[i] = charCode; 156 | else if (charCode < 0x800) { 157 | nameCharsUtf8[i] = ((0xC0 | ((charCode >> 6) & 0x1F)) << 8) | 158 | (0x80 | (charCode & 0x3F)); 159 | } 160 | else { 161 | nameCharsUtf8[i] = ((0xE0 | ((charCode >> 12) & 0x0F)) << 16) | 162 | ((0x80 | ((charCode >> 6) & 0x3F)) << 8) | 163 | (0x80 | (charCode & 0x3F)); 164 | } 165 | *needsScreenUpdate = 1; 166 | } 167 | *currentPos = length; 168 | } 169 | } 170 | 171 | KeyboardGameInterface * GetKeyboardGameInterface() { 172 | static KeyboardGameInterface gameInterface = { 173 | .Alloc = Alloc, 174 | .Free = Free, 175 | .OnOverlayLoaded = OnOverlayLoaded, 176 | .ShouldShowKeyboard = ShouldShowKeyboard, 177 | .GetMaxInputLength = GetMaxInputLength, 178 | .LoadGlyph = LoadGlyph, 179 | .KeycodeToChar = KeycodeToChar, 180 | .CanContinueInput = CanContinueInput, 181 | .OnInputFinished = OnInputFinished 182 | }; 183 | return &gameInterface; 184 | } -------------------------------------------------------------------------------- /example/dragon_quest_5/patch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import struct 5 | 6 | from script.patch_util import PatchUtil 7 | 8 | def read_config_mk(file_path): 9 | config = {} 10 | pattern = r'([A-Z_]+)\s*:=\s*(.+)$' 11 | 12 | with open(file_path, 'r') as f: 13 | for line in f: 14 | line = line.strip() 15 | if line: 16 | match = re.match(pattern, line) 17 | if match: 18 | key, value = match.groups() 19 | 20 | if value.startswith('0x'): 21 | config[key] = int(value, 16) 22 | 23 | elif value.isdigit(): 24 | config[key] = int(value) 25 | else: 26 | config[key] = value.strip() 27 | 28 | return config 29 | 30 | if __name__ == '__main__': 31 | config_mk_path = 'common/config.mk' 32 | base_rom_path = 'rom/base_rom.nds' 33 | overlay_ldr_elf = 'overlay_ldr.elf' 34 | overlay_ldr_bin = 'overlay_ldr.bin' 35 | config = read_config_mk(config_mk_path) 36 | overlay_id = config['OVERLAY_ID'] 37 | overlay_elf = config['OVERLAY_NAME'] + '.elf' 38 | overlay_bin = config['OVERLAY_NAME'] + '.bin' 39 | overlay_addr = config['OVERLAY_ADDR'] 40 | overlay_ldr_addr = config['OVERLAY_LDR_ADDR'] 41 | inject_overlay_id = config['INJECT_OVERLAY_ID'] 42 | 43 | patch_util = PatchUtil(base_rom_path) 44 | patch_util.add_overlay_entry(overlay_elf, overlay_id, overlay_addr) 45 | patch_util.modify_overlay_init_functions(inject_overlay_id, overlay_ldr_elf) 46 | patch_util.modify_arena_lo(overlay_elf, overlay_addr) 47 | patch_util.inject_overlay_ldr(overlay_ldr_bin, overlay_ldr_addr) 48 | patch_util.save_arm9_binary(os.path.join('rom','arm9.bin')) 49 | patch_util.save_overlay_table(os.path.join('rom','overlay_table.bin')) 50 | 51 | dest_dir = os.path.join('rom', 'overlay') 52 | shutil.copy2(overlay_bin, os.path.join(dest_dir, os.path.basename(overlay_bin))) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/dragon_quest_5/rom_nitrofs/keyboard/keys.tex: -------------------------------------------------------------------------------- 1 | @T@@P@PUUA@@D@P@AUU@@@A@@@@@@@A@@@U@@@@@@A@@@A@@@P@UUA@@@@P@PUU@@@@@@@P@@T@@ET@PP@ETTQUUE@TEUTATT@@PTTQUUE@TEUTDAPA@A@@UDD@A@@ATP@APU@A@@DATPQTAT@@TUEUTPQTAT@@TD@DA@@@@@D@T@UA@@T@@@@UTE@@@UUTE@@@UUAT@TU@UPUUTUQPEEUAAA@@P@AAD@@P@ATATT@@PTTAADT@@DQAA@@D@A@@@@TTQPU@UPUAPQUATPTQUPQEQ@UPAAAD@AAAD@T@AATTTADTA@ADA@DAD@@A@TAAD@@A@TPQU@PDPTPTUTQQTU@PPUPPUUUEAEQQQE@AAA@AA@PAAATDP@@AAD@PUPTT@PDP@PT@@TP@TUUUUUTUUP@ -------------------------------------------------------------------------------- /example/dragon_quest_5/rom_nitrofs/keyboard/pinyin_table.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/example/dragon_quest_5/rom_nitrofs/keyboard/pinyin_table.bin -------------------------------------------------------------------------------- /example/layton/common/config.mk: -------------------------------------------------------------------------------- 1 | OVERLAY_ID := 1 2 | OVERLAY_ADDR := 0x022455C0 3 | OVERLAY_NAME := overlay_0001 4 | OVERLAY_LDR_ADDR := 0x020000E0 5 | INJECT_OVERLAY_ID := 60 6 | IS_NITROSDK_THUMB := 0 7 | NITROSDK_VER := 0x0302 -------------------------------------------------------------------------------- /example/layton/common/symbols.ld: -------------------------------------------------------------------------------- 1 | PtrToArenaLo = 0x020A492C; 2 | FS_InitFile = 0x020A9CD0; 3 | FS_OpenFile = 0x020A9980; 4 | FS_SeekFile = 0x020A97DC; 5 | FS_CloseFile = 0x020A9938; 6 | FS_ReadFile = 0x020A9848; 7 | FS_LoadOverlay = 0x020AA06C; 8 | OS_FreeToHeap = 0x020A4DD0; 9 | OS_AllocFromHeap = 0x020A4E44; 10 | OS_CreateThread = 0x020A3BF0; 11 | OS_SleepThread = 0x020A3958; 12 | OS_WakeupThreadDirect = 0x020A38A4; 13 | OS_SaveContext = 0x020A41A4; 14 | OSi_IrqThreadQueue = 0x027E1040; 15 | TP_GetCalibratedPoint = 0x020AB630; 16 | LanucherThreadContext = 0x022157C4; 17 | gUsingBoldFont = 0x020E1A6C; 18 | HW_BUTTON_XY_BUF = 0x027FFFA8; 19 | HW_TOUCHPANEL_BUF = 0x027FFFAA; 20 | -------------------------------------------------------------------------------- /example/layton/overlay/src/keyboard_game_interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "nitro/fs.h" 5 | #include "nitro/heap.h" 6 | #include "nitro/pad.h" 7 | #include "hook.h" 8 | #include "keyboard.h" 9 | 10 | 11 | #define IMPORT __attribute__((naked)) 12 | 13 | typedef struct { 14 | FSFile fontFile; 15 | void *tpHandwritingContextAlt; 16 | int glyphNum; 17 | } GameInternalContext; 18 | 19 | typedef struct { 20 | u16 charCode; 21 | u8 width; 22 | u8 flag; 23 | u8 data[12 * 12 / 8]; 24 | } CustomGlyphEntry; 25 | 26 | static GameInternalContext gInternalContext; 27 | 28 | static u16 gBppConvTable[256]; 29 | 30 | static u32 (*Orig_HandleTPHandwriting)(void *arg); 31 | 32 | extern u8 gUsingBoldFont; 33 | 34 | const KeycodeConvItem KeycodeConvTable[] = { 35 | {KEYCODE_FULLWIDTH_SPACE, 0x8140}, 36 | {KEYCODE_CHINESE_PERIOD, 0x8142}, 37 | {KEYCODE_FULLWIDTH_EXCLAMATION, 0x8149}, 38 | {KEYCODE_FULLWIDTH_HASH, 0x8194}, 39 | {KEYCODE_FULLWIDTH_DOLLAR, 0x8190}, 40 | {KEYCODE_FULLWIDTH_PERCENT, 0x8193}, 41 | {KEYCODE_FULLWIDTH_AMPERSAND, 0x8195}, 42 | {KEYCODE_FULLWIDTH_LEFT_PAREN, 0x8169}, 43 | {KEYCODE_FULLWIDTH_RIGHT_PAREN, 0x816A}, 44 | {KEYCODE_FULLWIDTH_ASTERISK, 0x8196}, 45 | {KEYCODE_FULLWIDTH_PLUS, 0x817B}, 46 | {KEYCODE_FULLWIDTH_COMMA, 0x8143}, 47 | {KEYCODE_FULLWIDTH_MINUS, 0x817C}, 48 | {KEYCODE_FULLWIDTH_PERIOD, 0x8144}, 49 | {KEYCODE_FULLWIDTH_SLASH, 0x815E}, 50 | {KEYCODE_FULLWIDTH_COLON, 0x8146}, 51 | {KEYCODE_FULLWIDTH_SEMICOLON, 0x8147}, 52 | {KEYCODE_FULLWIDTH_LESS, 0x8183}, 53 | {KEYCODE_FULLWIDTH_EQUAL, 0x8181}, 54 | {KEYCODE_FULLWIDTH_GREATER, 0x8184}, 55 | {KEYCODE_FULLWIDTH_QUESTION, 0x8148}, 56 | {KEYCODE_FULLWIDTH_AT, 0x8197}, 57 | {KEYCODE_FULLWIDTH_LEFT_BRACKET, 0x816D}, 58 | {KEYCODE_FULLWIDTH_BACKSLASH, 0x815F}, 59 | {KEYCODE_FULLWIDTH_RIGHT_BRACKET, 0x816E}, 60 | {KEYCODE_FULLWIDTH_CARET, 0x814F}, 61 | {KEYCODE_FULLWIDTH_UNDERSCORE, 0x8151}, 62 | {KEYCODE_FULLWIDTH_GRAVE, 0x814D}, 63 | {KEYCODE_FULLWIDTH_LEFT_BRACE, 0x816F}, 64 | {KEYCODE_FULLWIDTH_PIPE, 0x8162}, 65 | {KEYCODE_FULLWIDTH_RIGHT_BRACE, 0x8170}, 66 | {KEYCODE_FULLWIDTH_TILDE, 0x8160} 67 | }; 68 | 69 | void InitBppConvTable() { 70 | for (int i = 0; i < 256; i++) 71 | { 72 | u16 value = 0; 73 | for (int j = 0; j < 8; j++) 74 | { 75 | if (i & (1 << j)) 76 | value |= 0x01 << (j * 2); 77 | } 78 | gBppConvTable[i] = value; 79 | } 80 | } 81 | 82 | u32 Hook_HandleTPHandwriting(void *arg) { 83 | gInternalContext.tpHandwritingContextAlt = arg; 84 | u8 usingBoldFont = gUsingBoldFont; 85 | gUsingBoldFont = 0; 86 | u32 result = Orig_HandleTPHandwriting(arg); 87 | gInternalContext.tpHandwritingContextAlt = NULL; 88 | gUsingBoldFont = usingBoldFont; 89 | return result; 90 | } 91 | 92 | static void InitInternalContext() { 93 | static bool initialized = false; 94 | static HookARMEntry handleTPHandwritingHookData = { 95 | .functionAddr = (void*)0x020413D4, 96 | .hookFunction = Hook_HandleTPHandwriting, 97 | .origFunctionRef = (void**)&Orig_HandleTPHandwriting 98 | }; 99 | if (initialized) 100 | return; 101 | InitBppConvTable(); 102 | 103 | if (FS_OpenFile(&gInternalContext.fontFile, "keyboard/font.bin")) 104 | { 105 | FS_ReadFile(&gInternalContext.fontFile, &gInternalContext.glyphNum, sizeof(u32)); 106 | } 107 | HookFunction(&handleTPHandwritingHookData); 108 | initialized = true; 109 | } 110 | 111 | static void* Alloc(u32 size) { 112 | return OS_AllocFromHeap(0, -1, size); 113 | } 114 | 115 | static void Free(void *ptr) { 116 | OS_FreeToHeap(0,-1, ptr); 117 | } 118 | 119 | static void OnOverlayLoaded() { 120 | InitInternalContext(); 121 | } 122 | 123 | static bool ShouldShowKeyboard() { 124 | return KEY_PRESSED(KEY_R | KEY_X) && gInternalContext.tpHandwritingContextAlt; 125 | } 126 | 127 | static int GetMaxInputLength() { 128 | if (gInternalContext.tpHandwritingContextAlt) 129 | return *((int*)gInternalContext.tpHandwritingContextAlt + 42); 130 | else 131 | return 0; 132 | } 133 | 134 | static bool LoadGlyphFromCustomFont(u16 charCode, u8 *output, int *advance) { 135 | memset(output, 0, 64); 136 | // binary search in font file 137 | int left = 0; 138 | int right = gInternalContext.glyphNum - 1; 139 | while (left <= right) { 140 | int mid = (left + right) / 2; 141 | CustomGlyphEntry entry; 142 | FS_SeekFile(&gInternalContext.fontFile, 4 + mid * sizeof(CustomGlyphEntry), 0); 143 | FS_ReadFile(&gInternalContext.fontFile, &entry, sizeof(CustomGlyphEntry)); 144 | if (entry.charCode == charCode) { 145 | for (int i = 0; i < 12; i++) { 146 | u16 line = 0; 147 | int index = i / 2 * 3; 148 | if ((i & 1) == 0) 149 | line = entry.data[index] | (entry.data[index + 1] << 8 & 0xF00); 150 | else 151 | line = entry.data[index + 1] >> 4 | (entry.data[index + 2] << 4); 152 | u16 lineLeft = gBppConvTable[line & 0xFF]; 153 | u16 lineRight = gBppConvTable[line >> 8]; 154 | index = (i + 2) * 4; 155 | output[index] = lineLeft & 0xFF; 156 | output[index + 1] = lineLeft >> 8; 157 | output[index + 2] = lineRight & 0xFF; 158 | output[index + 3] = lineRight >> 8; 159 | } 160 | *advance = entry.width; 161 | return true; 162 | } 163 | else if (entry.charCode < charCode) { 164 | left = mid + 1; 165 | } 166 | else { 167 | right = mid - 1; 168 | } 169 | } 170 | return false; 171 | } 172 | 173 | static bool KeycodeToChar(u16 keycode, u16 *output) { 174 | if (keycode >= KEYCODE_SPACE && keycode <= KEYCODE_TILDE) { 175 | *output = keycode; 176 | return true; 177 | } 178 | else if (keycode >= KEYCODE_FULLWIDTH_0 && keycode <= KEYCODE_FULLWIDTH_9) { 179 | *output = keycode - KEYCODE_FULLWIDTH_0 + 0x824F; 180 | return true; 181 | } 182 | else if (keycode >= KEYCODE_FULLWIDTH_A && keycode <= KEYCODE_FULLWIDTH_Z) { 183 | *output = keycode - KEYCODE_FULLWIDTH_A + 0x8260; 184 | return true; 185 | } 186 | else if (keycode >= KEYCODE_FULLWIDTH_a && keycode <= KEYCODE_FULLWIDTH_z) { 187 | *output = keycode - KEYCODE_FULLWIDTH_a + 0x8281; 188 | return true; 189 | } 190 | int charCode = FindCustomCharCode(KeycodeConvTable, ARRAY_SIZE(KeycodeConvTable), keycode); 191 | if (charCode != -1) { 192 | *output = charCode; 193 | return true; 194 | } 195 | return false; 196 | } 197 | 198 | static bool CanContinueInput(u16 *inputText, int length, u16 nextChar) { 199 | return true; 200 | } 201 | 202 | void OnInputFinished(u16 *inputText, int length, bool isCanceled) { 203 | int max = *((int *)gInternalContext.tpHandwritingContextAlt + 42); 204 | int i = 0; 205 | void **inputCharEntities = (void **)((u8*)gInternalContext.tpHandwritingContextAlt + 0x94); 206 | for (i = 0; i < length; i++) { 207 | *(u16 *)((u8 *)inputCharEntities[i] + 0x8a) = 1; 208 | *(u32 *)((u8 *)inputCharEntities[i] + 0x8c) = inputText[i]; 209 | } 210 | for (; i < max; i++) { 211 | *(u16 *)((u8 *)inputCharEntities[i] + 0x8a) = 1; 212 | *(u32 *)((u8 *)inputCharEntities[i] + 0x8c) = 0x8140; 213 | } 214 | return; 215 | } 216 | 217 | KeyboardGameInterface * GetKeyboardGameInterface() { 218 | static KeyboardGameInterface gameInterface = { 219 | .Alloc = Alloc, 220 | .Free = Free, 221 | .OnOverlayLoaded = OnOverlayLoaded, 222 | .ShouldShowKeyboard = ShouldShowKeyboard, 223 | .GetMaxInputLength = GetMaxInputLength, 224 | .LoadGlyph = LoadGlyphFromCustomFont, 225 | .KeycodeToChar = KeycodeToChar, 226 | .CanContinueInput = CanContinueInput, 227 | .OnInputFinished = OnInputFinished 228 | }; 229 | return &gameInterface; 230 | } -------------------------------------------------------------------------------- /example/layton/overlay_ldr/Makefile: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | 3 | include ../common/common.mk 4 | 5 | OUTPUT := overlay_ldr 6 | OUTPUT_ELF := ../$(OUTPUT).elf 7 | OUTPUT_BIN := ../$(OUTPUT).bin 8 | INC_DIRS := include $(COMMON_INC_DIRS) 9 | 10 | 11 | C_SRCS := $(wildcard src/*.c) 12 | ASM_SRCS:= $(wildcard src/*.S) 13 | C_OBJS := $(C_SRCS:%.c=$(OBJ_DIR)/%.o) 14 | ASM_OBJS:= $(ASM_SRCS:%.S=$(OBJ_DIR)/%.o) 15 | OBJS := $(C_OBJS) $(ASM_OBJS) 16 | 17 | ALL_OBJS := $(OBJS) $(NITRO_LIB_OBJS) 18 | DEPS := $(ALL_OBJS:%.o=%.d) 19 | 20 | CFLAGS := -g -w -Os -std=gnu99 -ffreestanding -mabi=aapcs -march=armv5te -marm -mtune=arm946e-s -mthumb-interwork $(foreach dir,$(INC_DIRS),-I $(dir)) -DOVERLAY_ID=$(OVERLAY_ID) -DARM9=1 -D__NDS__ -DNITROSDK_VER=$(NITROSDK_VER) 21 | LDFLAGS := -nostartfiles -nodefaultlibs -Ttext=$(OVERLAY_LDR_ADDR) -T linker.ld -T $(SYMBOLS_LD) -Wl,--use-blx 22 | 23 | $(shell mkdir -p $(sort $(dir $(ALL_OBJS)))) 24 | 25 | .PHONY: all clean 26 | 27 | all: $(OUTPUT_BIN) 28 | @: 29 | 30 | -include $(DEPS) 31 | 32 | $(OBJ_DIR)/%.o: %.c 33 | $(CC) -MMD -MP -MF $(OBJ_DIR)/$*.d $(CFLAGS) -o $(OBJ_DIR)/$*.o -c $< 34 | 35 | $(OBJ_DIR)/%.o: %.S 36 | $(CC) -D__ASSEMBLY__ $(CFLAGS) -c $< -o $@ 37 | 38 | $(OUTPUT_ELF): $(OBJS) $(NITRO_LIB) 39 | $(LD) $(LDFLAGS) -o $@ $^ $(NITRO_LIB) $(LIBS) 40 | 41 | $(OUTPUT_BIN): $(OUTPUT_ELF) 42 | $(OBJCOPY) --set-section-flags .bss=alloc,load,contents -O binary $< $@ 43 | 44 | clean: 45 | rm -rf $(OBJ_DIR) 46 | rm $(OUTPUT_ELF) $(OUTPUT_BIN) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/layton/overlay_ldr/patch.S: -------------------------------------------------------------------------------- 1 | .nds 2 | .open "rom/arm9.bin", 0x02000000 3 | 4 | .arm 5 | 6 | .org 0x020000E0 7 | .incbin "overlay_ldr.bin" 8 | 9 | .org 0x020A4830 10 | b 0x020000E0 11 | 12 | .close -------------------------------------------------------------------------------- /example/layton/overlay_ldr/src/overlay_ldr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nitro/fs.h" 3 | 4 | #ifndef OVERLAY_ID 5 | #define OVERLAY_ID -1 6 | #endif 7 | 8 | void LoadOverlay() { 9 | static int counter; 10 | if (!counter++) 11 | return; 12 | FS_LoadOverlay(0, OVERLAY_ID); 13 | } -------------------------------------------------------------------------------- /example/layton/patch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import struct 5 | import subprocess 6 | import sys 7 | 8 | from script.patch_util import PatchUtil 9 | 10 | def read_config_mk(file_path): 11 | config = {} 12 | pattern = r'([A-Z_]+)\s*:=\s*(.+)$' 13 | 14 | with open(file_path, 'r') as f: 15 | for line in f: 16 | line = line.strip() 17 | if line: 18 | match = re.match(pattern, line) 19 | if match: 20 | key, value = match.groups() 21 | 22 | if value.startswith('0x'): 23 | config[key] = int(value, 16) 24 | 25 | elif value.isdigit(): 26 | config[key] = int(value) 27 | else: 28 | config[key] = value.strip() 29 | 30 | return config 31 | 32 | def run_armips(asm_file): 33 | # Define paths 34 | armips_exe = os.path.join('tools', 'armips') 35 | 36 | # Validate files exist 37 | if not os.path.isfile(armips_exe): 38 | print(f"Error: armips not found at {armips_exe}") 39 | return False 40 | 41 | if not os.path.isfile(asm_file): 42 | print(f"Error: assembly file not found at {asm_file}") 43 | return False 44 | 45 | # Ensure executable permission 46 | os.chmod(armips_exe, 0o755) 47 | 48 | try: 49 | result = subprocess.run( 50 | [armips_exe, asm_file], 51 | check=True, 52 | capture_output=True, 53 | text=True 54 | ) 55 | 56 | if result.stdout: 57 | print(result.stdout) 58 | 59 | return True 60 | 61 | except subprocess.CalledProcessError as e: 62 | print(f"Execution failed: {e.stderr}") 63 | return False 64 | 65 | except Exception as e: 66 | print(f"Error occurred: {e}") 67 | return False 68 | 69 | if __name__ == '__main__': 70 | config_mk_path = 'common/config.mk' 71 | base_rom_path = 'rom/base_rom.nds' 72 | overlay_ldr_elf = 'overlay_ldr.elf' 73 | overlay_ldr_bin = 'overlay_ldr.bin' 74 | config = read_config_mk(config_mk_path) 75 | overlay_id = config['OVERLAY_ID'] 76 | overlay_elf = config['OVERLAY_NAME'] + '.elf' 77 | overlay_bin = config['OVERLAY_NAME'] + '.bin' 78 | overlay_addr = config['OVERLAY_ADDR'] 79 | overlay_ldr_addr = config['OVERLAY_LDR_ADDR'] 80 | 81 | patch_util = PatchUtil(base_rom_path) 82 | patch_util.add_overlay_entry(overlay_elf, overlay_id, overlay_addr) 83 | patch_util.modify_arena_lo(overlay_elf, overlay_addr) 84 | patch_util.save_arm9_binary(os.path.join('rom','arm9.bin')) 85 | patch_util.save_overlay_table(os.path.join('rom','overlay_table.bin')) 86 | 87 | run_armips(os.path.join('overlay_ldr', 'patch.S')) 88 | 89 | dest_dir = os.path.join('rom', 'overlay') 90 | shutil.copy2(overlay_bin, os.path.join(dest_dir, os.path.basename(overlay_bin))) 91 | 92 | -------------------------------------------------------------------------------- /example/layton/rom_nitrofs/data/script/qscript/q64_param.gds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/example/layton/rom_nitrofs/data/script/qscript/q64_param.gds -------------------------------------------------------------------------------- /example/layton/rom_nitrofs/keyboard/font.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/example/layton/rom_nitrofs/keyboard/font.bin -------------------------------------------------------------------------------- /example/layton/rom_nitrofs/keyboard/keys.tex: -------------------------------------------------------------------------------- 1 | @T@@P@PUUA@@D@P@AUU@@@A@@@@@@@A@@@U@@@@@@A@@@A@@@P@UUA@@@@P@PUU@@@@@@@P@@T@@ET@PP@ETTQUUE@TEUTATT@@PTTQUUE@TEUTDAPA@A@@UDD@A@@ATP@APU@A@@DATPQTAT@@TUEUTPQTAT@@TD@DA@@@@@D@T@UA@@T@@@@UTE@@@UUTE@@@UUAT@TU@UPUUTUQPEEUAAA@@P@AAD@@P@ATATT@@PTTAADT@@DQAA@@D@A@@@@TTQPU@UPUAPQUATPTQUPQEQ@UPAAAD@AAAD@T@AATTTADTA@ADA@DAD@@A@TAAD@@A@TPQU@PDPTPTUTQQTU@PPUPPUUUEAEQQQE@AAA@AA@PAAATDP@@AAD@PUPTT@PDP@PT@@TP@TUUUUUTUUP@ -------------------------------------------------------------------------------- /example/layton/rom_nitrofs/keyboard/pinyin_table.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/example/layton/rom_nitrofs/keyboard/pinyin_table.bin -------------------------------------------------------------------------------- /example/pokemon_conquest/common/config.mk: -------------------------------------------------------------------------------- 1 | OVERLAY_ID := 16 2 | OVERLAY_ADDR := 0x022650A0 3 | OVERLAY_NAME := overlay_0016 4 | OVERLAY_LDR_ADDR := 0x020B00E4 5 | INJECT_OVERLAY_ID := 10 6 | IS_NITROSDK_THUMB := 0 7 | NITROSDK_VER := 0x0505 -------------------------------------------------------------------------------- /example/pokemon_conquest/common/symbols.ld: -------------------------------------------------------------------------------- 1 | Orig_OverlayStaticInitBegin = 0x022371FC; 2 | Orig_OverlayStaticInitEnd = 0x02237200; 3 | FS_LoadOverlay = 0x02097A08; 4 | PtrToArenaLo = 0x0201C504; 5 | PtrToArenaLo_twl = 0x0201C500; 6 | FS_InitFile = 0x0200BE74; 7 | FS_OpenFile = 0x0200C29C; 8 | FS_ReadFile = 0x0200C1CC; 9 | FS_SeekFile = 0x0200C180; 10 | FS_GetLength = 0x0200C2AC; 11 | FS_CloseFile = 0x0200C090; 12 | OS_CreateThread = 0x0201B264; 13 | OS_SleepThread = 0x0201B544; 14 | OS_WakeupThreadDirect = 0x0201B5FC; 15 | OS_SaveContext = 0x0201B99C; 16 | OSi_IrqThreadQueue = 0x02FE0160; 17 | TP_GetCalibratedPoint = 0x02020B28; 18 | FndAllocFromExpHeapEx = 0x02071724; 19 | FndFreeToExpHeap = 0x02071744; 20 | LanucherThreadContext = 0x020BA2C0; 21 | HW_BUTTON_XY_BUF = 0x02FFFFA8; 22 | HW_TOUCHPANEL_BUF = 0x02FFFFAA; 23 | OS_IsRunOnTwl = 0x0201BAD0; 24 | GetGameInternalGlyph = 0x02086EB8; -------------------------------------------------------------------------------- /example/pokemon_conquest/overlay/src/keyboard_game_interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "keyboard.h" 7 | #include "hook.h" 8 | 9 | typedef struct { 10 | void *inputContextAlt; 11 | } GameInternalContext; 12 | 13 | static GameInternalContext gInternalContext; 14 | 15 | static u16 gBppConvTable[256]; 16 | 17 | const KeycodeConvItem gKeycodeConvTable[] = { 18 | {KEYCODE_FULLWIDTH_SPACE, 0x8140}, 19 | {KEYCODE_CHINESE_PERIOD, 0x8142}, 20 | {KEYCODE_FULLWIDTH_EXCLAMATION, 0x8149}, 21 | {KEYCODE_FULLWIDTH_PERCENT, 0x8193}, 22 | {KEYCODE_FULLWIDTH_LEFT_PAREN, 0x8169}, 23 | {KEYCODE_FULLWIDTH_RIGHT_PAREN, 0x816A}, 24 | {KEYCODE_FULLWIDTH_PLUS, 0x817B}, 25 | {KEYCODE_FULLWIDTH_COMMA, 0x8143}, 26 | {KEYCODE_FULLWIDTH_MINUS, 0x817C}, 27 | {KEYCODE_FULLWIDTH_PERIOD, 0x8144}, 28 | {KEYCODE_FULLWIDTH_SLASH, 0x815E}, 29 | {KEYCODE_FULLWIDTH_COLON, 0x8146}, 30 | {KEYCODE_FULLWIDTH_SEMICOLON, 0x8147}, 31 | {KEYCODE_FULLWIDTH_EQUAL, 0x8181}, 32 | {KEYCODE_FULLWIDTH_QUESTION, 0x8148}, 33 | {KEYCODE_FULLWIDTH_AT, 0x8197}, 34 | {KEYCODE_FULLWIDTH_TILDE, 0x8160}, 35 | }; 36 | 37 | void *(*Orig_FS_LoadOverlay)(u32 target, u32 id); 38 | void *(*Orig_InputContext$$ctor)(void *this); 39 | void *(*Orig_InputContext$$dtor)(void *this); 40 | 41 | void *GetGameInternalGlyph(void *context, u16 charCode, u32 a3); 42 | 43 | void InitBppConvTable() { 44 | for (int i = 0; i < 256; i++) { 45 | u16 value = 0; 46 | for (int j = 0; j < 8; j++) { 47 | if (i & (1 << j)) 48 | value |= 0x4000 >> (j * 2); 49 | } 50 | gBppConvTable[i] = value; 51 | } 52 | } 53 | 54 | void *Hook_InputContext$$ctor(void *this) { 55 | gInternalContext.inputContextAlt = this; 56 | return Orig_InputContext$$ctor(this); 57 | } 58 | 59 | void *Hook_InputContext$$dtor(void *this) { 60 | if (this == gInternalContext.inputContextAlt) 61 | gInternalContext.inputContextAlt = NULL; 62 | return Orig_InputContext$$dtor(this); 63 | } 64 | 65 | void *Hook_FS_LoadOverlay(u32 target, u32 id) { 66 | static HookARMEntry entries[] = { 67 | { 68 | .functionAddr = (void *)0x224A35C, 69 | .origFunctionRef = (void **)&Orig_InputContext$$ctor, 70 | .hookFunction = Hook_InputContext$$ctor 71 | }, 72 | { 73 | .functionAddr = (void*)0x224A400, 74 | .origFunctionRef = (void**)&Orig_InputContext$$dtor, 75 | .hookFunction = Hook_InputContext$$dtor 76 | } 77 | }; 78 | void *result = Orig_FS_LoadOverlay(target, id); 79 | if (id == 12) { 80 | for (int i = 0; i < ARRAY_SIZE(entries); i++) 81 | HookFunction(&entries[i]); 82 | } 83 | else if (id == 15) 84 | PATCH_WORD(0x02000154, 0xDA000003); 85 | return result; 86 | } 87 | 88 | static void * Alloc(u32 size) { 89 | if (!OS_IsRunOnTwl()) 90 | return (void*)0x225EB40; 91 | else 92 | return FndAllocFromExpHeapEx((void*)0x20CC33C, size, 4); 93 | } 94 | 95 | static void Free(void * ptr) { 96 | if (OS_IsRunOnTwl()) 97 | FndFreeToExpHeap((void*)0x20CC33C, ptr); 98 | } 99 | 100 | static void OnOverlayLoaded() { 101 | static bool initialized = false; 102 | if (initialized) 103 | return; 104 | static HookARMEntry entry = { 105 | .functionAddr = (void*)FS_LoadOverlay, 106 | .origFunctionRef = (void**)&Orig_FS_LoadOverlay, 107 | .hookFunction = Hook_FS_LoadOverlay 108 | }; 109 | InitBppConvTable(); 110 | HookFunction(&entry); 111 | initialized = true; 112 | } 113 | 114 | static bool ShouldShowKeyboard() { 115 | return KEY_PRESSED(KEY_R | KEY_X) && gInternalContext.inputContextAlt; 116 | } 117 | 118 | static int GetMaxInputLength() { 119 | return 5; 120 | } 121 | 122 | static bool LoadGlyph(u16 charCode, u8 *output, int *advance) { 123 | u8 *glyph = GetGameInternalGlyph((void*)0x20CC488, charCode, 0); 124 | if (!glyph) 125 | return false; 126 | memset(output, 0, 64); 127 | for (int i = 0; i < 12; i++) { 128 | u16 line = 0; 129 | int index = i / 2 * 3; 130 | if ((i & 1) == 0) 131 | line = (glyph[index] << 4) | (glyph[index + 1] >> 4); 132 | else 133 | line = (glyph[index + 1] << 8 & 0xF00) | glyph[index + 2]; 134 | line <<= 4; 135 | u16 lineLeft = gBppConvTable[line >> 8]; 136 | u16 lineRight = gBppConvTable[line & 0xFF]; 137 | index = (i + 2) * 4; 138 | output[index] = lineLeft & 0xFF; 139 | output[index + 1] = lineLeft >> 8; 140 | output[index + 2] = lineRight & 0xFF; 141 | output[index + 3] = lineRight >> 8; 142 | *advance = 12; 143 | } 144 | return true; 145 | } 146 | 147 | static bool KeycodeToChar(u16 keycode, u16 * output) { 148 | if (keycode >= KEYCODE_FULLWIDTH_0 && keycode <= KEYCODE_FULLWIDTH_9) { 149 | *output = 0x824F + keycode - KEYCODE_FULLWIDTH_0; 150 | return true; 151 | } 152 | if (keycode >= KEYCODE_FULLWIDTH_A && keycode <= KEYCODE_FULLWIDTH_Z) { 153 | *output = 0x8260 + keycode - KEYCODE_FULLWIDTH_A; 154 | return true; 155 | } 156 | if (keycode >= KEYCODE_FULLWIDTH_a && keycode <= KEYCODE_FULLWIDTH_z) { 157 | *output = 0x8281 + keycode - KEYCODE_FULLWIDTH_a; 158 | return true; 159 | } 160 | int charCode = FindCustomCharCode(gKeycodeConvTable, ARRAY_SIZE(gKeycodeConvTable), keycode); 161 | if (charCode != -1) { 162 | *output = charCode; 163 | return true; 164 | } 165 | return false; 166 | } 167 | 168 | static bool CanContinueInput(u16 * inputText, int length, u16 nextChar) { 169 | return true; 170 | } 171 | 172 | static void OnInputFinished(u16 * inputText, int length, bool isCanceled) { 173 | if (isCanceled) 174 | return; 175 | if (gInternalContext.inputContextAlt) { 176 | u16 *inputTextAlt = (u16*)((u32)gInternalContext.inputContextAlt + 0x390); 177 | s16 *inputPoses = (s16*)((u32)gInternalContext.inputContextAlt + 0x3A2); 178 | u8 *flag = (u8*)((u32)gInternalContext.inputContextAlt + 0x3B2); 179 | u8 *textLength = (u8*)((u32)gInternalContext.inputContextAlt + 0x3B4); 180 | for (int i = 0; i < length; i++) { 181 | inputTextAlt[i] = inputText[i] >> 8 | inputText[i] << 8; 182 | inputPoses[i] = 1; 183 | } 184 | *(u8*)&inputTextAlt[length] = 0; 185 | *flag |= 1; 186 | *textLength = length < GetMaxInputLength() ? length : GetMaxInputLength() - 1; 187 | } 188 | } 189 | 190 | KeyboardGameInterface * GetKeyboardGameInterface() { 191 | static KeyboardGameInterface gameInterface = { 192 | .Alloc = Alloc, 193 | .Free = Free, 194 | .OnOverlayLoaded = OnOverlayLoaded, 195 | .ShouldShowKeyboard = ShouldShowKeyboard, 196 | .GetMaxInputLength = GetMaxInputLength, 197 | .LoadGlyph = LoadGlyph, 198 | .KeycodeToChar = KeycodeToChar, 199 | .CanContinueInput = CanContinueInput, 200 | .OnInputFinished = OnInputFinished 201 | }; 202 | return &gameInterface; 203 | } -------------------------------------------------------------------------------- /example/pokemon_conquest/patch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import struct 5 | 6 | from script.patch_util import PatchUtil 7 | 8 | def read_config_mk(file_path): 9 | config = {} 10 | pattern = r'([A-Z_]+)\s*:=\s*(.+)$' 11 | 12 | with open(file_path, 'r') as f: 13 | for line in f: 14 | line = line.strip() 15 | if line: 16 | match = re.match(pattern, line) 17 | if match: 18 | key, value = match.groups() 19 | 20 | if value.startswith('0x'): 21 | config[key] = int(value, 16) 22 | 23 | elif value.isdigit(): 24 | config[key] = int(value) 25 | else: 26 | config[key] = value.strip() 27 | 28 | return config 29 | 30 | 31 | if __name__ == '__main__': 32 | config_mk_path = 'common/config.mk' 33 | base_rom_path = 'rom/base_rom.nds' 34 | overlay_ldr_elf = 'overlay_ldr.elf' 35 | overlay_ldr_bin = 'overlay_ldr.bin' 36 | config = read_config_mk(config_mk_path) 37 | overlay_id = config['OVERLAY_ID'] 38 | overlay_elf = config['OVERLAY_NAME'] + '.elf' 39 | overlay_bin = config['OVERLAY_NAME'] + '.bin' 40 | overlay_addr = config['OVERLAY_ADDR'] 41 | overlay_ldr_addr = config['OVERLAY_LDR_ADDR'] 42 | inject_overlay_id = config['INJECT_OVERLAY_ID'] 43 | 44 | patch_util = PatchUtil(base_rom_path) 45 | patch_util.add_overlay_entry(overlay_elf, overlay_id, overlay_addr) 46 | patch_util.modify_overlay_init_functions(inject_overlay_id, overlay_ldr_elf) 47 | patch_util.modify_arena_lo(overlay_elf, overlay_addr) 48 | patch_util.modify_arena_lo(overlay_elf, overlay_addr, patch_twl=True) 49 | patch_util.inject_overlay_ldr(overlay_ldr_bin, overlay_ldr_addr) 50 | patch_util.patch_word(0x0200C538, overlay_id + 1) 51 | patch_util.save_arm9_binary(os.path.join('rom','arm9.bin')) 52 | patch_util.save_overlay_table(os.path.join('rom','overlay_table.bin')) 53 | 54 | dest_dir = os.path.join('rom', 'overlay') 55 | shutil.copy2(overlay_bin, os.path.join(dest_dir, os.path.basename(overlay_bin))) 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/pokemon_conquest/rom_nitrofs/keyboard/keys.tex: -------------------------------------------------------------------------------- 1 | @T@@P@PUUA@@D@P@AUU@@@A@@@@@@@A@@@U@@@@@@A@@@A@@@P@UUA@@@@P@PUU@@@@@@@P@@T@@ET@PP@ETTQUUE@TEUTATT@@PTTQUUE@TEUTDAPA@A@@UDD@A@@ATP@APU@A@@DATPQTAT@@TUEUTPQTAT@@TD@DA@@@@@D@T@UA@@T@@@@UTE@@@UUTE@@@UUAT@TU@UPUUTUQPEEUAAA@@P@AAD@@P@ATATT@@PTTAADT@@DQAA@@D@A@@@@TTQPU@UPUAPQUATPTQUPQEQ@UPAAAD@AAAD@T@AATTTADTA@ADA@DAD@@A@TAAD@@A@TPQU@PDPTPTUTQQTU@PPUPPUUUEAEQQQE@AAA@AA@PAAATDP@@AAD@PUPTT@PDP@PT@@TP@TUUUUUTUUP@ -------------------------------------------------------------------------------- /example/pokemon_conquest/rom_nitrofs/keyboard/pinyin_table.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/example/pokemon_conquest/rom_nitrofs/keyboard/pinyin_table.bin -------------------------------------------------------------------------------- /example/pokemon_hg/common/config.mk: -------------------------------------------------------------------------------- 1 | OVERLAY_ID := 128 2 | OVERLAY_ADDR := 0x0226E140 3 | OVERLAY_NAME := overlay_0128 4 | OVERLAY_LDR_ADDR := 0x0200008C 5 | INJECT_OVERLAY_ID := 60 6 | IS_NITROSDK_THUMB := 0 7 | NITROSDK_VER := 0x0400 -------------------------------------------------------------------------------- /example/pokemon_hg/common/symbols.ld: -------------------------------------------------------------------------------- 1 | Orig_OverlayStaticInitBegin = 0x021EAD40; 2 | Orig_OverlayStaticInitEnd = 0x021EAD44; 3 | FS_LoadOverlay = 0x020D85D8; 4 | FS_LoadOverlayInfo = 0x020D8138; 5 | FS_LoadOverlayImage = 0x020D82C0; 6 | FS_StartOverlay = 0x020D83E4; 7 | PtrToArenaLo = 0x020D24D4; 8 | NitroMain = 0x2000C89; 9 | FS_InitFile = 0x020D7810; 10 | FS_OpenFile = 0x020D7AF0; 11 | FS_ReadFile = 0x020D7C44; 12 | FS_SeekFile = 0x020D7C54; 13 | FS_CloseFile = 0x020D7B38; 14 | OS_CreateThread = 0x020D134C; 15 | OS_SleepThread = 0x020D16F4; 16 | OS_WakeupThreadDirect = 0x020D17AC; 17 | OS_SaveContext = 0x020D1B50; 18 | OSi_IrqThreadQueue = 0x027E0060; 19 | TP_GetCalibratedPoint = 0x020D9F58; 20 | FillTextWindow = 0x0201D62C|1; 21 | DrawTextToWindow = 0x02083FE0|1; 22 | MoveCharCursor = 0x020848B8|1; 23 | OnHandleNamingEvent = 0x208256C|1; 24 | DeinitNamingContext = 0x2082930|1; 25 | LanucherThreadContext = 0x021E0CB0; 26 | HW_BUTTON_XY_BUF = 0x027FFFA8; 27 | HW_TOUCHPANEL_BUF = 0x027FFFAA; 28 | HW_ARENA_INFO_BUF = 0x027FFDA0; -------------------------------------------------------------------------------- /example/pokemon_hg/overlay/src/keyboard_game_interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "nitro/fs.h" 5 | #include "nitro/heap.h" 6 | #include "nitro/pad.h" 7 | #include "keyboard.h" 8 | 9 | #define IMPORT __attribute__((naked)) 10 | 11 | IMPORT void DrawTextToWindow(u8 *, u16 *, u32, u32, u32, u32, u32, u32) {} 12 | IMPORT void FillTextWindow(u8 *, u8) {} 13 | IMPORT void MoveCharCursor(u8 *, u16, u16) {} 14 | IMPORT int OnHandleNamingEvent(u32, u32) {} 15 | IMPORT int DeinitNamingContext(u32, u32) {} 16 | 17 | 18 | extern u8 *HW_ARENA_INFO_BUF[]; 19 | 20 | typedef struct { 21 | u32 glyphOffset; 22 | u32 widthOffset; 23 | u32 glyphNum; 24 | } PMFontHeader; 25 | 26 | typedef struct { 27 | FSFile fontFile; 28 | PMFontHeader header; 29 | void *namingContextAlt; 30 | int glyphNum; 31 | } GameInternalContext; 32 | 33 | typedef struct { 34 | u16 charCode; 35 | u8 width; 36 | u8 flag; 37 | u8 data[12 * 12 / 8]; 38 | } CustomGlyphEntry; 39 | 40 | static GameInternalContext gInternalContext; 41 | 42 | static u16 gBppConvTable[256]; 43 | 44 | const KeycodeConvItem KeycodeConvTable[] = { 45 | {KEYCODE_SPACE, 0x01DE}, 46 | {KEYCODE_EXCLAMATION, 0x01AB}, 47 | {KEYCODE_HASH, 0x01C0}, 48 | {KEYCODE_DOLLAR, 0x01A8}, 49 | {KEYCODE_PERCENT, 0x01D2}, 50 | {KEYCODE_AMPERSAND, 0x01C2}, 51 | {KEYCODE_LEFT_PAREN, 0x01B9}, 52 | {KEYCODE_RIGHT_PAREN, 0x01BA}, 53 | {KEYCODE_ASTERISK, 0x01BF}, 54 | {KEYCODE_PLUS, 0x01BD}, 55 | {KEYCODE_COMMA, 0x01AD}, 56 | {KEYCODE_MINUS, 0x01BE}, 57 | {KEYCODE_PERIOD, 0x01AE}, 58 | {KEYCODE_SLASH, 0x01B1}, 59 | {KEYCODE_COLON, 0x01C4}, 60 | {KEYCODE_SEMICOLON, 0x01C5}, 61 | {KEYCODE_EQUAL, 0x01C1}, 62 | {KEYCODE_QUESTION, 0x01AC}, 63 | {KEYCODE_AT, 0x01D0}, 64 | {KEYCODE_UNDERSCORE, 0x01E9}, 65 | {KEYCODE_TILDE, 0x01C3}, 66 | {KEYCODE_FULLWIDTH_SPACE, 0x01FB}, 67 | {KEYCODE_CHINESE_PERIOD, 0x00E4}, 68 | {KEYCODE_FULLWIDTH_EXCLAMATION, 0x00E1}, 69 | {KEYCODE_FULLWIDTH_PERCENT, 0x0106}, 70 | {KEYCODE_FULLWIDTH_AMPERSAND, 0x0120}, 71 | {KEYCODE_FULLWIDTH_LEFT_PAREN, 0x00EC}, 72 | {KEYCODE_FULLWIDTH_RIGHT_PAREN, 0x00ED}, 73 | {KEYCODE_FULLWIDTH_PLUS, 0x00F0}, 74 | {KEYCODE_FULLWIDTH_COMMA, 0x00F9}, 75 | {KEYCODE_FULLWIDTH_PERIOD, 0x00F8}, 76 | {KEYCODE_FULLWIDTH_SLASH, 0x00E7}, 77 | {KEYCODE_FULLWIDTH_COLON, 0x00F6}, 78 | {KEYCODE_FULLWIDTH_SEMICOLON, 0x00F7}, 79 | {KEYCODE_FULLWIDTH_LESS, 0x01FC}, 80 | {KEYCODE_FULLWIDTH_EQUAL, 0x00F4}, 81 | {KEYCODE_FULLWIDTH_GREATER, 0x01FD}, 82 | {KEYCODE_FULLWIDTH_QUESTION, 0x00E2}, 83 | {KEYCODE_FULLWIDTH_AT, 0x0104}, 84 | {KEYCODE_FULLWIDTH_UNDERSCORE, 0x01EA}, 85 | {KEYCODE_FULLWIDTH_TILDE, 0x00F5}, 86 | }; 87 | 88 | void InitBppConvTable() { 89 | for (int i = 0; i < 256; i++) 90 | { 91 | u16 value = 0; 92 | for (int j = 0; j < 8; j++) 93 | { 94 | if (i & (1 << j)) 95 | value |= 0x01 << (j * 2); 96 | } 97 | gBppConvTable[i] = value; 98 | } 99 | } 100 | 101 | static int Hook_OnHandleNamingEvent(u32 a1, u32 a2) { 102 | int result = OnHandleNamingEvent(a1, a2); 103 | gInternalContext.namingContextAlt = *(void**)(a1 + 0x1C); 104 | return result; 105 | } 106 | 107 | static int Hook_DeinitNamingContext(u32 a1, u32 a2){ 108 | int result = DeinitNamingContext(a1, a2); 109 | gInternalContext.namingContextAlt = NULL; 110 | return result; 111 | } 112 | 113 | static void InitInternalContext() { 114 | static bool initialized = false; 115 | if (initialized) 116 | return; 117 | FS_InitFile(&gInternalContext.fontFile); 118 | if (FS_OpenFile(&gInternalContext.fontFile, "a/0/1/6")) { 119 | FS_SeekFile(&gInternalContext.fontFile, 0x84, 0); 120 | FS_ReadFile(&gInternalContext.fontFile, &gInternalContext.header, sizeof(PMFontHeader)); 121 | } 122 | *(void**)0x2101D94 = Hook_OnHandleNamingEvent; 123 | *(void**)0x2101D98 = Hook_DeinitNamingContext; 124 | initialized = true; 125 | } 126 | 127 | static void* Alloc(u32 size) { 128 | if (HW_ARENA_INFO_BUF[0] + size <= HW_ARENA_INFO_BUF[9]) { 129 | return HW_ARENA_INFO_BUF[0]; 130 | } 131 | return NULL; 132 | } 133 | 134 | static void Free(void *ptr) { 135 | // do nothing 136 | } 137 | 138 | static void OnOverlayLoaded() { 139 | InitInternalContext(); 140 | } 141 | 142 | static bool ShouldShowKeyboard() { 143 | return KEY_PRESSED(KEY_R | KEY_X) && gInternalContext.namingContextAlt; 144 | } 145 | 146 | static int GetMaxInputLength() { 147 | if (gInternalContext.namingContextAlt) { 148 | return ((u32*)gInternalContext.namingContextAlt)[3]; 149 | } 150 | return 5; 151 | } 152 | 153 | u32 Reverse2bitUnits(u32 pixelData) { 154 | u32 result = 0; 155 | for (int i = 0; i < 16; ++i) { 156 | u8 two_bits = (pixelData >> (i * 2)) & 0x3; 157 | result |= (two_bits << ((15 - i) * 2)); 158 | } 159 | return result; 160 | } 161 | 162 | static bool LoadGlyph(u16 charCode, u8 *output, int *advance) { 163 | charCode--; 164 | u16 glyphData[16 * 16 / 4 / sizeof(u16)]; 165 | u8 width; 166 | FS_SeekFile(&gInternalContext.fontFile, 0x84 + gInternalContext.header.glyphOffset + charCode * 16 * 16 / 4, 0); 167 | FS_ReadFile(&gInternalContext.fontFile, glyphData, sizeof(glyphData)); 168 | for (int i = 0; i < 2; i++) { 169 | for (int j = 0; j < 8; j++) { 170 | u32 pixelData = (glyphData[i * 16 + j] << 16) | glyphData[i * 16 + j + 8]; 171 | pixelData = (pixelData & 0x55555555) & ((pixelData >> 1) ^ pixelData); 172 | pixelData = Reverse2bitUnits(pixelData); 173 | memcpy(output, &pixelData, sizeof(u32)); 174 | output += 4; 175 | } 176 | } 177 | if (charCode <= gInternalContext.header.glyphNum) { 178 | FS_SeekFile(&gInternalContext.fontFile, 0x84 + gInternalContext.header.widthOffset + charCode, 0); 179 | FS_ReadFile(&gInternalContext.fontFile, &width, sizeof(u8)); 180 | *advance = width; 181 | } 182 | else { 183 | *advance = 12; 184 | } 185 | 186 | return true; 187 | } 188 | 189 | static bool KeycodeToChar(u16 keycode, u16 *output) { 190 | if (keycode >= KEYCODE_0 && keycode <= KEYCODE_9) { 191 | *output = keycode - KEYCODE_0 + 0x0121; 192 | return true; 193 | } 194 | else if (keycode >= KEYCODE_A && keycode <= KEYCODE_Z) { 195 | *output = keycode - KEYCODE_A + 0x012B; 196 | return true; 197 | } 198 | else if (keycode >= KEYCODE_a && keycode <= KEYCODE_z) { 199 | *output = keycode - KEYCODE_a + 0x0145; 200 | return true; 201 | } 202 | else if (keycode >= KEYCODE_FULLWIDTH_0 && keycode <= KEYCODE_FULLWIDTH_9) { 203 | *output = keycode - KEYCODE_FULLWIDTH_0 + 0x00A2; 204 | return true; 205 | } 206 | else if (keycode >= KEYCODE_FULLWIDTH_A && keycode <= KEYCODE_FULLWIDTH_Z) { 207 | *output = keycode - KEYCODE_FULLWIDTH_A + 0x00AC; 208 | return true; 209 | } 210 | else if (keycode >= KEYCODE_FULLWIDTH_a && keycode <= KEYCODE_FULLWIDTH_z) { 211 | *output = keycode - KEYCODE_FULLWIDTH_a + 0x00C6; 212 | return true; 213 | } 214 | int charCode = FindCustomCharCode(KeycodeConvTable, ARRAY_SIZE(KeycodeConvTable), keycode); 215 | if (charCode != -1) { 216 | *output = charCode; 217 | return true; 218 | } 219 | return false; 220 | } 221 | 222 | static bool CanContinueInput(u16 *inputText, int length, u16 nextChar) { 223 | return true; 224 | } 225 | 226 | void OnInputFinished(u16 *inputText, int length, bool isCanceled) { 227 | u8 *namingContext = (u8 *)gInternalContext.namingContextAlt; 228 | if (!namingContext) 229 | return; 230 | if (!isCanceled) { 231 | u16 *name = (u16*)(namingContext + 216); 232 | memcpy(name, inputText, length * sizeof(u16)); 233 | name[length] = 0xFFFF; 234 | u16 *nameLength = (u16*)(namingContext + 344); 235 | *nameLength = length; 236 | FillTextWindow(namingContext + 1000, 1); 237 | DrawTextToWindow(namingContext + 1000, name, 0, 0, 12, 0, 0x000E0F01, 0); 238 | MoveCharCursor(namingContext + 868, *nameLength, *(u32*)(namingContext + 12)); 239 | } 240 | 241 | int bg0H = *(int *)(namingContext + 0x468); 242 | int bg0V = *(int *)(namingContext + 0x46C); 243 | REG_BG0HOFS = 512 + bg0H; 244 | REG_BG0VOFS = 512 + bg0V; 245 | return; 246 | } 247 | 248 | KeyboardGameInterface * GetKeyboardGameInterface() { 249 | static KeyboardGameInterface gameInterface = { 250 | .Alloc = Alloc, 251 | .Free = Free, 252 | .OnOverlayLoaded = OnOverlayLoaded, 253 | .ShouldShowKeyboard = ShouldShowKeyboard, 254 | .GetMaxInputLength = GetMaxInputLength, 255 | .LoadGlyph = LoadGlyph, 256 | .KeycodeToChar = KeycodeToChar, 257 | .CanContinueInput = CanContinueInput, 258 | .OnInputFinished = OnInputFinished 259 | }; 260 | return &gameInterface; 261 | } -------------------------------------------------------------------------------- /example/pokemon_hg/patch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import struct 5 | 6 | from script.patch_util import PatchUtil 7 | 8 | def read_config_mk(file_path): 9 | config = {} 10 | pattern = r'([A-Z_]+)\s*:=\s*(.+)$' 11 | 12 | with open(file_path, 'r') as f: 13 | for line in f: 14 | line = line.strip() 15 | if line: 16 | match = re.match(pattern, line) 17 | if match: 18 | key, value = match.groups() 19 | 20 | if value.startswith('0x'): 21 | config[key] = int(value, 16) 22 | 23 | elif value.isdigit(): 24 | config[key] = int(value) 25 | else: 26 | config[key] = value.strip() 27 | 28 | return config 29 | 30 | if __name__ == '__main__': 31 | config_mk_path = 'common/config.mk' 32 | base_rom_path = 'rom/base_rom.nds' 33 | overlay_ldr_elf = 'overlay_ldr.elf' 34 | overlay_ldr_bin = 'overlay_ldr.bin' 35 | config = read_config_mk(config_mk_path) 36 | overlay_id = config['OVERLAY_ID'] 37 | overlay_elf = config['OVERLAY_NAME'] + '.elf' 38 | overlay_bin = config['OVERLAY_NAME'] + '.bin' 39 | overlay_addr = config['OVERLAY_ADDR'] 40 | overlay_ldr_addr = config['OVERLAY_LDR_ADDR'] 41 | inject_overlay_id = config['INJECT_OVERLAY_ID'] 42 | 43 | patch_util = PatchUtil(base_rom_path) 44 | patch_util.add_overlay_entry(overlay_elf, overlay_id, overlay_addr) 45 | patch_util.modify_overlay_init_functions(inject_overlay_id, overlay_ldr_elf) 46 | patch_util.modify_arena_lo(overlay_elf, overlay_addr) 47 | patch_util.inject_overlay_ldr(overlay_ldr_bin, overlay_ldr_addr) 48 | patch_util.save_arm9_binary(os.path.join('rom','arm9.bin')) 49 | patch_util.save_overlay_table(os.path.join('rom','overlay_table.bin')) 50 | 51 | dest_dir = os.path.join('rom', 'overlay') 52 | shutil.copy2(overlay_bin, os.path.join(dest_dir, os.path.basename(overlay_bin))) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/pokemon_hg/rom_nitrofs/keyboard/keys.tex: -------------------------------------------------------------------------------- 1 | @T@@P@PUUA@@D@P@AUU@@@A@@@@@@@A@@@U@@@@@@A@@@A@@@P@UUA@@@@P@PUU@@@@@@@P@@T@@ET@PP@ETTQUUE@TEUTATT@@PTTQUUE@TEUTDAPA@A@@UDD@A@@ATP@APU@A@@DATPQTAT@@TUEUTPQTAT@@TD@DA@@@@@D@T@UA@@T@@@@UTE@@@UUTE@@@UUAT@TU@UPUUTUQPEEUAAA@@P@AAD@@P@ATATT@@PTTAADT@@DQAA@@D@A@@@@TTQPU@UPUAPQUATPTQUPQEQ@UPAAAD@AAAD@T@AATTTADTA@ADA@DAD@@A@TAAD@@A@TPQU@PDPTPTUTQQTU@PPUPPUUUEAEQQQE@AAA@AA@PAAATDP@@AAD@PUPTT@PDP@PT@@TP@TUUUUUTUUP@ -------------------------------------------------------------------------------- /example/pokemon_hg/rom_nitrofs/keyboard/pinyin_table.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/example/pokemon_hg/rom_nitrofs/keyboard/pinyin_table.bin -------------------------------------------------------------------------------- /overlay/Makefile: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | 3 | include ../common/common.mk 4 | 5 | OUTPUT := $(OVERLAY_NAME) 6 | OUTPUT_ELF := ../$(OUTPUT).elf 7 | OUTPUT_BIN := ../$(OUTPUT).bin 8 | 9 | INC_DIRS := include $(COMMON_INC_DIRS) 10 | 11 | C_SRCS := $(wildcard src/*.c) 12 | ASM_SRCS:= $(wildcard src/*.S) 13 | C_OBJS := $(C_SRCS:%.c=$(OBJ_DIR)/%.o) 14 | ASM_OBJS:= $(ASM_SRCS:%.S=$(OBJ_DIR)/%.o) 15 | OBJS := $(C_OBJS) $(ASM_OBJS) 16 | 17 | ALL_OBJS := $(OBJS) $(NITRO_LIB_OBJS) 18 | DEPS := $(ALL_OBJS:%.o=%.d) 19 | 20 | COMPILE_MODE := -mthumb 21 | DEFS := -DARM9 -D__NDS__ -D__thumb__ -D__ARM_ARCH=5 -DNITROSDK_VER=$(NITROSDK_VER) 22 | CFLAGS := -g -w -Os -std=gnu99 -ffreestanding -mabi=aapcs -march=armv5te -mtune=arm946e-s -mthumb-interwork $(foreach dir,$(INC_DIRS),-I $(dir)) 23 | LDFLAGS := -nostartfiles -nodefaultlibs -Ttext=$(OVERLAY_ADDR) -T linker.ld -T $(SYMBOLS_LD) -Wl,--use-blx -Wl,--wrap=glInit,--wrap=glResetTextures 24 | 25 | $(OBJ_DIR)/src/overlay_entry.o : COMPILE_MODE := -marm 26 | 27 | $(shell mkdir -p $(sort $(dir $(ALL_OBJS)))) 28 | 29 | .PHONY: all clean 30 | 31 | all: $(OUTPUT_BIN) 32 | @: 33 | 34 | -include $(DEPS) 35 | 36 | $(OBJ_DIR)/%.o: %.c 37 | $(CC) -MMD -MP -MF $(OBJ_DIR)/$*.d $(CFLAGS) $(COMPILE_MODE) $(DEFS) -o $(OBJ_DIR)/$*.o -c $< 38 | 39 | $(OBJ_DIR)/%.o: %.S 40 | $(CC) -D__ASSEMBLY__ $(CFLAGS) -c $< -o $@ 41 | 42 | $(OUTPUT_ELF): $(OBJS) $(NITRO_LIB) 43 | $(LD) $(LDFLAGS) -o $@ $^ $(NITRO_LIB) $(LIBS) 44 | 45 | $(OUTPUT_BIN): $(OUTPUT_ELF) 46 | $(OBJCOPY) --set-section-flags .init_functions=alloc,load,contents -O binary $< $@ 47 | 48 | clean: 49 | rm -rf $(OBJ_DIR) 50 | rm $(OUTPUT_ELF) $(OUTPUT_BIN) 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /overlay/include/arm_instruction.h: -------------------------------------------------------------------------------- 1 | #ifndef ARM_INSTRUCTION_H 2 | #define ARM_INSTRUCTION_H 3 | 4 | #define REG_R0 0 5 | #define REG_R1 1 6 | #define REG_R2 2 7 | #define REG_R3 3 8 | #define REG_R4 4 9 | #define REG_R5 5 10 | #define REG_R6 6 11 | #define REG_R7 7 12 | #define REG_R8 8 13 | #define REG_R9 9 14 | #define REG_R10 10 15 | #define REG_R11 11 16 | #define REG_R12 12 17 | #define REG_SP 13 18 | #define REG_LR 14 19 | #define REG_PC 15 20 | 21 | #define REG_R0_F (1 << REG_R0) 22 | #define REG_R1_F (1 << REG_R1) 23 | #define REG_R2_F (1 << REG_R2) 24 | #define REG_R3_F (1 << REG_R3) 25 | #define REG_R4_F (1 << REG_R4) 26 | #define REG_R5_F (1 << REG_R5) 27 | #define REG_R6_F (1 << REG_R6) 28 | #define REG_R7_F (1 << REG_R7) 29 | #define REG_R8_F (1 << REG_R8) 30 | #define REG_R9_F (1 << REG_R9) 31 | #define REG_R10_F (1 << REG_R10) 32 | #define REG_R11_F (1 << REG_R11) 33 | #define REG_R12_F (1 << REG_R12) 34 | #define REG_SP_F (1 << REG_SP) 35 | #define REG_LR_F (1 << REG_LR) 36 | #define REG_PC_F (1 << REG_PC) 37 | 38 | 39 | #define COND_EQ 0x0 // Equal 40 | #define COND_NE 0x1 // Not equal 41 | #define COND_CS 0x2 // Carry set/unsigned higher or same 42 | #define COND_CC 0x3 // Carry clear/unsigned lower 43 | #define COND_MI 0x4 // Minus/negative 44 | #define COND_PL 0x5 // Plus/positive or zero 45 | #define COND_VS 0x6 // Overflow 46 | #define COND_VC 0x7 // No overflow 47 | #define COND_HI 0x8 // Unsigned higher 48 | #define COND_LS 0x9 // Unsigned lower or same 49 | #define COND_GE 0xA // Signed greater than or equal 50 | #define COND_LT 0xB // Signed less than 51 | #define COND_GT 0xC // Signed greater than 52 | #define COND_LE 0xD // Signed less than or equal 53 | #define COND_AL 0xE // Always (unconditional) 54 | 55 | #define NOP 0xE1A00000 56 | #define NOP_T 0x46C0 57 | #define BX_PC_T 0x4778 58 | 59 | #define CALC_PC_RELATIVE_OFFSET_A(current_pc, target) (((s32)(target) - 8 - (s32)(current_pc)) >> 2) 60 | #define CALC_PC_RELATIVE_OFFSET_T(current_pc, target) (((s32)(target) - 4 - (s32)(current_pc)) >> 1) 61 | #define CALC_PC_RELATIVE_OFFSET_ALIGNED_T(current_pc, target) ((((s32)(target) & ~3) - 4 - ((s32)(current_pc) & ~3)) >> 2) 62 | 63 | #define MAKE_BRANCH(src, dest) ((CALC_PC_RELATIVE_OFFSET_A(src, dest) & 0xFFFFFF) | 0xEA000000) 64 | #define MAKE_BRANCH_LINK(src, dest) ((CALC_PC_RELATIVE_OFFSET_A(src, dest) & 0xFFFFFF) | 0xEB000000) 65 | #define MAKE_BRANCH_LINK_EXCHANGE(src, dest) ((CALC_PC_RELATIVE_OFFSET_A(src, dest) & 0xFFFFFF) | (((((s32)(dest) - 8 - (s32)(src)) >> 1) & 1) << 24) | 0xFA000000) 66 | #define MAKE_BRANCH_T(src, dest) (((((s32)(dest) - 4 - (s32)(src)) >> 1) & 0x7FF) | 0xE000) 67 | #define MAKE_BARNCH_COND_T(src, dest, cond) (((((s32)(dest) - 4 - (s32)(src)) >> 1) & 0xFF) | ((cond) << 8) | 0xD000) 68 | #define MAKE_BRANCH_LINK_T_H(src, dest) (((((s32)(dest) - 4 - (s32)(src)) >> 12) & 0x7FF) | 0xF000) 69 | #define MAKE_BRANCH_LINK_T_L(src, dest) (((((s32)(dest) - 4 - (s32)(src)) >> 1) & 0x7FF) | 0xF800) 70 | #define MAKE_BRANCH_LINK_EXCHAGE_T_H(src, dest) ((((((s32)(dest) & ~3) - 4 - ((s32)(src) & ~3)) >> 12) & 0x7FF) | 0xF000) 71 | #define MAKE_BRANCH_LINK_EXCHAGE_T_L(src, dest) ((((((s32)(dest) & ~3) - 4 - ((s32)(src) & ~3)) >> 1) & 0x7FF) | 0xE800) 72 | 73 | #define MAKE_PUSH_T(reg_flags) (0xB400 | (((reg_flags) & REG_LR_F) ? 0x80 : 0) | ((reg_flags) & 0x7F)) 74 | #define MAKE_POP_T(reg_flags) (0xBC00 | (((reg_flags) & REG_PC_F) ? 0x80 : 0) | ((reg_flags) & 0x7F)) 75 | #define MAKE_ADD_RD_RS_RN_T(rd, rs, rn) (0x1800 | (rn) << 6 | (rs) << 3 | (rd)) 76 | #define MAKE_MOV_RD_RS_T(rd, rs) (0x4600 | ((rs) << 3) | ((rd) & 7) | ((rd) & 8) << 4) 77 | #define MAKE_ADD_RD_PC_OFF_T(rd, current_pc, addr) ((CALC_PC_RELATIVE_OFFSET_ALIGNED_T(current_pc, addr) & 0xFF) | (((rd) & 7) << 8) | 0xA000) 78 | #define MAKE_LDR_PC_OFF_T(rd, current_pc, addr) ((CALC_PC_RELATIVE_OFFSET_ALIGNED_T(current_pc, addr) & 0xFF) | (((rd) & 7) << 8) | 0x4800) 79 | 80 | 81 | 82 | static inline u32 CreateSubsInstruction(u8 rd, u8 rs, u16 imm) { 83 | u32 instruction = 0xE2500000; // Base opcode for SUBS (immediate) 84 | instruction |= (rd & 0xF) << 12; // Set destination register 85 | instruction |= (rs & 0xF) << 16; // Set source register 86 | instruction |= imm & 0xFFF; // Set immediate value 87 | return instruction; 88 | } 89 | 90 | static inline u32 CreateCmpInstruction(u8 rs, u16 imm) { 91 | u32 instruction = 0xE3500000; // Base opcode for CMP (immediate) 92 | instruction |= (rs & 0xF) << 16; // Set source register 93 | instruction |= imm & 0xFFF; // Set immediate value 94 | return instruction; 95 | } 96 | 97 | static inline u32 CreateBInstruction(u32 address, u32 target, u8 cond) { 98 | s32 offset = (s32)(target - address - 8) >> 2; // Calculate the offset 99 | u32 instruction = 0x0A000000; // Base opcode for B (branch) 100 | instruction |= (cond & 0xF) << 28; // Set condition code 101 | instruction |= offset & 0xFFFFFF; // Set offset 102 | return instruction; 103 | } 104 | 105 | #endif -------------------------------------------------------------------------------- /overlay/include/hook.h: -------------------------------------------------------------------------------- 1 | #ifndef HOOK_H 2 | #define HOOK_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | void * functionAddr; 8 | void ** origFunctionRef; 9 | u32 ldrInstruction; 10 | void *hookFunction; 11 | u32 oldInstruction; 12 | u32 jumpInstruction; 13 | u32 extraData; 14 | } HookARMEntry; 15 | 16 | typedef struct { 17 | void * functionAddr; 18 | void ** origFunctionRef; 19 | u32 ldrInstruction; 20 | void *hookFunction; 21 | u16 *oldInstructions; 22 | } HookThumbEntry; 23 | 24 | void HookFunction(HookARMEntry * entry); 25 | void ForceMakingBranchLink(void *origAddr, void *targetAddr); 26 | void HookFunctionThumb(HookThumbEntry *entry); 27 | void SetupHookMemory(void *memory, u32 size); 28 | 29 | #define PATCH_WORD(addr, value) do { \ 30 | *(vu32 *)(addr) = (vu32)(value); \ 31 | DC_FlushRange((void *)(addr), sizeof(u32)); \ 32 | } while (0) 33 | 34 | #endif -------------------------------------------------------------------------------- /overlay/include/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYBOARD_H 2 | #define KEYBOARD_H 3 | 4 | #include 5 | 6 | #define KEY_BUTTON_WIDTH 19 7 | #define KEY_BUTTON_HEIGHT 20 8 | #define KEY_BUTTON_SPACING 1 9 | 10 | #define KEYBOARD_LANG_CHS 0 11 | #define KEYBOARD_LANG_ENG 1 12 | #define KEYBOARD_LANG_MAX 2 13 | 14 | #define EXTERNAL_FONT_PALETTE_SIZE 2 15 | 16 | #define KEYBOARD_HEAP_SIZE (24 * 1024) 17 | 18 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) 19 | 20 | typedef enum { 21 | KEYCODE_NONE = 0, 22 | // 大写字母 23 | KEYCODE_A = 65, // ASCII 'A' 24 | KEYCODE_B = 66, // ASCII 'B' 25 | KEYCODE_C = 67, // ASCII 'C' 26 | KEYCODE_D = 68, // ASCII 'D' 27 | KEYCODE_E = 69, // ASCII 'E' 28 | KEYCODE_F = 70, // ASCII 'F' 29 | KEYCODE_G = 71, // ASCII 'G' 30 | KEYCODE_H = 72, // ASCII 'H' 31 | KEYCODE_I = 73, // ASCII 'I' 32 | KEYCODE_J = 74, // ASCII 'J' 33 | KEYCODE_K = 75, // ASCII 'K' 34 | KEYCODE_L = 76, // ASCII 'L' 35 | KEYCODE_M = 77, // ASCII 'M' 36 | KEYCODE_N = 78, // ASCII 'N' 37 | KEYCODE_O = 79, // ASCII 'O' 38 | KEYCODE_P = 80, // ASCII 'P' 39 | KEYCODE_Q = 81, // ASCII 'Q' 40 | KEYCODE_R = 82, // ASCII 'R' 41 | KEYCODE_S = 83, // ASCII 'S' 42 | KEYCODE_T = 84, // ASCII 'T' 43 | KEYCODE_U = 85, // ASCII 'U' 44 | KEYCODE_V = 86, // ASCII 'V' 45 | KEYCODE_W = 87, // ASCII 'W' 46 | KEYCODE_X = 88, // ASCII 'X' 47 | KEYCODE_Y = 89, // ASCII 'Y' 48 | KEYCODE_Z = 90, // ASCII 'Z' 49 | // 小写字母 50 | KEYCODE_a = 97, // ASCII 'a' 51 | KEYCODE_b = 98, // ASCII 'b' 52 | KEYCODE_c = 99, // ASCII 'c' 53 | KEYCODE_d = 100, // ASCII 'd' 54 | KEYCODE_e = 101, // ASCII 'e' 55 | KEYCODE_f = 102, // ASCII 'f' 56 | KEYCODE_g = 103, // ASCII 'g' 57 | KEYCODE_h = 104, // ASCII 'h' 58 | KEYCODE_i = 105, // ASCII 'i' 59 | KEYCODE_j = 106, // ASCII 'j' 60 | KEYCODE_k = 107, // ASCII 'k' 61 | KEYCODE_l = 108, // ASCII 'l' 62 | KEYCODE_m = 109, // ASCII 'm' 63 | KEYCODE_n = 110, // ASCII 'n' 64 | KEYCODE_o = 111, // ASCII 'o' 65 | KEYCODE_p = 112, // ASCII 'p' 66 | KEYCODE_q = 113, // ASCII 'q' 67 | KEYCODE_r = 114, // ASCII 'r' 68 | KEYCODE_s = 115, // ASCII 's' 69 | KEYCODE_t = 116, // ASCII 't' 70 | KEYCODE_u = 117, // ASCII 'u' 71 | KEYCODE_v = 118, // ASCII 'v' 72 | KEYCODE_w = 119, // ASCII 'w' 73 | KEYCODE_x = 120, // ASCII 'x' 74 | KEYCODE_y = 121, // ASCII 'y' 75 | KEYCODE_z = 122, // ASCII 'z' 76 | // 数字 77 | KEYCODE_0 = 48, // ASCII '0' 78 | KEYCODE_1 = 49, // ASCII '1' 79 | KEYCODE_2 = 50, // ASCII '2' 80 | KEYCODE_3 = 51, // ASCII '3' 81 | KEYCODE_4 = 52, // ASCII '4' 82 | KEYCODE_5 = 53, // ASCII '5' 83 | KEYCODE_6 = 54, // ASCII '6' 84 | KEYCODE_7 = 55, // ASCII '7' 85 | KEYCODE_8 = 56, // ASCII '8' 86 | KEYCODE_9 = 57, // ASCII '9' 87 | // 符号 88 | KEYCODE_EXCLAMATION = 33, // ASCII '!' 89 | KEYCODE_AT = 64, // ASCII '@' 90 | KEYCODE_HASH = 35, // ASCII '#' 91 | KEYCODE_DOLLAR = 36, // ASCII '$' 92 | KEYCODE_PERCENT = 37, // ASCII '%' 93 | KEYCODE_CARET = 94, // ASCII '^' 94 | KEYCODE_AMPERSAND = 38, // ASCII '&' 95 | KEYCODE_ASTERISK = 42, // ASCII '*' 96 | KEYCODE_LEFT_PAREN = 40, // ASCII '(' 97 | KEYCODE_RIGHT_PAREN = 41, // ASCII ')' 98 | KEYCODE_MINUS = 45, // ASCII '-' 99 | KEYCODE_UNDERSCORE = 95, // ASCII '_' 100 | KEYCODE_EQUAL = 61, // ASCII '=' 101 | KEYCODE_PLUS = 43, // ASCII '+' 102 | KEYCODE_LEFT_BRACE = 123, // ASCII '{' 103 | KEYCODE_RIGHT_BRACE = 125, // ASCII '}' 104 | KEYCODE_LEFT_BRACKET = 91, // ASCII '[' 105 | KEYCODE_RIGHT_BRACKET = 93, // ASCII ']' 106 | KEYCODE_PIPE = 124, // ASCII '|' 107 | KEYCODE_BACKSLASH = 92, // ASCII '\' 108 | KEYCODE_COLON = 58, // ASCII ':' 109 | KEYCODE_SEMICOLON = 59, // ASCII ';' 110 | KEYCODE_QUOTE = 39, // ASCII ''' 111 | KEYCODE_DOUBLE_QUOTE = 34, // ASCII '"' 112 | KEYCODE_COMMA = 44, // ASCII ',' 113 | KEYCODE_PERIOD = 46, // ASCII '.' 114 | KEYCODE_SLASH = 47, // ASCII '/' 115 | KEYCODE_QUESTION = 63, // ASCII '?' 116 | KEYCODE_TILDE = 126, // ASCII '~' 117 | KEYCODE_GRAVE = 96, // ASCII '`' 118 | KEYCODE_LESS = 60, // ASCII '<' 119 | KEYCODE_GREATER = 62, // ASCII '>' 120 | // 控制键 121 | KEYCODE_ENTER = 13, // ASCII Carriage Return 122 | KEYCODE_SPACE = 32, // ASCII Space 123 | KEYCODE_BACKSPACE = 8, // ASCII Backspace 124 | KEYCODE_SHIFT, 125 | KEYCODE_CTRL, 126 | KEYCODE_ALT, 127 | KEYCODE_CAPS_LOCK = 20, 128 | KEYCODE_ESC = 27, // ASCII Escape 129 | // 大写字母 130 | KEYCODE_FULLWIDTH_A = 0xFF21, // full width 'A' 131 | KEYCODE_FULLWIDTH_B = 0xFF22, // full width 'B' 132 | KEYCODE_FULLWIDTH_C = 0xFF23, // full width 'C' 133 | KEYCODE_FULLWIDTH_D = 0xFF24, // full width 'D' 134 | KEYCODE_FULLWIDTH_E = 0xFF25, // full width 'E' 135 | KEYCODE_FULLWIDTH_F = 0xFF26, // full width 'F' 136 | KEYCODE_FULLWIDTH_G = 0xFF27, // full width 'G' 137 | KEYCODE_FULLWIDTH_H = 0xFF28, // full width 'H' 138 | KEYCODE_FULLWIDTH_I = 0xFF29, // full width 'I' 139 | KEYCODE_FULLWIDTH_J = 0xFF2A, // full width 'J' 140 | KEYCODE_FULLWIDTH_K = 0xFF2B, // full width 'K' 141 | KEYCODE_FULLWIDTH_L = 0xFF2C, // full width 'L' 142 | KEYCODE_FULLWIDTH_M = 0xFF2D, // full width 'M' 143 | KEYCODE_FULLWIDTH_N = 0xFF2E, // full width 'N' 144 | KEYCODE_FULLWIDTH_O = 0xFF2F, // full width 'O' 145 | KEYCODE_FULLWIDTH_P = 0xFF30, // full width 'P' 146 | KEYCODE_FULLWIDTH_Q = 0xFF31, // full width 'Q' 147 | KEYCODE_FULLWIDTH_R = 0xFF32, // full width 'R' 148 | KEYCODE_FULLWIDTH_S = 0xFF33, // full width 'S' 149 | KEYCODE_FULLWIDTH_T = 0xFF34, // full width 'T' 150 | KEYCODE_FULLWIDTH_U = 0xFF35, // full width 'U' 151 | KEYCODE_FULLWIDTH_V = 0xFF36, // full width 'V' 152 | KEYCODE_FULLWIDTH_W = 0xFF37, // full width 'W' 153 | KEYCODE_FULLWIDTH_X = 0xFF38, // full width 'X' 154 | KEYCODE_FULLWIDTH_Y = 0xFF39, // full width 'Y' 155 | KEYCODE_FULLWIDTH_Z = 0xFF3A, // full width 'Z' 156 | // 小写字母 157 | KEYCODE_FULLWIDTH_a = 0xFF41, // full width 'a' 158 | KEYCODE_FULLWIDTH_b = 0xFF42, // full width 'b' 159 | KEYCODE_FULLWIDTH_c = 0xFF43, // full width 'c' 160 | KEYCODE_FULLWIDTH_d = 0xFF44, // full width 'd' 161 | KEYCODE_FULLWIDTH_e = 0xFF45, // full width 'e' 162 | KEYCODE_FULLWIDTH_f = 0xFF46, // full width 'f' 163 | KEYCODE_FULLWIDTH_g = 0xFF47, // full width 'g' 164 | KEYCODE_FULLWIDTH_h = 0xFF48, // full width 'h' 165 | KEYCODE_FULLWIDTH_i = 0xFF49, // full width 'i' 166 | KEYCODE_FULLWIDTH_j = 0xFF4A, // full width 'j' 167 | KEYCODE_FULLWIDTH_k = 0xFF4B, // full width 'k' 168 | KEYCODE_FULLWIDTH_l = 0xFF4C, // full width 'l' 169 | KEYCODE_FULLWIDTH_m = 0xFF4D, // full width 'm' 170 | KEYCODE_FULLWIDTH_n = 0xFF4E, // full width 'n' 171 | KEYCODE_FULLWIDTH_o = 0xFF4F, // full width 'o' 172 | KEYCODE_FULLWIDTH_p = 0xFF50, // full width 'p' 173 | KEYCODE_FULLWIDTH_q = 0xFF51, // full width 'q' 174 | KEYCODE_FULLWIDTH_r = 0xFF52, // full width 'r' 175 | KEYCODE_FULLWIDTH_s = 0xFF53, // full width 's' 176 | KEYCODE_FULLWIDTH_t = 0xFF54, // full width 't' 177 | KEYCODE_FULLWIDTH_u = 0xFF55, // full width 'u' 178 | KEYCODE_FULLWIDTH_v = 0xFF56, // full width 'v' 179 | KEYCODE_FULLWIDTH_w = 0xFF57, // full width 'w' 180 | KEYCODE_FULLWIDTH_x = 0xFF58, // full width 'x' 181 | KEYCODE_FULLWIDTH_y = 0xFF59, // full width 'y' 182 | KEYCODE_FULLWIDTH_z = 0xFF5A, // full width 'z' 183 | // 数字 184 | KEYCODE_FULLWIDTH_0 = 0xFF10, // full width '0' 185 | KEYCODE_FULLWIDTH_1 = 0xFF11, // full width '1' 186 | KEYCODE_FULLWIDTH_2 = 0xFF12, // full width '2' 187 | KEYCODE_FULLWIDTH_3 = 0xFF13, // full width '3' 188 | KEYCODE_FULLWIDTH_4 = 0xFF14, // full width '4' 189 | KEYCODE_FULLWIDTH_5 = 0xFF15, // full width '5' 190 | KEYCODE_FULLWIDTH_6 = 0xFF16, // full width '6' 191 | KEYCODE_FULLWIDTH_7 = 0xFF17, // full width '7' 192 | KEYCODE_FULLWIDTH_8 = 0xFF18, // full width '8' 193 | KEYCODE_FULLWIDTH_9 = 0xFF19, // full width '9' 194 | // 符号 195 | KEYCODE_FULLWIDTH_EXCLAMATION = 0xFF01, // full width '!' 196 | KEYCODE_FULLWIDTH_AT = 0xFF20, // full width '@' 197 | KEYCODE_FULLWIDTH_HASH = 0xFF03, // full width '#' 198 | KEYCODE_FULLWIDTH_DOLLAR = 0xFF04, // full width '$' 199 | KEYCODE_FULLWIDTH_PERCENT = 0xFF05, // full width '%' 200 | KEYCODE_FULLWIDTH_CARET = 0xFF3E, // full width '^' 201 | KEYCODE_FULLWIDTH_AMPERSAND = 0xFF06, // full width '&' 202 | KEYCODE_FULLWIDTH_ASTERISK = 0xFF0A, // full width '*' 203 | KEYCODE_FULLWIDTH_LEFT_PAREN = 0xFF08, // full width '(' 204 | KEYCODE_FULLWIDTH_RIGHT_PAREN = 0xFF09, // full width ')' 205 | KEYCODE_FULLWIDTH_MINUS = 0xFF0D, // full width '-' 206 | KEYCODE_FULLWIDTH_UNDERSCORE = 0xFF3F, // full width '_' 207 | KEYCODE_FULLWIDTH_EQUAL = 0xFF1D, // full width '=' 208 | KEYCODE_FULLWIDTH_PLUS = 0xFF0B, // full width '+' 209 | KEYCODE_FULLWIDTH_LEFT_BRACE = 0xFF5B, // full width '{' 210 | KEYCODE_FULLWIDTH_RIGHT_BRACE = 0xFF5D, // full width '}' 211 | KEYCODE_FULLWIDTH_LEFT_BRACKET = 0xFF3B, // full width '[' 212 | KEYCODE_FULLWIDTH_RIGHT_BRACKET = 0xFF3D, // full width ']' 213 | KEYCODE_FULLWIDTH_PIPE = 0xFF5C, // full width '|' 214 | KEYCODE_FULLWIDTH_BACKSLASH = 0xFF3C, // full width '\' 215 | KEYCODE_FULLWIDTH_COLON = 0xFF1A, // full width ':' 216 | KEYCODE_FULLWIDTH_SEMICOLON = 0xFF1B, // full width ';' 217 | KEYCODE_FULLWIDTH_QUOTE = 0xFF07, // full width ''' 218 | KEYCODE_FULLWIDTH_DOUBLE_QUOTE = 0xFF02, // full width '"' 219 | KEYCODE_FULLWIDTH_COMMA = 0xFF0C, // full width ',' 220 | KEYCODE_FULLWIDTH_PERIOD = 0xFF0E, // full width '.' 221 | KEYCODE_FULLWIDTH_SLASH = 0xFF0F, // full width '/' 222 | KEYCODE_FULLWIDTH_QUESTION = 0xFF1F, // full width '?' 223 | KEYCODE_FULLWIDTH_TILDE = 0xFF5E, // full width '~' 224 | KEYCODE_FULLWIDTH_GRAVE = 0xFF40, // full width '`' 225 | KEYCODE_FULLWIDTH_LESS = 0xFF1C, // full width '<' 226 | KEYCODE_FULLWIDTH_GREATER = 0xFF1E, // full width '>' 227 | 228 | KEYCODE_FULLWIDTH_SPACE = 0x3000, // full width space ' ' 229 | KEYCODE_CHINESE_PERIOD = 0x3002, // Chinese period '。' 230 | 231 | KEYCODE_FLAG_LANGUAGE = 1 << 16, 232 | KEYCODE_LANGUAGE_CHINISE = KEYCODE_FLAG_LANGUAGE | KEYBOARD_LANG_CHS, 233 | KEYCODE_LANGUAGE_ENGLISH = KEYCODE_FLAG_LANGUAGE | KEYBOARD_LANG_ENG, 234 | } KeyCode; 235 | 236 | typedef enum { 237 | KEY_STATE_NOT_PRESSED = 0, 238 | KEY_STATE_PRESSED = 1 << 0, 239 | KEY_STATE_HELD = 1 << 1, 240 | KEY_STATE_RELEASED = 1 << 2, 241 | KEY_STATE_FINISHED = 1 << 3, 242 | KEY_STATE_EXIT = 1 << 4 243 | } KeyState; 244 | 245 | typedef struct { 246 | int x; 247 | int y; 248 | int width; 249 | int height; 250 | KeyCode code; 251 | u16 charCode; 252 | bool isPressed; 253 | bool isHeld; 254 | glImage *glyph; 255 | } Key; 256 | 257 | typedef struct { 258 | int x; 259 | int y; 260 | int width; 261 | int height; 262 | u16 *text; 263 | int maxLength; 264 | int length; 265 | } TextBox; 266 | 267 | typedef struct { 268 | u16 keyCode; 269 | u16 charCode; 270 | } KeycodeConvItem; 271 | 272 | typedef struct VirtualKeyboard VirtualKeyboard; 273 | 274 | typedef struct { 275 | void * (*Alloc)(u32 size); 276 | void (*Free)(void *ptr); 277 | void (*OnOverlayLoaded)(); 278 | bool (*ShouldShowKeyboard)(); 279 | int (*GetMaxInputLength)(); 280 | bool (*LoadGlyph)(u16 charCode, u8 *output, int *advance); 281 | bool (*KeycodeToChar)(u16 keycode, u16 *output); 282 | bool (*CanContinueInput)(u16 *inputText, int length, u16 nextChar); 283 | void (*OnInputFinished)(u16 *inputText, int length, bool isCanceled); 284 | } KeyboardGameInterface; 285 | 286 | typedef struct { 287 | bool (*OnKeyPressed)(VirtualKeyboard *keyboard, Key *key); 288 | bool (*OnKeyDraw)(const VirtualKeyboard *keyboard, const Key *key); 289 | bool (*OnInputStringDraw)(VirtualKeyboard *keyboard, TextBox *textBox); 290 | } KeyboardInputMethodInterface; 291 | 292 | typedef struct VirtualKeyboard { 293 | Key normalKeys[46]; 294 | Key functionKeys[5]; 295 | Key *currentKey; 296 | int x; 297 | int y; 298 | int glyphBaseline; 299 | bool isShifted; 300 | bool isCapsLocked; 301 | bool isPressed; 302 | int language; 303 | int state; 304 | int keyTexPalId; 305 | int glyphTexPalId; 306 | int externalGlyphKeyPalIds[EXTERNAL_FONT_PALETTE_SIZE]; 307 | int externalGlyphTextBoxPalIds[EXTERNAL_FONT_PALETTE_SIZE]; 308 | TextBox inputTextBox; 309 | const KeyboardGameInterface *gameInterface; 310 | KeyboardInputMethodInterface *inputMethodInterface[KEYBOARD_LANG_MAX]; 311 | } VirtualKeyboard; 312 | 313 | void StartKeyboardMonitorThread(); 314 | 315 | void InitHeap(void *start, u32 size); 316 | 317 | void InitializeKeyboard(const KeyboardGameInterface *gameInterface); 318 | void FinalizeKeyboard(bool isCancelled); 319 | void DrawKeyboard(); 320 | int HandleKeyboardInput(); 321 | void TryAddCharToInput(u16 charCode); 322 | void TryAddKeycodeToInput(KeyCode keyCode); 323 | void RegisterKeyboardInputMethod(int lang, KeyboardInputMethodInterface *inputMethodInterface); 324 | 325 | static inline u16 HalfToFullWidth(u16 halfWidth) { 326 | if (halfWidth >= 0x21 && halfWidth <= 0x7e) { 327 | return halfWidth + 0xfee0; 328 | } 329 | if (halfWidth == 0x20) { 330 | return 0x3000; 331 | } 332 | return 0x0000; 333 | } 334 | 335 | static inline int FindCustomCharCode(const KeycodeConvItem *table, int tableSize, u16 keyCode) { 336 | int left = 0; 337 | int right = tableSize - 1; 338 | while (left <= right) { 339 | int mid = (left + right) / 2; 340 | if (table[mid].keyCode == keyCode) { 341 | return table[mid].charCode; 342 | } 343 | else if (table[mid].keyCode < keyCode) { 344 | left = mid + 1; 345 | } 346 | else { 347 | right = mid - 1; 348 | } 349 | } 350 | return -1; 351 | } 352 | 353 | void InitPinyinInputMethod(); 354 | void DeinitPinyinInputMethod(); 355 | KeyboardInputMethodInterface *GetPinyinInputMethodInterface(); 356 | 357 | void InitKeyboardFont(); 358 | void DeinitKeyboardFont(); 359 | glImage *GetDefaultGlyph(KeyCode code); 360 | void SetDefaultKeysPalette(int palId); 361 | void CreateExternalFontPalette(int *paletteIds, u16 textColor, u16 bgColor); 362 | void RegisterGlyphLoader(bool (*LoadGlyph)(u16 charCode, u8 *output, int *advance)); 363 | bool GetExternalGlyph(u16 charCode, glImage *glyphImage, int *palIndex, int *advance); 364 | 365 | KeyboardGameInterface *GetKeyboardGameInterface(); 366 | 367 | #endif -------------------------------------------------------------------------------- /overlay/include/touch.h: -------------------------------------------------------------------------------- 1 | #ifndef TOUCH_H 2 | #define TOUCH_H 3 | 4 | bool GetCalibratedPoint(int *x, int *y); 5 | 6 | void RequestSamplingTPData(); 7 | 8 | void ResetTPData(); 9 | 10 | #endif -------------------------------------------------------------------------------- /overlay/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 2 | OUTPUT_ARCH(arm) 3 | 4 | ENTRY(_start) 5 | 6 | SECTIONS 7 | { 8 | .text : { 9 | *src/overlay_entry.o(.text) 10 | *src/*.o(.text*) 11 | *nitro.a:import.o(.text) 12 | *libc.a:libc_a-memcmp.o(.text.memcmp) 13 | *libc.a:libc_a-memcpy-stub.o(.text.memcpy) 14 | *libc.a:libc_a-memset.o(.text.memset) 15 | *libgcc.a:_divsi3.o(.text) 16 | *libgcc.a:_dvmd_tls.o(.text) 17 | *libcalico_ds9.a:arm-cache.32.o(.text.armDCacheFlush) 18 | *libcalico_ds9.a:arm-cache.32.o(.text.armDrainWriteBuffer) 19 | *libcalico_ds9.a:arm-shims.32.o(.text.armIrqLockByPsr) 20 | *libcalico_ds9.a:arm-shims.32.o(.text.armIrqUnlockByPsr) 21 | *libcalico_ds9.a:bios.o(.text.svcCpuSet) 22 | *libcalico_ds9.a:bios.o(.text.svcWaitByLoop) 23 | *libnds9.a:dynamicArray.o(.text.DynamicArrayDelete) 24 | *libnds9.a:dynamicArray.o(.text.DynamicArrayGet) 25 | *libnds9.a:dynamicArray.o(.text.DynamicArrayInit) 26 | *libnds9.a:dynamicArray.o(.text.DynamicArraySet) 27 | *libnds9.a:gl2d.o(.text.SetOrtho) 28 | *libnds9.a:gl2d.o(.text.glBegin2D) 29 | *libnds9.a:gl2d.o(.text.glBoxFilled) 30 | *libnds9.a:gl2d.o(.text.glEnd2D) 31 | *libnds9.a:gl2d.o(.text.glLine) 32 | *libnds9.a:gl2d.o(.text.glLoadSpriteSet) 33 | *libnds9.a:gl2d.o(.text.glLoadTileSet) 34 | *libnds9.a:gl2d.o(.text.glSprite) 35 | *libnds9.a:gl2d.o(.text.glTexImage2D) 36 | *libnds9.a:video.o(.text.vramRestoreBanks_EFG) 37 | *libnds9.a:video.o(.text.vramRestorePrimaryBanks) 38 | *libnds9.a:videoGL.o(.text.glAssignColorTable) 39 | *libnds9.a:videoGL.o(.text.glBindTexture) 40 | *libnds9.a:videoGL.o(.text.glColorTableEXT) 41 | *libnds9.a:videoGL.o(.text.glGenTextures) 42 | *libnds9.a:videoGL.o(.text.glGetGlobals) 43 | *libnds9.a:videoGL.o(.text.glGetTexturePointer) 44 | *libnds9.a:videoGL.o(.text.glTexImage2D) 45 | *libnds9.a:videoGL.o(.text.removePaletteFromTexture.part.0) 46 | *libnds9.a:videoGL.o(.text.vramBlock_Construct) 47 | *libnds9.a:videoGL.o(.text.vramBlock__allocateBlock) 48 | *libnds9.a:videoGL.o(.text.vramBlock__deallocateBlock.part.0) 49 | *libnds9.a:videoGL.o(.text.vramBlock_allocateBlock) 50 | *libnds9.a:videoGL.o(.text.vramBlock_allocateSpecial) 51 | *libnds9.a:videoGL.o(.text.vramBlock_deallocateBlock) 52 | *libnds9.a:videoGL.o(.text.vramBlock_examineSpecial) 53 | *libnds9.a:videoGL.o(.text.vramBlock_init) 54 | *libnds9.a:videoGL.o(.text.vramBlock_terminate) 55 | *libnds9.a:videoGL.o(.text.vramGetBank) 56 | } 57 | . = ALIGN(4); 58 | 59 | .rodata : { 60 | *src/*.o(.rodata*) 61 | *libnds9.a:videoGL.o(.rodata) 62 | } 63 | . = ALIGN(4); 64 | 65 | .data : { 66 | *src/*.o(.data) 67 | *libnds9.a:videoGL.o(.data.glGlob) 68 | } 69 | . = ALIGN(4); 70 | 71 | __init_functions_begin__ = .; 72 | .init_functions : {*src/misc.o(.init_functions)} 73 | __init_functions_end__ = .; 74 | . = ALIGN(4); 75 | __ram_size__ = . - ADDR(.text); 76 | 77 | .bss : { 78 | *src/*.o(.bss*) 79 | *libnds9.a:gl2d.o(.bss.gCurrentTexture) 80 | *libnds9.a:gl2d.o(.bss.g_depth) 81 | *libnds9.a:videoGL.o(.bss.diffuse_ambient.1) 82 | *libnds9.a:videoGL.o(.bss.glGlobalData) 83 | *libnds9.a:videoGL.o(.bss.specular_emission.0) 84 | *libnds9.a:videoGL_base.o(.bss.glCurClearColor) 85 | } 86 | . = ALIGN(4); 87 | __bss_size__ = . - ADDR(.bss); 88 | __size__ = . - ADDR(.text); 89 | 90 | /* DWARF debug sections. 91 | Symbols in the DWARF debugging sections are relative to the beginning 92 | of the section so we begin them at 0. */ 93 | 94 | /* DWARF 1 */ 95 | .debug 0 : { *(.debug) } 96 | .line 0 : { *(.line) } 97 | 98 | /* GNU DWARF 1 extensions */ 99 | .debug_srcinfo 0 : { *(.debug_srcinfo) } 100 | .debug_sfnames 0 : { *(.debug_sfnames) } 101 | 102 | /* DWARF 1.1 and DWARF 2 */ 103 | .debug_aranges 0 : { *(.debug_aranges) } 104 | .debug_pubnames 0 : { *(.debug_pubnames) } 105 | 106 | /* DWARF 2 */ 107 | .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } 108 | .debug_abbrev 0 : { *(.debug_abbrev) } 109 | .debug_line 0 : { *(.debug_line) } 110 | .debug_frame 0 : { *(.debug_frame) } 111 | .debug_str 0 : { *(.debug_str) } 112 | .debug_loc 0 : { *(.debug_loc) } 113 | .debug_macinfo 0 : { *(.debug_macinfo) } 114 | 115 | /DISCARD/ : 116 | { 117 | *(*) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /overlay/src/hook.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "arm_instruction.h" 4 | #include "hook.h" 5 | 6 | static void *sHookMemory = NULL; 7 | static u32 sHookMemorySize = 0; 8 | 9 | void HookFunction(HookARMEntry * entry) { 10 | entry->oldInstruction = *(u32*)entry->functionAddr; 11 | if ((entry->oldInstruction & 0xFFFF0000) == 0xE59F0000) { 12 | entry->extraData = *(u32*)((u32)entry->functionAddr + 8 + (entry->oldInstruction & 0xFFF)); 13 | entry->oldInstruction = entry->oldInstruction & ~0xFFF; 14 | } 15 | else if ((entry->oldInstruction & 0xFF000000) == 0xEA000000) { 16 | u32 targetAddr = (u32)entry->functionAddr + 8 + ((s32)entry->oldInstruction << 8 >> 6); 17 | entry->oldInstruction = MAKE_BRANCH(&entry->oldInstruction, targetAddr); 18 | } 19 | *(u32*)entry->functionAddr = MAKE_BRANCH(entry->functionAddr, &entry->ldrInstruction); 20 | entry->ldrInstruction = 0xE51FF004; 21 | entry->jumpInstruction = MAKE_BRANCH(&entry->jumpInstruction, (u32)entry->functionAddr + 4); 22 | if (entry->origFunctionRef) 23 | *entry->origFunctionRef = (void*)&entry->oldInstruction; 24 | DC_FlushRange(entry->functionAddr, sizeof(u32)); 25 | DC_FlushRange(entry, sizeof(HookARMEntry)); 26 | } 27 | 28 | 29 | 30 | void SetupHookMemory(void *memory, u32 size) { 31 | if ((u32)memory & 3) { 32 | u32 adjust = 4 - ((u32)memory & 3); 33 | sHookMemory = (void*)((u32)memory + adjust); 34 | sHookMemorySize = size - adjust; 35 | } 36 | else { 37 | sHookMemory = memory; 38 | sHookMemorySize = size; 39 | } 40 | sHookMemorySize &= ~3; 41 | } 42 | 43 | static void *AllocHookMemory(u32 size) { 44 | void *result = NULL; 45 | size = (size + 3) & ~3; 46 | if (size <= sHookMemorySize) { 47 | result = sHookMemory; 48 | sHookMemory = (void*)((u32)sHookMemory + size); 49 | sHookMemorySize -= size; 50 | } 51 | return result; 52 | } 53 | 54 | void HookFunctionThumb(HookThumbEntry * entry) { 55 | u16 *begin = (u16*)((u32)entry->functionAddr & ~1); 56 | u16 *end = (u16*)(((u32)entry->functionAddr & ~3) + 8); 57 | int require = 0; 58 | int offsets[5] = {-1, -1, -1, -1, -1}; 59 | int *pOffsets = offsets; 60 | int offset = 0; 61 | while (begin < end) { 62 | *pOffsets++ = offset; 63 | if ((*begin & 0xF800) == 0x4800) { // ldr rd, [pc, imm] 64 | require += 2; 65 | } 66 | else if ((*begin & 0xF000) == 0xD000 && (*begin & 0x0E00) != 0x0E00) { // b{cond} label 67 | u32 dest = (u32)begin + 4 + ((s32)(*begin & 0xFF) << 24 >> 23); 68 | if (dest < ((u32)entry->functionAddr & ~1) || dest >= end) 69 | require += 6; 70 | } 71 | else if ((*begin & 0xF800) == 0xE000) { // b label 72 | u32 dest = (u32)begin + 4 + ((s32)(*begin & 0x3FF) << 22 >> 21); 73 | if (dest < ((u32)entry->functionAddr & ~1) || dest >= end) 74 | require += 6; 75 | } 76 | else if ((*begin & 0xF800 == 0xA000)) { // add rd, pc, imm 77 | require += 2; 78 | } 79 | else if ((*begin & 0xF800) == 0xF000 && (*(begin + 1) & 0xE800) == 0xE800) {// bl label, blx label 80 | require++; 81 | begin++; 82 | *pOffsets++ = ++offset; 83 | } 84 | else if ((*begin & 0xff78) == 0x4478) { // add rd, pc 85 | require += 5; 86 | offset += 3; 87 | } 88 | else if ((*begin & 0xff78) == 0x4678) { // mov rd, pc 89 | if ((*begin & 0x80) == 0x80) { 90 | require += 5; 91 | offset += 3; 92 | } 93 | else { 94 | require += 2; 95 | } 96 | } 97 | require++; 98 | begin++; 99 | offset++; 100 | } 101 | begin = (u16*)((u32)entry->functionAddr & ~1); 102 | require = require * sizeof(u16) + 12 - (require & 1) * sizeof(u16); 103 | 104 | if (!entry->oldInstructions) 105 | entry->oldInstructions = (u16*)AllocHookMemory(require); 106 | 107 | offset = 0; 108 | int tail = require / sizeof(u16); 109 | while (begin < end) { 110 | if ((*begin & 0xF800) == 0x4800) { // ldr rd, [pc, imm] 111 | u32 dest = (u32)begin + 4 + ((*begin & 0xFF) << 2); 112 | dest &= ~3; 113 | tail -= 2; 114 | *(u32*)&entry->oldInstructions[tail] = *(u32*)dest; 115 | u32 rd = (*begin & 0x0700) >> 8; 116 | entry->oldInstructions[offset++] = MAKE_LDR_PC_OFF_T(rd, offset * 2, tail * 2); 117 | begin++; 118 | } 119 | else if ((*begin & 0xF000) == 0xD000 && (*begin & 0x0E00) != 0x0E00) { // b{cond} label 120 | u32 dest = (u32)begin + 4 + ((s32)(*begin & 0xFF) << 24 >> 23); 121 | if (dest < ((u32)entry->functionAddr & ~1) || dest >= (u32)end) { 122 | tail -= 6; 123 | entry->oldInstructions[tail] = BX_PC_T; 124 | entry->oldInstructions[tail + 1] = NOP_T; 125 | *(u32 *)(&entry->oldInstructions[tail + 2]) = 0xE51FF004; 126 | *(u32 *)(&entry->oldInstructions[tail + 4]) = dest|1; 127 | entry->oldInstructions[offset] = MAKE_BARNCH_COND_T(offset * 2, tail * 2, (*begin & 0xF00) >> 8); 128 | } 129 | else { 130 | entry->oldInstructions[offset] = MAKE_BARNCH_COND_T(offset * 2, offsets[(dest - ((u32)entry->functionAddr & ~1)) / 2] * 2, (*begin & 0xF00) >> 8); 131 | } 132 | offset++; 133 | begin++; 134 | } 135 | else if ((*begin & 0xF800) == 0xE000) { // b label 136 | u32 dest = (u32)begin + 4 + ((s32)(*begin & 0x7FF) << 21 >> 20); 137 | if (dest < ((u32)entry->functionAddr & ~1) || dest >= (u32)end) { 138 | tail -= 6; 139 | entry->oldInstructions[tail] = BX_PC_T; 140 | entry->oldInstructions[tail + 1] = NOP_T; 141 | *(u32 *)(&entry->oldInstructions[tail + 2]) = 0xE51FF004; 142 | *(u32 *)(&entry->oldInstructions[tail + 4]) = dest|1; 143 | entry->oldInstructions[offset] = MAKE_BRANCH_T(offset * 2, tail * 2); 144 | } 145 | else { 146 | entry->oldInstructions[offset] = MAKE_BRANCH_T(offset * 2, offsets[(dest - ((u32)entry->functionAddr & ~1)) / 2] * 2); 147 | } 148 | offset++; 149 | begin++; 150 | } 151 | else if ((*begin & 0xF800 == 0xA000)) { // add rd, pc, imm 152 | u32 dest = ((u32)begin & ~3) + 4 + ((*begin & 0xFF) << 2); 153 | tail -= 2; 154 | *(u32*)&entry->oldInstructions[tail] = *(u32*)dest; 155 | u32 rd = (*begin & 0x0700) >> 8; 156 | entry->oldInstructions[offset++] = MAKE_LDR_PC_OFF_T(rd, offset * 2, tail * 2); 157 | begin++; 158 | } 159 | else if ((*begin & 0xF800) == 0xF000 && (*(begin + 1) & 0xE800) == 0xE800) {// bl label, blx label 160 | u32 dest = (u32)begin + 4 + (s32)((*begin & 0x7FF) << 12 | (*(begin + 1) & 0x7FF) << 1) << 9 >> 9; 161 | if (*(begin + 1) & 0x1000) { 162 | entry->oldInstructions[offset] = MAKE_BRANCH_LINK_T_H(&entry->oldInstructions[offset], dest); 163 | entry->oldInstructions[offset + 1] = MAKE_BRANCH_LINK_T_L(&entry->oldInstructions[offset], dest); 164 | } 165 | else { 166 | entry->oldInstructions[offset] = MAKE_BRANCH_LINK_EXCHAGE_T_H(&entry->oldInstructions[offset], dest); 167 | entry->oldInstructions[offset + 1] = MAKE_BRANCH_LINK_EXCHAGE_T_L(&entry->oldInstructions[offset], dest); 168 | } 169 | offset += 2; 170 | begin += 2; 171 | } 172 | else if ((*begin & 0xff78) == 0x4478) { // add rd, pc 173 | u8 rd = *begin & 0x7; 174 | u8 rt = rd == REG_R0 ? REG_R1 : REG_R0; 175 | tail -= 2; 176 | *(u32*)&entry->oldInstructions[tail] = (u32)begin + 4; 177 | entry->oldInstructions[offset++] = MAKE_PUSH_T(1 << rt); 178 | entry->oldInstructions[offset++] = MAKE_LDR_PC_OFF_T(rt, offset * 2, tail * 2); 179 | entry->oldInstructions[offset++] = MAKE_ADD_RD_RS_RN_T(rd, rd, rt); 180 | entry->oldInstructions[offset++] = MAKE_POP_T(1 << rt); 181 | begin++; 182 | } 183 | else if ((*begin & 0xff78) == 0x4678) { // mov rd, pc 184 | u32 rd = (*begin & 0x7) | (*begin & 0x80) >> 4; 185 | if (rd >= REG_R8) { 186 | tail -= 2; 187 | *(u32*)&entry->oldInstructions[tail] = (u32)begin + 4; 188 | entry->oldInstructions[offset++] = MAKE_PUSH_T(REG_R0_F); 189 | entry->oldInstructions[offset++] = MAKE_LDR_PC_OFF_T(REG_R0, offset * 2, tail * 2); 190 | entry->oldInstructions[offset++] = MAKE_MOV_RD_RS_T(rd, REG_R0); 191 | entry->oldInstructions[offset++] = MAKE_POP_T(REG_R0_F); 192 | } 193 | else { 194 | tail -= 2; 195 | *(u32*)&entry->oldInstructions[tail] = (u32)begin + 4; 196 | entry->oldInstructions[offset++] = MAKE_LDR_PC_OFF_T(rd, offset * 2, tail * 2); 197 | } 198 | begin++; 199 | } 200 | else { 201 | entry->oldInstructions[offset++] = *begin++; 202 | } 203 | } 204 | entry->oldInstructions[offset++] = BX_PC_T; 205 | if (offset & 1){ 206 | entry->oldInstructions[offset++] = NOP_T; 207 | } 208 | *(u32*)&entry->oldInstructions[offset] = 0xE51FF004; 209 | *(u32*)&entry->oldInstructions[offset + 2] = (u32)begin | 1; 210 | 211 | DC_FlushRange(entry->oldInstructions, require); 212 | 213 | begin = (u16*)((u32)entry->functionAddr & ~1); 214 | *begin++ = BX_PC_T; 215 | if ((u32)begin % 4 != 0) { 216 | *begin++ = NOP_T; 217 | } 218 | *(u32*)begin = MAKE_BRANCH(begin, &entry->ldrInstruction); 219 | DC_FlushRange((void*)((u32)entry->functionAddr & ~3), 8); 220 | 221 | entry->ldrInstruction = 0xE51FF004; 222 | DC_FlushRange(&entry->ldrInstruction, sizeof(u32)); 223 | 224 | if (entry->origFunctionRef) 225 | *entry->origFunctionRef = (void*)((u32)entry->oldInstructions | 1); 226 | } 227 | 228 | void ForceMakingBranchLink(void* origAddr, void* targetAddr) { 229 | int mode; 230 | if ((u32)origAddr & 1) { 231 | if ((u32)targetAddr & 1) { 232 | mode = 2; 233 | } 234 | else { 235 | mode = 3; 236 | } 237 | } 238 | else { 239 | if ((u32)targetAddr & 1) { 240 | mode = 1; 241 | } 242 | else { 243 | mode = 0; 244 | } 245 | } 246 | 247 | if (mode == 0 || mode == 1) { 248 | origAddr = (void*)((u32)origAddr & ~3); 249 | } else { 250 | origAddr = (void*)((u32)origAddr & ~1); 251 | } 252 | 253 | if (mode == 0 || mode == 3) { 254 | targetAddr = (void*)((u32)targetAddr & ~3); 255 | } else { 256 | targetAddr = (void*)((u32)targetAddr & ~1); 257 | } 258 | 259 | if (mode == 0) 260 | *(u32*)origAddr = MAKE_BRANCH_LINK(origAddr, targetAddr); 261 | else if (mode == 1) 262 | *(u32*)origAddr = MAKE_BRANCH_LINK_EXCHANGE(origAddr, targetAddr); 263 | else if (mode == 2) { 264 | *(u16*)origAddr = MAKE_BRANCH_LINK_T_H(origAddr, targetAddr); 265 | *(u16*)((u32)origAddr + 2) = MAKE_BRANCH_LINK_T_L(origAddr, targetAddr); 266 | } 267 | else { 268 | *(u16*)origAddr = MAKE_BRANCH_LINK_EXCHAGE_T_H(origAddr, targetAddr); 269 | *(u16*)((u32)origAddr + 2) = MAKE_BRANCH_LINK_EXCHAGE_T_L(origAddr, targetAddr); 270 | } 271 | 272 | DC_FlushRange((void *)((u32)origAddr & ~3), 8); 273 | } -------------------------------------------------------------------------------- /overlay/src/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nitro/fs.h" 3 | #include "keyboard.h" 4 | #include "touch.h" 5 | 6 | #define KEYBOARD_BG_COLOR RGB15(30 >> 3, 144 >> 3, 255 >> 3) 7 | #define KEY_GLYPH_COLOR RGB15(56 >> 3, 8 >> 3, 120 >> 3) 8 | #define KEY_BG_COLOR RGB15(135 >> 3, 206 >> 3, 250 >> 3) 9 | #define KEY_BG_COLOR_PRESSED RGB15(31, 31, 31) 10 | #define TEXTBOX_GLYPH_COLOR RGB15(31, 31, 31) 11 | #define TEXTBOX_BG_COLOR RGB15(0, 86 >> 3, 179 >> 3) 12 | 13 | const u8 KeyboardMap[] = { 14 | KEYCODE_1, KEYCODE_2, KEYCODE_3, KEYCODE_4, KEYCODE_5, KEYCODE_6, KEYCODE_7, KEYCODE_8, KEYCODE_9, KEYCODE_0, KEYCODE_MINUS, KEYCODE_EQUAL, 15 | KEYCODE_q, KEYCODE_w, KEYCODE_e, KEYCODE_r, KEYCODE_t, KEYCODE_y, KEYCODE_u, KEYCODE_i, KEYCODE_o, KEYCODE_p, 16 | KEYCODE_a, KEYCODE_s, KEYCODE_d, KEYCODE_f, KEYCODE_g, KEYCODE_h, KEYCODE_j, KEYCODE_k, KEYCODE_l, 17 | KEYCODE_z, KEYCODE_x, KEYCODE_c, KEYCODE_v, KEYCODE_b, KEYCODE_n, KEYCODE_m, KEYCODE_COMMA, KEYCODE_PERIOD, KEYCODE_SLASH, 18 | KEYCODE_SEMICOLON, KEYCODE_QUOTE, KEYCODE_SPACE, KEYCODE_LEFT_BRACKET, KEYCODE_RIGHT_BRACKET 19 | }; 20 | 21 | const u8 KeyboardMapUppercase[] = { 22 | KEYCODE_1, KEYCODE_2, KEYCODE_3, KEYCODE_4, KEYCODE_5, KEYCODE_6, KEYCODE_7, KEYCODE_8, KEYCODE_9, KEYCODE_0, KEYCODE_MINUS, KEYCODE_EQUAL, 23 | KEYCODE_Q, KEYCODE_W, KEYCODE_E, KEYCODE_R, KEYCODE_T, KEYCODE_Y, KEYCODE_U, KEYCODE_I, KEYCODE_O, KEYCODE_P, 24 | KEYCODE_A, KEYCODE_S, KEYCODE_D, KEYCODE_F, KEYCODE_G, KEYCODE_H, KEYCODE_J, KEYCODE_K, KEYCODE_L, 25 | KEYCODE_Z, KEYCODE_X, KEYCODE_C, KEYCODE_V, KEYCODE_B, KEYCODE_N, KEYCODE_M, KEYCODE_COMMA, KEYCODE_PERIOD, KEYCODE_SLASH, 26 | KEYCODE_SEMICOLON, KEYCODE_QUOTE, KEYCODE_SPACE, KEYCODE_LEFT_BRACKET, KEYCODE_RIGHT_BRACKET 27 | }; 28 | 29 | const u8 KeyboardMapShift[] = { 30 | KEYCODE_EXCLAMATION, KEYCODE_AT, KEYCODE_HASH, KEYCODE_DOLLAR, KEYCODE_PERCENT, KEYCODE_CARET, KEYCODE_AMPERSAND, KEYCODE_ASTERISK, KEYCODE_LEFT_PAREN, KEYCODE_RIGHT_PAREN, KEYCODE_UNDERSCORE, KEYCODE_PLUS, 31 | KEYCODE_Q, KEYCODE_W, KEYCODE_E, KEYCODE_R, KEYCODE_T, KEYCODE_Y, KEYCODE_U, KEYCODE_I, KEYCODE_O, KEYCODE_P, 32 | KEYCODE_A, KEYCODE_S, KEYCODE_D, KEYCODE_F, KEYCODE_G, KEYCODE_H, KEYCODE_J, KEYCODE_K, KEYCODE_L, 33 | KEYCODE_Z, KEYCODE_X, KEYCODE_C, KEYCODE_V, KEYCODE_B, KEYCODE_N, KEYCODE_M, KEYCODE_LESS, KEYCODE_GREATER, KEYCODE_QUESTION, 34 | KEYCODE_COLON, KEYCODE_TILDE, KEYCODE_SPACE, KEYCODE_LEFT_BRACE, KEYCODE_RIGHT_BRACE 35 | }; 36 | 37 | const u16 gKeysTexPal[] = { 38 | RGB15(0, 0, 0), 39 | KEY_GLYPH_COLOR, 40 | RGB15(0, 0, 0), 41 | RGB15(0, 0, 0), 42 | }; 43 | 44 | const u16 gGlyphTexPal[] = { 45 | RGB15(0, 0, 0), 46 | TEXTBOX_GLYPH_COLOR, 47 | RGB15(0, 0, 0), 48 | RGB15(0, 0, 0), 49 | }; 50 | 51 | static VirtualKeyboard *gVirtualKeyboard; 52 | 53 | void InitializeKeyboard(const KeyboardGameInterface *gameInterface) { 54 | InitKeyboardFont(); 55 | RegisterGlyphLoader(gameInterface->LoadGlyph); 56 | gVirtualKeyboard = malloc(sizeof(VirtualKeyboard)); 57 | memset(gVirtualKeyboard, 0, sizeof(VirtualKeyboard)); 58 | gVirtualKeyboard->gameInterface = gameInterface; 59 | int rowOffsets[] = {0, (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) / 2, (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING), (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 3 / 2, (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 2}; 60 | int rowLengths[] = {12, 10, 9, 10, 3}; 61 | 62 | int keyIndex = 0; 63 | for (int row = 0; row < 5; row++) { 64 | for (int col = 0; col < rowLengths[row]; col++) { 65 | gVirtualKeyboard->normalKeys[keyIndex].x = col * (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) + rowOffsets[row]; 66 | gVirtualKeyboard->normalKeys[keyIndex].y = row * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING); 67 | gVirtualKeyboard->normalKeys[keyIndex].width = KEY_BUTTON_WIDTH; 68 | gVirtualKeyboard->normalKeys[keyIndex].height = KEY_BUTTON_HEIGHT; 69 | gVirtualKeyboard->normalKeys[keyIndex].code = KeyboardMap[keyIndex]; 70 | keyIndex++; 71 | } 72 | } 73 | 74 | gVirtualKeyboard->normalKeys[43].width = KEY_BUTTON_WIDTH * 5 + KEY_BUTTON_SPACING * 4; 75 | for (int i = 44; i < 46; i++) { 76 | gVirtualKeyboard->normalKeys[i].x = gVirtualKeyboard->normalKeys[43].x + gVirtualKeyboard->normalKeys[43].width + KEY_BUTTON_SPACING + (i - 44) * (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING); 77 | gVirtualKeyboard->normalKeys[i].y = 4 * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING); 78 | gVirtualKeyboard->normalKeys[i].width = KEY_BUTTON_WIDTH; 79 | gVirtualKeyboard->normalKeys[i].height = KEY_BUTTON_HEIGHT; 80 | gVirtualKeyboard->normalKeys[i].code = KeyboardMap[i]; 81 | } 82 | 83 | Key functionKeys[] = { 84 | {0, 2 * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING), KEY_BUTTON_WIDTH, KEY_BUTTON_HEIGHT, KEYCODE_CAPS_LOCK}, 85 | {0, 3 * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING), (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 3 / 2 - 1, KEY_BUTTON_HEIGHT, KEYCODE_SHIFT}, 86 | {(KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 21 / 2, 1 * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING), (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 3 / 2 - 1, KEY_BUTTON_HEIGHT, KEYCODE_BACKSPACE}, 87 | {(KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 10, 2 * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING), (KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING) * 2 - 1, KEY_BUTTON_HEIGHT, KEYCODE_ENTER}, 88 | {KEY_BUTTON_WIDTH + KEY_BUTTON_SPACING, 4 * (KEY_BUTTON_HEIGHT + KEY_BUTTON_SPACING), KEY_BUTTON_WIDTH, KEY_BUTTON_HEIGHT, KEYCODE_LANGUAGE_CHINISE} 89 | }; 90 | 91 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->functionKeys); i++) { 92 | gVirtualKeyboard->functionKeys[i] = functionKeys[i]; 93 | } 94 | 95 | gVirtualKeyboard->x = 8; 96 | gVirtualKeyboard->y = 60; 97 | 98 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->normalKeys); i++) { 99 | gVirtualKeyboard->normalKeys[i].glyph = GetDefaultGlyph(gVirtualKeyboard->normalKeys[i].code); 100 | } 101 | 102 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->functionKeys); i++) { 103 | gVirtualKeyboard->functionKeys[i].glyph = GetDefaultGlyph(gVirtualKeyboard->functionKeys[i].code); 104 | } 105 | 106 | 107 | gVirtualKeyboard->inputTextBox.x = 8; 108 | gVirtualKeyboard->inputTextBox.y = 16; 109 | gVirtualKeyboard->inputTextBox.width = 240; 110 | gVirtualKeyboard->inputTextBox.height = 16; 111 | gVirtualKeyboard->inputTextBox.maxLength = gameInterface->GetMaxInputLength(); 112 | gVirtualKeyboard->inputTextBox.text = malloc(gameInterface->GetMaxInputLength() * sizeof(u16)); 113 | gVirtualKeyboard->inputTextBox.length = 0; 114 | 115 | gVirtualKeyboard->language = KEYBOARD_LANG_CHS; 116 | gVirtualKeyboard->glyphBaseline = KEY_BUTTON_HEIGHT - (KEY_BUTTON_HEIGHT - 12) / 2; 117 | 118 | glGenTextures(1, &gVirtualKeyboard->keyTexPalId); 119 | glBindTexture(0, gVirtualKeyboard->keyTexPalId); 120 | glColorTableEXT(0, 0, 4, 0, 0, gKeysTexPal); 121 | 122 | glGenTextures(1, &gVirtualKeyboard->glyphTexPalId); 123 | glBindTexture(0, gVirtualKeyboard->glyphTexPalId); 124 | glColorTableEXT(0, 0, 4, 0, 0, gGlyphTexPal); 125 | 126 | CreateExternalFontPalette(gVirtualKeyboard->externalGlyphKeyPalIds, KEY_GLYPH_COLOR, KEY_BG_COLOR); 127 | CreateExternalFontPalette(gVirtualKeyboard->externalGlyphTextBoxPalIds, TEXTBOX_GLYPH_COLOR, TEXTBOX_BG_COLOR); 128 | } 129 | 130 | void FinalizeKeyboard(bool isCancelled) { 131 | if (gVirtualKeyboard->inputTextBox.length == 0) 132 | isCancelled = true; 133 | gVirtualKeyboard->gameInterface->OnInputFinished(gVirtualKeyboard->inputTextBox.text, gVirtualKeyboard->inputTextBox.length, isCancelled); 134 | DeinitKeyboardFont(); 135 | free(gVirtualKeyboard->inputTextBox.text); 136 | free(gVirtualKeyboard); 137 | gVirtualKeyboard = NULL; 138 | } 139 | 140 | void DrawInputTextBox() { 141 | KeyboardInputMethodInterface *inputMethodInterface = gVirtualKeyboard->inputMethodInterface[gVirtualKeyboard->language]; 142 | TextBox textBox = gVirtualKeyboard->inputTextBox; 143 | glBoxFilled(textBox.x, textBox.y, textBox.x + textBox.width - 1, textBox.y + textBox.height - 1, TEXTBOX_BG_COLOR); 144 | if (inputMethodInterface && inputMethodInterface[gVirtualKeyboard->language].OnInputStringDraw(gVirtualKeyboard, &textBox)) { 145 | return; 146 | } 147 | SetDefaultKeysPalette(gVirtualKeyboard->glyphTexPalId); 148 | int drawFrom = 0; 149 | int width = 0; 150 | glImage glyph; 151 | int palIndex; 152 | int advance; 153 | for (int i = textBox.length - 1; i >= 0; i--) { 154 | if (GetExternalGlyph(textBox.text[i], &glyph, &palIndex, &advance)) { 155 | width += advance; 156 | if (width > textBox.width) { 157 | drawFrom = i + 1; 158 | break; 159 | } 160 | } 161 | } 162 | 163 | width = 0; 164 | 165 | for (int i = drawFrom; i < textBox.length; i++) { 166 | if (GetExternalGlyph(textBox.text[i], &glyph, &palIndex, &advance)) { 167 | glSetActiveTexture(glyph.textureID); 168 | glAssignColorTable(0, gVirtualKeyboard->externalGlyphTextBoxPalIds[palIndex]); 169 | glSprite(textBox.x + width, textBox.y + (textBox.height - glyph.height) / 2, GL_FLIP_NONE, &glyph); 170 | width += advance; 171 | } 172 | } 173 | } 174 | 175 | void DrawKey(Key *key) { 176 | KeyboardInputMethodInterface *inputMethodInterface = gVirtualKeyboard->inputMethodInterface[gVirtualKeyboard->language]; 177 | 178 | int x = key->x + gVirtualKeyboard->x; 179 | int y = key->y + gVirtualKeyboard->y; 180 | 181 | bool isActive = key->isPressed || 182 | (key->code == KEYCODE_SHIFT && gVirtualKeyboard->isShifted) || 183 | (key->code == KEYCODE_CAPS_LOCK && gVirtualKeyboard->isCapsLocked); 184 | 185 | u16 color = isActive ? KEY_BG_COLOR_PRESSED : KEY_BG_COLOR; 186 | 187 | glBoxFilled(x, y, x + key->width - 1, y + key->height - 1, color); 188 | 189 | if (inputMethodInterface && inputMethodInterface->OnKeyDraw(gVirtualKeyboard, key)) { 190 | return; 191 | } 192 | 193 | if (key->glyph) { 194 | int glyphX = x + (key->width + 1 - key->glyph->width) / 2; 195 | int glyphY = y + gVirtualKeyboard->glyphBaseline - key->glyph->height; 196 | glSprite(glyphX, glyphY, GL_FLIP_NONE, key->glyph); 197 | } 198 | } 199 | 200 | void DrawKeyboard() { 201 | glBoxFilled(0, 0, 256, 192, KEYBOARD_BG_COLOR); 202 | SetDefaultKeysPalette(gVirtualKeyboard->keyTexPalId); 203 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->normalKeys); i++) { 204 | Key key = gVirtualKeyboard->normalKeys[i]; 205 | DrawKey(&key); 206 | } 207 | 208 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->functionKeys); i++) { 209 | Key key = gVirtualKeyboard->functionKeys[i]; 210 | DrawKey(&key); 211 | } 212 | DrawInputTextBox(); 213 | } 214 | 215 | void SwitchKeyboardLayer(u8 * keyboardMap) { 216 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->normalKeys); i++) { 217 | gVirtualKeyboard->normalKeys[i].code = keyboardMap[i]; 218 | gVirtualKeyboard->normalKeys[i].glyph = GetDefaultGlyph(keyboardMap[i]); 219 | } 220 | } 221 | 222 | void TryAddCharToInput(u16 charCode) { 223 | TextBox *textBox = &gVirtualKeyboard->inputTextBox; 224 | KeyboardGameInterface *gameInterface = gVirtualKeyboard->gameInterface; 225 | if (textBox->length >= textBox->maxLength) 226 | return; 227 | if (!gameInterface || !gameInterface->CanContinueInput || gameInterface->CanContinueInput(textBox->text, textBox->length, charCode)) { 228 | textBox->text[textBox->length++] = charCode; 229 | } 230 | } 231 | 232 | void TryAddKeycodeToInput(KeyCode keyCode) { 233 | TextBox *textBox = &gVirtualKeyboard->inputTextBox; 234 | KeyboardGameInterface *gameInterface = gVirtualKeyboard->gameInterface; 235 | if (textBox->length >= textBox->maxLength) 236 | return; 237 | u16 charCode = keyCode; 238 | if (!gameInterface || !gameInterface->KeycodeToChar || gameInterface->KeycodeToChar(keyCode, &charCode)) { 239 | TryAddCharToInput(charCode); 240 | } 241 | } 242 | 243 | void ProcessKey(Key *key, int x, int y, int *result, KeyboardInputMethodInterface *inputMethodInterface) { 244 | int keyX = key->x + gVirtualKeyboard->x; 245 | int keyY = key->y + gVirtualKeyboard->y; 246 | 247 | 248 | if (x >= keyX && x < keyX + key->width && y >= keyY && y < keyY + key->height) { 249 | if (gVirtualKeyboard->currentKey && gVirtualKeyboard->currentKey != key) { 250 | gVirtualKeyboard->currentKey->isPressed = false; 251 | gVirtualKeyboard->currentKey->isHeld = false; 252 | *result |= KEY_STATE_RELEASED; 253 | } 254 | gVirtualKeyboard->currentKey = key; 255 | if (!key->isPressed) { 256 | key->isPressed = true; 257 | *result |= KEY_STATE_PRESSED; 258 | if (gVirtualKeyboard->isPressed) 259 | return; 260 | if (inputMethodInterface && inputMethodInterface->OnKeyPressed(gVirtualKeyboard, key)) { 261 | return; 262 | } 263 | if (key->code == KEYCODE_SHIFT) { 264 | gVirtualKeyboard->isShifted = !gVirtualKeyboard->isShifted; 265 | gVirtualKeyboard->isCapsLocked = false; 266 | SwitchKeyboardLayer(gVirtualKeyboard->isShifted ? KeyboardMapShift : KeyboardMap); 267 | } 268 | else if (key->code == KEYCODE_CAPS_LOCK) { 269 | gVirtualKeyboard->isCapsLocked = !gVirtualKeyboard->isCapsLocked; 270 | gVirtualKeyboard->isShifted = false; 271 | SwitchKeyboardLayer(gVirtualKeyboard->isCapsLocked ? KeyboardMapUppercase : KeyboardMap); 272 | } 273 | else if (key->code == KEYCODE_BACKSPACE) { 274 | TextBox *textBox = &gVirtualKeyboard->inputTextBox; 275 | if (textBox->length > 0) 276 | { 277 | textBox->text[textBox->length - 1] = 0; 278 | textBox->length--; 279 | } 280 | else 281 | { 282 | *result |= KEY_STATE_EXIT; 283 | } 284 | } 285 | else if (key->code & KEYCODE_FLAG_LANGUAGE) { 286 | gVirtualKeyboard->isShifted = false; 287 | gVirtualKeyboard->isCapsLocked = false; 288 | gVirtualKeyboard->language++; 289 | if (gVirtualKeyboard->language >= KEYBOARD_LANG_MAX) 290 | { 291 | gVirtualKeyboard->language = KEYBOARD_LANG_CHS; 292 | } 293 | key->code = KEYCODE_FLAG_LANGUAGE | gVirtualKeyboard->language; 294 | key->glyph = GetDefaultGlyph(key->code); 295 | SwitchKeyboardLayer(KeyboardMap); 296 | } 297 | else if (key->code == KEYCODE_ENTER) { 298 | if (gVirtualKeyboard->inputTextBox.length > 0) 299 | { 300 | *result |= KEY_STATE_FINISHED; 301 | } 302 | else 303 | { 304 | *result |= KEY_STATE_EXIT; 305 | } 306 | } 307 | else { 308 | TryAddKeycodeToInput(key->code); 309 | } 310 | } 311 | else { 312 | key->isHeld = true; 313 | *result |= KEY_STATE_HELD; 314 | } 315 | } 316 | } 317 | 318 | int ProcessKeyTouch(int x, int y) { 319 | int result = KEY_STATE_NOT_PRESSED; 320 | KeyboardInputMethodInterface *inputMethodInterface = gVirtualKeyboard->inputMethodInterface[gVirtualKeyboard->language]; 321 | 322 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->normalKeys) && result == KEY_STATE_NOT_PRESSED; i++) { 323 | ProcessKey(&gVirtualKeyboard->normalKeys[i], x, y, &result, inputMethodInterface); 324 | } 325 | 326 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->functionKeys) && result == KEY_STATE_NOT_PRESSED; i++) { 327 | ProcessKey(&gVirtualKeyboard->functionKeys[i], x, y, &result, inputMethodInterface); 328 | } 329 | 330 | if (result == KEY_STATE_NOT_PRESSED && gVirtualKeyboard->currentKey) { 331 | gVirtualKeyboard->currentKey->isPressed = false; 332 | gVirtualKeyboard->currentKey->isHeld = false; 333 | gVirtualKeyboard->currentKey = NULL; 334 | result = KEY_STATE_RELEASED; 335 | } 336 | 337 | return result; 338 | } 339 | 340 | void ClearKeyboardState() { 341 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->normalKeys); i++) { 342 | gVirtualKeyboard->normalKeys[i].isPressed = false; 343 | gVirtualKeyboard->normalKeys[i].isHeld = false; 344 | } 345 | for (int i = 0; i < ARRAY_SIZE(gVirtualKeyboard->functionKeys); i++) { 346 | gVirtualKeyboard->functionKeys[i].isPressed = false; 347 | gVirtualKeyboard->functionKeys[i].isHeld = false; 348 | } 349 | } 350 | 351 | int HandleKeyboardInput() { 352 | int x, y, keyState; 353 | switch (gVirtualKeyboard->state) { 354 | case 0: 355 | if (GetCalibratedPoint(&x, &y)) { 356 | keyState = ProcessKeyTouch(x, y); 357 | gVirtualKeyboard->isPressed = true; 358 | if (keyState & KEY_STATE_FINISHED) 359 | return 2; 360 | else if (keyState & KEY_STATE_EXIT) 361 | return 3; 362 | else if (keyState & KEY_STATE_PRESSED) 363 | { 364 | gVirtualKeyboard->state++; 365 | return 1; 366 | } 367 | } 368 | else { 369 | gVirtualKeyboard->isPressed = false; 370 | } 371 | break; 372 | case 1: 373 | if (GetCalibratedPoint(&x, &y)) { 374 | keyState = ProcessKeyTouch(x, y); 375 | if (keyState == KEY_STATE_RELEASED || keyState == KEY_STATE_NOT_PRESSED) { 376 | gVirtualKeyboard->state++; 377 | return 0; 378 | } 379 | else if (keyState & KEY_STATE_PRESSED) { 380 | return 1; 381 | } 382 | break; 383 | } 384 | else { 385 | gVirtualKeyboard->isPressed = false; 386 | } 387 | case 2: 388 | ClearKeyboardState(); 389 | gVirtualKeyboard->state = 0; 390 | return 1; 391 | 392 | } 393 | return 0; 394 | } 395 | 396 | void RegisterKeyboardInputMethod(int language, KeyboardInputMethodInterface *inputMethodInterface) { 397 | gVirtualKeyboard->inputMethodInterface[language] = inputMethodInterface; 398 | } -------------------------------------------------------------------------------- /overlay/src/keyboard_glyph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nitro/fs.h" 3 | #include "keyboard.h" 4 | 5 | #define EXTERNAL_GLYPH_WIDTH 16 6 | #define EXTERNAL_GLYPH_HEIGHT 16 7 | #define EXTERNAL_GLYPH_COUNT 64 8 | #define EXTERNAL_FONT_TEXTURE_WIDTH 128 9 | #define EXTERNAL_FONT_TEXTURE_HEIGHT 64 10 | 11 | struct KeyGlyph { 12 | KeyCode charCode; 13 | u8 x; 14 | u8 y; 15 | u8 width; 16 | u8 height; 17 | }; 18 | 19 | typedef struct { 20 | u16 charCode; 21 | u8 palIdx; 22 | u8 advance; 23 | glImage *image; 24 | } ExternalGlyph; 25 | 26 | typedef struct { 27 | ExternalGlyph *glyphs; 28 | glImage *glyphImages; 29 | int textureId; 30 | int glyphCount; 31 | int glyphCapacity; 32 | bool (*LoadGlyph)(u16 charCode, u8 *output, int *advance); 33 | } ExternalFont; 34 | 35 | typedef struct { 36 | glImage *keysGlyphs; 37 | int keysTextureId; 38 | int keyTexPalId; 39 | int glyphTexPalId; 40 | ExternalFont externalFont; 41 | } KeyboardFont; 42 | 43 | const struct KeyGlyph gDefaultKeyGlyphs[] = { 44 | { KEYCODE_BACKSPACE, 0, 51, 13, 7 }, 45 | { KEYCODE_SHIFT, 0, 12, 25, 11 }, 46 | { KEYCODE_ENTER, 25, 12, 29, 11 }, 47 | { KEYCODE_CAPS_LOCK, 102, 0, 19, 11 }, 48 | { KEYCODE_EXCLAMATION, 54, 12, 6, 10 }, 49 | { KEYCODE_HASH, 60, 12, 6, 10 }, 50 | { KEYCODE_DOLLAR, 30, 0, 6, 11 }, 51 | { KEYCODE_PERCENT, 66, 12, 6, 10 }, 52 | { KEYCODE_AMPERSAND, 72, 12, 6, 10 }, 53 | { KEYCODE_QUOTE, 36, 0, 6, 11 }, 54 | { KEYCODE_LEFT_PAREN, 42, 0, 6, 11 }, 55 | { KEYCODE_RIGHT_PAREN, 48, 0, 6, 11 }, 56 | { KEYCODE_ASTERISK, 108, 33, 6, 9 }, 57 | { KEYCODE_PLUS, 78, 12, 6, 10 }, 58 | { KEYCODE_COMMA, 13, 51, 6, 4 }, 59 | { KEYCODE_MINUS, 12, 43, 6, 7 }, 60 | { KEYCODE_PERIOD, 19, 51, 6, 4 }, 61 | { KEYCODE_SLASH, 54, 0, 6, 11 }, 62 | { KEYCODE_0, 84, 12, 6, 10 }, 63 | { KEYCODE_1, 90, 12, 6, 10 }, 64 | { KEYCODE_2, 96, 12, 6, 10 }, 65 | { KEYCODE_3, 102, 12, 6, 10 }, 66 | { KEYCODE_4, 108, 12, 6, 10 }, 67 | { KEYCODE_5, 114, 12, 6, 10 }, 68 | { KEYCODE_6, 120, 12, 6, 10 }, 69 | { KEYCODE_7, 0, 23, 6, 10 }, 70 | { KEYCODE_8, 6, 23, 6, 10 }, 71 | { KEYCODE_9, 12, 23, 6, 10 }, 72 | { KEYCODE_COLON, 120, 33, 6, 8 }, 73 | { KEYCODE_SEMICOLON, 0, 43, 6, 8 }, 74 | { KEYCODE_LESS, 60, 0, 6, 11 }, 75 | { KEYCODE_EQUAL, 6, 43, 6, 8 }, 76 | { KEYCODE_GREATER, 66, 0, 6, 11 }, 77 | { KEYCODE_QUESTION, 18, 23, 6, 10 }, 78 | { KEYCODE_AT, 24, 23, 6, 10 }, 79 | { KEYCODE_A, 30, 23, 6, 10 }, 80 | { KEYCODE_B, 36, 23, 6, 10 }, 81 | { KEYCODE_C, 42, 23, 6, 10 }, 82 | { KEYCODE_D, 48, 23, 6, 10 }, 83 | { KEYCODE_E, 54, 23, 6, 10 }, 84 | { KEYCODE_F, 60, 23, 6, 10 }, 85 | { KEYCODE_G, 66, 23, 6, 10 }, 86 | { KEYCODE_H, 72, 23, 6, 10 }, 87 | { KEYCODE_I, 78, 23, 6, 10 }, 88 | { KEYCODE_J, 84, 23, 6, 10 }, 89 | { KEYCODE_K, 90, 23, 6, 10 }, 90 | { KEYCODE_L, 96, 23, 6, 10 }, 91 | { KEYCODE_M, 102, 23, 6, 10 }, 92 | { KEYCODE_N, 108, 23, 6, 10 }, 93 | { KEYCODE_O, 114, 23, 6, 10 }, 94 | { KEYCODE_P, 120, 23, 6, 10 }, 95 | { KEYCODE_Q, 0, 33, 6, 10 }, 96 | { KEYCODE_R, 6, 33, 6, 10 }, 97 | { KEYCODE_S, 12, 33, 6, 10 }, 98 | { KEYCODE_T, 18, 33, 6, 10 }, 99 | { KEYCODE_U, 24, 33, 6, 10 }, 100 | { KEYCODE_V, 30, 33, 6, 10 }, 101 | { KEYCODE_W, 36, 33, 6, 10 }, 102 | { KEYCODE_X, 42, 33, 6, 10 }, 103 | { KEYCODE_Y, 48, 33, 6, 10 }, 104 | { KEYCODE_Z, 54, 33, 6, 10 }, 105 | { KEYCODE_LEFT_BRACKET, 72, 0, 6, 11 }, 106 | { KEYCODE_RIGHT_BRACKET, 78, 0, 6, 11 }, 107 | { KEYCODE_CARET, 84, 0, 6, 11 }, 108 | { KEYCODE_UNDERSCORE, 25, 51, 6, 2 }, 109 | { KEYCODE_a, 18, 43, 6, 7 }, 110 | { KEYCODE_b, 60, 33, 6, 10 }, 111 | { KEYCODE_c, 24, 43, 6, 7 }, 112 | { KEYCODE_d, 66, 33, 6, 10 }, 113 | { KEYCODE_e, 30, 43, 6, 7 }, 114 | { KEYCODE_f, 72, 33, 6, 10 }, 115 | { KEYCODE_g, 36, 43, 6, 7 }, 116 | { KEYCODE_h, 78, 33, 6, 10 }, 117 | { KEYCODE_i, 84, 33, 6, 10 }, 118 | { KEYCODE_j, 90, 33, 6, 10 }, 119 | { KEYCODE_k, 96, 33, 6, 10 }, 120 | { KEYCODE_l, 102, 33, 6, 10 }, 121 | { KEYCODE_m, 42, 43, 6, 7 }, 122 | { KEYCODE_n, 48, 43, 6, 7 }, 123 | { KEYCODE_o, 54, 43, 6, 7 }, 124 | { KEYCODE_p, 60, 43, 6, 7 }, 125 | { KEYCODE_q, 66, 43, 6, 7 }, 126 | { KEYCODE_r, 72, 43, 6, 7 }, 127 | { KEYCODE_s, 78, 43, 6, 7 }, 128 | { KEYCODE_t, 114, 33, 6, 9 }, 129 | { KEYCODE_u, 84, 43, 6, 7 }, 130 | { KEYCODE_v, 90, 43, 6, 7 }, 131 | { KEYCODE_w, 96, 43, 6, 7 }, 132 | { KEYCODE_x, 102, 43, 6, 7 }, 133 | { KEYCODE_y, 108, 43, 6, 7 }, 134 | { KEYCODE_z, 114, 43, 6, 7 }, 135 | { KEYCODE_LEFT_BRACE, 90, 0, 6, 11 }, 136 | { KEYCODE_RIGHT_BRACE, 96, 0, 6, 11 }, 137 | { KEYCODE_TILDE, 0, 0, 6, 12 }, 138 | { KEYCODE_LANGUAGE_CHINISE, 6, 0, 12, 12}, 139 | { KEYCODE_LANGUAGE_ENGLISH, 18, 0, 12, 12} 140 | }; 141 | 142 | KeyboardFont *gKeyboardFont; 143 | void InitExternalFont(); 144 | 145 | void InitKeyboardFont() { 146 | gKeyboardFont = malloc(sizeof(KeyboardFont)); 147 | memset(gKeyboardFont, 0, sizeof(KeyboardFont)); 148 | 149 | gKeyboardFont->keysGlyphs = malloc(sizeof(glImage) * ARRAY_SIZE(gDefaultKeyGlyphs)); 150 | u8 *keysTex = NULL; 151 | FSFile file; 152 | FS_InitFile(&file); 153 | if (FS_OpenFile(&file, "keyboard/keys.tex")) { 154 | u32 fileLen = FS_GetLength(&file); 155 | keysTex = malloc(fileLen); 156 | if (keysTex) 157 | FS_ReadFile(&file, keysTex, fileLen); 158 | FS_CloseFile(&file); 159 | } 160 | 161 | int * coords = malloc(sizeof(int) * ARRAY_SIZE(gDefaultKeyGlyphs) * 4); 162 | for (int i = 0; i < ARRAY_SIZE(gDefaultKeyGlyphs); i++) { 163 | coords[i * 4] = gDefaultKeyGlyphs[i].x; 164 | coords[i * 4 + 1] = gDefaultKeyGlyphs[i].y; 165 | coords[i * 4 + 2] = gDefaultKeyGlyphs[i].width; 166 | coords[i * 4 + 3] = gDefaultKeyGlyphs[i].height; 167 | } 168 | 169 | gKeyboardFont->keysTextureId = glLoadSpriteSet(gKeyboardFont->keysGlyphs, 170 | ARRAY_SIZE(gDefaultKeyGlyphs), 171 | coords, 172 | GL_RGB4, 173 | TEXTURE_SIZE_128, 174 | TEXTURE_SIZE_64, 175 | GL_TEXTURE_WRAP_S|GL_TEXTURE_WRAP_T|TEXGEN_OFF|GL_TEXTURE_COLOR0_TRANSPARENT, 176 | 4, 177 | NULL, 178 | keysTex); 179 | 180 | free(coords); 181 | free(keysTex); 182 | 183 | InitExternalFont(); 184 | } 185 | 186 | void InitExternalFont() { 187 | ExternalFont *externalFont = &gKeyboardFont->externalFont; 188 | externalFont->glyphs = malloc(EXTERNAL_GLYPH_COUNT * sizeof(ExternalGlyph)); 189 | memset(externalFont->glyphs, 0, EXTERNAL_GLYPH_COUNT * sizeof(ExternalGlyph)); 190 | externalFont->glyphCapacity = EXTERNAL_GLYPH_COUNT; 191 | externalFont->glyphImages = malloc(externalFont->glyphCapacity * sizeof(glImage) / 2); 192 | void *textureData = malloc(EXTERNAL_FONT_TEXTURE_WIDTH * EXTERNAL_FONT_TEXTURE_HEIGHT / 4); 193 | memset(textureData, 0, 128 * 64 / 4); 194 | externalFont->textureId = glLoadTileSet(externalFont->glyphImages, 195 | EXTERNAL_GLYPH_WIDTH, 196 | EXTERNAL_GLYPH_HEIGHT, 197 | 128, 198 | 64, 199 | GL_RGB4, 200 | TEXTURE_SIZE_128, 201 | TEXTURE_SIZE_64, 202 | GL_TEXTURE_WRAP_S | GL_TEXTURE_WRAP_T | TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT, 203 | 0, 204 | NULL, 205 | textureData); 206 | 207 | for (int i = 0; i < externalFont->glyphCapacity / 2; i++) { 208 | externalFont->glyphs[i * 2].charCode = 0; 209 | externalFont->glyphs[i * 2].palIdx = 0; 210 | externalFont->glyphs[i * 2].image = &externalFont->glyphImages[i]; 211 | 212 | externalFont->glyphs[i * 2 + 1].charCode = 0; 213 | externalFont->glyphs[i * 2 + 1].palIdx = 1; 214 | externalFont->glyphs[i * 2 + 1].image = &externalFont->glyphImages[i]; 215 | } 216 | free(textureData); 217 | } 218 | 219 | void DeinitKeyboardFont() { 220 | free(gKeyboardFont->keysGlyphs); 221 | free(gKeyboardFont->externalFont.glyphs); 222 | free(gKeyboardFont->externalFont.glyphImages); 223 | free(gKeyboardFont); 224 | } 225 | 226 | glImage *GetDefaultGlyph(KeyCode code) { 227 | // binary search in gDefaultKeyGlyphs, and get the glyph from keyGlyphs 228 | int left = 0; 229 | int right = ARRAY_SIZE(gDefaultKeyGlyphs) - 1; 230 | while (left <= right) { 231 | int mid = (left + right) / 2; 232 | if (gDefaultKeyGlyphs[mid].charCode == code) { 233 | return &gKeyboardFont->keysGlyphs[mid]; 234 | } else if (gDefaultKeyGlyphs[mid].charCode < code) { 235 | left = mid + 1; 236 | } else { 237 | right = mid - 1; 238 | } 239 | } 240 | return NULL; 241 | } 242 | 243 | void SetDefaultKeysPalette(int palId) { 244 | glSetActiveTexture(gKeyboardFont->keysTextureId); 245 | glAssignColorTable(0, palId); 246 | } 247 | 248 | void CreateExternalFontPalette(int * paletteIds, u16 textColor, u16 bgColor) { 249 | u16 palettes[EXTERNAL_FONT_PALETTE_SIZE][4] = { 250 | {bgColor, textColor, bgColor, textColor}, 251 | {bgColor, bgColor, textColor, textColor} 252 | }; 253 | for (int i = 0; i < EXTERNAL_FONT_PALETTE_SIZE; i++) { 254 | glGenTextures(1, &paletteIds[i]); 255 | glBindTexture(0, paletteIds[i]); 256 | glColorTableEXT(0, 0, 4, 0, 0, palettes[i]); 257 | } 258 | } 259 | 260 | void RegisterGlyphLoader(bool (*LoadGlyph)(u16 charCode, u8 *output, int *advance)) { 261 | gKeyboardFont->externalFont.LoadGlyph = LoadGlyph; 262 | } 263 | 264 | bool LoadExternalGlyph(u16 charCode, int index) { 265 | ExternalFont *externalFont = &gKeyboardFont->externalFont; 266 | if (!externalFont->LoadGlyph) { 267 | return false; 268 | } 269 | u8 glyphData[EXTERNAL_GLYPH_WIDTH * EXTERNAL_GLYPH_HEIGHT / 4]; 270 | int advance; 271 | if (!externalFont->LoadGlyph(charCode, glyphData, &advance)) { 272 | return false; 273 | } 274 | externalFont->glyphs[index].charCode = charCode; 275 | externalFont->glyphs[index].advance = advance; 276 | u32 * texBuffer = glGetTexturePointer(externalFont->textureId); 277 | u8 vramACR = VRAM_A_CR; 278 | VRAM_A_CR = VRAM_ENABLE; 279 | u32 mask = 0x55555555 << externalFont->glyphs[index].palIdx; 280 | glImage *image = externalFont->glyphs[index].image; 281 | for (int i = 0; i < sizeof(glyphData) / 4; i++) { 282 | int texOffset = ((image->v_off + i) * 128 + image->u_off) / 16; 283 | texBuffer[texOffset] = texBuffer[texOffset] & ~mask | ((u32 *)glyphData)[i] << externalFont->glyphs[index].palIdx; 284 | } 285 | VRAM_A_CR = vramACR; 286 | return true; 287 | } 288 | 289 | bool GetExternalGlyph(u16 charCode, glImage *glyphImage, int *palIndex, int *advance) { 290 | // externalFont->glyphs is lru cache, so we need to check if the glyph is already loaded 291 | ExternalFont *externalFont = &gKeyboardFont->externalFont; 292 | for (int i = 0; i < externalFont->glyphCount; i++) { 293 | if (externalFont->glyphs[i].charCode == charCode) { 294 | *glyphImage = *externalFont->glyphs[i].image; 295 | *palIndex = externalFont->glyphs[i].palIdx; 296 | *advance = externalFont->glyphs[i].advance; 297 | // if loaded, get the glyph from the cache, and bring it to the front, then return true 298 | ExternalGlyph temp = externalFont->glyphs[i]; 299 | for (int j = i; j > 0; j--) { 300 | externalFont->glyphs[j] = externalFont->glyphs[j - 1]; 301 | } 302 | externalFont->glyphs[0] = temp; 303 | return true; 304 | } 305 | } 306 | 307 | int loadedIndex = externalFont->glyphCount < externalFont->glyphCapacity ? externalFont->glyphCount : externalFont->glyphCapacity - 1; 308 | if (!LoadExternalGlyph(charCode, loadedIndex)) { 309 | return false; 310 | } 311 | *glyphImage = *externalFont->glyphs[loadedIndex].image; 312 | *palIndex = externalFont->glyphs[loadedIndex].palIdx; 313 | *advance = externalFont->glyphs[loadedIndex].advance; 314 | ExternalGlyph temp = externalFont->glyphs[loadedIndex]; 315 | for (int i = loadedIndex; i > 0; i--) { 316 | externalFont->glyphs[i] = externalFont->glyphs[i - 1]; 317 | } 318 | externalFont->glyphs[0] = temp; 319 | if (externalFont->glyphCount < externalFont->glyphCapacity) { 320 | externalFont->glyphCount++; 321 | } 322 | return true; 323 | } -------------------------------------------------------------------------------- /overlay/src/keyboard_heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct BlockHeader { 5 | u32 size; 6 | struct BlockHeader* next; 7 | int free; 8 | } BlockHeader; 9 | 10 | static u8* heap_start; 11 | static u32 heap_size; 12 | static BlockHeader* free_list; 13 | 14 | #define ALIGN_SIZE 4 15 | #define ALIGN(x) (((x) + (ALIGN_SIZE - 1)) & ~(ALIGN_SIZE - 1)) 16 | #define MIN_BLOCK_SIZE (sizeof(BlockHeader) + ALIGN_SIZE) 17 | 18 | void InitHeap(void *start, u32 size) { 19 | if (!start || size < MIN_BLOCK_SIZE) return; 20 | 21 | u32 addr = (u32)start; 22 | if (addr & (ALIGN_SIZE - 1)) { 23 | u32 adjust = ALIGN_SIZE - (addr & (ALIGN_SIZE - 1)); 24 | start = (void*)((u8*)start + adjust); 25 | size -= adjust; 26 | } 27 | 28 | heap_start = (u8*)start; 29 | heap_size = size & ~(ALIGN_SIZE - 1); 30 | 31 | free_list = (BlockHeader*)heap_start; 32 | free_list->size = heap_size - sizeof(BlockHeader); 33 | free_list->next = NULL; 34 | free_list->free = 1; 35 | } 36 | 37 | void *malloc(u32 size) { 38 | if (size == 0) 39 | return NULL; 40 | 41 | size = ALIGN(size); 42 | 43 | BlockHeader* current = free_list; 44 | BlockHeader* previous = NULL; 45 | 46 | while (current != NULL) { 47 | if (current->free && current->size >= size) { 48 | if (current->size >= size + MIN_BLOCK_SIZE) { 49 | BlockHeader* new_block = (BlockHeader*)((u8*)current + sizeof(BlockHeader) + size); 50 | new_block->size = current->size - size - sizeof(BlockHeader); 51 | new_block->next = current->next; 52 | new_block->free = 1; 53 | 54 | current->size = size; 55 | current->next = new_block; 56 | } 57 | 58 | current->free = 0; 59 | return (void*)((u8*)current + sizeof(BlockHeader)); 60 | } 61 | 62 | previous = current; 63 | current = current->next; 64 | } 65 | 66 | return NULL; 67 | } 68 | 69 | void free(void* ptr) { 70 | if (ptr == NULL) return; 71 | 72 | BlockHeader* block = (BlockHeader*)((u8*)ptr - sizeof(BlockHeader)); 73 | if ((u8*)block < heap_start || 74 | (u8*)block >= heap_start + heap_size) { 75 | return; 76 | } 77 | 78 | block->free = 1; 79 | 80 | BlockHeader* current = free_list; 81 | while (current != NULL && current->next != NULL) { 82 | if (current->free) { 83 | while (current->next != NULL && current->next->free) { 84 | current->size += current->next->size + sizeof(BlockHeader); 85 | current->next = current->next->next; 86 | } 87 | } 88 | current = current->next; 89 | } 90 | } 91 | 92 | void *calloc(u32 nmemb, u32 size) { 93 | if (nmemb != 0 && size > UINT32_MAX / nmemb) { 94 | return NULL; 95 | } 96 | 97 | u32 total_size = nmemb * size; 98 | void* ptr = malloc(total_size); 99 | if (ptr) { 100 | memset(ptr, 0, total_size); 101 | } 102 | return ptr; 103 | } 104 | 105 | void *realloc(void *ptr, u32 size) { 106 | if (ptr == NULL) { 107 | return malloc(size); 108 | } 109 | if (size == 0) { 110 | free(ptr); 111 | return NULL; 112 | } 113 | 114 | BlockHeader* block = (BlockHeader*)((u8*)ptr - sizeof(BlockHeader)); 115 | 116 | if ((u8*)block < heap_start || 117 | (u8*)block >= heap_start + heap_size) { 118 | return NULL; 119 | } 120 | 121 | size = ALIGN(size); 122 | 123 | 124 | if (block->size >= size) { 125 | if (block->size >= size + MIN_BLOCK_SIZE) { 126 | BlockHeader* new_block = (BlockHeader*)((u8*)ptr + size); 127 | new_block->size = block->size - size - sizeof(BlockHeader); 128 | new_block->next = block->next; 129 | new_block->free = 1; 130 | block->size = size; 131 | block->next = new_block; 132 | } 133 | return ptr; 134 | } 135 | 136 | void* new_ptr = malloc(size); 137 | if (new_ptr) { 138 | memcpy(new_ptr, ptr, block->size); 139 | free(ptr); 140 | } 141 | return new_ptr; 142 | } 143 | -------------------------------------------------------------------------------- /overlay/src/keyboard_thread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "nitro/thread.h" 11 | #include "hook.h" 12 | #include "keyboard.h" 13 | #include "touch.h" 14 | 15 | static bool gKeyboardVisible; 16 | extern u32 OrigLauncherThreadLR; 17 | extern u32 OrigLauncherThreadPC; 18 | extern OSContext LanucherThreadContext; 19 | extern void *OSi_IrqThreadQueue[]; 20 | 21 | void JumpFromLauncherThread(); 22 | void *MpuGetDTCMRegion(); 23 | 24 | void OS_SaveContext(); 25 | __attribute__((weak)) extern void *SVC_WaitVBlankIntr; 26 | __attribute__((weak)) extern u32 SVC_WaitVBlankIntr_Caller; 27 | void Hook_SVC_WaitVBlankIntr(); 28 | 29 | OSThread gMonitorThread; 30 | 31 | void WaitVBlankIntr() { 32 | vu32 *irqCheckFlags = (vu32 *)MpuGetDTCMRegion() + 0x3FF8 / sizeof(u32); 33 | swiDelay(1); 34 | ArmIrqState state = armIrqLockByPsr(); 35 | *irqCheckFlags &= ~IRQ_VBLANK; 36 | armIrqUnlockByPsr(state); 37 | while (!(*irqCheckFlags & IRQ_VBLANK)) 38 | OS_SleepThread(OSi_IrqThreadQueue); 39 | } 40 | 41 | bool GetBranchLinkAddr(u32 lr, u32 *addr) { 42 | if (lr >= 0x1FF8000 && lr < 0x23E0000) { 43 | if (lr & 1) { // Thumb mode 44 | lr &= ~1; 45 | lr -= 4; 46 | u16 instr1 = *(u16 *)lr; 47 | u16 instr2 = *(u16 *)(lr + 2); 48 | 49 | // Check for Thumb BLX 50 | if ((instr1 & 0xF800) == 0xF000 && (instr2 & 0xE800) == 0xE800) { 51 | // Extract immediate value and compute target address 52 | s32 offset = (s32)(((instr1 & 0x7FF) << 12) | ((instr2 & 0x7FF) << 1)) << 9 >> 9; 53 | *addr = (lr + 4) + offset; 54 | if (!(instr2 & 0x1000)) { 55 | *addr &= ~3; 56 | } 57 | return true; 58 | } 59 | } 60 | else { // ARM mode 61 | lr -= 4; 62 | u32 instr = *(u32 *)lr; 63 | 64 | // Check for ARM BL 65 | if ((instr & 0x0F000000) == 0x0B000000) { 66 | s32 offset = (s32)(instr & 0x00FFFFFF) << 8 >> 6; 67 | *addr = (lr + 8) + offset; 68 | return true; 69 | } 70 | } 71 | } 72 | return false; // Don't forget to return false if no branch found 73 | } 74 | 75 | 76 | void MonitorThreadEntry(void* arg) { 77 | u32 state = 0; 78 | OSContext *context = &LanucherThreadContext; 79 | KeyboardGameInterface *interface = GetKeyboardGameInterface(); 80 | u32 addr, mode; 81 | u32 caller = (u32)&SVC_WaitVBlankIntr_Caller; 82 | void * SVC_WaitVBlankIntrPtr = &SVC_WaitVBlankIntr; 83 | mode = 0; 84 | if (caller && SVC_WaitVBlankIntrPtr) { 85 | int ime = enterCriticalSection(); 86 | ForceMakingBranchLink((void*)(caller - 4), Hook_SVC_WaitVBlankIntr); 87 | leaveCriticalSection(ime); 88 | mode = 1; 89 | } 90 | for (;;) { 91 | switch (state) 92 | { 93 | case 0: 94 | if (interface->ShouldShowKeyboard()) 95 | state++; 96 | else 97 | OS_SleepThread(OSi_IrqThreadQueue); 98 | break; 99 | case 1: 100 | int ime = enterCriticalSection(); 101 | if (mode == 1) { 102 | gKeyboardVisible = true; 103 | state++; 104 | } 105 | else { 106 | if (context->lr >= 0x01FF8000 && context->lr < 0x023E0000) { 107 | if (GetBranchLinkAddr(context->lr, &addr)) { 108 | if (addr == (u32)OS_SaveContext) { 109 | gKeyboardVisible = true; 110 | OrigLauncherThreadLR = context->lr; 111 | OrigLauncherThreadPC = context->pc_plus4 - 4; 112 | context->lr = (u32)JumpFromLauncherThread; 113 | state++; 114 | } 115 | } 116 | } 117 | } 118 | leaveCriticalSection(ime); 119 | OS_SleepThread(OSi_IrqThreadQueue); 120 | break; 121 | case 2: 122 | OS_SleepThread(NULL); 123 | state = 0; 124 | break; 125 | } 126 | } 127 | } 128 | 129 | void SetBrightness(u8 screen, s8 bright) { 130 | u16 mode = 1 << 14; 131 | 132 | if (bright < 0) { 133 | mode = 2 << 14; 134 | bright = -bright; 135 | } 136 | if (bright > 31) { 137 | bright = 31; 138 | } 139 | *(vu16*)(0x0400006C + (0x1000 * screen)) = bright + mode; 140 | } 141 | 142 | void LanucherThreadExt() { 143 | KeyboardGameInterface *interface = GetKeyboardGameInterface(); 144 | void *heap = interface->Alloc(KEYBOARD_HEAP_SIZE); 145 | 146 | if (!heap) { 147 | gKeyboardVisible = false; 148 | OS_WakeupThreadDirect(&gMonitorThread); 149 | return; 150 | } 151 | 152 | InitHeap(heap, KEYBOARD_HEAP_SIZE); 153 | 154 | u32 i; 155 | 156 | u32 dispcnt = REG_DISPCNT; 157 | u32 disp3dcnt = GFX_CONTROL; 158 | u16 bg0cnt = REG_BG0CNT; 159 | u16 bg1cnt = REG_BG1CNT; 160 | u16 bg2cnt = REG_BG2CNT; 161 | u16 bg3cnt = REG_BG3CNT; 162 | 163 | u8 vramCRs[10]; 164 | for (i = 0; i < 10; i++) { 165 | if (i == 7) continue; 166 | vramCRs[i] = *(vu8 *)(0x04000240 + i); 167 | *(vu8 *)(0x04000240 + i) = 0; 168 | } 169 | 170 | u8 *vramABackup = malloc(1024 * 4); 171 | 172 | u8 *vramEBackup = malloc(256); 173 | 174 | VRAM_A_CR = VRAM_ENABLE; 175 | VRAM_E_CR = VRAM_ENABLE; 176 | 177 | 178 | memcpy(vramABackup, VRAM_A, 1024 * 4); 179 | memcpy(vramEBackup, VRAM_E, 256); 180 | 181 | u16 powercnt = REG_POWERCNT; 182 | 183 | u16 masterBright = *(vu16 *)0x0400106C; 184 | 185 | m4x4 matrixProjection; 186 | 187 | REG_DISPCNT = MODE_0_3D | DISPLAY_BG0_ACTIVE; 188 | REG_BG0CNT = 0; 189 | REG_BG1CNT = 0; 190 | REG_BG2CNT = 0; 191 | REG_BG3CNT = 0; 192 | REG_BG0HOFS = 0; 193 | REG_BG0VOFS = 0; 194 | 195 | REG_POWERCNT |= POWER_3D_CORE | POWER_MATRIX; 196 | 197 | glGetFixed(GL_GET_MATRIX_PROJECTION, matrixProjection.m); 198 | 199 | glInit(); 200 | 201 | //enable textures 202 | glEnable(GL_TEXTURE_2D); 203 | 204 | // enable antialiasing 205 | glEnable(GL_ANTIALIAS); 206 | 207 | //this should work the same as the normal gl call 208 | glViewport(0,0,255,191); 209 | 210 | vramSetBankA(VRAM_A_TEXTURE); 211 | vramSetBankE(VRAM_E_TEX_PALETTE); 212 | 213 | // If main screen is on auto, then force the bottom 214 | REG_POWERCNT &= ~POWER_SWAP_LCDS; 215 | 216 | SetBrightness(0, 0); 217 | // REG_MOSAIC = 0; // Register is write only, can't back up 218 | // REG_BLDCNT = 0; // Register is write only, can't back up 219 | // REG_BLDALPHA = 0; // Register is write only, can't back up 220 | // REG_BLDY = 0; // Register is write only, can't back up 221 | InitializeKeyboard(interface); 222 | InitPinyinInputMethod(); 223 | RegisterKeyboardInputMethod(KEYBOARD_LANG_CHS, GetPinyinInputMethodInterface()); 224 | 225 | int state = 0; 226 | int result = 0; 227 | while (result != 2 && result != 3) { 228 | switch (state) { 229 | case 0: 230 | glBegin2D(); 231 | DrawKeyboard(); 232 | glEnd2D(); 233 | glFlush(0); 234 | state++; 235 | break; 236 | case 1: 237 | result = HandleKeyboardInput(); 238 | if (result == 1) 239 | state = 0; 240 | break; 241 | default: 242 | break; 243 | } 244 | RequestSamplingTPData(); 245 | WaitVBlankIntr(); 246 | } 247 | 248 | i = 30; 249 | while (i--) 250 | WaitVBlankIntr(); 251 | 252 | 253 | DeinitPinyinInputMethod(); 254 | glResetTextures(); 255 | 256 | REG_DISPCNT = dispcnt; 257 | GFX_CONTROL = disp3dcnt; 258 | REG_BG0CNT = bg0cnt; 259 | REG_BG1CNT = bg1cnt; 260 | REG_BG2CNT = bg2cnt; 261 | REG_BG3CNT = bg3cnt; 262 | 263 | REG_POWERCNT = powercnt; 264 | 265 | VRAM_A_CR = VRAM_ENABLE; 266 | VRAM_E_CR = VRAM_ENABLE; 267 | 268 | memcpy(VRAM_A, vramABackup, 1024 * 4); 269 | memcpy(VRAM_E, vramEBackup, 256); 270 | 271 | free(vramABackup); 272 | free(vramEBackup); 273 | 274 | for (i = 0; i < 10; i++) { 275 | if (i == 7) 276 | continue; 277 | *(vu8 *)(0x04000240 + i) = vramCRs[i]; 278 | } 279 | 280 | // GFX_CLEAR_COLOR = 0x3F003FFF; 281 | // GFX_CLEAR_DEPTH = 0x7FFF; 282 | 283 | 284 | glMatrixMode(GL_PROJECTION); 285 | glLoadMatrix4x4(&matrixProjection); 286 | 287 | ResetTPData(); 288 | FinalizeKeyboard(result != 2); 289 | // glFlush(0); 290 | 291 | interface->Free(heap); 292 | gKeyboardVisible = false; 293 | OS_WakeupThreadDirect(&gMonitorThread); 294 | } 295 | 296 | void Hook_SVC_WaitVBlankIntr() { 297 | void (* volatile func)() = (void (* volatile)())&SVC_WaitVBlankIntr; 298 | func(); 299 | if (gKeyboardVisible) 300 | LanucherThreadExt(); 301 | } 302 | 303 | void StartKeyboardMonitorThread() { 304 | static u32 stack[512 / sizeof(u32)]; 305 | KeyboardGameInterface *interface = GetKeyboardGameInterface(); 306 | interface->OnOverlayLoaded(); 307 | OS_CreateThread(&gMonitorThread, MonitorThreadEntry, 0, stack + ARRAY_SIZE(stack), sizeof(stack), 8); 308 | OS_WakeupThreadDirect(&gMonitorThread); 309 | } -------------------------------------------------------------------------------- /overlay/src/keyborad_pinyin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "nitro/fs.h" 4 | #include "keyboard.h" 5 | 6 | #define MAX_INPUT_LETTER_NUM 6 7 | #define MAX_CANDIDATE_NUM 10 8 | 9 | typedef struct { 10 | u32 pinyinNum; 11 | u32 candidateOffset; 12 | u32 maxCandidateNum; 13 | u32 reserved; 14 | } PinyinTableHeader; 15 | 16 | typedef struct { 17 | u32 pinyin; 18 | u16 candidateOffset; 19 | u16 candidateNum; 20 | } PinyinTableEntry; 21 | 22 | typedef struct { 23 | PinyinTableHeader header; 24 | PinyinTableEntry *entries; 25 | FSFile file; 26 | u8 inputLetter[6]; 27 | int inputLetterNum; 28 | u16 *candidate; 29 | int candidateNum; 30 | int candidateOffset; 31 | KeyboardInputMethodInterface interface; 32 | } PinyinInputMethod; 33 | 34 | PinyinInputMethod *gPinyinInputMethod; 35 | 36 | static bool OnKeyPressed(VirtualKeyboard *keyboard, Key *key); 37 | static bool OnKeyDraw(const VirtualKeyboard *keyboard, const Key *key); 38 | static bool OnInputStringDraw(struct VirtualKeyboard *keyboard, TextBox *textBox); 39 | 40 | KeyboardInputMethodInterface * GetPinyinInputMethodInterface() { 41 | if (!gPinyinInputMethod) 42 | return NULL; 43 | else 44 | return &gPinyinInputMethod->interface; 45 | } 46 | 47 | void InitPinyinInputMethod() { 48 | gPinyinInputMethod = malloc(sizeof(PinyinInputMethod)); 49 | if (!gPinyinInputMethod) { 50 | return; 51 | } 52 | memset(gPinyinInputMethod, 0, sizeof(PinyinInputMethod)); 53 | gPinyinInputMethod->interface.OnKeyPressed = OnKeyPressed; 54 | gPinyinInputMethod->interface.OnKeyDraw = OnKeyDraw; 55 | gPinyinInputMethod->interface.OnInputStringDraw = OnInputStringDraw; 56 | FS_InitFile(&gPinyinInputMethod->file); 57 | if(FS_OpenFile(&gPinyinInputMethod->file, "/keyboard/pinyin_table.bin")) { 58 | FS_ReadFile(&gPinyinInputMethod->file, &gPinyinInputMethod->header, sizeof(PinyinTableHeader)); 59 | gPinyinInputMethod->entries = malloc(sizeof(PinyinTableEntry) * gPinyinInputMethod->header.pinyinNum); 60 | FS_SeekFile(&gPinyinInputMethod->file, sizeof(PinyinTableHeader), 0); 61 | FS_ReadFile(&gPinyinInputMethod->file, gPinyinInputMethod->entries, sizeof(PinyinTableEntry) * gPinyinInputMethod->header.pinyinNum); 62 | gPinyinInputMethod->candidate = malloc(sizeof(u16) * gPinyinInputMethod->header.maxCandidateNum); 63 | } 64 | else { 65 | free(gPinyinInputMethod); 66 | gPinyinInputMethod = NULL; 67 | } 68 | } 69 | 70 | void DeinitPinyinInputMethod() { 71 | if(gPinyinInputMethod) { 72 | FS_CloseFile(&gPinyinInputMethod->file); 73 | if(gPinyinInputMethod->entries) { 74 | free(gPinyinInputMethod->entries); 75 | } 76 | if(gPinyinInputMethod->candidate) { 77 | free(gPinyinInputMethod->candidate); 78 | } 79 | free(gPinyinInputMethod); 80 | gPinyinInputMethod = NULL; 81 | } 82 | } 83 | 84 | int SearchPinyinTable() { 85 | u32 pinyin = 0; 86 | for (int i = 0; i < gPinyinInputMethod->inputLetterNum; i++) { 87 | pinyin |= (((gPinyinInputMethod->inputLetter[i] - 'a' + 1) & 0x1F) << (25 - i * 5)); 88 | } 89 | int left = 0; 90 | int right = gPinyinInputMethod->header.pinyinNum - 1; 91 | while(left <= right) { 92 | int mid = (left + right) / 2; 93 | if(gPinyinInputMethod->entries[mid].pinyin == pinyin) { 94 | return mid; 95 | } else if(gPinyinInputMethod->entries[mid].pinyin < pinyin) { 96 | left = mid + 1; 97 | } else { 98 | right = mid - 1; 99 | } 100 | } 101 | return -1; 102 | } 103 | 104 | void LoadPinyinCandidate() { 105 | int index = SearchPinyinTable(); 106 | if(index != -1) { 107 | FS_SeekFile(&gPinyinInputMethod->file, gPinyinInputMethod->header.candidateOffset + gPinyinInputMethod->entries[index].candidateOffset * sizeof(u16), 0); 108 | FS_ReadFile(&gPinyinInputMethod->file, gPinyinInputMethod->candidate, gPinyinInputMethod->entries[index].candidateNum * sizeof(u16)); 109 | gPinyinInputMethod->candidateNum = gPinyinInputMethod->entries[index].candidateNum; 110 | gPinyinInputMethod->candidateOffset = 0; 111 | } else { 112 | gPinyinInputMethod->candidateNum = 0; 113 | gPinyinInputMethod->candidateOffset = 0; 114 | } 115 | } 116 | 117 | void OnSwitchCandidate(bool isNextPage) { 118 | if (gPinyinInputMethod->candidateNum <= MAX_CANDIDATE_NUM || 119 | (isNextPage && gPinyinInputMethod->candidateOffset + MAX_CANDIDATE_NUM >= gPinyinInputMethod->candidateNum) || 120 | (!isNextPage && gPinyinInputMethod->candidateOffset == 0)) { 121 | return; 122 | } 123 | if(isNextPage) { 124 | gPinyinInputMethod->candidateOffset += MAX_CANDIDATE_NUM; 125 | } else { 126 | gPinyinInputMethod->candidateOffset -= MAX_CANDIDATE_NUM; 127 | } 128 | } 129 | 130 | static bool OnKeyPressed(VirtualKeyboard *keyboard, Key *key) { 131 | if(key->code >= KEYCODE_a && key->code <= KEYCODE_z) { 132 | if(gPinyinInputMethod->inputLetterNum < MAX_INPUT_LETTER_NUM) { 133 | gPinyinInputMethod->inputLetter[gPinyinInputMethod->inputLetterNum++] = key->code; 134 | LoadPinyinCandidate(); 135 | } 136 | return true; 137 | } 138 | else if(key->code == KEYCODE_BACKSPACE) { 139 | if(gPinyinInputMethod->inputLetterNum > 0) { 140 | gPinyinInputMethod->inputLetterNum--; 141 | LoadPinyinCandidate(); 142 | return true; 143 | } 144 | } 145 | else if (key->code == KEYCODE_ENTER) { 146 | if (gPinyinInputMethod->inputLetterNum > 0) { 147 | for (int i = 0; i < gPinyinInputMethod->inputLetterNum; i++) 148 | { 149 | TryAddKeycodeToInput(HalfToFullWidth(gPinyinInputMethod->inputLetter[i])); 150 | } 151 | gPinyinInputMethod->inputLetterNum = 0; 152 | gPinyinInputMethod->candidateNum = 0; 153 | return true; 154 | } 155 | } 156 | else if (key->code == KEYCODE_SHIFT || key->code == KEYCODE_CAPS_LOCK) { 157 | gPinyinInputMethod->inputLetterNum = 0; 158 | gPinyinInputMethod->candidateNum = 0; 159 | } 160 | else if (key->code == KEYCODE_MINUS) { 161 | OnSwitchCandidate(false); 162 | return true; 163 | } 164 | else if (key->code == KEYCODE_EQUAL) { 165 | OnSwitchCandidate(true); 166 | return true; 167 | } 168 | int index = -1; 169 | if (key->code >= KEYCODE_1 && key->code <= KEYCODE_9) { 170 | index = key->code - KEYCODE_1; 171 | } else if (key->code == KEYCODE_0) { 172 | index = 9; 173 | } 174 | 175 | if (index >= 0 && index + gPinyinInputMethod->candidateOffset < gPinyinInputMethod->candidateNum) { 176 | TryAddCharToInput(gPinyinInputMethod->candidate[index + gPinyinInputMethod->candidateOffset]); 177 | gPinyinInputMethod->inputLetterNum = 0; 178 | gPinyinInputMethod->candidateNum = 0; 179 | return true; 180 | } 181 | 182 | if (key->code >= KEYCODE_SPACE && key->code <= KEYCODE_TILDE) { 183 | if (key->code == KEYCODE_PERIOD) { 184 | TryAddKeycodeToInput(KEYCODE_CHINESE_PERIOD); 185 | } else { 186 | TryAddKeycodeToInput(HalfToFullWidth(key->code)); 187 | } 188 | return true; 189 | } 190 | return false; 191 | } 192 | 193 | static bool OnKeyDraw(const VirtualKeyboard *keyboard, const Key *key) { 194 | if (gPinyinInputMethod->candidateNum == 0) { 195 | return false; 196 | } 197 | int palIndex, x, y, adv; 198 | int index = -1; 199 | if (key->code >= KEYCODE_1 && key->code <= KEYCODE_9) { 200 | index = key->code - KEYCODE_1; 201 | } else if (key->code == KEYCODE_0) { 202 | index = 9; 203 | } 204 | if (index >= 0) { 205 | index += gPinyinInputMethod->candidateOffset; 206 | if (index < gPinyinInputMethod->candidateNum) { 207 | u16 charCode = gPinyinInputMethod->candidate[index]; 208 | glImage glyph; 209 | if (GetExternalGlyph(charCode, &glyph, &palIndex, &adv)) { 210 | glSetActiveTexture(glyph.textureID); 211 | glAssignColorTable(0, keyboard->externalGlyphKeyPalIds[palIndex]); 212 | x = keyboard->x + key->x + (key->width - adv + 1) / 2; 213 | y = keyboard->y + key->y + (key->height - glyph.height + 1) / 2; 214 | glSprite(x, y, GL_FLIP_NONE, &glyph); 215 | return true; 216 | } 217 | } 218 | } 219 | return false; 220 | } 221 | 222 | static bool OnInputStringDraw(VirtualKeyboard *keyboard, TextBox *textBox) { 223 | if (gPinyinInputMethod->inputLetterNum == 0) { 224 | return false; 225 | } 226 | int inputStringWidth = 0; 227 | int inputLettersWidth = 0; 228 | glImage glyph; 229 | int palIndex, adv; 230 | int x = 0; 231 | 232 | for (int i = 0; i < gPinyinInputMethod->inputLetterNum; i++) { 233 | glImage *defaultGlyph = GetDefaultGlyph(gPinyinInputMethod->inputLetter[i]); 234 | if (defaultGlyph) { 235 | inputLettersWidth += defaultGlyph->width; 236 | inputLettersWidth++; 237 | } 238 | } 239 | 240 | for (int i = textBox->length - 1; i >= 0; i--) { 241 | if (GetExternalGlyph(textBox->text[i], &glyph, &palIndex, &adv)) { 242 | inputStringWidth += adv; 243 | if (inputStringWidth + inputLettersWidth > textBox->width) { 244 | break; 245 | } 246 | } 247 | } 248 | 249 | x = inputStringWidth; 250 | 251 | glLine(x + textBox->x, 252 | textBox->y + keyboard->glyphBaseline, 253 | x + inputLettersWidth + textBox->x - 1, 254 | textBox->y + keyboard->glyphBaseline, 255 | RGB15(0, 31, 0)); 256 | 257 | for (int i = 0; i < gPinyinInputMethod->inputLetterNum; i++) { 258 | glImage *defaultGlyph = GetDefaultGlyph(gPinyinInputMethod->inputLetter[i]); 259 | if (defaultGlyph) { 260 | glSetActiveTexture(defaultGlyph->textureID); 261 | SetDefaultKeysPalette(keyboard->glyphTexPalId); 262 | glSprite(textBox->x + x, textBox->y + keyboard->glyphBaseline - defaultGlyph->height, GL_FLIP_NONE, defaultGlyph); 263 | x += defaultGlyph->width; 264 | x++; 265 | } 266 | } 267 | return false; 268 | } 269 | -------------------------------------------------------------------------------- /overlay/src/misc.S: -------------------------------------------------------------------------------- 1 | .include "function.inc" 2 | .section .text 3 | .syntax unified 4 | 5 | .global OrigLauncherThreadLR 6 | .global OrigLauncherThreadPC 7 | 8 | arm_func_start JumpFromLauncherThread 9 | JumpFromLauncherThread: 10 | ldr lr, OrigLauncherThreadPC 11 | push {lr} 12 | push {r0-r12} 13 | bl LanucherThreadExt 14 | pop {r0-r12} 15 | ldr lr, OrigLauncherThreadLR 16 | pop {pc} 17 | OrigLauncherThreadLR: 18 | .word 0x0 19 | OrigLauncherThreadPC: 20 | .word 0x0 21 | 22 | arm_func_start MpuGetDTCMRegion 23 | MpuGetDTCMRegion: 24 | mrc p15, 0, r0, c9, c1, 0 25 | mov r0, r0, lsr #12 26 | mov r0, r0, lsl #12 27 | bx lr 28 | 29 | .section .init_functions 30 | .word OverlayInit -------------------------------------------------------------------------------- /overlay/src/overlay_entry.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "keyboard.h" 3 | 4 | void OverlayInit() { 5 | StartKeyboardMonitorThread(); 6 | } -------------------------------------------------------------------------------- /overlay/src/touch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "nitro/tp.h" 6 | #include "touch.h" 7 | 8 | extern u8 HW_TOUCHPANEL_BUF[]; 9 | 10 | struct SPITpData { 11 | u32 x:12; 12 | u32 y:12; 13 | u32 touch:1; 14 | u32 validity:2; 15 | u32 dummy:5; 16 | }; 17 | 18 | bool GetCalibratedPoint(int *x, int *y) { 19 | struct SPITpData data; 20 | TPData tpData; 21 | 22 | *(u8*)&data = HW_TOUCHPANEL_BUF[0]; 23 | *((u8*)&data + 1) = HW_TOUCHPANEL_BUF[1]; 24 | *((u8*)&data + 2) = HW_TOUCHPANEL_BUF[2]; 25 | *((u8*)&data + 3) = HW_TOUCHPANEL_BUF[3]; 26 | 27 | tpData.x = data.x; 28 | tpData.y = data.y; 29 | tpData.touch = data.touch; 30 | tpData.validity = data.validity; 31 | TP_GetCalibratedPoint(&tpData, &tpData); 32 | if (tpData.touch) 33 | { 34 | *x = tpData.x; 35 | *y = tpData.y; 36 | return true; 37 | } 38 | else { 39 | return false; 40 | } 41 | } 42 | 43 | void RequestSamplingTPData() { 44 | if (REG_IPC_FIFO_CR & IPC_FIFO_ERROR) { 45 | REG_IPC_FIFO_CR |= (IPC_FIFO_ENABLE | IPC_FIFO_ERROR); 46 | return; 47 | } 48 | ArmIrqState state = armIrqLockByPsr(); 49 | if (REG_IPC_FIFO_CR & IPC_FIFO_SEND_FULL) { 50 | armIrqUnlockByPsr(state); 51 | return; 52 | } 53 | REG_IPC_FIFO_TX = 0xC0000006; 54 | armIrqUnlockByPsr(state); 55 | } 56 | 57 | void ResetTPData() { 58 | struct SPITpData data = {0}; 59 | data.validity = 3; 60 | HW_TOUCHPANEL_BUF[0] = *(u8*)&data; 61 | HW_TOUCHPANEL_BUF[1] = *((u8*)&data + 1); 62 | HW_TOUCHPANEL_BUF[2] = *((u8*)&data + 2); 63 | HW_TOUCHPANEL_BUF[3] = *((u8*)&data + 3); 64 | } -------------------------------------------------------------------------------- /overlay/src/videoGL_alt.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | s_vramBlock *vramBlock_Construct(uint8 *start, uint8 *end); 4 | void vramBlock_terminate(s_vramBlock *mb); 5 | 6 | //--------------------------------------------------------------------------------- 7 | void __wrap_glInit(void) { 8 | //--------------------------------------------------------------------------------- 9 | int i; 10 | 11 | // powerOn(POWER_3D_CORE | POWER_MATRIX); // enable 3D core & geometry engine 12 | 13 | if( glGlob->isActive ) 14 | return; 15 | 16 | // Allocate the designated layout for each memory block 17 | glGlob->vramBlocks[ 0 ] = vramBlock_Construct( (uint8*)VRAM_A, (uint8*)VRAM_B ); 18 | glGlob->vramBlocks[ 1 ] = vramBlock_Construct( (uint8*)VRAM_E, (uint8*)VRAM_F ); 19 | 20 | glGlob->vramLock[ 0 ] = 0; 21 | glGlob->vramLock[ 1 ] = 0; 22 | 23 | // init texture globals 24 | 25 | glCurClearColor = 0; 26 | 27 | glGlob->activeTexture = 0; 28 | glGlob->activePalette = 0; 29 | glGlob->texCount = 1; 30 | glGlob->palCount = 1; 31 | glGlob->deallocTexSize = 0; 32 | glGlob->deallocPalSize = 0; 33 | 34 | // Clean out all this crap 35 | DynamicArrayInit( &glGlob->texturePtrs, 16 ); 36 | DynamicArrayInit( &glGlob->palettePtrs, 16 ); 37 | DynamicArrayInit( &glGlob->deallocTex, 16 ); 38 | DynamicArrayInit( &glGlob->deallocPal, 16 ); 39 | 40 | for(i = 0; i < 16; i++) { 41 | DynamicArraySet( &glGlob->texturePtrs, i, (void*)0 ); 42 | DynamicArraySet( &glGlob->palettePtrs, i, (void*)0 ); 43 | DynamicArraySet( &glGlob->deallocTex, i, (void*)0 ); 44 | DynamicArraySet( &glGlob->deallocPal, i, (void*)0 ); 45 | } 46 | 47 | while (GFX_STATUS & (1<<27)); // wait till gfx engine is not busy 48 | 49 | // Clear the FIFO 50 | GFX_STATUS |= (1<<29); 51 | 52 | // Clear overflows from list memory 53 | // glResetMatrixStack(); 54 | 55 | // prime the vertex/polygon buffers 56 | glFlush(0); 57 | 58 | // reset the control bits 59 | GFX_CONTROL = 0; 60 | 61 | GFX_TEX_FORMAT = 0; 62 | GFX_POLY_FORMAT = 0; 63 | 64 | glMatrixMode(GL_PROJECTION); 65 | glLoadIdentity(); 66 | 67 | glMatrixMode(GL_MODELVIEW); 68 | glLoadIdentity(); 69 | 70 | glMatrixMode(GL_TEXTURE); 71 | glLoadIdentity(); 72 | 73 | glGlob->isActive = 1; 74 | } 75 | 76 | //--------------------------------------------------------------------------------- 77 | void __wrap_glResetTextures(void) { 78 | //--------------------------------------------------------------------------------- 79 | int i; 80 | 81 | glGlob->activeTexture = 0; 82 | glGlob->activePalette = 0; 83 | glGlob->texCount = 1; 84 | glGlob->palCount = 1; 85 | glGlob->deallocTexSize = 0; 86 | glGlob->deallocPalSize = 0; 87 | 88 | // Any textures in use will be clean of all their data 89 | for(i = 0; i < (int)glGlob->texturePtrs.cur_size; i++) { 90 | gl_texture_data* texture = (gl_texture_data*)DynamicArrayGet( &glGlob->texturePtrs, i ); 91 | if( texture ) { 92 | free( texture ); 93 | DynamicArraySet(&glGlob->texturePtrs, i, (void*)0 ); 94 | } 95 | } 96 | 97 | // Any palettes in use will be cleaned of all their data 98 | for( i = 0; i < (int)glGlob->palettePtrs.cur_size; i++ ) { 99 | gl_palette_data* palette = (gl_palette_data*)DynamicArrayGet( &glGlob->palettePtrs, i ); 100 | if( palette ) { 101 | free( palette ); 102 | DynamicArraySet( &glGlob->palettePtrs, i, (void*)0 ); 103 | } 104 | } 105 | 106 | // Clean out both blocks 107 | for( i = 0; i < 2; i++ ) { 108 | vramBlock_terminate(glGlob->vramBlocks[i]); 109 | free(glGlob->vramBlocks[i]); 110 | glGlob->vramBlocks[i] = NULL; 111 | } 112 | 113 | DynamicArrayDelete( &glGlob->texturePtrs ); 114 | DynamicArrayDelete( &glGlob->palettePtrs ); 115 | DynamicArrayDelete( &glGlob->deallocTex ); 116 | DynamicArrayDelete( &glGlob->deallocPal ); 117 | 118 | memset( glGlob, 0, sizeof(gl_hidden_globals)); 119 | } -------------------------------------------------------------------------------- /overlay_ldr/Makefile: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | 3 | include ../common/common.mk 4 | 5 | OUTPUT := overlay_ldr 6 | OUTPUT_ELF := ../$(OUTPUT).elf 7 | OUTPUT_BIN := ../$(OUTPUT).bin 8 | INC_DIRS := include $(COMMON_INC_DIRS) 9 | 10 | 11 | C_SRCS := $(wildcard src/*.c) 12 | ASM_SRCS:= $(wildcard src/*.S) 13 | C_OBJS := $(C_SRCS:%.c=$(OBJ_DIR)/%.o) 14 | ASM_OBJS:= $(ASM_SRCS:%.S=$(OBJ_DIR)/%.o) 15 | OBJS := $(C_OBJS) $(ASM_OBJS) 16 | 17 | ALL_OBJS := $(OBJS) $(NITRO_LIB_OBJS) 18 | DEPS := $(ALL_OBJS:%.o=%.d) 19 | 20 | CFLAGS := -g -w -Os -std=gnu99 -ffreestanding -mabi=aapcs -march=armv5te -mthumb -mtune=arm946e-s -mthumb-interwork $(foreach dir,$(INC_DIRS),-I $(dir)) -DOVERLAY_ID=$(OVERLAY_ID) -DARM9=1 -D__NDS__ -DNITROSDK_VER=$(NITROSDK_VER) 21 | LDFLAGS := -nostartfiles -nodefaultlibs -Ttext=$(OVERLAY_LDR_ADDR) -T linker.ld -T $(SYMBOLS_LD) -Wl,--use-blx 22 | 23 | $(shell mkdir -p $(sort $(dir $(ALL_OBJS)))) 24 | 25 | .PHONY: all clean 26 | 27 | all: $(OUTPUT_BIN) 28 | @: 29 | 30 | -include $(DEPS) 31 | 32 | $(OBJ_DIR)/%.o: %.c 33 | $(CC) -MMD -MP -MF $(OBJ_DIR)/$*.d $(CFLAGS) -o $(OBJ_DIR)/$*.o -c $< 34 | 35 | $(OBJ_DIR)/%.o: %.S 36 | $(CC) -D__ASSEMBLY__ $(CFLAGS) -c $< -o $@ 37 | 38 | $(OUTPUT_ELF): $(OBJS) $(NITRO_LIB) 39 | $(LD) $(LDFLAGS) -o $@ $^ $(NITRO_LIB) $(LIBS) 40 | 41 | $(OUTPUT_BIN): $(OUTPUT_ELF) 42 | $(OBJCOPY) --set-section-flags .bss=alloc,load,contents -O binary $< $@ 43 | 44 | clean: 45 | rm -rf $(OBJ_DIR) 46 | rm $(OUTPUT_ELF) $(OUTPUT_BIN) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /overlay_ldr/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 2 | OUTPUT_ARCH(arm) 3 | 4 | ENTRY(_start) 5 | 6 | SECTIONS 7 | { 8 | .text : { 9 | *src/*.o(.text*) 10 | } 11 | . = ALIGN(4); 12 | 13 | .rodata : { 14 | *src/*.o(.rodata*) 15 | } 16 | . = ALIGN(4); 17 | 18 | .data : { 19 | *src/*.o(.data*) 20 | } 21 | . = ALIGN(4); 22 | 23 | .bss : { 24 | *src/*.o(.bss*) 25 | } 26 | 27 | 28 | 29 | /DISCARD/ : 30 | { 31 | *(*) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /overlay_ldr/src/overlay_ldr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nitro/fs.h" 3 | 4 | #ifndef OVERLAY_ID 5 | #define OVERLAY_ID -1 6 | #endif 7 | 8 | extern void (*Orig_OverlayStaticInitBegin[])(); 9 | extern void (*Orig_OverlayStaticInitEnd[])(); 10 | 11 | void LoadOverlay(); 12 | 13 | void (*const OverlayStaticInitFunc)() = LoadOverlay; 14 | 15 | void LoadOverlay() { 16 | static bool loaded = false; 17 | if (!loaded) { 18 | FS_LoadOverlay(0, OVERLAY_ID); 19 | loaded = true; 20 | } 21 | if (Orig_OverlayStaticInitBegin && Orig_OverlayStaticInitEnd) { 22 | for (void (**func)() = Orig_OverlayStaticInitBegin; func < Orig_OverlayStaticInitEnd; func++) { 23 | if (*func) 24 | (*func)(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /preview/preview_conquest.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/preview/preview_conquest.gif -------------------------------------------------------------------------------- /preview/preview_dq5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/preview/preview_dq5.gif -------------------------------------------------------------------------------- /preview/preview_hg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/preview/preview_hg.gif -------------------------------------------------------------------------------- /preview/preview_layton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enler/NitroKeyboardPlugin/fdccc3daf1f0cfd16a687f65c9e78d6fe212b7b6/preview/preview_layton.gif -------------------------------------------------------------------------------- /resource/keys.tex: -------------------------------------------------------------------------------- 1 | @T@@P@PUUA@@D@P@AUU@@@A@@@@@@@A@@@U@@@@@@A@@@A@@@P@UUA@@@@P@PUU@@@@@@@P@@T@@ET@PP@ETTQUUE@TEUTATT@@PTTQUUE@TEUTDAPA@A@@UDD@A@@ATP@APU@A@@DATPQTAT@@TUEUTPQTAT@@TD@DA@@@@@D@T@UA@@T@@@@UTE@@@UUTE@@@UUAT@TU@UPUUTUQPEEUAAA@@P@AAD@@P@ATATT@@PTTAADT@@DQAA@@D@A@@@@TTQPU@UPUAPQUATPTQUPQEQ@UPAAAD@AAAD@T@AATTTADTA@ADA@DAD@@A@TAAD@@A@TPQU@PDPTPTUTQQTU@PPUPPUUUEAEQQQE@AAA@AA@PAAATDP@@AAD@PUPTT@PDP@PT@@TP@TUUUUUTUUP@ -------------------------------------------------------------------------------- /script/common.py: -------------------------------------------------------------------------------- 1 | import re 2 | def get_code_char_pattern(): 3 | return re.compile(r'([0-9A-Fa-f]{2,4})=(.)') 4 | 5 | def read_encoding_table(filename): 6 | encoding_table = {} 7 | with open(filename, 'r', encoding='utf-8') as file: 8 | pattern = get_code_char_pattern() 9 | for line in file: 10 | if not line.strip(): 11 | continue 12 | match = pattern.match(line) 13 | if match: 14 | code = int(match.group(1), 16) 15 | character = match.group(2) 16 | if code not in encoding_table: 17 | encoding_table[code] = character 18 | else: 19 | print(f'Duplicate code: {code:#04x} for character {character}') 20 | else: 21 | print(f'Invalid line: {line.strip()}') 22 | return encoding_table 23 | 24 | def read_encoding_table_reverse(filename): 25 | encoding_table = {} 26 | with open(filename, 'r', encoding='utf-8') as file: 27 | pattern = get_code_char_pattern() 28 | for line in file: 29 | if not line.strip(): 30 | continue 31 | match = pattern.match(line) 32 | if match: 33 | code = int(match.group(1), 16) 34 | character = match.group(2) 35 | if character not in encoding_table: 36 | encoding_table[character] = code 37 | else: 38 | print(f'Duplicate character: {character} for code {code:#04x}') 39 | else: 40 | print(f'Invalid line: {line.strip()}') 41 | return encoding_table -------------------------------------------------------------------------------- /script/create_font.py: -------------------------------------------------------------------------------- 1 | import freetype 2 | import sys 3 | import struct 4 | from common import read_encoding_table 5 | 6 | def create_glyphs(font_path, size, code_table): 7 | face = freetype.Face(font_path) 8 | face.set_char_size(size * 64) 9 | 10 | characters = [] 11 | for custom_code, character in code_table.items(): 12 | face.load_char(character) 13 | 14 | bitmap = face.glyph.bitmap 15 | width = bitmap.width 16 | rows = bitmap.rows 17 | pitch = bitmap.pitch 18 | buffer = bytes(bitmap.buffer) 19 | output_buffer = bytearray(12 * 12 // 8) 20 | 21 | # 需要将1bpp的字符buffer转换为16x12的1bpp的buffer 22 | # 如果不为1bpp,跳过 23 | if face.glyph.bitmap.pixel_mode != freetype.FT_PIXEL_MODE_MONO: 24 | print(f"Code {custom_code:04x} Character {character} is not 1bpp, skipping") 25 | continue 26 | else: 27 | for row in range(rows): 28 | line = buffer[row * pitch: (row + 1) * pitch] 29 | if pitch == 1: 30 | line += b'\x00' 31 | # 翻转字节的bit序 32 | # 例如:0b10000000 -> 0b00000001 33 | new_line_bytes = bytearray() 34 | for byte in line: 35 | reversed_byte = 0 36 | for i in range(8): 37 | reversed_byte |= ((byte >> i) & 1) << (7 - i) 38 | new_line_bytes.append(reversed_byte) 39 | if row % 2 == 0: 40 | output_buffer[row // 2 * 3] = new_line_bytes[0] 41 | output_buffer[row // 2 * 3 + 1] = new_line_bytes[1] & 0x0F 42 | else: 43 | output_buffer[row // 2 * 3 + 1] = output_buffer[row // 2 * 3 + 1] | ((new_line_bytes[0] << 4) & 0xF0) 44 | output_buffer[row // 2 * 3 + 2] = ((new_line_bytes[0] & 0xF0) >> 4) | ((new_line_bytes[1] & 0x0F) << 4) 45 | 46 | character_info = { 47 | 'code': custom_code, 48 | 'width': width, 49 | 'flag': 0, 50 | 'bitmap_data': bytes(output_buffer) 51 | } 52 | 53 | characters.append(character_info) 54 | return characters 55 | 56 | def main(code_table_path, font_path): 57 | output_file = 'font.bin' 58 | font_size = 12 59 | 60 | code_table = read_encoding_table(code_table_path) 61 | characters = create_glyphs(font_path, font_size, code_table) 62 | 63 | unique_characters = {} 64 | for char in reversed(characters): 65 | unique_characters[char['code']] = char 66 | characters = list(unique_characters.values()) 67 | characters = sorted(characters, key=lambda x: x['code']) 68 | 69 | # 写入文件,先写入len(characters),再写入每个character的信息 70 | with open(output_file, 'wb') as f: 71 | f.write(struct.pack('I', len(characters))) 72 | for character in characters: 73 | f.write(struct.pack(' ") 78 | sys.exit(1) 79 | code_table_path = sys.argv[1] 80 | font_path = sys.argv[2] 81 | main(code_table_path, font_path) -------------------------------------------------------------------------------- /script/create_keycode_conv_table.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import sys 4 | from common import read_encoding_table_reverse 5 | 6 | def parse_keycodes(file_path): 7 | keycode_dict = {} 8 | pattern = re.compile(r'KEYCODE_\w+\s*=\s*(0x[0-9A-Fa-f]+|\d+)\s*,') 9 | 10 | with open(file_path, 'r', encoding='utf-8') as file: 11 | for line in file: 12 | match = pattern.search(line) 13 | if match: 14 | keycode_str = match.group(0) 15 | keycode_name = keycode_str.split('=')[0].strip() 16 | keycode_value = match.group(1).strip() 17 | 18 | # 识别数字格式 19 | if keycode_value.startswith('0x'): 20 | keycode_value = int(keycode_value, 16) 21 | else: 22 | keycode_value = int(keycode_value) 23 | 24 | keycode_dict[keycode_value] = keycode_name 25 | 26 | return keycode_dict 27 | 28 | def filter_keycodes(keycode_dict): 29 | filtered_dict = {} 30 | for keycode_value, keycode_name in keycode_dict.items(): 31 | # 跳过半角和全角的数字、字母 32 | if not (0x30 <= keycode_value <= 0x39 or 0xFF10 <= keycode_value <= 0xFF19 or 33 | 0x41 <= keycode_value <= 0x5A or 0xFF21 <= keycode_value <= 0xFF3A or 34 | 0x61 <= keycode_value <= 0x7A or 0xFF41 <= keycode_value <= 0xFF5A or 35 | 0x00 <= keycode_value <= 0x1F): 36 | filtered_dict[keycode_name] = keycode_value 37 | return filtered_dict 38 | 39 | def main(): 40 | # 获取脚本参数 41 | if len(sys.argv) != 2: 42 | print("Usage: python create_keycode_conv_table.py ") 43 | sys.exit(1) 44 | 45 | char_table_path = sys.argv[1] 46 | 47 | # 计算keyboard.h的路径 48 | script_dir = os.path.dirname(os.path.abspath(__file__)) 49 | keyboard_h_path = os.path.join(script_dir, '../overlay/include/keyboard.h') 50 | keycode_dict = parse_keycodes(keyboard_h_path) 51 | filtered_keycode_dict = filter_keycodes(keycode_dict) 52 | 53 | encoding_table = read_encoding_table_reverse(char_table_path) 54 | 55 | keycode_to_custom_code = {} 56 | for keycode_name, keycode_value in filtered_keycode_dict.items(): 57 | keycode_value_unicode = chr(keycode_value) 58 | if keycode_value_unicode in encoding_table: 59 | keycode_to_custom_code[keycode_name] = encoding_table[keycode_value_unicode] 60 | else: 61 | print(f"Keycode {keycode_name} ('{keycode_value_unicode}') not found in encoding table") 62 | 63 | sorted_keycode_to_custom_code = dict(sorted(keycode_to_custom_code.items(), key=lambda item: filtered_keycode_dict[item[0]])) 64 | 65 | # 输出C数组 66 | print("const KeycodeConvItem gKeycodeConvTable[] = {") 67 | for keycode_name, custom_code in sorted_keycode_to_custom_code.items(): 68 | print(f' {{{keycode_name}, 0x{custom_code:04X}}},') 69 | print("};") 70 | 71 | if __name__ == "__main__": 72 | main() -------------------------------------------------------------------------------- /script/create_pinyin_table.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import sys 4 | from common import read_encoding_table_reverse 5 | 6 | def read_pinyin_table(filename): 7 | pinyin_table = {} 8 | with open(filename, 'r', encoding='utf-16') as file: 9 | for line in file: 10 | line = line.strip() 11 | if not line: 12 | continue 13 | parts = line.split(None, 1) 14 | if len(parts) != 2: 15 | continue 16 | pinyin, characters = parts 17 | characters = characters.replace(' ', '') 18 | pinyin_table[pinyin] = characters 19 | return pinyin_table 20 | 21 | def filter_pinyin_table(pinyin_table, encoding_table): 22 | filtered_pinyin_table = {} 23 | for pinyin, characters in pinyin_table.items(): 24 | filtered_chars = ''.join([char for char in characters if char in encoding_table]) 25 | if filtered_chars: 26 | filtered_pinyin_table[pinyin] = filtered_chars 27 | return filtered_pinyin_table 28 | 29 | def encode_pinyin(pinyin): 30 | # 将字母 'a'-'z' 映射为数字 1-26 31 | letter_map = {chr(i + ord('a') - 1): i for i in range(1, 27)} 32 | pinyin = pinyin.lower() 33 | code = 0 34 | for i in range(min(len(pinyin), 6)): 35 | c = pinyin[i] 36 | value = letter_map.get(c, 0) 37 | if value == 0: 38 | raise ValueError(f"拼音 '{pinyin}' 中包含无效字符 '{c}'") 39 | code |= (value & 0x1F) << (25 - 5 * i) 40 | return code 41 | 42 | def create_binary_pinyin_table(filtered_pinyin_table, output_filename, encoding_table): 43 | # 准备拼音索引表和汉字数据 44 | pinyin_entries = [] 45 | all_codes = [] 46 | offset = 0 # 第三部分的偏移量,以编码数量为单位 47 | max_num_chars = 0 # 用于记录单个拼音的最大汉字数量 48 | for pinyin, characters in filtered_pinyin_table.items(): 49 | pinyin_code = encode_pinyin(pinyin) 50 | num_chars = len(characters) 51 | if num_chars > max_num_chars: 52 | max_num_chars = num_chars 53 | entry = { 54 | 'pinyin_code': pinyin_code, 55 | 'offset': offset, 56 | 'num_chars': num_chars 57 | } 58 | pinyin_entries.append(entry) 59 | # 将汉字转换为编码 60 | for char in characters: 61 | code = encoding_table.get(char) 62 | all_codes.append(code) 63 | offset += num_chars 64 | 65 | # 排序拼音索引表 66 | pinyin_entries.sort(key=lambda x: x['pinyin_code']) 67 | 68 | # 计算第三部分的地址,并对齐到0x10 69 | num_entries = len(pinyin_entries) 70 | header_size = 0x10 # 第一部分的大小改为0x10 71 | index_entry_size = 4 + 2 + 2 # int32 + int16 + int16 72 | index_table_size = num_entries * index_entry_size 73 | third_part_offset = header_size + index_table_size 74 | if third_part_offset % 0x10 != 0: 75 | third_part_offset = (third_part_offset + 0x10) & ~0xF # 对齐到0x10 76 | 77 | # 写入二进制文件 78 | with open(output_filename, 'wb') as f: 79 | # 写入头部 80 | f.write(struct.pack('") 110 | sys.exit(1) 111 | 112 | encoding_file = sys.argv[1] 113 | 114 | main(encoding_file) -------------------------------------------------------------------------------- /script/ndspy_hotfix.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import ndspy.rom 3 | import ndspy.codeCompression as codeCompression 4 | from ndspy.code import MainCodeFile 5 | 6 | class NdspyHotfix: 7 | original_code_init = MainCodeFile.__init__ 8 | original_code_save = MainCodeFile.save 9 | original_section_init = MainCodeFile.Section.__init__ 10 | 11 | @staticmethod 12 | def new_code_init(self, data: bytes, ramAddress: int, codeSettingsPointerAddress: int | None = None): 13 | self.sections = [] 14 | self.ramAddress = ramAddress 15 | self.is_twl = False 16 | 17 | data = codeCompression.decompress(data) 18 | 19 | self.codeSettingsOffs = None 20 | if codeSettingsPointerAddress: 21 | # (codeSettingsPointerAddress might be None if it's not 22 | # available, or 0 if the ROM has it set to 0) 23 | try: 24 | codeSettingsAddr, = struct.unpack_from( 25 | '= 5: 42 | for i in range(0, 0x8000, 4): 43 | if data[i:i+8] == b'\x63\x14\xC0\xDE\xDE\xC0\x14\x63': 44 | self.is_twl = True 45 | break 46 | copyTableBegin -= ramAddress 47 | copyTableEnd -= ramAddress 48 | dataBegin -= ramAddress 49 | else: 50 | copyTableBegin = copyTableEnd = 0 51 | dataBegin = len(data) 52 | 53 | def makeSection( 54 | ramAddr: int, 55 | ramLen: int, 56 | fileOffs: int, 57 | initFuncTable: int | None, 58 | bssSize: int, 59 | implicit: bool = False, 60 | ) -> None: 61 | sdata = data[fileOffs : fileOffs + ramLen] 62 | self.sections.append(self.Section(sdata, 63 | ramAddr, 64 | bssSize, 65 | implicit=implicit, 66 | initFuncTable=initFuncTable)) 67 | 68 | makeSection(ramAddress, dataBegin, 0, 0, 0, implicit=True) 69 | 70 | copyTablePos = copyTableBegin 71 | while copyTablePos < copyTableEnd: 72 | initFuncTable = None 73 | if self.is_twl: 74 | secRamAddr, secSize, initFuncTable, bssSize = \ 75 | struct.unpack_from('<4I', data, copyTablePos) 76 | copyTablePos += 16 77 | else: 78 | secRamAddr, secSize, bssSize = \ 79 | struct.unpack_from('<3I', data, copyTablePos) 80 | copyTablePos += 12 81 | 82 | makeSection(secRamAddr, secSize, dataBegin, initFuncTable, bssSize) 83 | 84 | dataBegin += secSize 85 | 86 | @staticmethod 87 | def new_code_save(self, *, compress: bool = False) -> bytes: 88 | """ 89 | Generate a bytes object representing this code file. 90 | """ 91 | data = bytearray() 92 | 93 | for s in self.sections: 94 | data.extend(s.data) 95 | 96 | # Align to 0x04 97 | while len(data) % 4: 98 | data.append(0) 99 | 100 | # These loops are NOT identical! 101 | # The first one only operates on sections with length != 0, 102 | # and the second operates on sections with length == 0! 103 | 104 | sectionTable = bytearray() 105 | 106 | for s in self.sections: 107 | if s.implicit: continue 108 | if len(s.data) == 0: continue 109 | if hasattr(self, 'is_twl') and self.is_twl: 110 | sectionTable.extend( 111 | struct.pack('<4I', s.ramAddress, len(s.data), getattr(s, 'initFuncTable', 0), s.bssSize)) 112 | else: 113 | sectionTable.extend( 114 | struct.pack('<3I', s.ramAddress, len(s.data), s.bssSize)) 115 | 116 | 117 | for s in self.sections: 118 | if s.implicit: continue 119 | if len(s.data) != 0: continue 120 | if hasattr(self, 'is_twl') and self.is_twl: 121 | sectionTable.extend( 122 | struct.pack('<4I', s.ramAddress, len(s.data), getattr(s, 'initFuncTable', 0), s.bssSize)) 123 | else: 124 | sectionTable.extend( 125 | struct.pack('<3I', s.ramAddress, len(s.data), s.bssSize)) 126 | 127 | 128 | sectionTableOffset = len(data) 129 | data.extend(sectionTable) 130 | 131 | def setInt(addr: int, val: int) -> None: 132 | struct.pack_into(' overlay_ram_size: 74 | print(f"Warning: Overlay data size ({len(overlay_data)}) exceeds RAM size ({overlay_ram_size}), truncating...") 75 | overlay_data = overlay_data[:overlay_ram_size] 76 | 77 | ram_buffer = bytearray(overlay_ram_size) 78 | ram_buffer[:len(overlay_data)] = overlay_data 79 | 80 | self.codefile.sections.append(ndspy.code.MainCodeFile.Section( 81 | data=bytes(ram_buffer), 82 | ramAddress=overlay_addr, 83 | bssSize=overlay_bss_size 84 | )) 85 | 86 | 87 | def modify_overlay_init_functions(self, overlay_id, overlay_ldr_elf_path): 88 | overlay_ldr_static_init_func = None 89 | with open(overlay_ldr_elf_path, 'rb') as f: 90 | overlay_ldr_elf = ELFFile(f) 91 | symbol_table = overlay_ldr_elf.get_section_by_name('.symtab') 92 | if symbol_table: 93 | for symbol in symbol_table.iter_symbols(): 94 | if symbol.name == 'OverlayStaticInitFunc': 95 | overlay_ldr_static_init_func = symbol['st_value'] 96 | 97 | if overlay_ldr_static_init_func is None: 98 | raise ValueError("Required symbol OverlayStaticInitFunc not found") 99 | 100 | self.overlay_table[overlay_id].staticInitStart = overlay_ldr_static_init_func 101 | self.overlay_table[overlay_id].staticInitEnd = overlay_ldr_static_init_func + 4 102 | 103 | def patch_word(self, offset, value): 104 | for section in self.codefile.sections: 105 | if section.ramAddress <= offset < section.ramAddress + len(section.data): 106 | offset = offset - section.ramAddress 107 | struct.pack_into('