diff --git a/.gitmodules b/.gitmodules index b284ab66..21b31fb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "lib/BRender-v1.3.2"] path = lib/BRender-v1.3.2 - url = https://github.com/dethrace-labs/BRender-v1.3.2.git + url = https://github.com/MrHuu/BRender-v1.3.2.git + branch = 3DS \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 762e138f..c9101a03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,3 +133,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/BRender-v1.3.2 b/lib/BRender-v1.3.2 index 9c340863..16cd96a8 160000 --- a/lib/BRender-v1.3.2 +++ b/lib/BRender-v1.3.2 @@ -1 +1 @@ -Subproject commit 9c34086300f4f0bbb3a55206380f25b17dad6c12 +Subproject commit 16cd96a8785ee11877fdf36cb507dad2bb626c96 diff --git a/lib/miniaudio/include/miniaudio/miniaudio.h b/lib/miniaudio/include/miniaudio/miniaudio.h index 47332e11..6362490c 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 { @@ -16131,7 +16133,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority if (stackSize > 0) { pthread_attr_setstacksize(&attr, stackSize); } - +#ifndef __3DS__ if (scheduler != -1) { int priorityMin = sched_get_priority_min(scheduler); int priorityMax = sched_get_priority_max(scheduler); @@ -16157,6 +16159,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority pthread_attr_setschedparam(&attr, &sched); } } +#endif } #else /* It's the emscripten build. We'll have a few unused parameters. */ @@ -74085,6 +74088,10 @@ static ma_uint64 ma_engine_node_get_required_input_frame_count(const ma_engine_n static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float volume) { +#ifdef __3DS__ + // Without this hack, some sounds overflow causing horrible crackling + volume /= 2.0f; +#endif if (pEngineNode == NULL) { return MA_INVALID_ARGS; } 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..2d09b644 --- /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 : 80MB # 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 : 124MB # 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/DETHRACE/CMakeLists.txt b/src/DETHRACE/CMakeLists.txt index f44e5bd7..78d79093 100644 --- a/src/DETHRACE/CMakeLists.txt +++ b/src/DETHRACE/CMakeLists.txt @@ -36,6 +36,13 @@ else() -Wstrict-prototypes ) endif() + +if (NINTENDO_3DS) + target_compile_options(dethrace_obj PRIVATE + -O3 -funroll-loops -fno-math-errno -ffast-math + ) +endif() + target_compile_definitions(dethrace_obj PRIVATE INSIDE_DETHRACE) if(DETHRACE_FIX_BUGS) target_compile_definitions(dethrace_obj PRIVATE DETHRACE_FIX_BUGS) @@ -183,6 +190,10 @@ if(MSVC) target_compile_definitions(dethrace PRIVATE -D_CRT_SECURE_NO_WARNINGS -DSDL_MAIN_HANDLED -DWIN32_LEAN_AND_MEAN) endif() +if (NINTENDO_3DS) + target_compile_definitions(dethrace PRIVATE -DSDL_MAIN_HANDLED) +endif() + if(DETHRACE_IDE_ROOT_DIR) set_target_properties(dethrace PROPERTIES VS_DEBUGGER_ENVIRONMENT "DETHRACE_ROOT_DIR=${DETHRACE_IDE_ROOT_DIR}" diff --git a/src/DETHRACE/main.c b/src/DETHRACE/main.c index 626b1f60..4edb64ea 100644 --- a/src/DETHRACE/main.c +++ b/src/DETHRACE/main.c @@ -24,7 +24,51 @@ void BR_CALLBACK _BrBeginHook(void) { void BR_CALLBACK _BrEndHook(void) { } +#ifdef __3DS__ +#include <3ds.h> +PrintConsole console; + +int dethrace_main(int argc, char* argv[]); +void mainThreadFunc(void* arg) { + char** argv = (char**)arg; + int argc = 0; + while (argv[argc] != NULL) { + argc++; + } + + int result = dethrace_main(argc, argv); + threadExit(result); +} + int main(int argc, char* argv[]) { + APT_SetAppCpuTimeLimit(89); + osSetSpeedupEnable(true); + gfxInitDefault(); + consoleInit(GFX_BOTTOM, &console); + + if (argc == 0 || argv == NULL) { + static char program_name[] = "dethrace"; + static char* fake_argv[] = {program_name, NULL}; + + argc = 1; + argv = fake_argv; + } + + Thread mainThread = threadCreate(mainThreadFunc, argv, 0x800000, 0x30, -1, true); + if (!mainThread) { + return -1; + } + + threadJoin(mainThread, U64_MAX); + threadFree(mainThread); + + return 0; +} + +int dethrace_main(int argc, char* argv[]) { +#else +int main(int argc, char* argv[]) { +#endif #ifdef _WIN32 /* Attach to the console that started us if any */ if (AttachConsole(ATTACH_PARENT_PROCESS)) { @@ -42,7 +86,6 @@ int main(int argc, char* argv[]) { } } #endif - Harness_Init(&argc, argv); return original_main(argc, argv); diff --git a/src/DETHRACE/pc-win95/win95net.c b/src/DETHRACE/pc-win95/win95net.c index d2baf993..1f372487 100644 --- a/src/DETHRACE/pc-win95/win95net.c +++ b/src/DETHRACE/pc-win95/win95net.c @@ -165,7 +165,11 @@ int ReceiveHostResponses(void) { LOG_TRACE("()"); char addr_string[32]; +#ifdef __3DS__ + long unsigned int sa_len; +#else unsigned int sa_len; +#endif int wsa_error; sa_len = sizeof(gRemote_addr); @@ -252,7 +256,11 @@ int PDNetInitialise(void) { int mess_num; struct linger so_linger; +#ifdef __3DS__ + long unsigned int sa_len; +#else unsigned int sa_len; +#endif WSADATA wsadata; LOG_TRACE("()"); @@ -573,7 +581,11 @@ tNet_message* PDNetGetNextMessage(tNet_game_details* pDetails, void** pSender_ad LOG_TRACE("(%p, %p)", pDetails, pSender_address); char addr_str[32]; +#ifdef __3DS__ + long unsigned int sa_len; +#else unsigned int sa_len; +#endif int res; tNet_message* msg; diff --git a/src/harness/CMakeLists.txt b/src/harness/CMakeLists.txt index 9349d4b7..be256f5f 100644 --- a/src/harness/CMakeLists.txt +++ b/src/harness/CMakeLists.txt @@ -2,6 +2,10 @@ configure_file(version.h.in version.h @ONLY) add_library(harness STATIC) +if (NINTENDO_3DS) + set(IO_PLATFORM "N3DS") +endif() + if (NOT DEFINED IO_PLATFORM) set(IO_PLATFORM "SDL2") endif() @@ -79,6 +83,19 @@ elseif(APPLE) target_sources(harness PRIVATE os/macos.c ) +elseif(NINTENDO_3DS) + set(CITRO3D_LIBRARY "${DEVKITPRO}/libctru/lib/libcitro3d.a") + set(CITRO3D_INCLUDE_DIR "${DEVKITPRO}/libctru/include") + target_include_directories(harness PRIVATE "${CITRO3D_INCLUDE_DIR}" "platforms/ctr") + target_sources(harness PRIVATE + os/ctr.c + platforms/ctr.c + platforms/ctr/ctr_gfx.c + platforms/ctr/vshader.shbin.o + ) + target_link_libraries(harness PRIVATE "${CITRO3D_LIBRARY}") +# dethrace still depends on SDL2 + target_link_libraries(harness PRIVATE SDL2::SDL2) else() target_sources(harness PRIVATE os/linux.c diff --git a/src/harness/audio/miniaudio.c b/src/harness/audio/miniaudio.c index 054cbd7d..7b43e197 100644 --- a/src/harness/audio/miniaudio.c +++ b/src/harness/audio/miniaudio.c @@ -6,6 +6,12 @@ #include "harness/os.h" #include "harness/trace.h" +#ifdef __3DS__ +#define MA_NO_PTHREAD_IN_HEADER +#define MA_NO_RUNTIME_LINKING +#include <3ds.h> +#endif + // Must come before miniaudio.h #define STB_VORBIS_HEADER_ONLY #include "stb/stb_vorbis.c" @@ -47,17 +53,106 @@ ma_engine engine; ma_sound cda_sound; int cda_sound_initialized; +#ifdef __3DS__ +#define NUM_SAMPLES (1024) +#define NUM_CHANNELS (2) +#define SAMPLERATE (48000) + +static volatile bool dsp_active = true; +Thread audioThread; + +static void fill_buffer_from_engine(const void* audioBuffer, ma_engine* engine, float* bufferF32, size_t nsamples) { + ma_uint32 bufferSizeInFrames = (ma_uint32)(NUM_SAMPLES * NUM_CHANNELS * 4) / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(engine)); + ma_engine_read_pcm_frames(engine, bufferF32, bufferSizeInFrames, NULL); + + int16_t* dest = (int16_t*)audioBuffer; + for (size_t i = 0; i < NUM_SAMPLES * NUM_CHANNELS; i++) { + dest[i] = (int16_t)(bufferF32[i] * 32767.0f - 1.0f); + } + DSP_FlushDataCache(audioBuffer, NUM_SAMPLES * NUM_CHANNELS * sizeof(int16_t)); +} + +static void audio_thread(void* arg) { + float* bufferF32 = (float*)malloc(NUM_SAMPLES * NUM_CHANNELS * sizeof(float)); + int16_t* bufferS16[2]; + bufferS16[0] = (int16_t*)linearAlloc(NUM_SAMPLES * NUM_CHANNELS * sizeof(int16_t)); + bufferS16[1] = (int16_t*)linearAlloc(NUM_SAMPLES * NUM_CHANNELS * sizeof(int16_t)); + + if (!bufferF32 || !bufferS16[0] || !bufferS16[1]) { + printf("Failed to allocate linear memory for buffers\n"); + dsp_active = false; + return; + } + + if (R_FAILED(ndspInit())) { + printf("Failed to initialize DSP\n"); + dsp_active = false; + return; + } + + ndspChnReset(0); + ndspChnSetInterp(0, NDSP_INTERP_LINEAR); + ndspChnSetRate(0, SAMPLERATE); + ndspChnSetFormat(0, (NUM_CHANNELS == 1) ? NDSP_FORMAT_MONO_PCM16 : NDSP_FORMAT_STEREO_PCM16); + + ndspWaveBuf waveBuf[2] = {0}; + waveBuf[0].data_vaddr = bufferS16[0]; + waveBuf[0].nsamples = NUM_SAMPLES; + waveBuf[1].data_vaddr = bufferS16[1]; + waveBuf[1].nsamples = NUM_SAMPLES; + + int buf_idx = 0; + + fill_buffer_from_engine(bufferS16[0], &engine, bufferF32, NUM_SAMPLES); + fill_buffer_from_engine(bufferS16[1], &engine, bufferF32, NUM_SAMPLES); + + ndspChnWaveBufAdd(0, &waveBuf[0]); + ndspChnWaveBufAdd(0, &waveBuf[1]); + + while (dsp_active) { + while (waveBuf[buf_idx].status != NDSP_WBUF_DONE) { + svcSleepThread(1000000); + } + if (!dsp_active) + break; + + fill_buffer_from_engine(waveBuf[buf_idx].data_vaddr, &engine, bufferF32, NUM_SAMPLES); + ndspChnWaveBufAdd(0, &waveBuf[buf_idx]); + + buf_idx = (buf_idx + 1) % 2; + } + + ndspChnReset(0); + ndspExit(); + + free(bufferF32); + linearFree(bufferS16[0]); + linearFree(bufferS16[1]); +} +#endif + tAudioBackend_error_code AudioBackend_Init(void) { ma_result result; ma_engine_config config; config = ma_engine_config_init(); +#ifdef __3DS__ + config.noDevice = MA_TRUE; + config.channels = NUM_CHANNELS; + config.sampleRate = SAMPLERATE; +#endif result = ma_engine_init(&config, &engine); if (result != MA_SUCCESS) { printf("Failed to initialize audio engine."); return eAB_error; } +#ifdef __3DS__ + audioThread = threadCreate(audio_thread, NULL, 64 * 1024, 0x20, 2, true); + if (audioThread == NULL) + LOG_INFO("Audio thread failed to start\n"); +#else LOG_INFO("Playback device: '%s'", engine.pDevice->playback.name); +#endif ma_engine_set_volume(&engine, harness_game_config.volume_multiplier); return eAB_success; @@ -72,6 +167,11 @@ tAudioBackend_error_code AudioBackend_InitCDA(void) { } void AudioBackend_UnInit(void) { +#ifdef __3DS__ + dsp_active = false; + threadJoin(audioThread, U64_MAX); + threadFree(audioThread); +#endif ma_engine_uninit(&engine); } diff --git a/src/harness/harness.c b/src/harness/harness.c index 0923cd11..647c8719 100644 --- a/src/harness/harness.c +++ b/src/harness/harness.c @@ -12,6 +12,10 @@ #include #include +#ifdef __3DS__ +#include <3ds.h> +#endif + br_pixelmap* palette; uint32_t* screen_buffer; @@ -170,11 +174,13 @@ void Harness_Init(int* argc, char* argv[]) { Harness_ProcessCommandLine(argc, argv); +#ifndef __3DS__ if (harness_game_config.install_signalhandler) { OS_InstallSignalHandler(argv[0]); } - +#endif char* root_dir = getenv("DETHRACE_ROOT_DIR"); + if (root_dir != NULL) { LOG_INFO("DETHRACE_ROOT_DIR is set to '%s'", root_dir); } else { diff --git a/src/harness/os/ctr.c b/src/harness/os/ctr.c new file mode 100644 index 00000000..eb0b2bde --- /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 "sdmc:/3ds/dethrace"; +} + +char* OS_GetWorkingDirectory(char* argv0) { + return "sdmc:/3ds/dethrace"; +} diff --git a/src/harness/platforms/ctr.c b/src/harness/platforms/ctr.c new file mode 100644 index 00000000..961d8ef9 --- /dev/null +++ b/src/harness/platforms/ctr.c @@ -0,0 +1,237 @@ +#include <3ds.h> + +#include "platforms/ctr/ctr_gfx.h" + +#include "harness.h" +#include "harness/config.h" +#include "harness/hooks.h" +#include "harness/trace.h" + +uint32_t* rgba_buffer = NULL; +uint32_t converted_palette[256]; +br_pixelmap* last_screen_src; + +u32 last_frame_time; + +uint8_t directinput_key_state[256] = {0}; +float pos_x, pos_y; +bool button_1, button_2; + +static void* ctr_create_renderer(char* title, int x, int y, int width, int height) { + ctr_gfx_init(); + + return NULL; +} + +static int ctr_showcursor(int toggle) { + return 1; +} + +static int ctr_set_window_pos(void* hWnd, int x, int y, int nWidth, int nHeight) { + return 0; +} + +static void ctr_destroy_renderer(void* hWnd) { + ctr_gfx_exit(); + + if (rgba_buffer) { + free(rgba_buffer); + rgba_buffer = NULL; + } +} + +static int map_3ds_key_to_direct_input(u32 key) { + switch (key) { + case KEY_A: return 0x39; // DIK_SPACE; + case KEY_B: return 0x30; // DIK_B; + case KEY_X: return 0x2D; // DIK_X; + case KEY_Y: return 0x15; // DIK_Y; + case KEY_L: return 0x26; // DIK_L; +// case KEY_R: return 0x13; // DIK_R // used for mouse button instead + case KEY_ZL: return 0x0C; // DIK_MINUS + case KEY_ZR: return 0x0D; // DIK_EQUALS + case KEY_START: return 0x1C; // DIK_RETURN + case KEY_SELECT: return 0x01; // DIK_ESCAPE + case KEY_DUP: return 0x48; // DIK_NUMPAD8 + case KEY_DDOWN: return 0x50; // DIK_NUMPAD2 + case KEY_DLEFT: return 0x4B; // DIK_NUMPAD4 + case KEY_DRIGHT: return 0x4D; // DIK_NUMPAD6 + default: return -1; + } +} + +static int ctr_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) { + dinput_key = map_3ds_key_to_direct_input(key); + if (dinput_key >= 0) { + if (kHeld & key) { + directinput_key_state[dinput_key] = 0x80; + } else { + directinput_key_state[dinput_key] = 0x00; + } + } + } + + if (circlePad.dy > 20) { + directinput_key_state[0xC8] = 0x80; // DIK_UP + } else if (circlePad.dy < -20) { + directinput_key_state[0xD0] = 0x80; // DIK_DOWN + } else { + directinput_key_state[0xC8] = 0x00; + directinput_key_state[0xD0] = 0x00; + } + + if (circlePad.dx > 20) { + directinput_key_state[0xCD] = 0x80; // DIK_RIGHT + } else if (circlePad.dx < -20) { + directinput_key_state[0xCB] = 0x80; // DIK_LEFT + } else { + directinput_key_state[0xCD] = 0x00; + directinput_key_state[0xCB] = 0x00; + } + + return 0; +} + +static void ctr_get_keyboard_state(unsigned int count, uint8_t* buffer) { + memcpy(buffer, directinput_key_state, count); +} + +static int ctr_get_mouse_buttons(int* pButton1, int* pButton2) { + *pButton1 = false; + *pButton2 = false; + + return 0; +} + +static int ctr_get_mouse_position(int* pX, int* pY) { + float lX = 0; + float lY = 0; + + 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; + + *pX = (int)lX; + *pY = (int)lY; + + return 0; +} + +static void limit_fps(void) { + u32 now = gHarness_platform.GetTicks(); + if (last_frame_time != 0) { + unsigned int frame_time = now - last_frame_time; + last_frame_time = now; + if (frame_time < 100) { + int sleep_time = (1000 / harness_game_config.fps) - frame_time; + if (sleep_time > 5) { + gHarness_platform.Sleep(sleep_time); + } + } + } + last_frame_time = gHarness_platform.GetTicks(); +} + +static void ctr_present_screen(br_pixelmap* src) { + static int buffer_size = 0; + + int pixel_count = src->width * src->height; + if (buffer_size < pixel_count) { + if (rgba_buffer) { + free(rgba_buffer); + } + + rgba_buffer = (uint32_t*)malloc(pixel_count * sizeof(uint32_t)); + if (!rgba_buffer) { + fprintf(stderr, "Failed to allocate RGBA buffer\n"); + return; + } + buffer_size = pixel_count; + } + + uint8_t* src_pixels = src->pixels; + + for (int i = 0; i < pixel_count; i++) { + rgba_buffer[i] = converted_palette[src_pixels[i]]; + } + + ctr_gfx_draw(rgba_buffer, src->width, src->height); + + last_screen_src = src; + + if (harness_game_config.fps != 0) { + limit_fps(); + } +} + +static void ctr_set_palette(PALETTEENTRY_* pal) { + for (int i = 0; i < 256; i++) { + converted_palette[i] = (pal[i].peRed << 24) | (pal[i].peGreen << 16) | (pal[i].peBlue << 8) | 0xFF; + } + + if (last_screen_src != NULL) { + ctr_present_screen(last_screen_src); + } +} + +static int ctr_show_error_message(void* window, char* text, char* caption) { + fprintf(stderr, "%s", text); + + bool gfxInited = gspHasGpuRight(); + + if (!gfxInited) + gfxInitDefault(); + + errorConf msg; + errorInit(&msg, ERROR_TEXT, CFG_LANGUAGE_EN); + errorText(&msg, text); + errorDisp(&msg); + + if (!gfxInited) + gfxExit(); + + return 0; +} + +static u32 ctr_getTicks(void) { + u64 ticks = svcGetSystemTick(); + return (u32)(ticks * 1000 / SYSCLOCK_ARM11); +} + +static void ctr_sleep(u32 ms) +{ + u64 ns = (u64)ms * 1000000; + svcSleepThread(ns); +} + +void Harness_Platform_Init(tHarness_platform* platform) { + platform->ProcessWindowMessages = ctr_get_and_handle_message; + platform->Sleep = ctr_sleep; + platform->GetTicks = ctr_getTicks; + platform->CreateWindowAndRenderer = ctr_create_renderer; + platform->ShowCursor = ctr_showcursor; + platform->SetWindowPos = ctr_set_window_pos; + platform->DestroyWindow = ctr_destroy_renderer; + platform->GetKeyboardState = ctr_get_keyboard_state; + platform->GetMousePosition = ctr_get_mouse_position; + platform->GetMouseButtons = ctr_get_mouse_buttons; + platform->ShowErrorMessage = ctr_show_error_message; + platform->Renderer_SetPalette = ctr_set_palette; + platform->Renderer_Present = ctr_present_screen; +} diff --git a/src/harness/platforms/ctr/ctr_gfx.c b/src/harness/platforms/ctr/ctr_gfx.c new file mode 100644 index 00000000..307d827f --- /dev/null +++ b/src/harness/platforms/ctr/ctr_gfx.c @@ -0,0 +1,171 @@ +#include + +#include "ctr_gfx.h" +#include "citro3d.h" +#include "vshader_shbin.h" + +static DVLB_s *vshader_dvlb; +static shaderProgram_s program; +static int8_t uLoc_projection; + +static C3D_Mtx topProjection; +static C3D_RenderTarget *topRenderTarget; + +static uint8_t *renderTextureData; +static const uint16_t renderTextureWidth = 512; +static const uint16_t renderTextureHeight = 256; +static const uint32_t renderTextureStride = renderTextureWidth * 4; +static const uint32_t renderTextureByteCount = renderTextureStride * renderTextureHeight; + +static C3D_Tex render_tex; +static int activeTexFilter; + +static bool isN3DS; + +void beginRender(bool vSync); +void drawTopRenderTarget(uint32_t clearColor); +void finishRender(void); + +void setTextureFilter(int texFilter) +{ + int newTexFilter; + + if (texFilter != -1) + newTexFilter = texFilter; + else + newTexFilter = isN3DS ? GPU_LINEAR : GPU_NEAREST; + + if (newTexFilter != activeTexFilter) { + C3D_TexSetFilter(&render_tex, GPU_LINEAR, (GPU_TEXTURE_FILTER_PARAM)newTexFilter); + activeTexFilter = newTexFilter; + } +} + +void drawRect(const float subTexX, const float subTexY, const float subTexW, const float subTexH, + const float posX, const float posY, const float newWidth, const float newHeight) +{ + C3D_ImmDrawBegin(GPU_TRIANGLE_STRIP); + { + const float texW = renderTextureWidth; + const float texH = renderTextureHeight; + + const float vertZ = 0.5f; + const float vertW = 1.f; + + // Bottom left corner + C3D_ImmSendAttrib(posX, posY, vertZ, vertW); // v0 = position xyzw + C3D_ImmSendAttrib(subTexX / texW, 1.f - (subTexY + subTexH) / texH, 0.f, 0.f); // v1 = texcoord uv + + // Bottom right corner + C3D_ImmSendAttrib(newWidth + posX, posY, vertZ, vertW); + C3D_ImmSendAttrib((subTexX + subTexW) / texW, 1.f - (subTexY + subTexH) / texH, 0.f, 0.f); + + // Top left corner + C3D_ImmSendAttrib(posX, newHeight + posY, vertZ, vertW); + C3D_ImmSendAttrib(subTexX / texW, 1.f - subTexY / texH, 0.f, 0.f); + + // Top right corner + C3D_ImmSendAttrib(newWidth + posX, newHeight + posY, vertZ, vertW); + C3D_ImmSendAttrib((subTexX + subTexW) / texW, 1.f - subTexY / texH, 0.f, 0.f); + } + C3D_ImmDrawEnd(); +} + +void drawRects(void) +{ + beginRender(false); + drawTopRenderTarget(0x00000ff); + drawRect(0, 0, 320, 200, 0, 0, 400, 240); + finishRender(); +} + +void ctr_gfx_draw(uint32_t* pixelData, int width, int height) +{ + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int bufferIndex = (y * renderTextureWidth + x) * 4; + ((u32*)renderTextureData)[bufferIndex / sizeof(u32)] = pixelData[y * width + x]; + } + } + + GSPGPU_FlushDataCache(renderTextureData, renderTextureByteCount); + + C3D_SyncDisplayTransfer((u32*)renderTextureData, GX_BUFFER_DIM(renderTextureWidth, renderTextureHeight), + (u32*)render_tex.data, GX_BUFFER_DIM(renderTextureWidth, renderTextureHeight), TEXTURE_TRANSFER_FLAGS); + + GSPGPU_FlushDataCache(render_tex.data, renderTextureByteCount); + + C3D_TexBind(0, &render_tex); + + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + + drawRects(); +} + +void beginRender(bool vSync) +{ + C3D_FrameBegin(vSync ? C3D_FRAME_SYNCDRAW : 0); +} + +void drawTopRenderTarget(uint32_t clearColor) +{ + C3D_RenderTargetClear(topRenderTarget, C3D_CLEAR_ALL, clearColor, 0); + C3D_FrameDrawOn(topRenderTarget); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &topProjection); +} + +void finishRender() +{ + C3D_FrameEnd(0); +} + +void initTransferTexture(void) +{ + renderTextureData = (uint8_t *)linearAlloc(renderTextureByteCount); + + memset(renderTextureData, 0, renderTextureByteCount); + + C3D_TexInitVRAM(&render_tex, renderTextureWidth, renderTextureHeight, GPU_RGBA8); + + setTextureFilter(-1); +} + +void ctr_gfx_init() +{ + APT_CheckNew3DS(&isN3DS); + + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + + topRenderTarget = C3D_RenderTargetCreate(GSP_SCREEN_WIDTH, GSP_SCREEN_HEIGHT_TOP, GPU_RB_RGBA8, GPU_RB_DEPTH16); + C3D_RenderTargetSetOutput(topRenderTarget, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); + + vshader_dvlb = DVLB_ParseFile((uint32_t *)vshader_shbin, vshader_shbin_size); + shaderProgramInit(&program); + shaderProgramSetVsh(&program, &vshader_dvlb->DVLE[0]); + C3D_BindProgram(&program); + + uLoc_projection = shaderInstanceGetUniformLocation(program.vertexShader, "projection"); + + C3D_AttrInfo *attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 2); // v1=texcoord + + Mtx_OrthoTilt(&topProjection, 0, GSP_SCREEN_HEIGHT_TOP, 0, GSP_SCREEN_WIDTH, 0.1f, 1.0f, true); + + initTransferTexture(); +} + +void ctr_gfx_exit() +{ + linearFree(renderTextureData); + shaderProgramFree(&program); + DVLB_Free(vshader_dvlb); + C3D_Fini(); +} diff --git a/src/harness/platforms/ctr/ctr_gfx.h b/src/harness/platforms/ctr/ctr_gfx.h new file mode 100644 index 00000000..d2705451 --- /dev/null +++ b/src/harness/platforms/ctr/ctr_gfx.h @@ -0,0 +1,22 @@ +#ifndef CTR_GFX_H_ +#define CTR_GFX_H_ + +#include <3ds.h> +#include + +#define DISPLAY_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | \ + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \ + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) + +#define TEXTURE_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) | \ + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGBA8) | \ + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) + +void ctr_gfx_draw(uint32_t* pixelData, int width, int height); + +void ctr_gfx_init(void); +void ctr_gfx_exit(void); + +#endif /* CTR_GFX_H_ */ diff --git a/src/harness/platforms/ctr/vshader.shbin b/src/harness/platforms/ctr/vshader.shbin new file mode 100644 index 00000000..4fccf16d Binary files /dev/null and b/src/harness/platforms/ctr/vshader.shbin differ diff --git a/src/harness/platforms/ctr/vshader.shbin.o b/src/harness/platforms/ctr/vshader.shbin.o new file mode 100644 index 00000000..422146e9 Binary files /dev/null and b/src/harness/platforms/ctr/vshader.shbin.o differ diff --git a/src/harness/platforms/ctr/vshader.v.pica b/src/harness/platforms/ctr/vshader.v.pica new file mode 100644 index 00000000..4dfff0d5 --- /dev/null +++ b/src/harness/platforms/ctr/vshader.v.pica @@ -0,0 +1,35 @@ +; Example PICA200 vertex shader + +; Uniforms +.fvec projection[4] + +; Constants +.constf myconst(0.0, 1.0, -1.0, 0.1) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones + +; Outputs +.out outpos position +.out outtc0 texcoord0 + +; Inputs (defined as aliases for convenience) +.alias inpos v0 +.alias intex v1 + +.proc main + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, ones + + ; outpos = projectionMatrix * inpos + dp4 outpos.x, projection[0], r0 + dp4 outpos.y, projection[1], r0 + dp4 outpos.z, projection[2], r0 + dp4 outpos.w, projection[3], r0 + + mov outtc0, intex + + end +.end + +;done \ No newline at end of file diff --git a/src/harness/platforms/ctr/vshader_shbin.h b/src/harness/platforms/ctr/vshader_shbin.h new file mode 100644 index 00000000..59cd36d9 --- /dev/null +++ b/src/harness/platforms/ctr/vshader_shbin.h @@ -0,0 +1,12 @@ +/* Generated by BIN2S - please don't edit directly */ +#pragma once +#include +#include + +extern const uint8_t vshader_shbin[]; +extern const uint8_t vshader_shbin_end[]; +#if __cplusplus >= 201103L +static constexpr size_t vshader_shbin_size=260; +#else +static const size_t vshader_shbin_size=260; +#endif diff --git a/src/harness/win95/polyfill.c b/src/harness/win95/polyfill.c index f6c029a3..bedf76e0 100644 --- a/src/harness/win95/polyfill.c +++ b/src/harness/win95/polyfill.c @@ -152,6 +152,19 @@ int FindNextFileA_(HANDLE_ hFindFile, WIN32_FIND_DATAA_* lpFindFileData) { if (hFindFile == NULL) { return 0; } +#ifdef __3DS_ + while ((entry = readdir(hFindFile)) != NULL) { + char fullPath[256]; + snprintf(fullPath, sizeof(fullPath), "%s/%s", ".", entry->d_name); + + int fd = open(fullPath, O_RDONLY); + if (fd != -1) { + close(fd); + strcpy(lpFindFileData, entry->d_name); + return 1; + } + } +#else while ((entry = readdir(hFindFile)) != NULL) { if (entry->d_type == DT_REG) { strcpy(lpFindFileData->cFileName, entry->d_name); @@ -160,6 +173,7 @@ int FindNextFileA_(HANDLE_ hFindFile, WIN32_FIND_DATAA_* lpFindFileData) { } return 0; #endif +#endif } int FindClose_(HANDLE_ hFindFile) {