diff --git a/librecomp/include/librecomp/addresses.hpp b/librecomp/include/librecomp/addresses.hpp index 6717a7f..38102bc 100644 --- a/librecomp/include/librecomp/addresses.hpp +++ b/librecomp/include/librecomp/addresses.hpp @@ -6,8 +6,10 @@ #include "librecomp/recomp.h" namespace recomp { - // 2GB (Addressable upper half of rdram) - constexpr size_t mem_size = 2U * 1024U * 1024U * 1024U; + // 512GB (kseg0 size) + constexpr size_t mem_size = 512U * 1024U * 1024U; + // 2GB (Addressable upper half of the address space) + constexpr size_t allocation_size = 2048U * 1024U * 1024U; // We need a place in rdram to hold the PI handles, so pick an address in extended rdram constexpr int32_t cart_handle = 0x80800000; constexpr int32_t drive_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); diff --git a/librecomp/src/eep.cpp b/librecomp/src/eep.cpp index ee73929..f386d67 100644 --- a/librecomp/src/eep.cpp +++ b/librecomp/src/eep.cpp @@ -16,7 +16,16 @@ extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { - assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR + uint8_t eep_address = ctx->r5; + gpr buffer = ctx->r6; + int32_t nbytes = eeprom_block_size; + + assert(!(nbytes & 7)); + assert(eep_address * eeprom_block_size + nbytes <= eep16_size); + + save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); + + ctx->r2 = 0; } extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { @@ -33,7 +42,16 @@ extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { - assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR + uint8_t eep_address = ctx->r5; + gpr buffer = ctx->r6; + int32_t nbytes = eeprom_block_size; + + assert(!(nbytes & 7)); + assert(eep_address * eeprom_block_size + nbytes <= eep16_size); + + save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); + + ctx->r2 = 0; } extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 96e178f..00ab67e 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -154,7 +154,7 @@ void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { { std::lock_guard lock { save_context.save_buffer_mutex }; - for (uint32_t i = 0; i < count; i++) { + for (gpr i = 0; i < count; i++) { save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); } } @@ -164,7 +164,7 @@ void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t cou void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { std::lock_guard lock { save_context.save_buffer_mutex }; - for (size_t i = 0; i < count; i++) { + for (gpr i = 0; i < count; i++) { MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; } } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index d7e328f..e3509da 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -609,15 +609,30 @@ void recomp::start( } // Allocate rdram without comitting it. Use a platform-specific virtual allocation function - // that initializes to zero. + // that initializes to zero. Protect the region above the memory size to catch accesses to invalid addresses. uint8_t* rdram; bool alloc_failed; #ifdef _WIN32 - rdram = reinterpret_cast(VirtualAlloc(nullptr, mem_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + rdram = reinterpret_cast(VirtualAlloc(nullptr, allocation_size, MEM_COMMIT | MEM_RESERVE, PAGE_NOACCESS)); + DWORD old_protect = 0; alloc_failed = (rdram == nullptr); + if (!alloc_failed) { + // VirtualProtect returns 0 on failure. + alloc_failed = (VirtualProtect(rdram, mem_size, PAGE_READWRITE, &old_protect) == 0); + if (alloc_failed) { + VirtualFree(rdram, 0, MEM_RELEASE); + } + } #else - rdram = (uint8_t*)mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + rdram = (uint8_t*)mmap(NULL, allocation_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); alloc_failed = rdram == reinterpret_cast(MAP_FAILED); + if (!alloc_failed) { + // mprotect returns -1 on failure. + alloc_failed = (mprotect(rdram, mem_size, PROT_READ | PROT_WRITE) == -1); + if (alloc_failed) { + munmap(rdram, allocation_size); + } + } #endif if (alloc_failed) { @@ -659,7 +674,7 @@ void recomp::start( free_failed = (VirtualFree(rdram, 0, MEM_RELEASE) == 0); #else // munmap returns -1 on failure. - free_failed = (munmap(rdram, mem_size) == -1); + free_failed = (munmap(rdram, allocation_size) == -1); #endif if (free_failed) { diff --git a/ultramodern/src/audio.cpp b/ultramodern/src/audio.cpp index 82c4206..3198be0 100644 --- a/ultramodern/src/audio.cpp +++ b/ultramodern/src/audio.cpp @@ -30,7 +30,7 @@ void ultramodern::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_ uint32_t sample_count = byte_count / sizeof(int16_t); // Queue the swapped audio data. - if (audio_callbacks.queue_samples) { + if (sample_count > 0 && audio_callbacks.queue_samples) { audio_callbacks.queue_samples(TO_PTR(int16_t, audio_data_), sample_count); } } @@ -52,16 +52,16 @@ uint32_t ultramodern::get_remaining_audio_bytes() { else { buffered_byte_count = 100; } - // 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 samples_per_vi = (sample_rate / 60); - if (buffered_byte_count > static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) { - buffered_byte_count -= static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi); - } - else { - buffered_byte_count = 0; - } - return buffered_byte_count; + // 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 samples_per_vi = (sample_rate / 60); + if (buffered_byte_count > static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) { + buffered_byte_count -= static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi); + } + else { + buffered_byte_count = 0; + } + return buffered_byte_count; } diff --git a/ultramodern/src/threads.cpp b/ultramodern/src/threads.cpp index 3c7df50..812c556 100644 --- a/ultramodern/src/threads.cpp +++ b/ultramodern/src/threads.cpp @@ -147,7 +147,7 @@ void ultramodern::set_native_thread_priority(ThreadPriority pri) {} #endif void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context) { - TO_PTR(OSThread, ultramodern::this_thread())->context->running.wait(); + thread_context->running.wait(); // If this thread's context was replaced by another thread or deleted, destroy it again from its own context. // This will trigger thread cleanup instead. if (TO_PTR(OSThread, ultramodern::this_thread())->context != thread_context) { @@ -198,7 +198,10 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry debug_printf("[Thread] Thread waiting to be started: %d\n", self->id); // Wait until the thread is marked as running. - wait_for_resumed(PASS_RDRAM thread_context); + try { + wait_for_resumed(PASS_RDRAM thread_context); + } catch (ultramodern::thread_terminated& terminated) { + } // Make sure the thread wasn't replaced or destroyed before it was started. if (self->context == thread_context) { @@ -228,11 +231,6 @@ extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) { OSThread* t = TO_PTR(OSThread, t_); debug_printf("[os] Start Thread %d\n", t->id); - // Wait until the thread is initialized to indicate that it's ready to be started. - t->context->initialized.wait(); - - debug_printf("[os] Thread %d is ready to be started\n", t->id); - // If this is a game thread, insert the new thread into the running queue and then check the running queue. if (thread_self) { ultramodern::schedule_running_thread(PASS_RDRAM t_); @@ -259,12 +257,26 @@ extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_f // Spawn a new thread, which will immediately pause itself and wait until it's been started. // Pass the context as an argument to the thread function to ensure that it can't get cleared before the thread captures its value. - t->context = new UltraThreadContext{}; - t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context}; + UltraThreadContext* context = new UltraThreadContext{}; + t->context = context; + context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context}; + + // Wait until the thread is initialized to indicate that it's ready to be started. + context->initialized.wait(); + debug_printf("[os] Thread %d is ready to be started\n", t->id); } extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) { - assert(false); + if (t_ == NULLPTR) { + t_ = thread_self; + } + // Check if the thread is stopping itself (arg is null or thread_self). + if (t_ == thread_self) { + ultramodern::run_next_thread_and_wait(PASS_RDRAM1); + } + else { + assert(false); + } } extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {