From 0b30638e657153d5eeae4f13de77b8c7a5ba9533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Oct 2026 21:46:06 +0100 Subject: [PATCH 1/3] HLE: Fix SAS thread wakeup, SAS raw output size, ME and syscon bounds - The SAS thread waited without checking the state first, so a mix queued before it got to the wait was missed, and the next drain hung. The state is now atomic too. - SAS raw output mode writes four planes, twice the size it asked for. - DMACplus colour conversion with a huge stride overflowed its size math. The syscon serial data index wasn't bounded on reads, writes or load. - Replace_memcpy_swizzled copies whole 16x8 blocks, more than the pitch * h it asked for. The God Eater depth hook tested the color buffer twice instead of the depth buffer, and its size could wrap. - GranularMixer used 1/0 before the first FPS sample, and read the granule being written next when the queue was empty. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/ReplaceTables.cpp | 18 +++++++++++++----- Core/HLE/sceSas.cpp | 9 +++++++-- Core/HW/GpioMMIO.cpp | 9 +++++++-- Core/HW/GranularMixer.cpp | 6 ++++-- Core/HW/MeMMIO.cpp | 3 ++- Core/HW/SasAudio.cpp | 4 +++- 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index a158d1de3b..8398564c68 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -294,10 +294,15 @@ static int Replace_memcpy_swizzled(MIPSState *mips) { gpu->PerformReadbackToMemory(srcPtr, pitch * h); } } - u8 *dstp = Memory::GetPointerWriteRangeOrException(destPtr, pitch * h); - const u8 *srcp = Memory::GetPointerRangeOrException(srcPtr, pitch * h); + // The loops go in whole 16x8 blocks, so they can touch more than pitch * h. + const u64 rows = ((u64)h + 7) & ~7ULL; + const u64 cols = ((u64)pitch + 15) & ~15ULL; + const u64 dstSize = rows * cols; + const u64 srcSize = rows ? (rows - 1) * pitch + cols : 0; + u8 *dstp = dstSize <= 0xFFFFFFFF ? Memory::GetPointerWriteRangeOrException(destPtr, (u32)dstSize) : nullptr; + const u8 *srcp = srcSize <= 0xFFFFFFFF ? Memory::GetPointerRangeOrException(srcPtr, (u32)srcSize) : nullptr; - if (dstp && srcp) { + if (dstp && srcp && h != 0 && pitch != 0) { const u8 *ysrcp = srcp; for (u32 y = 0; y < h; y += 8) { const u8 *xsrcp = ysrcp; @@ -761,12 +766,15 @@ static int Hook_godseaterburst_depthmask_5551(MIPSState *mips) { uint32_t depthBuffer = mips->r[MIPS_REG_T2]; uint32_t byteStride = mips->r[MIPS_REG_A2]; uint32_t height = mips->r[MIPS_REG_T1]; - uint32_t size = byteStride * height; + const u64 size64 = (u64)byteStride * height; + if (size64 > 0xFFFFFFFF) + return 0; + uint32_t size = (uint32_t)size64; if (!Memory::IsVRAMAddress(colorBuffer) || !Memory::IsValidRange(colorBuffer, size)) return 0; if (depthBuffer != 0) { - if (!Memory::IsVRAMAddress(colorBuffer) || !Memory::IsValidRange(depthBuffer, size)) + if (!Memory::IsVRAMAddress(depthBuffer) || !Memory::IsValidRange(depthBuffer, size)) return 0; // This is added to read from the linearized mirror. diff --git a/Core/HLE/sceSas.cpp b/Core/HLE/sceSas.cpp index d617444143..bf7d901847 100644 --- a/Core/HLE/sceSas.cpp +++ b/Core/HLE/sceSas.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -77,7 +78,8 @@ static std::mutex sasWakeMutex; static std::mutex sasDoneMutex; static std::condition_variable sasWake; static std::condition_variable sasDone; -static volatile int sasThreadState = SasThreadState::DISABLED; +// Changed under sasWakeMutex (and sasDoneMutex for READY), but also read outside them. +static std::atomic sasThreadState{ SasThreadState::DISABLED }; static SasThreadParams sasThreadParams; static int sasMixEvent = -1; @@ -92,7 +94,10 @@ int __SasThread() { std::unique_lock guard(sasWakeMutex); while (sasThreadState != SasThreadState::DISABLED) { - sasWake.wait(guard); + // Check before sleeping, the mix may have been queued before we got here. + while (sasThreadState == SasThreadState::READY) { + sasWake.wait(guard); + } if (sasThreadState == SasThreadState::QUEUED) { const bool mute = g_sasMuteFlag; sas->Mix(sasThreadParams.outAddr, sasThreadParams.inAddr, sasThreadParams.leftVol, sasThreadParams.rightVol, mute); diff --git a/Core/HW/GpioMMIO.cpp b/Core/HW/GpioMMIO.cpp index c2568890c7..ab24b2f4bb 100644 --- a/Core/HW/GpioMMIO.cpp +++ b/Core/HW/GpioMMIO.cpp @@ -123,7 +123,8 @@ public: switch (offset) { case 0x008: { - *value = (data_[dataIndex_] << 8) | data_[dataIndex_ + 1]; + // Writes can move the index anywhere. + *value = (dataIndex_ >= 0 && dataIndex_ + 1 < DATA_SIZE) ? (data_[dataIndex_] << 8) | data_[dataIndex_ + 1] : 0; dataIndex_ += 2; if (dataIndex_ > responseLen_) { dataIndex_ = 0; @@ -162,7 +163,8 @@ public: data_[dataIndex_] = (value >> 8) & 0xFF; data_[dataIndex_ + 1] = value & 0xFF; } - dataIndex_ += 2; + if (dataIndex_ < DATA_SIZE) + dataIndex_ += 2; break; default: break; @@ -184,6 +186,9 @@ public: return; DoArray(p, data_, DATA_SIZE); Do(p, dataIndex_); + if (p.mode == PointerWrap::MODE_READ && (dataIndex_ < 0 || dataIndex_ > DATA_SIZE)) { + dataIndex_ = 0; + } Do(p, responseLen_); Do(p, dataExhausted_); Do(p, alarm_); diff --git a/Core/HW/GranularMixer.cpp b/Core/HW/GranularMixer.cpp index 3f2374e177..2bf1404a02 100644 --- a/Core/HW/GranularMixer.cpp +++ b/Core/HW/GranularMixer.cpp @@ -77,7 +77,8 @@ void GranularMixer::Mix(s16 *samples, u32 num_samples, int outSampleRate, float if (!samples) return; memset(samples, 0, num_samples * 2 * sizeof(s16)); - frameTimeEstimate_ = 1.0f / fpsEstimate; + // The estimate is 0 until a second has gone by. + frameTimeEstimate_ = 1.0f / std::max(fpsEstimate, 1.0f); smoothedReadSize_ = smoothedReadSize_ == 0 ? num_samples : (smoothedReadSize_ * 0.95f + num_samples * 0.05f); @@ -262,7 +263,8 @@ void GranularMixer::Dequeue(Granule *granule) { /*if (!looping && !smoothedQueueSize_ < granule_queue_size / 2) { // Repeat a single block occasionally to make sure we have a reasonably sized queue. next_tail = tail; - } else*/ if (next_tail == head) { + } else*/ if (next_tail == head || tail == head) { + // Empty (or about to be). tail == head would read the granule being written next. // Only fill gaps when running to prevent stutter on pause. CoreState state = coreState; const bool is_running = state == CORE_RUNNING_CPU || state == CORE_RUNNING_GE; diff --git a/Core/HW/MeMMIO.cpp b/Core/HW/MeMMIO.cpp index bf3cf36e39..318e1859a8 100644 --- a/Core/HW/MeMMIO.cpp +++ b/Core/HW/MeMMIO.cpp @@ -289,7 +289,8 @@ private: const bool separate = (output & 1) != 0; d.csc[(0x160 - 0x120) / 4] &= ~1; d.intTCStatus |= 2; - if (width == 0 || height == 0 || stride < width) { + // A wider stride than any framebuffer would only overflow the size math below. + if (width == 0 || height == 0 || stride < width || stride > 4096) { ERROR_LOG(Log::ME, "DMACplus: bad colour space conversion %dx%d, stride %d", width, height, stride); return; } diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index d2147e0884..0d5170cb97 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -671,7 +671,9 @@ void SasInstance::Mix(u32 outAddr, u32 inAddr, int leftVol, int rightVol, bool m // Then mix the send buffer in with the rest. // Alright, all voices mixed. Let's convert and clip, and at the same time, wipe mixBuffer for next time. Could also dither. - s16 *outp = (s16 *)Memory::GetPointerWriteRangeOrException(outAddr, 4 * grainSize); + // Raw mode writes four planes of grainSize samples, mixed mode two interleaved. + const u32 outBytes = (outputMode == PSP_SAS_OUTPUTMODE_MIXED ? 4 : 8) * grainSize; + s16 *outp = (s16 *)Memory::GetPointerWriteRangeOrException(outAddr, outBytes); const s16 *inp = inAddr ? (const s16 *)Memory::GetPointerRangeOrException(inAddr, 4 * grainSize) : 0; if (!outp) { WARN_LOG_REPORT(Log::sceSas, "Bad SAS Mix output address: %08x, grain=%d", outAddr, grainSize); From 3880999ada8021a78b3096d6893c5da4ebc78bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Oct 2026 21:47:03 +0100 Subject: [PATCH 2/3] Misc HLE: Fix heap leak, USB waits, RNG indices and small races - sceHeapDeleteHeap never freed the heap's memory. Allocation failure returned -1 rather than a null pointer, and a near-4GB size wrapped. - The USB wait timeout resumed threads that were no longer waiting on USB, and UsbUpdateState resumed them too, and never rescheduled. - The Mersenne Twister drew from mt_[index_] with the index taken from guest memory. It's now taken modulo the state size. sceSfmt19937 checks its index, and that fill arrays are at least a state long, which the library writes regardless of the length it's given. - sceCccEncodeUTF16's surrogate test also caught U+F800 - U+FFFF, which includes the fullwidth forms. - sceAudioRoutingSetVolumeMode stored the routing mode. - A sample rate of 0 to sceAudioInput or the camera mic divided by zero, and a huge sample count sized a host buffer. sceP3daBridgeCore's sizes could wrap. sceRtc's tz offset could overflow tm_min. - Camera shutdown freed the frame buffer and config without the lock the capture thread uses. GPS data is set from the platform's location thread, so it's now locked too, and the satellite index is bounded. - A loop in the sys event handler list hung the unregister. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/Data/Random/Rng.h | 8 +++++--- Core/HLE/sceAudioRouting.cpp | 2 +- Core/HLE/sceCcc.cpp | 2 +- Core/HLE/sceHeap.cpp | 14 ++++++++++++++ Core/HLE/sceP3da.cpp | 4 +++- Core/HLE/sceRtc.cpp | 8 ++++++-- Core/HLE/sceSfmt19937.cpp | 16 ++++++++++++---- Core/HLE/sceSysreg.cpp | 4 +++- Core/HLE/sceUsb.cpp | 10 +++++++++- Core/HLE/sceUsbCam.cpp | 4 ++++ Core/HLE/sceUsbGps.cpp | 15 ++++++++++++--- Core/HLE/sceUsbMic.cpp | 12 ++++++++++++ 12 files changed, 82 insertions(+), 17 deletions(-) diff --git a/Common/Data/Random/Rng.h b/Common/Data/Random/Rng.h index b72c073124..0ddca96f52 100644 --- a/Common/Data/Random/Rng.h +++ b/Common/Data/Random/Rng.h @@ -55,9 +55,11 @@ public: // Read first, then regenerate that word in place - done in index order this is the // same as twisting the whole array in one go, and it matches what hardware leaves // behind in the context after each draw. - uint32_t y = mt_[index_]; - twist(index_); - index_ = (index_ + 1) % MT_SIZE; + // The context can live in emulated memory, so don't trust the index. + const uint32_t i = index_ % MT_SIZE; + uint32_t y = mt_[i]; + twist(i); + index_ = (i + 1) % MT_SIZE; y ^= y >> 11; y ^= (y << 7) & 2636928640UL; y ^= (y << 15) & 4022730752UL; diff --git a/Core/HLE/sceAudioRouting.cpp b/Core/HLE/sceAudioRouting.cpp index 35e4320539..c7c91f3f2f 100644 --- a/Core/HLE/sceAudioRouting.cpp +++ b/Core/HLE/sceAudioRouting.cpp @@ -35,7 +35,7 @@ static int sceAudioRoutingGetMode() { static int sceAudioRoutingSetVolumeMode(int mode) { INFO_LOG(Log::sceAudio, "sceAudioRoutingSetVolumeMode %d", mode); int previousMode = audioRoutineVolumeMode; - audioRoutineVolumeMode = audioRoutingMode; + audioRoutineVolumeMode = mode; return previousMode; } diff --git a/Core/HLE/sceCcc.cpp b/Core/HLE/sceCcc.cpp index f74636b0cb..b8cf497a03 100644 --- a/Core/HLE/sceCcc.cpp +++ b/Core/HLE/sceCcc.cpp @@ -373,7 +373,7 @@ static void sceCccEncodeUTF16(u32 dstAddrAddr, u32 ucs) } DEBUG_LOG(Log::sceMisc, "sceCccEncodeUTF16(%08x, U+%04x)", dstAddrAddr, ucs); // Anything above 0x10FFFF is unencodable, and 0xD800 - 0xDFFF are reserved for surrogate pairs. - if (ucs > 0x10FFFF || (ucs & 0xD800) == 0xD800) + if (ucs > 0x10FFFF || (ucs & 0xF800) == 0xD800) ucs = errorUTF16; *dstp += UTF16LE::encode(*dstp, ucs); } diff --git a/Core/HLE/sceHeap.cpp b/Core/HLE/sceHeap.cpp index ae91ec8632..c076653ee0 100644 --- a/Core/HLE/sceHeap.cpp +++ b/Core/HLE/sceHeap.cpp @@ -140,8 +140,15 @@ static u32 sceHeapAllocHeapMemoryWithOption(u32 heapAddr, u32 memSize, u32 param } // There's 8 bytes at the end of every block, reserved. + if (memSize > 0xFFFFFF00) { + return hleLogError(Log::HLE, 0, "invalid size %08x", memSize); + } memSize += 8; u32 addr = heap->alloc.AllocAligned(memSize, grain, grain, true); + // This returns a pointer, so failure is null rather than the allocator's -1 (like sceKernelAllocHeapMemory). + if (addr == (u32)-1) { + return hleLogError(Log::HLE, 0, "failed to allocate %d bytes", memSize); + } return hleLogDebug(Log::HLE, addr); } @@ -179,6 +186,7 @@ static int sceHeapDeleteHeap(u32 heapAddr) { } heapList.erase(heapAddr); + userMemory.Free(heap->address); delete heap; return hleLogDebug(Log::HLE, 0); } @@ -222,9 +230,15 @@ static u32 sceHeapAllocHeapMemory(u32 heapAddr, u32 memSize) { } // There's 8 bytes at the end of every block, reserved. + if (memSize > 0xFFFFFF00) { + return hleLogError(Log::HLE, 0, "invalid size %08x", memSize); + } memSize += 8; // Always goes down, regardless of whether the heap is high or low. u32 addr = heap->alloc.Alloc(memSize, true); + if (addr == (u32)-1) { + return hleLogError(Log::HLE, 0, "failed to allocate %d bytes", memSize); + } return hleLogDebug(Log::HLE, addr); } diff --git a/Core/HLE/sceP3da.cpp b/Core/HLE/sceP3da.cpp index f225bfaee0..4293f2f40e 100644 --- a/Core/HLE/sceP3da.cpp +++ b/Core/HLE/sceP3da.cpp @@ -43,7 +43,9 @@ static inline int getScaleValue(u32 channelsNum) { // What is this?? static u32 sceP3daBridgeCore(u32 p3daCoreAddr, u32 channelsNum, u32 samplesNum, u32 inputAddr, u32 outputAddr) { - if (Memory::IsValidRange(inputAddr, channelsNum * 4) && Memory::IsValidRange(outputAddr, samplesNum * sizeof(s16) * 2)) { + // The sizes could wrap in 32 bits. + if ((u64)channelsNum * 4 <= 0xFFFFFFFF && (u64)samplesNum * sizeof(s16) * 2 <= 0xFFFFFFFF && + Memory::IsValidRange(inputAddr, channelsNum * 4) && Memory::IsValidRange(outputAddr, samplesNum * sizeof(s16) * 2)) { int scaleval = getScaleValue(channelsNum); s16_le *outbuf = (s16_le *)Memory::GetPointerWriteUnchecked(outputAddr); memset(outbuf, 0, samplesNum * sizeof(s16) * 2); diff --git a/Core/HLE/sceRtc.cpp b/Core/HLE/sceRtc.cpp index 89314c8316..420cb863ca 100644 --- a/Core/HLE/sceRtc.cpp +++ b/Core/HLE/sceRtc.cpp @@ -23,6 +23,8 @@ #include #endif +#include +#include #include #include "Common/Serialize/Serializer.h" @@ -998,7 +1000,8 @@ static int __RtcFormatRFC2822(u32 outPtr, u32 srcTickPtr, int tz) { local.tm_year += 400; while (local.tm_year >= 470) local.tm_year -= 400; - local.tm_min += tz; + // tz is from the game, keep the sum from overflowing. + local.tm_min = (int)std::clamp((s64)local.tm_min + tz, INT_MIN / 2, INT_MAX / 2); rtc_timegm(&local); char *out = (char *)Memory::GetPointerWriteUnchecked(outPtr); @@ -1029,7 +1032,8 @@ static int __RtcFormatRFC3339(u32 outPtr, u32 srcTickPtr, int tz) { local.tm_year += 400; while (local.tm_year >= 470) local.tm_year -= 400; - local.tm_min += tz; + // tz is from the game, keep the sum from overflowing. + local.tm_min = (int)std::clamp((s64)local.tm_min + tz, INT_MIN / 2, INT_MAX / 2); rtc_timegm(&local); char *out = (char *)Memory::GetPointerWriteUnchecked(outPtr); diff --git a/Core/HLE/sceSfmt19937.cpp b/Core/HLE/sceSfmt19937.cpp index f05aa5138a..ff1414a923 100644 --- a/Core/HLE/sceSfmt19937.cpp +++ b/Core/HLE/sceSfmt19937.cpp @@ -26,6 +26,12 @@ #include "Core/HLE/sceSfmt19937.h" +// The library indexes its state with idx, which lives in game memory. +static bool ValidSfmtIndex(u32 sfmt) { + const sfmt_t *psfmt = (const sfmt_t *)Memory::GetPointerUnchecked(sfmt); + return psfmt->idx >= 0 && psfmt->idx <= SFMT_N32; +} + static int sceSfmt19937InitGenRand(u32 sfmt, u32 seed) { if (!Memory::IsValidAddress(sfmt)) { ERROR_LOG(Log::HLE, "sceSfmt19937InitGenRand(sfmt=%08x, seed=%08x) - bad address(es)", sfmt, seed); @@ -54,7 +60,7 @@ static int sceSfmt19937InitByArray(u32 sfmt, u32 seeds, int seedslen) { } static u32 sceSfmt19937GenRand32(u32 sfmt) { - if (!Memory::IsValidAddress(sfmt)) { + if (!Memory::IsValidAddress(sfmt) || !ValidSfmtIndex(sfmt)) { ERROR_LOG(Log::HLE, "sceSfmt19937GenRand32(sfmt=%08x) - bad address(es)", sfmt); return -1; } @@ -67,7 +73,7 @@ static u32 sceSfmt19937GenRand32(u32 sfmt) { } static u64 sceSfmt19937GenRand64(u32 sfmt) { - if (!Memory::IsValidAddress(sfmt)) { + if (!Memory::IsValidAddress(sfmt) || !ValidSfmtIndex(sfmt)) { ERROR_LOG(Log::HLE, "sceSfmt19937GenRand64(sfmt=%08x) - bad address(es)", sfmt); return -1; } @@ -80,7 +86,8 @@ static u64 sceSfmt19937GenRand64(u32 sfmt) { } static int sceSfmt19937FillArray32(u32 sfmt, u32 array, int arraylen) { - if (!Memory::IsValidAddress(sfmt) || !Memory::IsValidAddress(array) || !Memory::IsValidAddress(array + 4 * (arraylen - 1))) { + // The library fills whole blocks, and needs at least a state's worth of them. + if (arraylen < SFMT_N32 || (arraylen % 4) != 0 || !Memory::IsValidAddress(sfmt) || !Memory::IsValidAddress(array) || !Memory::IsValidAddress(array + 4 * (arraylen - 1))) { ERROR_LOG(Log::HLE, "sceSfmt19937FillArray32(sfmt=%08x, ar=%08x, arlen=%08x) - bad address(es)", sfmt, array, arraylen); return -1; } @@ -94,7 +101,8 @@ static int sceSfmt19937FillArray32(u32 sfmt, u32 array, int arraylen) { } static int sceSfmt19937FillArray64(u32 sfmt, u32 array, int arraylen) { - if (!Memory::IsValidAddress(sfmt) || !Memory::IsValidAddress(array) || !Memory::IsValidAddress(array + 8 * (arraylen - 1))) { + // The library fills whole blocks, and needs at least a state's worth of them. + if (arraylen < SFMT_N64 || (arraylen % 2) != 0 || !Memory::IsValidAddress(sfmt) || !Memory::IsValidAddress(array) || !Memory::IsValidAddress(array + 8 * (arraylen - 1))) { ERROR_LOG(Log::HLE, "sceSfmt19937FillArray64(sfmt=%08x, ar=%08x, arlen=%08x) - bad address(es)", sfmt, array, arraylen); return -1; } diff --git a/Core/HLE/sceSysreg.cpp b/Core/HLE/sceSysreg.cpp index 34b776e846..045ab149f7 100644 --- a/Core/HLE/sceSysreg.cpp +++ b/Core/HLE/sceSysreg.cpp @@ -168,7 +168,9 @@ static int sceKernelUnregisterSysEventHandler(u32 handler) { if (!MeCore::g_meState) return hleLogWarning(Log::ME, 0, "no handlers"); u32 *link = &MeCore::g_meState->sysEventHandlers; - while (*link && Memory::IsValidRange(*link, 0x40)) { + // The list is in guest memory, so a loop in it shouldn't hang us. + int count = 0; + while (*link && Memory::IsValid4AlignedRange(*link, 0x40) && count++ < 0x10000) { if (*link == handler) { *link = Memory::ReadUnchecked_U32(handler + 0x18); return hleLogDebug(Log::ME, 0); diff --git a/Core/HLE/sceUsb.cpp b/Core/HLE/sceUsb.cpp index e7960325de..91479483d5 100644 --- a/Core/HLE/sceUsb.cpp +++ b/Core/HLE/sceUsb.cpp @@ -76,6 +76,12 @@ static void UsbWaitExecTimeout(u64 userdata, int cycleslate) { u32 error; SceUID threadID = (SceUID)userdata; + // The thread may have stopped waiting on USB since (for example, released), keeping the event. + if (__KernelGetWaitID(threadID, WAITTYPE_USB, error) == 0 || error != 0) { + HLEKernel::RemoveWaitingThread(waitingThreads, threadID); + return; + } + PSPPointer timeout = PSPPointer::Create(__KernelGetWaitTimeoutPtr(threadID, error)); if (timeout.IsValid()) *timeout = 0; @@ -91,7 +97,8 @@ static void UsbUpdateState() { for (size_t i = 0; i < waitingThreads.size(); ++i) { SceUID threadID = waitingThreads[i]; int state = __KernelGetWaitID(threadID, WAITTYPE_USB, error); - if (error != 0) + // 0 means it's not in a USB wait anymore. Waits always have a nonzero state. + if (error != 0 || state == 0) continue; u32 mode = __KernelGetWaitValue(threadID, error); @@ -109,6 +116,7 @@ static void UsbUpdateState() { } __KernelResumeThreadFromWait(threadID, UsbCurrentState()); + wokeThreads = true; } } diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 4ba4a09326..a819f6f6c1 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -94,6 +94,8 @@ void __UsbCamShutdown() { if (config->mode == Camera::Mode::Video) { // stillImage? TBD Camera::stopCapture(); } + // Capture can still deliver a frame after stopCapture(), on its own thread. + std::lock_guard lock(videoBufferMutex); delete[] videoBuffer; videoBuffer = nullptr; delete config; @@ -422,7 +424,9 @@ void Camera::onCameraDeviceChange() { } } +// Called from the capture thread. int Camera::getMaxFrameSize() { + std::lock_guard lock(videoBufferMutex); int framesize = 0; if (config) { if (config->type == Camera::ConfigType::CfVideoEx) { diff --git a/Core/HLE/sceUsbGps.cpp b/Core/HLE/sceUsbGps.cpp index f97d430e16..ad39306736 100644 --- a/Core/HLE/sceUsbGps.cpp +++ b/Core/HLE/sceUsbGps.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include "Common/System/System.h" #include "Common/System/Request.h" @@ -39,6 +40,8 @@ GpsStatus gpsStatus = GPS_STATE_OFF; GpsData gpsData; SatData satData; time_t lastGPSTime; +// The platform's location callbacks set these from their own thread. +static std::recursive_mutex gpsLock; void __UsbGpsInit() { gpsStatus = GPS_STATE_OFF; @@ -99,6 +102,7 @@ static int sceUsbGpsClose() { } static int sceUsbGpsGetData(u32 gpsDataAddr, u32 satDataAddr) { + std::lock_guard guard(gpsLock); time_t currentTime; time(¤tTime); if (difftime(currentTime, lastGPSTime) > AUTO_UPDATE_GPSTIME) @@ -116,7 +120,7 @@ static int sceUsbGpsGetData(u32 gpsDataAddr, u32 satDataAddr) { auto satData = PSPPointer::Create(satDataAddr); if (satData.IsValid()) { *satData = *GPS::getSatData(); - gpsData.NotifyWrite("UsbGpsGetData"); + satData.NotifyWrite("UsbGpsGetData"); } return 0; } @@ -167,6 +171,7 @@ void GPS::init() { } void GPS::setGpsTime(time_t *time) { + std::lock_guard guard(gpsLock); struct tm *gpsTime; gpsTime = gmtime(time); @@ -179,8 +184,9 @@ void GPS::setGpsTime(time_t *time) { } void GPS::setGpsData(long long gpsTime, float hdop, float latitude, float longitude, float altitude, float speed, float bearing) { - lastGPSTime = gpsTime; - setGpsTime((time_t*)&gpsTime); + std::lock_guard guard(gpsLock); + lastGPSTime = (time_t)gpsTime; + setGpsTime(&lastGPSTime); gpsData.hdop = hdop; gpsData.latitude = latitude; @@ -191,6 +197,9 @@ void GPS::setGpsData(long long gpsTime, float hdop, float latitude, float longit } void GPS::setSatInfo(short index, unsigned char id, unsigned char elevation, short azimuth, unsigned char snr, unsigned char good) { + if (index < 0 || index >= (short)ARRAY_SIZE(satData.satInfo)) + return; + std::lock_guard guard(gpsLock); satData.satInfo[index].id = id; satData.satInfo[index].elevation = elevation; satData.satInfo[index].azimuth = azimuth; diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp index 2567876078..8e465d81e7 100644 --- a/Core/HLE/sceUsbMic.cpp +++ b/Core/HLE/sceUsbMic.cpp @@ -168,6 +168,10 @@ void __UsbMicDoState(PointerWrap &p) { Do(p, waitingThreads); Do(p, isNeedInput); Do(p, curSampleRate); + if (curSampleRate == 0) { + // Old states could have one, from before __MicInput checked it. + curSampleRate = 44100; + } Do(p, curChannels); Do(p, micState); if (s > 1) { @@ -433,6 +437,14 @@ void Microphone::onMicDeviceChange() { } u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool block) { + // sceAudioInput and the camera mic pass these straight through. The wait below divides by the + // rate, and the size sizes a host buffer. Not tested on the PSP. + if (sampleRate == 0) { + return SCE_ERROR_USBMIC_INVALID_SAMPLERATE; + } + if (maxSamples == 0 || maxSamples > 0x100000) { + return SCE_ERROR_USBMIC_INVALID_MAX_SAMPLES; + } if (micPolling && curSampleRate != sampleRate) { Microphone::stopMic(); } From 561badca05b0949fe09540f51a371a45a39ca0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Oct 2026 10:56:46 +0100 Subject: [PATCH 3/3] sceAudio: Check sceAudioInput's arguments and routing modes like the PSP pspautotests audio/sceaudio/input and audio/sceaudio/routing. - sceAudioInput fails until sceAudioInputInit, then checks that the sample count is a nonzero multiple of 64 before checking the rate (11025, 22050 or 44100 only). A rate of 0 now gets the PSP's error instead of the USB mic's. Init takes about 3ms. - A blocking mic input woke with the byte count rather than the sample count that the non-blocking call returns. - The routing functions are exported by both sceAudio and sceAudioRouting, and the latter had its own copy that ignored the stored modes. Both now share the one in sceAudio, with the ranges checked. SetVolumeMode returns 0, not the previous mode, and both modes start at 0. sceAudio's savestate section goes to version 5 for the input init flag. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/ErrorCodes.h | 2 + Core/HLE/__sceAudio.cpp | 10 ++++- Core/HLE/sceAudio.cpp | 81 ++++++++++++++++++++++++++---------- Core/HLE/sceAudio.h | 9 ++++ Core/HLE/sceAudioRouting.cpp | 34 +-------------- Core/HLE/sceUsbMic.cpp | 10 +++-- pspautotests | 2 +- test.py | 2 + 8 files changed, 89 insertions(+), 61 deletions(-) diff --git a/Core/HLE/ErrorCodes.h b/Core/HLE/ErrorCodes.h index 35e18fc256..9af26b28df 100644 --- a/Core/HLE/ErrorCodes.h +++ b/Core/HLE/ErrorCodes.h @@ -4,6 +4,7 @@ enum PSPErrorCode : u32 { SCE_KERNEL_ERROR_OK = 0, + SCE_KERNEL_ERROR_NOT_INITIALIZED = 0x80000001, // Name guessed, from sceAudioInput before init. SCE_KERNEL_ERROR_BAD_ARGUMENT = 0x80000004, // Maybe SCE_KERNEL_ERROR_ALREADY = 0x80000020, SCE_KERNEL_ERROR_BUSY = 0x80000021, @@ -373,6 +374,7 @@ enum PSPErrorCode : u32 { SCE_ERROR_AUDIO_INVALID_FREQUENCY = 0x8026000A, SCE_ERROR_AUDIO_INVALID_VOLUME = 0x8026000B, SCE_ERROR_AUDIO_CHANNEL_ALREADY_RESERVED = 0x80268002, + SCE_ERROR_AUDIO_ROUTING_INVALID_MODE = 0x802C01FE, SCE_ERROR_USBMIC_INVALID_MAX_SAMPLES = 0x80243806, SCE_ERROR_USBMIC_INVALID_SAMPLERATE = 0x8024380A, diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 1f35e600b9..e5a0b95566 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -112,6 +112,8 @@ void __AudioInit() { g_audioChans[i].clear(); } g_audioSRC.clear(); + __AudioRoutingInit(); + audioInputInited = false; mixBuffer = new s32[hwBlockSize * 2]; clampedMixBuffer = new s16[hwBlockSize * 2]; @@ -121,7 +123,7 @@ void __AudioInit() { } void __AudioDoState(PointerWrap &p) { - auto s = p.Section("sceAudio", 1, 4); + auto s = p.Section("sceAudio", 1, 5); if (!s) return; @@ -197,6 +199,12 @@ void __AudioDoState(PointerWrap &p) { } else { mixerDMABusy = false; } + if (s >= 5) { + Do(p, audioInputInited); + } else { + // A game that was already capturing had initialized it. + audioInputInited = true; + } // For older states the routing modes were read back once per channel, above. __AudioCPUMHzChange(); diff --git a/Core/HLE/sceAudio.cpp b/Core/HLE/sceAudio.cpp index 5a02ca265b..81b3b0b8c7 100644 --- a/Core/HLE/sceAudio.cpp +++ b/Core/HLE/sceAudio.cpp @@ -33,10 +33,11 @@ const u32 PSP_AUDIO_SAMPLE_MAX = 65536 - 64; const int PSP_AUDIO_ERROR_SRC_FORMAT_4 = 0x80000003; -const int AUDIO_ROUTING_SPEAKER_OFF = 0; -const int AUDIO_ROUTING_SPEAKER_ON = 1; -int defaultRoutingMode = AUDIO_ROUTING_SPEAKER_ON; -int defaultRoutingVolMode = AUDIO_ROUTING_SPEAKER_ON; +// Both read 0 at boot on a PSP. +int defaultRoutingMode = 0; +int defaultRoutingVolMode = 0; +// sceAudioInput fails until sceAudioInputInit. +bool audioInputInited = false; // Only still here to read savestates written before the channels held buffer pointers. struct AudioChannelWaitInfo { @@ -157,6 +158,11 @@ void AudioSRCChannel::DoState(PointerWrap &p) { Do(p, waitingThreads); } +void __AudioRoutingInit() { + defaultRoutingMode = 0; + defaultRoutingVolMode = 0; +} + void __AudioRoutingDoState(PointerWrap &p) { auto s = p.Section("sceAudioRouting", 1); if (!s) @@ -543,7 +549,25 @@ static u32 sceAudioSRCOutputBlocking(u32 vol, u32 buf) { return hleLogDebug(Log::sceAudio, __AudioSRCEnqueueBlocking(g_audioSRC, buf, vol)); } +// From pspautotests audio/sceaudio/input. The size is checked first. +static int AudioInputCheck(u32 maxSamples, u32 sampleRate) { + if (!audioInputInited) { + return SCE_KERNEL_ERROR_NOT_INITIALIZED; + } + if (maxSamples == 0 || (maxSamples & 63) != 0 || maxSamples >= 0x80000000) { + return SCE_ERROR_AUDIO_OUTPUT_SAMPLE_DATA_SIZE_NOT_ALIGNED; + } + if (sampleRate != 44100 && sampleRate != 22050 && sampleRate != 11025) { + return SCE_ERROR_AUDIO_INVALID_FREQUENCY; + } + return 0; +} + static int sceAudioInputBlocking(u32 maxSamples, u32 sampleRate, u32 bufAddr) { + int error = AudioInputCheck(maxSamples, sampleRate); + if (error != 0) { + return hleLogError(Log::HLE, error); + } if (!Memory::IsValidAddress(bufAddr)) { return hleLogError(Log::HLE, -1, "invalid address"); } @@ -551,6 +575,13 @@ static int sceAudioInputBlocking(u32 maxSamples, u32 sampleRate, u32 bufAddr) { } static int sceAudioInput(u32 maxSamples, u32 sampleRate, u32 bufAddr) { + int error = AudioInputCheck(maxSamples, sampleRate); + if (error != 0) { + return hleLogError(Log::HLE, error); + } + if (bufAddr == 0) { + return hleLogWarning(Log::HLE, 0, "NULL buffer"); + } if (!Memory::IsValidAddress(bufAddr)) { return hleLogError(Log::HLE, -1, "invalid address"); } @@ -561,11 +592,14 @@ static int sceAudioInput(u32 maxSamples, u32 sampleRate, u32 bufAddr) { static int sceAudioInputInit(int unknown1, int gain, int unknown2) { ERROR_LOG(Log::HLE, "UNIMPL sceAudioInputInit: unknown1: %d, gain: %d, unknown2: %d", unknown1, gain, unknown2); - return 0; + audioInputInited = true; + // Takes about 3ms on a PSP. + return hleDelayResult(0, "audio input init", 3000); } static int sceAudioInputInitEx(u32 paramAddr) { ERROR_LOG(Log::HLE, "UNIMPL sceAudioInputInitEx: paramAddr: %08x", paramAddr); + audioInputInited = true; return 0; } @@ -583,28 +617,31 @@ static int sceAudioGetInputLength() { return ret; } -static u32 sceAudioRoutingSetMode(u32 mode) { - ERROR_LOG(Log::sceAudio, "sceAudioRoutingSetMode(%08x)", mode); +// Ranges and return values from pspautotests audio/sceaudio/routing. Also exported as sceAudioRouting. +int sceAudioRoutingSetMode(int mode) { + if (mode < 0 || mode > 3) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_ROUTING_INVALID_MODE, "invalid mode"); + } int previousMode = defaultRoutingMode; defaultRoutingMode = mode; - return previousMode; + return hleLogInfo(Log::sceAudio, previousMode); } -static u32 sceAudioRoutingGetMode() { - ERROR_LOG(Log::sceAudio, "sceAudioRoutingGetMode()"); - return defaultRoutingMode; +int sceAudioRoutingGetMode() { + return hleLogDebug(Log::sceAudio, defaultRoutingMode); } -static u32 sceAudioRoutingSetVolumeMode(u32 mode) { - ERROR_LOG(Log::sceAudio, "sceAudioRoutingSetVolumeMode(%08x)", mode); - int previousMode = defaultRoutingVolMode; +// Unlike SetMode, this returns 0 rather than the previous mode. +int sceAudioRoutingSetVolumeMode(int mode) { + if (mode < 0 || mode > 1) { + return hleLogError(Log::sceAudio, SCE_ERROR_AUDIO_ROUTING_INVALID_MODE, "invalid mode"); + } defaultRoutingVolMode = mode; - return previousMode; + return hleLogInfo(Log::sceAudio, 0); } -static u32 sceAudioRoutingGetVolumeMode() { - ERROR_LOG(Log::sceAudio, "sceAudioRoutingGetVolumeMode()"); - return defaultRoutingVolMode; +int sceAudioRoutingGetVolumeMode() { + return hleLogDebug(Log::sceAudio, defaultRoutingVolMode); } const HLEFunction sceAudio[] = @@ -653,10 +690,10 @@ const HLEFunction sceAudio[] = {0XA633048E, &WrapI_V, "sceAudioPollInputEnd", 'i', "" }, {0X87B2E651, &WrapI_V, "sceAudioWaitInputEnd", 'i', "" }, - {0X36FD8AA9, &WrapU_U, "sceAudioRoutingSetMode", 'x', "x" }, - {0X39240E7D, &WrapU_V, "sceAudioRoutingGetMode", 'x', "" }, - {0XBB548475, &WrapU_U, "sceAudioRoutingSetVolumeMode", 'x', "x" }, - {0X28235C56, &WrapU_V, "sceAudioRoutingGetVolumeMode", 'x', "" }, + {0X36FD8AA9, &WrapI_I, "sceAudioRoutingSetMode", 'i', "i" }, + {0X39240E7D, &WrapI_V, "sceAudioRoutingGetMode", 'i', "" }, + {0XBB548475, &WrapI_I, "sceAudioRoutingSetVolumeMode", 'i', "i" }, + {0X28235C56, &WrapI_V, "sceAudioRoutingGetVolumeMode", 'i', "" }, }; diff --git a/Core/HLE/sceAudio.h b/Core/HLE/sceAudio.h index 097b91ffee..eb53988e3d 100644 --- a/Core/HLE/sceAudio.h +++ b/Core/HLE/sceAudio.h @@ -114,7 +114,16 @@ extern AudioSRCChannel g_audioSRC; bool SRCFrequencyAllowed(int freq); // The two routing modes are globals rather than per-channel; they get their own little block. +void __AudioRoutingInit(); + +extern bool audioInputInited; void __AudioRoutingDoState(PointerWrap &p); +// sceAudioRouting exports the same four functions. +int sceAudioRoutingSetMode(int mode); +int sceAudioRoutingGetMode(); +int sceAudioRoutingSetVolumeMode(int mode); +int sceAudioRoutingGetVolumeMode(); + void Register_sceAudio(); diff --git a/Core/HLE/sceAudioRouting.cpp b/Core/HLE/sceAudioRouting.cpp index c7c91f3f2f..d941b25aa0 100644 --- a/Core/HLE/sceAudioRouting.cpp +++ b/Core/HLE/sceAudioRouting.cpp @@ -17,39 +17,7 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" -#include "Core/HLE/sceAudiocodec.h" - -enum { - AUDIO_ROUTING_SPEAKER_OFF = 0, - AUDIO_ROUTING_SPEAKER_ON = 1, -}; - -u32 audioRoutingMode = AUDIO_ROUTING_SPEAKER_ON; -u32 audioRoutineVolumeMode = AUDIO_ROUTING_SPEAKER_ON; - -static int sceAudioRoutingGetMode() { - INFO_LOG(Log::sceAudio, "sceAudioRoutingGetMode"); - return 0; -} - -static int sceAudioRoutingSetVolumeMode(int mode) { - INFO_LOG(Log::sceAudio, "sceAudioRoutingSetVolumeMode %d", mode); - int previousMode = audioRoutineVolumeMode; - audioRoutineVolumeMode = mode; - return previousMode; -} - -static int sceAudioRoutingGetVolumeMode() { - INFO_LOG(Log::sceAudio, "sceAudioRoutingGetMode"); - return 0; -} - -static int sceAudioRoutingSetMode(int mode) { - INFO_LOG(Log::sceAudio, "sceAudioRoutingSetMode %d", mode); - int previousMode = audioRoutingMode; - audioRoutingMode = mode; - return previousMode; -} +#include "Core/HLE/sceAudio.h" const HLEFunction sceAudioRouting[] = { diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp index 8e465d81e7..499596fd26 100644 --- a/Core/HLE/sceUsbMic.cpp +++ b/Core/HLE/sceUsbMic.cpp @@ -437,8 +437,8 @@ void Microphone::onMicDeviceChange() { } u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool block) { - // sceAudioInput and the camera mic pass these straight through. The wait below divides by the - // rate, and the size sizes a host buffer. Not tested on the PSP. + // The camera mic passes these straight through (sceAudioInput checks them first). The wait below + // divides by the rate, and the size sizes a host buffer. if (sampleRate == 0) { return SCE_ERROR_USBMIC_INVALID_SAMPLERATE; } @@ -484,9 +484,11 @@ u32 __MicInput(u32 maxSamples, u32 sampleRate, u32 bufAddr, MICTYPE type, bool b MicWaitInfo waitInfo = { __KernelGetCurThread(), bufAddr, size, sampleRate }; waitingThreads.push_back(waitInfo); DEBUG_LOG(Log::HLE, "MicInputBlocking: blocking thread(%d)", (int)__KernelGetCurThread()); - __KernelWaitCurThread(WAITTYPE_MICINPUT, 1, size, 0, false, "blocking microphone"); + // The wait value is what the thread gets back when it's woken. + const u32 result = type == CAMERAMIC ? size : maxSamples; + __KernelWaitCurThread(WAITTYPE_MICINPUT, 1, result, 0, false, "blocking microphone"); - return type == CAMERAMIC ? size : maxSamples; + return result; } // A non-blocking input keeps filling its buffer from addAudioData() after it returns. diff --git a/pspautotests b/pspautotests index fab33ea469..e2d098fcf3 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit fab33ea469ae6d82980064f3e29299e6920bf2e4 +Subproject commit e2d098fcf3c3396729a915094195b15a8ce8171e diff --git a/test.py b/test.py index f8cc384c5e..1bd99b35e5 100755 --- a/test.py +++ b/test.py @@ -173,6 +173,8 @@ tests_good = [ "audio/blocking/vaudio", "audio/sceaudio/reserve", "audio/sceaudio/datalen", + "audio/sceaudio/input", + "audio/sceaudio/routing", "audio/output2/changelength", "audio/output2/release", "audio/output2/reserve",