diff --git a/CMakeLists.txt b/CMakeLists.txt index 4327fd02..dda63008 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,3 +129,58 @@ if(DETHRACE_INSTALL) include(CPack) endif() + +if (NINTENDO_3DS) + set(APP_TITLE "${PROJECT_NAME}") + set(APP_DESCRIPTION "${PROJECT_NAME} port for 3DS") + set(APP_AUTHOR "dethrace-labs") + set(APP_UNIQUE_ID "0xF02F6") + set(APP_ICON "${PROJECT_SOURCE_DIR}/packaging/ctr/icon.png") + set(APP_BANNER "${PROJECT_SOURCE_DIR}/packaging/ctr/banner.png") + set(APP_AUDIO "${PROJECT_SOURCE_DIR}/packaging/ctr/audio_silent.wav") + set(APP_ROMFS_DIR "${PROJECT_SOURCE_DIR}/packaging/ctr/romfs") + set(APP_RSF "${PROJECT_SOURCE_DIR}/packaging/ctr/template.rsf") + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.3dsx + COMMAND 3dsxtool ${PROJECT_NAME}.elf ${PROJECT_NAME}${ROM}.3dsx + --romfs=${APP_ROMFS_DIR} + --smdh=${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.smdh + DEPENDS ${PROJECT_NAME} ${PROJECT_NAME}.smdh + ) + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.cia + COMMAND makerom -f cia + -target t + -exefslogo + -o ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.cia + -elf ${PROJECT_NAME}.elf + -rsf ${APP_RSF} + -DAPP_TITLE=${APP_TITLE} + -DAPP_UNIQUE_ID=${APP_UNIQUE_ID} + -DAPP_ROMFS_DIR=${APP_ROMFS_DIR} + -banner ${PROJECT_NAME}.bnr + -icon ${PROJECT_NAME}.smdh + DEPENDS ${PROJECT_NAME} ${APP_RSF} ${PROJECT_NAME}.smdh ${PROJECT_NAME}.bnr + ) + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.smdh + COMMAND bannertool makesmdh -o ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.smdh + -s ${APP_TITLE} + -l ${APP_DESCRIPTION} + -p ${APP_AUTHOR} + -i ${APP_ICON} + + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.bnr + COMMAND bannertool makebanner -o ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.bnr + -i ${APP_BANNER} + -a ${APP_AUDIO} + ) + + add_custom_target( N3DS ALL + DEPENDS ${PROJECT_NAME}.3dsx + ${PROJECT_NAME}.cia + ) +endif () diff --git a/lib/miniaudio/include/miniaudio/miniaudio.h b/lib/miniaudio/include/miniaudio/miniaudio.h index 47332e11..6b0b45e7 100644 --- a/lib/miniaudio/include/miniaudio/miniaudio.h +++ b/lib/miniaudio/include/miniaudio/miniaudio.h @@ -16116,9 +16116,11 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority #endif } else if (priority == ma_thread_priority_realtime) { #ifdef SCHED_FIFO +#ifndef __3DS__ if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO) == 0) { scheduler = SCHED_FIFO; } +#endif #endif #ifdef MA_LINUX } else { diff --git a/packaging/ctr/audio_silent.wav b/packaging/ctr/audio_silent.wav new file mode 100644 index 00000000..e0b684b6 Binary files /dev/null and b/packaging/ctr/audio_silent.wav differ diff --git a/packaging/ctr/banner.png b/packaging/ctr/banner.png new file mode 100644 index 00000000..c048f9ca Binary files /dev/null and b/packaging/ctr/banner.png differ diff --git a/packaging/ctr/icon.png b/packaging/ctr/icon.png new file mode 100644 index 00000000..f9d9dd11 Binary files /dev/null and b/packaging/ctr/icon.png differ diff --git a/packaging/ctr/romfs/.gitignore b/packaging/ctr/romfs/.gitignore new file mode 100644 index 00000000..86d0cb27 --- /dev/null +++ b/packaging/ctr/romfs/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/packaging/ctr/template.rsf b/packaging/ctr/template.rsf new file mode 100644 index 00000000..a77c7853 --- /dev/null +++ b/packaging/ctr/template.rsf @@ -0,0 +1,219 @@ +BasicInfo: + Title : $(APP_TITLE) + ProductCode : $(APP_PRODUCT_CODE) + Logo : Nintendo # Nintendo / Licensed / Distributed / iQue / iQueForSystem + +RomFs: + # Specifies the root path of the read only file system to include in the ROM. + RootPath : $(APP_ROMFS) + +TitleInfo: + Category : Application + UniqueId : $(APP_UNIQUE_ID) + +Option: + UseOnSD : true # true if App is to be installed to SD + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : false # Compresses where applicable (currently only exefs:/.code) + +AccessControlInfo: + CoreVersion : 2 + + # Exheader Format Version + DescVersion : 2 + + # Minimum Required Kernel Version (below is for 4.5.0) + ReleaseKernelMajor : "02" + ReleaseKernelMinor : "33" + + # ExtData + UseExtSaveData : false # enables ExtData + #ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId + + # FS:USER Archive Access Permissions + # Uncomment as required + FileSystemAccess: + #- CategorySystemApplication + #- CategoryHardwareCheck + - CategoryFileSystemTool + #- Debug + #- TwlCardBackup + #- TwlNandData + #- Boss + - DirectSdmc + #- Core + #- CtrNandRo + #- CtrNandRw + #- CtrNandRoWrite + #- CategorySystemSettings + #- CardBoard + #- ExportImportIvs + #- DirectSdmcWrite + #- SwitchCleanup + #- SaveDataMove + #- Shop + #- Shell + #- CategoryHomeMenu + + # Process Settings + MemoryType : Application # Application/System/Base + SystemMode : 64MB # 64MB(Default)/96MB/80MB/72MB/32MB + IdealProcessor : 0 + AffinityMask : 1 + Priority : 16 + MaxCpu : 0x9E # Default + HandleTableSize : 0x200 + DisableDebug : false + EnableForceDebug : false + CanWriteSharedPage : true + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : true + PermitMainFunctionArgument : true + CanShareDeviceMemory : true + RunnableOnSleep : false + SpecialMemoryArrange : true + + # New3DS Exclusive Process Settings + SystemModeExt : $(APP_SYSTEM_MODE_EXT) # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode + CpuSpeed : 804MHz # 256MHz(Default)/804MHz + EnableL2Cache : true # false(default)/true + CanAccessCore2 : true + + # Virtual Address Mappings + IORegisterMapping: + - 1ff00000-1ff7ffff # DSP memory + MemoryMapping: + - 1f000000-1f5fffff:r # VRAM + + # Accessible SVCs, : + SystemCallAccess: + ArbitrateAddress: 34 + Backdoor: 123 + Break: 60 + CancelTimer: 28 + ClearEvent: 25 + ClearTimer: 29 + CloseHandle: 35 + ConnectToPort: 45 + ControlMemory: 1 + ControlProcessMemory: 112 + CreateAddressArbiter: 33 + CreateEvent: 23 + CreateMemoryBlock: 30 + CreateMutex: 19 + CreateSemaphore: 21 + CreateThread: 8 + CreateTimer: 26 + DuplicateHandle: 39 + ExitProcess: 3 + ExitThread: 9 + GetCurrentProcessorNumber: 17 + GetHandleInfo: 41 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetProcessIdealProcessor: 6 + GetProcessInfo: 43 + GetResourceLimit: 56 + GetResourceLimitCurrentValues: 58 + GetResourceLimitLimitValues: 57 + GetSystemInfo: 42 + GetSystemTick: 40 + GetThreadContext: 59 + GetThreadId: 55 + GetThreadIdealProcessor: 15 + GetThreadInfo: 44 + GetThreadPriority: 11 + MapMemoryBlock: 31 + OutputDebugString: 61 + QueryMemory: 2 + ReleaseMutex: 20 + ReleaseSemaphore: 22 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + SetThreadPriority: 12 + SetTimer: 27 + SignalEvent: 24 + SleepThread: 10 + UnmapMemoryBlock: 32 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + + # Service List + # Maximum 34 services (32 if firmware is prior to 9.6.0) + ServiceAccessControl: + - APT:U + - ac:u + - am:net + - boss:U + - cam:u + - cecd:u + - cfg:nor + - cfg:u + - csnd:SND + - dsp::DSP + - frd:u + - fs:USER + - gsp::Gpu + - hid:USER + - http:C + - ir:rst + - ir:u + - ir:USER + - mic:u + - ndm:u + - news:u + - nwm::UDS + - ptm:u + - pxi:dev + - soc:U + - ssl:C + - y2r:u + + +SystemControlInfo: + SaveDataSize: 0KB # Change if the app uses savedata + RemasterVersion: 2 + StackSize: 0x40000 + + # Modules that run services listed above should be included below + # Maximum 48 dependencies + # : + Dependency: + ac: 0x0004013000002402 + act: 0x0004013000003802 + am: 0x0004013000001502 + boss: 0x0004013000003402 + camera: 0x0004013000001602 + cecd: 0x0004013000002602 + cfg: 0x0004013000001702 + codec: 0x0004013000001802 + csnd: 0x0004013000002702 + dlp: 0x0004013000002802 + dsp: 0x0004013000001a02 + friends: 0x0004013000003202 + gpio: 0x0004013000001b02 + gsp: 0x0004013000001c02 + hid: 0x0004013000001d02 + http: 0x0004013000002902 + i2c: 0x0004013000001e02 + ir: 0x0004013000003302 + mcu: 0x0004013000001f02 + mic: 0x0004013000002002 + ndm: 0x0004013000002b02 + news: 0x0004013000003502 + nfc: 0x0004013000004002 + nim: 0x0004013000002c02 + nwm: 0x0004013000002d02 + pdn: 0x0004013000002102 + ps: 0x0004013000003102 + ptm: 0x0004013000002202 + qtm: 0x0004013020004202 + ro: 0x0004013000003702 + socket: 0x0004013000002e02 + spi: 0x0004013000002302 + ssl: 0x0004013000002f02 diff --git a/src/harness/CMakeLists.txt b/src/harness/CMakeLists.txt index dc3ec120..a75b2d1d 100644 --- a/src/harness/CMakeLists.txt +++ b/src/harness/CMakeLists.txt @@ -81,6 +81,10 @@ elseif(APPLE) target_sources(harness PRIVATE os/macos.c ) +elseif(NINTENDO_3DS) + target_sources(harness PRIVATE + os/ctr.c + ) else() target_sources(harness PRIVATE os/linux.c diff --git a/src/harness/audio/miniaudio.c b/src/harness/audio/miniaudio.c index 054cbd7d..e27d08e6 100644 --- a/src/harness/audio/miniaudio.c +++ b/src/harness/audio/miniaudio.c @@ -6,6 +6,11 @@ #include "harness/os.h" #include "harness/trace.h" +#ifdef __3DS__ +#define MA_NO_PTHREAD_IN_HEADER +#define MA_NO_RUNTIME_LINKING +#endif + // Must come before miniaudio.h #define STB_VORBIS_HEADER_ONLY #include "stb/stb_vorbis.c" diff --git a/src/harness/harness.c b/src/harness/harness.c index 6ae60621..e99a1d90 100644 --- a/src/harness/harness.c +++ b/src/harness/harness.c @@ -11,6 +11,10 @@ #include #include +#ifdef __3DS__ +#include <3ds.h> +#endif + br_pixelmap* palette; uint32_t* screen_buffer; @@ -166,15 +170,25 @@ void Harness_Init(int* argc, char* argv[]) { Harness_ProcessCommandLine(argc, argv); +#ifdef __3DS__ + osSetSpeedupEnable(true); + gfxInitDefault(); + consoleInit(GFX_BOTTOM, NULL); + chdir("sdmc:/CARMA/"); + char* root_dir = NULL; +#else if (harness_game_config.install_signalhandler) { OS_InstallSignalHandler(argv[0]); } char* root_dir = getenv("DETHRACE_ROOT_DIR"); +#endif if (root_dir != NULL) { LOG_INFO("DETHRACE_ROOT_DIR is set to '%s'", root_dir); } else { +#ifndef __3DS__ root_dir = OS_GetWorkingDirectory(argv[0]); +#endif } // if root_dir is null or empty, no need to chdir if (root_dir != NULL && root_dir[0] != '\0') { diff --git a/src/harness/os/ctr.c b/src/harness/os/ctr.c new file mode 100644 index 00000000..8fed5c68 --- /dev/null +++ b/src/harness/os/ctr.c @@ -0,0 +1,27 @@ +#define _GNU_SOURCE +#include +#include + +void resolve_full_path(char* path, const char* argv0) { + return; +} + +FILE* OS_fopen(const char* pathname, const char* mode) { + FILE* f = fopen(pathname, mode); + if (f != NULL) { + return f; + } + return NULL; +} + +size_t OS_ConsoleReadPassword(char* pBuffer, size_t pBufferLen) { + return 0; +} + +char* OS_Basename(const char* path) { + return ""; +} + +char* OS_GetWorkingDirectory(char* argv0) { + return ""; +} diff --git a/src/harness/platforms/sdl2.c b/src/harness/platforms/sdl2.c index 6ba8ad8c..793bcbda 100644 --- a/src/harness/platforms/sdl2.c +++ b/src/harness/platforms/sdl2.c @@ -6,6 +6,10 @@ #include "harness/trace.h" #include "sdl2_scancode_to_dinput.h" +#ifdef __3DS__ +#include <3ds.h> +#endif + SDL_Window* window; SDL_Renderer* renderer; SDL_Texture* screen_texture; @@ -17,6 +21,12 @@ Uint32 last_frame_time; uint8_t directinput_key_state[SDL_NUM_SCANCODES]; +#ifdef __3DS__ +extern int sdlScanCodeToDirectInputKeyNum[SDL_NUM_SCANCODES]; +float pos_x, pos_y; +bool button_1, button_2; +#endif + static void* create_window_and_renderer(char* title, int x, int y, int width, int height) { render_width = width; render_height = height; @@ -24,7 +34,9 @@ static void* create_window_and_renderer(char* title, int x, int y, int width, in if (SDL_Init(SDL_INIT_VIDEO) != 0) { LOG_PANIC("SDL_INIT_VIDEO error: %s", SDL_GetError()); } - +#ifdef __3DS__ + consoleInit(GFX_BOTTOM, NULL); +#endif window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, @@ -61,10 +73,12 @@ static void* create_window_and_renderer(char* title, int x, int y, int width, in static int set_window_pos(void* hWnd, int x, int y, int nWidth, int nHeight) { // SDL_SetWindowPosition(hWnd, x, y); +#ifndef __3DS__ if (nWidth == 320 && nHeight == 200) { nWidth = 640; nHeight = 400; } +#endif SDL_SetWindowSize(hWnd, nWidth, nHeight); return 0; } @@ -76,6 +90,97 @@ static void destroy_window(void* hWnd) { window = NULL; } +#ifdef __3DS__ +static int map_3ds_key_to_sdl_scancode(u32 key) { + switch (key) { + case KEY_A: return SDL_SCANCODE_SPACE; + case KEY_B: return SDL_SCANCODE_B; + case KEY_X: return SDL_SCANCODE_X; + case KEY_Y: return SDL_SCANCODE_Y; + case KEY_L: return SDL_SCANCODE_L; +// case KEY_R: return SDL_SCANCODE_R; // used for mouse button instead + case KEY_ZL: return SDL_SCANCODE_MINUS; + case KEY_ZR: return SDL_SCANCODE_EQUALS; + case KEY_START: return SDL_SCANCODE_RETURN; + case KEY_SELECT: return SDL_SCANCODE_ESCAPE; + case KEY_DUP: return SDL_SCANCODE_KP_8; + case KEY_DDOWN: return SDL_SCANCODE_KP_2; + case KEY_DLEFT: return SDL_SCANCODE_KP_4; + case KEY_DRIGHT: return SDL_SCANCODE_KP_6; + + default: return -1; + } +} + +static int get_and_handle_message(MSG_* msg) { + hidScanInput(); + u32 kHeld = hidKeysHeld(); + int dinput_key; + + circlePosition circlePad; + hidCircleRead(&circlePad); + + for (u32 key = KEY_A; key <= KEY_ZR; key <<= 1) { + if (kHeld & key) { + int sdl_scancode = map_3ds_key_to_sdl_scancode(key); + if (sdl_scancode >= 0) { + dinput_key = sdlScanCodeToDirectInputKeyNum[sdl_scancode]; + if (dinput_key == 0) { + LOG_WARN("unexpected scan code %s (%d)", SDL_GetScancodeName(sdl_scancode), sdl_scancode); + continue; + } + directinput_key_state[dinput_key] = 0x80; + } + } else { + int sdl_scancode = map_3ds_key_to_sdl_scancode(key); + if (sdl_scancode >= 0) { + dinput_key = sdlScanCodeToDirectInputKeyNum[sdl_scancode]; + if (dinput_key != 0) { + directinput_key_state[dinput_key] = 0x00; + } + } + } + } + + if (circlePad.dy > 20) { + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_UP]; + directinput_key_state[dinput_key] = 0x80; + } else if (circlePad.dy < -20) { + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_DOWN]; + directinput_key_state[dinput_key] = 0x80; + } else { + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_UP]; + directinput_key_state[dinput_key] = 0x00; + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_DOWN]; + directinput_key_state[dinput_key] = 0x00; + } + + if (circlePad.dx > 20) { + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_RIGHT]; + directinput_key_state[dinput_key] = 0x80; + } else if (circlePad.dx < -20) { + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_LEFT]; + directinput_key_state[dinput_key] = 0x80; + } else { + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_RIGHT]; + directinput_key_state[dinput_key] = 0x00; + dinput_key = sdlScanCodeToDirectInputKeyNum[SDL_SCANCODE_LEFT]; + directinput_key_state[dinput_key] = 0x00; + } + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + msg->message = WM_QUIT; + return 1; + } + } + + button_1 = (kHeld & KEY_R); + + return 0; +} +#else // Checks whether the `flag_check` is the only modifier applied. // e.g. is_only_modifier(event.key.keysym.mod, KMOD_ALT) returns true when only the ALT key was pressed static int is_only_key_modifier(int modifier_flags, int flag_check) { @@ -122,21 +227,41 @@ static int get_and_handle_message(MSG_* msg) { } return 0; } +#endif static void get_keyboard_state(unsigned int count, uint8_t* buffer) { memcpy(buffer, directinput_key_state, count); } static int get_mouse_buttons(int* pButton1, int* pButton2) { +#ifdef __3DS__ + *pButton1 = button_1; + *pButton2 = false; +#else int state = SDL_GetMouseState(NULL, NULL); *pButton1 = state & SDL_BUTTON_LMASK; *pButton2 = state & SDL_BUTTON_RMASK; +#endif return 0; } static int get_mouse_position(int* pX, int* pY) { float lX, lY; +#ifdef __3DS__ + touchPosition touch; + hidTouchRead(&touch); +if ((touch.px > 0) && (touch.py > 0)) +{ + pos_x = touch.px; + pos_y = touch.py; +} + + *pX = pos_x; + *pY = pos_y; + +#else SDL_GetMouseState(pX, pY); +#endif SDL_RenderWindowToLogical(renderer, *pX, *pY, &lX, &lY); #if defined(DETHRACE_FIX_BUGS) @@ -202,7 +327,17 @@ static void set_palette(PALETTEENTRY_* pal) { int show_error_message(void* window, char* text, char* caption) { fprintf(stderr, "%s", text); +#ifdef __3DS__ + if (!gspHasGpuRight()) + gfxInitDefault(); + + errorConf msg; + errorInit(&msg, ERROR_TEXT, CFG_LANGUAGE_EN); + errorText(&msg, text); + errorDisp(&msg); +#else SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption, text, window); +#endif return 0; }