Don't drop an ISO's trailing partial sector

Not every disc image is a whole number of 2048-byte sectors - tools that build
pre-patched ISOs write images that stop partway through their last one, with a
file legitimately ending there. Two things then conspired to lose that tail.

FileBlockDevice::GetNumBlocks() rounds down, so the partial sector isn't
counted, and the file size clamp in ISOFileSystem measured what the image holds
in whole blocks. A file running to the last byte of such an image got clamped
short - by up to a sector - before anything read it.

FileBlockDevice::ReadBlock() then returned false for a short read of that
sector, and ISOFileSystem::ReadFile substitutes an all-zero sector when a read
fails, so even the bytes that were there came back as zeroes.

Measure the clamp in bytes via GetUncompressedSize() instead of blocks, and
treat a short read at the end of the image as a success with the rest of the
sector zeroed. GetUncompressedSize() defaults to the block-based value and is
only overridden by FileBlockDevice, so nothing else changes behaviour.

Also report why a module was rejected. "Failed to load module" named the file
and nothing else, and the truncation check logged only the byte count, which
points at the executable when the real cause is that the loader was handed
fewer bytes than the file has. ElfReader now keeps the reason for a failed
LoadInto, __KernelLoadELFFromPtr puts it in the error string that reaches the
user, and both messages say which header table overran and by how much.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Henrik Rydgård
2026-09-08 12:25:21 -06:00
co-authored by Claude Opus 5
parent 1fc823681f
commit 51ec045947
5 changed files with 48 additions and 8 deletions
+9 -2
View File
@@ -1468,7 +1468,13 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
int result = reader.LoadInto(loadAddress, fromTop);
if (result != SCE_KERNEL_ERROR_OK) {
ERROR_LOG(Log::sceModule, "LoadInto failed with error %08x",result);
// Carry the reader's reason up, so what the user is shown says more than an error code.
if (!reader.LoadError().empty()) {
*error_string = reader.LoadError();
} else {
*error_string = StringFromFormat("ELF load failed (%08x)", result);
}
ERROR_LOG(Log::sceModule, "LoadInto failed with error %08x: %s", result, error_string->c_str());
delete [] newptr;
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
@@ -2092,7 +2098,8 @@ static bool __KernelLoadExecFromPtr(MIPSState * mips, const u8 *data, size_t siz
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
}
ERROR_LOG(Log::Loader, "Failed to load module %s", filename);
ERROR_LOG(Log::Loader, "Failed to load module %s (%d bytes): %s", filename, (int)size,
error_string->empty() ? "no reason given" : error_string->c_str());
*error_string = "Failed to load executable: " + *error_string;
delete[] param_argp;
delete[] param_key;