-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathmain.cpp
More file actions
748 lines (609 loc) · 24.3 KB
/
main.cpp
File metadata and controls
748 lines (609 loc) · 24.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
#include <cstdio>
#include <cassert>
#include <unordered_map>
#include <vector>
#include <array>
#include <filesystem>
#include <numeric>
#include <stdexcept>
#include <cinttypes>
#include "nfd.h"
#include "ultramodern/ultra64.h"
#include "ultramodern/ultramodern.hpp"
#define SDL_MAIN_HANDLED
#ifdef _WIN32
#include "SDL.h"
#else
#include "SDL2/SDL.h"
#include "SDL2/SDL_syswm.h"
// Undefine x11 macros that get included by SDL_syswm.h.
#undef None
#undef Status
#undef LockMask
#undef ControlMask
#undef Success
#undef Always
#endif
#include "recomp_ui.h"
#include "recomp_input.h"
#include "zelda_config.h"
#include "zelda_sound.h"
#include "zelda_render.h"
#include "zelda_support.h"
#include "zelda_game.h"
#include "recomp_data.h"
#include "ovl_patches.hpp"
#include "librecomp/game.hpp"
#include "librecomp/mods.hpp"
#include "librecomp/helpers.hpp"
#include "../../patches/graphics.h"
#include "../../patches/input.h"
#include "../../patches/sound.h"
#include "../../patches/misc_funcs.h"
// #include "mods/mm_recomp_dpad_builtin.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <timeapi.h>
#include "SDL_syswm.h"
#endif
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
const std::string version_string = "1.0.3";
template<typename... Ts>
void exit_error(const char* str, Ts ...args) {
// TODO pop up an error
((void)fprintf(stderr, str, args), ...);
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() {
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) > 0) {
exit_error("Failed to initialize SDL2: %s\n", SDL_GetError());
}
fprintf(stdout, "SDL Video Driver: %s\n", SDL_GetCurrentVideoDriver());
return {};
}
#if defined(__gnu_linux__)
#include "icon_bytes.h"
bool SetImageAsIcon(const char* filename, SDL_Window* window)
{
// Read data
int width, height, bytesPerPixel;
void* data = stbi_load_from_memory(reinterpret_cast<const uint8_t*>(icon_bytes), sizeof(icon_bytes), &width, &height, &bytesPerPixel, 4);
// Calculate pitch
int pitch;
pitch = width * 4;
pitch = (pitch + 3) & ~3;
// Setup relevance bitmask
int Rmask, Gmask, Bmask, Amask;
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
Rmask = 0x000000FF;
Gmask = 0x0000FF00;
Bmask = 0x00FF0000;
Amask = 0xFF000000;
#else
Rmask = 0xFF000000;
Gmask = 0x00FF0000;
Bmask = 0x0000FF00;
Amask = 0x000000FF;
#endif
SDL_Surface* surface = nullptr;
if (data != nullptr) {
surface = SDL_CreateRGBSurfaceFrom(data, width, height, 32, pitch, Rmask, Gmask,
Bmask, Amask);
}
if (surface == nullptr) {
if (data != nullptr) {
stbi_image_free(data);
}
return false;
} else {
SDL_SetWindowIcon(window,surface);
SDL_FreeSurface(surface);
stbi_image_free(data);
return true;
}
}
#endif
SDL_Window* window;
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
uint32_t flags = SDL_WINDOW_RESIZABLE;
#if defined(__APPLE__)
flags |= SDL_WINDOW_METAL;
#elif defined(RT64_SDL_WINDOW_VULKAN)
flags |= SDL_WINDOW_VULKAN;
#endif
window = SDL_CreateWindow("Starfox 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags);
#if defined(__linux__)
SetImageAsIcon("icons/512.png",window);
if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP);
} else {
SDL_SetWindowFullscreen(window,0);
}
#endif
if (window == nullptr) {
exit_error("Failed to create window: %s\n", SDL_GetError());
}
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
SDL_GetWindowWMInfo(window, &wmInfo);
#if defined(_WIN32)
return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
#elif defined(__linux__) || defined(__ANDROID__)
return ultramodern::renderer::WindowHandle{ window };
#elif defined(__APPLE__)
SDL_MetalView view = SDL_Metal_CreateView(window);
return ultramodern::renderer::WindowHandle{ wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) };
#else
static_assert(false && "Unimplemented");
#endif
}
void update_gfx(void*) {
recomp::handle_events();
}
static SDL_AudioCVT audio_convert;
static SDL_AudioDeviceID audio_device = 0;
// Samples per channel per second.
static uint32_t sample_rate = 48000;
static uint32_t output_sample_rate = 48000;
// Channel count.
constexpr uint32_t input_channels = 2;
static uint32_t output_channels = 2;
// Terminology: a frame is a collection of samples for each channel. e.g. 2 input samples is one input frame. This is unrelated to graphical frames.
// Number of frames to duplicate for fixing interpolation at the start and end of a chunk.
constexpr uint32_t duplicated_input_frames = 4;
// The number of output frames to skip for playback (to avoid playing duplicate inputs twice).
static uint32_t discarded_output_frames;
constexpr uint32_t bytes_per_frame = input_channels * sizeof(float);
void queue_samples(int16_t* audio_data, size_t sample_count) {
// Buffer for holding the output of swapping the audio channels. This is reused across
// calls to reduce runtime allocations.
static std::vector<float> swap_buffer;
static std::array<float, duplicated_input_frames * input_channels> duplicated_sample_buffer;
// Make sure the swap buffer is large enough to hold the audio data, including any extra space needed for resampling.
size_t resampled_sample_count = sample_count + duplicated_input_frames * input_channels;
size_t max_sample_count = std::max(resampled_sample_count, resampled_sample_count * audio_convert.len_mult);
if (max_sample_count > swap_buffer.size()) {
swap_buffer.resize(max_sample_count);
}
// Copy the duplicated frames from last chunk into this chunk
for (size_t i = 0; i < duplicated_input_frames * input_channels; i++) {
swap_buffer[i] = duplicated_sample_buffer[i];
}
// Convert the audio from 16-bit values to floats and swap the audio channels into the
// swap buffer to correct for the address xor caused by endianness handling.
float cur_main_volume = zelda64::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
for (size_t i = 0; i < sample_count; i += input_channels) {
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (1.0f / 32768.0f) * cur_main_volume;
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (1.0f / 32768.0f) * cur_main_volume;
}
// TODO handle cases where a chunk is smaller than the duplicated frame count.
assert(sample_count > duplicated_input_frames * input_channels);
// Copy the last converted samples into the duplicated sample buffer to reuse in resampling the next queued chunk.
for (size_t i = 0; i < duplicated_input_frames * input_channels; i++) {
duplicated_sample_buffer[i] = swap_buffer[i + sample_count];
}
audio_convert.buf = reinterpret_cast<Uint8*>(swap_buffer.data());
audio_convert.len = (sample_count + duplicated_input_frames * input_channels) * sizeof(swap_buffer[0]);
int ret = SDL_ConvertAudio(&audio_convert);
if (ret < 0) {
printf("Error using SDL audio converter: %s\n", SDL_GetError());
throw std::runtime_error("Error using SDL audio converter");
}
uint64_t cur_queued_microseconds = uint64_t(SDL_GetQueuedAudioSize(audio_device)) / bytes_per_frame * 1000000 / sample_rate;
uint32_t num_bytes_to_queue = audio_convert.len_cvt - output_channels * discarded_output_frames * sizeof(swap_buffer[0]);
float* samples_to_queue = swap_buffer.data() + output_channels * discarded_output_frames / 2;
// Prevent audio latency from building up by skipping samples in incoming audio when too many samples are already queued.
// Skip samples based on how many microseconds of samples are queued already.
uint32_t skip_factor = cur_queued_microseconds / 100000;
if (skip_factor != 0) {
uint32_t skip_ratio = 1 << skip_factor;
num_bytes_to_queue /= skip_ratio;
for (size_t i = 0; i < num_bytes_to_queue / (output_channels * sizeof(swap_buffer[0])); i++) {
samples_to_queue[2 * i + 0] = samples_to_queue[2 * skip_ratio * i + 0];
samples_to_queue[2 * i + 1] = samples_to_queue[2 * skip_ratio * i + 1];
}
}
// Queue the swapped audio data.
// Offset the data start by only half the discarded frame count as the other half of the discarded frames are at the end of the buffer.
SDL_QueueAudio(audio_device, samples_to_queue, num_bytes_to_queue);
}
size_t get_frames_remaining() {
constexpr float buffer_offset_frames = 1.0f;
// Get the number of remaining buffered audio bytes.
uint64_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device);
// Scale the byte count based on the ratio of sample rates and channel counts.
buffered_byte_count = buffered_byte_count * 2 * sample_rate / output_sample_rate / output_channels;
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
// there are enough samples even if the audio thread experiences a small amount of lag. This prevents
// audio popping on games that use the buffered audio byte count to determine how many samples
// to generate.
uint32_t frames_per_vi = (sample_rate / 60);
if (buffered_byte_count > (buffer_offset_frames * bytes_per_frame * frames_per_vi)) {
buffered_byte_count -= (buffer_offset_frames * bytes_per_frame * frames_per_vi);
}
else {
buffered_byte_count = 0;
}
// Convert from byte count to sample count.
return static_cast<uint32_t>(buffered_byte_count / bytes_per_frame);
}
void update_audio_converter() {
int ret = SDL_BuildAudioCVT(&audio_convert, AUDIO_F32, input_channels, sample_rate, AUDIO_F32, output_channels, output_sample_rate);
if (ret < 0) {
printf("Error creating SDL audio converter: %s\n", SDL_GetError());
throw std::runtime_error("Error creating SDL audio converter");
}
// Calculate the number of samples to discard based on the sample rate ratio and the duplicate frame count.
discarded_output_frames = duplicated_input_frames * output_sample_rate / sample_rate;
}
void set_frequency(uint32_t freq) {
sample_rate = freq;
update_audio_converter();
}
void reset_audio(uint32_t output_freq) {
SDL_AudioSpec spec_desired{
.freq = (int)output_freq,
.format = AUDIO_F32,
.channels = (Uint8)output_channels,
.silence = 0, // calculated
.samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
.padding = 0, // unused
.size = 0, // calculated
.callback = nullptr,
.userdata = nullptr
};
audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0);
if (audio_device == 0) {
exit_error("SDL error opening audio device: %s\n", SDL_GetError());
}
SDL_PauseAudioDevice(audio_device, 0);
output_sample_rate = output_freq;
update_audio_converter();
}
// extern RspUcodeFunc njpgdspMain;
extern RspUcodeFunc aspMain;
RspUcodeFunc* get_rsp_microcode(const OSTask* task) {
switch (task->t.type) {
case M_AUDTASK:
return aspMain;
// case M_NJPEGTASK:
// return njpgdspMain;
default:
fprintf(stderr, "Unknown task: %" PRIu32 "\n", task->t.type);
return nullptr;
}
}
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
gpr get_entrypoint_address();
// array of supported GameEntry objects
std::vector<recomp::GameEntry> supported_games = {
{
.rom_hash = 0x163fd3fc3813f54eULL,
.internal_name = "STARFOX64",
.game_id = u8"sf64.n64.us.1.1",
.mod_game_id = "sf64",
.save_type = recomp::SaveType::Eep4k,
.is_enabled = true,
.decompression_routine = zelda64::decompress_sf64,
.has_compressed_code = true,
.entrypoint_address = get_entrypoint_address(),
.entrypoint = recomp_entrypoint,
},
};
// TODO: move somewhere else
namespace zelda64 {
std::string get_game_thread_name(const OSThread* t) {
std::string name = "[Game] ";
switch (t->id) {
case 0:
switch (t->priority) {
case 150:
name += "PIMGR";
break;
case 254:
name += "VIMGR";
break;
default:
name += std::to_string(t->id);
break;
}
break;
case 1:
name += "IDLE";
break;
case 2:
switch (t->priority) {
case 5:
name += "SLOWLY";
break;
case 127:
name += "FAULT";
break;
default:
name += std::to_string(t->id);
break;
}
break;
case 3:
name += "MAIN";
break;
case 4:
name += "GRAPH";
break;
case 5:
name += "SCHED";
break;
case 7:
name += "PADMGR";
break;
case 10:
name += "AUDIOMGR";
break;
case 13:
name += "FLASHROM";
break;
case 18:
name += "DMAMGR";
break;
case 19:
name += "IRQMGR";
break;
default:
name += std::to_string(t->id);
break;
}
return name;
}
}
#ifdef _WIN32
struct PreloadContext {
HANDLE handle;
HANDLE mapping_handle;
SIZE_T size;
PVOID view;
};
bool preload_executable(PreloadContext& context) {
wchar_t module_name[MAX_PATH];
GetModuleFileNameW(NULL, module_name, MAX_PATH);
context.handle = CreateFileW(module_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (context.handle == INVALID_HANDLE_VALUE) {
fprintf(stderr, "Failed to load executable into memory!");
context = {};
return false;
}
LARGE_INTEGER module_size;
if (!GetFileSizeEx(context.handle, &module_size)) {
fprintf(stderr, "Failed to get size of executable!");
CloseHandle(context.handle);
context = {};
return false;
}
context.size = module_size.QuadPart;
context.mapping_handle = CreateFileMappingW(context.handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (context.mapping_handle == nullptr) {
fprintf(stderr, "Failed to create file mapping of executable!");
CloseHandle(context.handle);
context = {};
return EXIT_FAILURE;
}
context.view = MapViewOfFile(context.mapping_handle, FILE_MAP_READ, 0, 0, 0);
if (context.view == nullptr) {
fprintf(stderr, "Failed to map view of of executable!");
CloseHandle(context.mapping_handle);
CloseHandle(context.handle);
context = {};
return false;
}
DWORD pid = GetCurrentProcessId();
HANDLE process_handle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_QUERY_INFORMATION, FALSE, pid);
if (process_handle == nullptr) {
fprintf(stderr, "Failed to open own process!");
CloseHandle(context.mapping_handle);
CloseHandle(context.handle);
context = {};
return false;
}
SIZE_T minimum_set_size, maximum_set_size;
if (!GetProcessWorkingSetSize(process_handle, &minimum_set_size, &maximum_set_size)) {
fprintf(stderr, "Failed to get working set size!");
CloseHandle(context.mapping_handle);
CloseHandle(context.handle);
context = {};
return false;
}
if (!SetProcessWorkingSetSize(process_handle, minimum_set_size + context.size, maximum_set_size + context.size)) {
fprintf(stderr, "Failed to set working set size!");
CloseHandle(context.mapping_handle);
CloseHandle(context.handle);
context = {};
return false;
}
if (VirtualLock(context.view, context.size) == 0) {
fprintf(stderr, "Failed to lock view of executable! (Error: %08lx)\n", GetLastError());
CloseHandle(context.mapping_handle);
CloseHandle(context.handle);
context = {};
return false;
}
return true;
}
void release_preload(PreloadContext& context) {
VirtualUnlock(context.view, context.size);
CloseHandle(context.mapping_handle);
CloseHandle(context.handle);
context = {};
}
#else
struct PreloadContext {
};
// TODO implement on other platforms
bool preload_executable(PreloadContext& context) {
return false;
}
void release_preload(PreloadContext& context) {
}
#endif
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
zelda64::renderer::enable_texture_pack(context, mod);
}
void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) {
zelda64::renderer::disable_texture_pack(mod);
}
void reorder_texture_pack(recomp::mods::ModContext&) {
zelda64::renderer::trigger_texture_pack_update();
}
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
int main(int argc, char** argv) {
(void)argc;
(void)argv;
recomp::Version project_version{};
if (!recomp::Version::from_string(version_string, project_version)) {
ultramodern::error_handling::message_box(("Invalid version string: " + version_string).c_str());
return EXIT_FAILURE;
}
// Map this executable into memory and lock it, which should keep it in physical memory. This ensures
// that there are no stutters from the OS having to load new pages of the executable whenever a new code page is run.
PreloadContext preload_context;
bool preloaded = preload_executable(preload_context);
if (!preloaded) {
fprintf(stderr, "Failed to preload executable!\n");
}
#ifdef _WIN32
// Set up high resolution timing period.
timeBeginPeriod(1);
// Process arguments.
// Set up console output to accept UTF-8 on windows
SetConsoleOutputCP(CP_UTF8);
// Initialize native file dialogs.
NFD_Init();
// Change to a font that supports Japanese characters
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof cfi;
cfi.nFont = 0;
cfi.dwFontSize.X = 0;
cfi.dwFontSize.Y = 16;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
wcscpy_s(cfi.FaceName, L"NSimSun");
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
#endif
#ifdef _WIN32
// Force wasapi on Windows, as there seems to be some issue with sample queueing with directsound currently.
SDL_setenv("SDL_AUDIODRIVER", "wasapi", true);
#endif
#if defined(__linux__) && defined(RECOMP_FLATPAK)
// When using Flatpak, applications tend to launch from the home directory by default.
// Mods might use the current working directory to store the data, so we switch it to a directory
// with persistent data storage and write permissions under Flatpak to ensure it works.
std::error_code ec;
std::filesystem::current_path("/var/data", ec);
#endif
// Initialize SDL audio and set the output frequency.
SDL_InitSubSystem(SDL_INIT_AUDIO);
reset_audio(48000);
// Source controller mappings file
std::u8string controller_db_path = (zelda64::get_program_path() / "recompcontrollerdb.txt").u8string();
if (SDL_GameControllerAddMappingsFromFile(reinterpret_cast<const char *>(controller_db_path.c_str())) < 0) {
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
}
recomp::register_config_path(zelda64::get_app_folder_path());
// Register supported games and patches
for (const auto& game : supported_games) {
recomp::register_game(game);
}
//recomp::mods::register_embedded_mod("mm_recomp_dpad_builtin", { (const uint8_t*)(mm_recomp_dpad_builtin), std::size(mm_recomp_dpad_builtin)});
//REGISTER_FUNC(recomp_get_window_resolution);
REGISTER_FUNC(recomp_get_target_aspect_ratio);
REGISTER_FUNC(recomp_get_target_framerate);
REGISTER_FUNC(recomp_get_film_grain_enabled);
REGISTER_FUNC(recomp_get_invert_y_axis_mode);
REGISTER_FUNC(recomp_get_radio_comm_box_mode);
REGISTER_FUNC(recomp_get_camera_inputs);
REGISTER_FUNC(recomp_get_targeting_mode);
REGISTER_FUNC(recomp_get_bgm_volume);
REGISTER_FUNC(recomp_get_low_health_beeps_enabled);
REGISTER_FUNC(recomp_get_gyro_deltas);
REGISTER_FUNC(recomp_get_mouse_deltas);
REGISTER_FUNC(recomp_get_inverted_axes);
REGISTER_FUNC(recomp_get_analog_inverted_axes);
recompui::register_ui_exports();
recomputil::register_data_api_exports();
zelda64::register_overlays();
zelda64::register_patches();
// recomputil::init_extended_actor_data();
zelda64::load_config();
recomp::rsp::callbacks_t rsp_callbacks{
.get_rsp_microcode = get_rsp_microcode,
};
ultramodern::renderer::callbacks_t renderer_callbacks{
.create_render_context = zelda64::renderer::create_render_context,
};
ultramodern::gfx_callbacks_t gfx_callbacks{
.create_gfx = create_gfx,
.create_window = create_window,
.update_gfx = update_gfx,
};
ultramodern::audio_callbacks_t audio_callbacks{
.queue_samples = queue_samples,
.get_frames_remaining = get_frames_remaining,
.set_frequency = set_frequency,
};
ultramodern::input::callbacks_t input_callbacks{
.poll_input = recomp::poll_inputs,
.get_input = recomp::get_n64_input,
.set_rumble = recomp::set_rumble,
.get_connected_device_info = recomp::get_connected_device_info,
};
ultramodern::events::callbacks_t thread_callbacks{
.vi_callback = recomp::update_rumble,
.gfx_init_callback = recompui::update_supported_options,
};
ultramodern::error_handling::callbacks_t error_handling_callbacks{
.message_box = recompui::message_box,
};
ultramodern::threads::callbacks_t threads_callbacks{
.get_game_thread_name = zelda64::get_game_thread_name,
};
// Register the texture pack content type with rt64.json as its content file.
recomp::mods::ModContentType texture_pack_content_type{
.content_filename = "rt64.json",
.allow_runtime_toggle = true,
.on_enabled = enable_texture_pack,
.on_disabled = disable_texture_pack,
.on_reordered = reorder_texture_pack,
};
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
recomp::Configuration cfg{
.project_version = project_version,
.window_handle = {},
.rsp_callbacks = rsp_callbacks,
.renderer_callbacks = renderer_callbacks,
.audio_callbacks = audio_callbacks,
.input_callbacks = input_callbacks,
.gfx_callbacks = gfx_callbacks,
.events_callbacks = thread_callbacks,
.error_handling_callbacks = error_handling_callbacks,
.threads_callbacks = threads_callbacks,
.message_queue_control = {},
};
recomp::start(cfg);
NFD_Quit();
if (preloaded) {
release_preload(preload_context);
}
#ifdef _WIN32
// End high resolution timing period.
timeEndPeriod(1);
#endif
return EXIT_SUCCESS;
}