Keep the SHA-1 context in game memory too, and scope the Tlspl partition range to user mode

sceKernelUtilsSha1Block* had the same single global context that MD5 did, so it gets the same
treatment: state, counters and block buffer now live at ctxAddr in the layout hash/sha1ctx
records off hardware. Unlike MD5, SHA-1 does not stream whole blocks through buf, which happens
to be what our sha1_update already does - so no fill-in step is needed there.

The Tlspl partition range from the last commit was too broad a cut. Hardware says only 1-6 exist,
but that recording is from user mode, and BlockAllocatorFromID deliberately maps 8 and 10 to the
user partition for a kernel-mode caller - rejecting them outright would have taken that away.
The tightened range now applies to user mode only and kernel mode keeps what it had.
threads/tls/partition also shows the answer doesn't depend on the compiled SDK version, checked
across 1.00 through 6.06, and that partition 5 is accepted - which no test had covered.
This commit is contained in:
Henrik Rydgård committed 2026-09-08 15:18:02 -06:00
1 parent 8a02d1ee0f
commit 705ea7eb02
4 files changed
+82 -30

No files matched your search

+11 -4
View File
@@ -1863,10 +1863,17 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name");
if ((attr & ~PSP_TLSPL_ATTR_KNOWN) >= 0x100)
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr);
// Tlspl draws the line at 6, unlike Vpl above: threads/tls/create records 7, 8, 9 and 10 all
// returning ILLEGAL_ARGUMENT on a real PSP, where threads/vpl/create has 8 and 9 falling
// through to ILLEGAL_PERM. Same-looking check, genuinely different range.
if (partition < 1 || partition > 6)
// From user mode only 1-6 exist: threads/tls/partition records 7 and up all returning
// ILLEGAL_ARGUMENT on a real PSP, where 1, 3 and 4 fall through to ILLEGAL_PERM below. Note
// this differs from sceKernelCreateVpl above, which does let 8 and 9 reach the permission
// check - the two used to share a range that was only ever right for Vpl.
//
// The kernel-mode range is left as it was, since 8 and 10 map to the user partition for a
// kernel caller and there's no hardware recording from kernel mode to check it against - a
// test PRX built on this suite's common code doesn't fit in the kernel partition alongside
// PSPLink.
const u32 highestPartition = hleIsKernelMode() ? 9 : 6;
if (partition < 1 || partition == 7 || partition > highestPartition)
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition);
BlockAllocator *allocator = BlockAllocatorFromID(partition);
+68 -25
View File
@@ -52,8 +52,8 @@ u32 sceKernelUtilsMt19937UInt(u32 ctx) {
// can be in flight at once. Layout confirmed against a real PSP by pspautotests hash/md5ctx:
// 96 bytes, and the word at offset 16 is never written by the kernel.
//
// sceKernelUtilsSha1Block* below still keeps one global state, for the same reason this used to -
// there's no hardware recording of the SHA-1 context layout yet.
// SHA-1 gets the same treatment further down. Its context is the same size with the same
// bookkeeping, but note it does not stream whole blocks through buf the way MD5 does.
struct PSPMd5Context {
u32_le h[4];
u32_le pad; // the kernel leaves this one alone
@@ -191,7 +191,55 @@ int sceKernelUtilsMd5BlockResult(u32 ctxAddr, u32 digestAddr) {
}
static sha1_context sha1_ctx;
// SHA-1's context, confirmed against a real PSP by pspautotests hash/sha1ctx. Same 96 bytes and
// same bookkeeping as MD5, but no pad word - and unlike MD5, a whole-block update leaves buf
// alone rather than copying the block through it, which is what our sha1 does anyway.
struct PSPSha1Context {
u32_le h[5];
u16_le usRemains;
u16_le usComputed;
u64_le ullTotalLen;
u8 buf[64];
};
static void Sha1ContextRead(const PSPPointer<PSPSha1Context> &ctx, sha1_context *out) {
for (int i = 0; i < 5; i++) {
out->state[i] = ctx->h[i];
}
u64 total = ctx->ullTotalLen;
out->total[0] = (u32)total;
out->total[1] = (u32)(total >> 32);
memcpy(out->buffer, ctx->buf, sizeof(out->buffer));
}
static void Sha1ContextWrite(PSPPointer<PSPSha1Context> &ctx, const sha1_context *in) {
for (int i = 0; i < 5; i++) {
ctx->h[i] = (u32)in->state[i];
}
u64 total = (u64)(u32)in->total[0] | ((u64)(u32)in->total[1] << 32);
ctx->ullTotalLen = total;
ctx->usRemains = (u16)(total & 0x3F);
ctx->usComputed = 0;
memcpy(ctx->buf, in->buffer, sizeof(ctx->buf));
ctx.NotifyWrite("Sha1Context");
}
static int Sha1BlockInit(u32 ctxAddr) {
auto ctx = PSPPointer<PSPSha1Context>::Create(ctxAddr);
if (!ctx.IsValid())
return hleLogError(Log::HLE, -1, "bad context address");
sha1_context fresh;
sha1_starts(&fresh);
for (int i = 0; i < 5; i++) {
ctx->h[i] = (u32)fresh.state[i];
}
ctx->usRemains = 0;
ctx->usComputed = 0;
ctx->ullTotalLen = 0;
ctx.NotifyWrite("Sha1Context");
return hleLogDebug(Log::HLE, 0);
}
int sceKernelUtilsSha1Digest(u32 dataAddr, int len, u32 digestAddr) {
DEBUG_LOG(Log::HLE, "sceKernelUtilsSha1Digest(%08x, %d, %08x)", dataAddr, len, digestAddr);
@@ -204,34 +252,29 @@ int sceKernelUtilsSha1Digest(u32 dataAddr, int len, u32 digestAddr) {
}
int sceKernelUtilsSha1BlockInit(u32 ctxAddr) {
DEBUG_LOG(Log::HLE, "sceKernelUtilsSha1BlockInit(%08x)", ctxAddr);
if (!Memory::IsValidAddress(ctxAddr))
return -1;
// TODO: Until I know how large a context is, we just go all lazy and use a global context,
// which will work just fine unless games do several MD5 concurrently.
sha1_starts(&sha1_ctx);
return 0;
return Sha1BlockInit(ctxAddr);
}
int sceKernelUtilsSha1BlockUpdate(u32 ctxAddr, u32 dataAddr, int len) {
DEBUG_LOG(Log::HLE, "sceKernelUtilsSha1BlockUpdate(%08x, %08x, %d)", ctxAddr, dataAddr, len);
if (!Memory::IsValidAddress(ctxAddr) || !Memory::IsValidAddress(dataAddr))
return -1;
sha1_update(&sha1_ctx, Memory::GetPointerWriteUnchecked(dataAddr), (int)len);
return 0;
auto ctx = PSPPointer<PSPSha1Context>::Create(ctxAddr);
if (!ctx.IsValid() || !Memory::IsValidRange(dataAddr, len))
return hleLogError(Log::HLE, -1, "bad address");
sha1_context work;
Sha1ContextRead(ctx, &work);
sha1_update(&work, Memory::GetPointerWriteUnchecked(dataAddr), (int)len);
Sha1ContextWrite(ctx, &work);
return hleLogDebug(Log::HLE, 0);
}
int sceKernelUtilsSha1BlockResult(u32 ctxAddr, u32 digestAddr) {
DEBUG_LOG(Log::HLE, "sceKernelUtilsSha1BlockResult(%08x, %08x)", ctxAddr, digestAddr);
if (!Memory::IsValidAddress(ctxAddr) || !Memory::IsValidAddress(digestAddr))
return -1;
sha1_finish(&sha1_ctx, Memory::GetPointerWriteUnchecked(digestAddr));
return 0;
auto ctx = PSPPointer<PSPSha1Context>::Create(ctxAddr);
if (!ctx.IsValid() || !Memory::IsValidRange(digestAddr, 20))
return hleLogError(Log::HLE, -1, "bad address");
sha1_context work;
Sha1ContextRead(ctx, &work);
sha1_finish(&work, Memory::GetPointerWriteUnchecked(digestAddr));
Sha1ContextWrite(ctx, &work);
return hleLogDebug(Log::HLE, 0);
}
+2
View File
@@ -212,6 +212,7 @@ tests_good = [
"hash/hash",
"hash/md5ctx",
"hash/mt19937ctx",
"hash/sha1ctx",
"hle/check_not_used_uids",
"intr/intr",
"intr/enablesub",
@@ -339,6 +340,7 @@ tests_good = [
"threads/threads/threadmanidtype",
"threads/threads/threads",
"threads/tls/create",
"threads/tls/partition",
"threads/tls/delete",
"threads/tls/get",
"threads/tls/free",