mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
Merge pull request #22939 from hrydgard/parser-hardening
Harden file format parsers against malformed input
This commit is contained in:
56 files changed
+729
-222
No files matched your search
@@ -54,7 +54,7 @@ bool compress_string(const std::string& str, std::string *dest, int compressionl
|
||||
}
|
||||
|
||||
/** Decompress an STL string using zlib and return the original data. */
|
||||
bool decompress_string(const std::string& str, std::string *dest) {
|
||||
bool decompress_string(const std::string& str, std::string *dest, size_t maxSize) {
|
||||
if (!str.size())
|
||||
return false;
|
||||
|
||||
@@ -85,7 +85,11 @@ bool decompress_string(const std::string& str, std::string *dest) {
|
||||
outstring.append(outbuffer,
|
||||
zs.total_out - outstring.size());
|
||||
}
|
||||
|
||||
if (outstring.size() > maxSize) {
|
||||
inflateEnd(&zs);
|
||||
ERROR_LOG(Log::IO, "Decompressed data is larger than %d bytes, giving up", (int)maxSize);
|
||||
return false;
|
||||
}
|
||||
} while (ret == Z_OK);
|
||||
|
||||
inflateEnd(&zs);
|
||||
@@ -95,6 +99,6 @@ bool decompress_string(const std::string& str, std::string *dest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*dest = outstring;
|
||||
*dest = std::move(outstring);
|
||||
return true;
|
||||
}
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
// inflate/deflate convenience wrapper. Uses zlib.
|
||||
bool compress_string(const std::string& str, std::string *dest, int compressionlevel = 9);
|
||||
bool decompress_string(const std::string& str, std::string *dest);
|
||||
// Fails if the output would be larger than maxSize (a small input can expand enormously).
|
||||
bool decompress_string(const std::string& str, std::string *dest, size_t maxSize = 256 * 1024 * 1024);
|
||||
@@ -110,6 +110,12 @@ int u8_charnum(const char *s, int offset)
|
||||
uint32_t u8_nextchar(const char *s, int *index, size_t size) {
|
||||
uint32_t ch = 0;
|
||||
_dbg_assert_(*index >= 0 && *index < 100000000);
|
||||
// At (or past) the end, there's nothing to read. Leave the index at the end so loops waiting
|
||||
// for end() finish.
|
||||
if (*index < 0 || (size_t)*index >= size) {
|
||||
*index = (int)size;
|
||||
return 0;
|
||||
}
|
||||
int sz = 0;
|
||||
int i = *index;
|
||||
do {
|
||||
@@ -138,6 +144,7 @@ void u8_dec(const char *s, int *i) {
|
||||
|
||||
bool AnyEmojiInString(std::string_view str, size_t byteCount) {
|
||||
int i = 0;
|
||||
byteCount = std::min(byteCount, str.size());
|
||||
while (i < byteCount) {
|
||||
uint32_t c = u8_nextchar(str.data(), &i, str.size());
|
||||
if (CodepointIsProbablyEmoji(c)) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <climits>
|
||||
|
||||
#include "Common/Data/Format/JSONReader.h"
|
||||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/File/Path.h"
|
||||
@@ -121,20 +123,31 @@ double JsonGet::getFloat(const char *child_name, double default_value) const {
|
||||
return val->value.toNumber();
|
||||
}
|
||||
|
||||
// Casting a double that's out of range (or NaN) to int is undefined.
|
||||
static int ClampToInt(double d) {
|
||||
if (!(d >= (double)INT_MIN)) {
|
||||
return d != d ? 0 : INT_MIN;
|
||||
}
|
||||
if (d > (double)INT_MAX) {
|
||||
return INT_MAX;
|
||||
}
|
||||
return (int)d;
|
||||
}
|
||||
|
||||
int JsonGet::getInt(const char *child_name) const {
|
||||
const JsonNode *val = get(child_name, JSON_NUMBER);
|
||||
if (!val) {
|
||||
ERROR_LOG(Log::IO, "Number '%s' missing from node", child_name);
|
||||
return 0;
|
||||
}
|
||||
return (int)val->value.toNumber();
|
||||
return ClampToInt(val->value.toNumber());
|
||||
}
|
||||
|
||||
int JsonGet::getInt(const char *child_name, int default_value) const {
|
||||
const JsonNode *val = get(child_name, JSON_NUMBER);
|
||||
if (!val)
|
||||
return default_value;
|
||||
return (int)val->value.toNumber();
|
||||
return ClampToInt(val->value.toNumber());
|
||||
}
|
||||
|
||||
bool JsonGet::getBool(const char *child_name) const {
|
||||
|
||||
@@ -23,13 +23,28 @@ int pngLoad(const char *file, int *pwidth, int *pheight, unsigned char **image_d
|
||||
*image_data_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
if (png.width == 0 || png.height == 0 || png.width > 8192 || png.height > 8192) {
|
||||
WARN_LOG(Log::IO, "pngLoad: Bad size %dx%d (%s)", (int)png.width, (int)png.height, file);
|
||||
png_image_free(&png);
|
||||
*image_data_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
*pwidth = png.width;
|
||||
*pheight = png.height;
|
||||
png.format = PNG_FORMAT_RGBA;
|
||||
|
||||
int stride = PNG_IMAGE_ROW_STRIDE(png);
|
||||
*image_data_ptr = (unsigned char *)malloc(PNG_IMAGE_SIZE(png));
|
||||
png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL);
|
||||
if (!*image_data_ptr) {
|
||||
png_image_free(&png);
|
||||
return 0;
|
||||
}
|
||||
if (!png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL)) {
|
||||
WARN_LOG(Log::IO, "pngLoad: %s (%s)", png.message, file);
|
||||
free(*image_data_ptr);
|
||||
*image_data_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -50,11 +65,13 @@ struct PngReadContext {
|
||||
|
||||
|
||||
int pngLoadPtr(const unsigned char *input_ptr, size_t input_len, int *pwidth, int *pheight, unsigned char **image_data_ptr, int maxWidth, int maxHeight) {
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngErrorHandler, pngWarningHandler);
|
||||
if (!png) {
|
||||
// The error handler below frees whatever this points to.
|
||||
*image_data_ptr = nullptr;
|
||||
if (input_len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (input_len == 0) {
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngErrorHandler, pngWarningHandler);
|
||||
if (!png) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,9 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int
|
||||
memcpy(height, zim + 8, 4);
|
||||
memcpy(flags, zim + 12, 4);
|
||||
|
||||
if (*width <= 0 || *height <= 0) {
|
||||
// Nothing we use is anywhere near this, and the data is often compressed, so the
|
||||
// size is otherwise only bounded by how much memory a decompression bomb can claim.
|
||||
if (*width <= 0 || *height <= 0 || *width > 8192 || *height > 8192) {
|
||||
ERROR_LOG(Log::IO, "Invalid ZIM dimensions %i x %i", *width, *height);
|
||||
return 0;
|
||||
}
|
||||
@@ -147,9 +149,12 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int
|
||||
*image = 0;
|
||||
return 0;
|
||||
}
|
||||
if (outlen != total_data_size) {
|
||||
// Shouldn't happen if return value was Z_OK.
|
||||
if (outlen != (long)total_data_size) {
|
||||
// The stream ended early, leaving the rest of the image uninitialized.
|
||||
ERROR_LOG(Log::IO, "Wrong size data in ZIM: %i vs %i", (int)outlen, (int)total_data_size);
|
||||
free(*image);
|
||||
*image = 0;
|
||||
return 0;
|
||||
}
|
||||
} else if (*flags & ZIM_ZSTD_COMPRESSED) {
|
||||
size_t outlen = ZSTD_decompress(*image, total_data_size, zim + 16, payload_size);
|
||||
|
||||
@@ -169,7 +169,10 @@ StringWriter &StringWriter::F(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int wouldHaveBeenWritten = vsnprintf(p_, remainder, format, args);
|
||||
p_ += std::min((int)remainder, wouldHaveBeenWritten);
|
||||
va_end(args);
|
||||
// -1 on encoding errors, which mustn't move p_ backwards.
|
||||
if (wouldHaveBeenWritten > 0) {
|
||||
p_ += std::min((int)remainder, wouldHaveBeenWritten);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -24,13 +24,17 @@ static std::string ToLowerASCII(std::string_view str) {
|
||||
return lower;
|
||||
}
|
||||
|
||||
void DirectoryReader::IndexDirectory(const Path &dir, const std::string &prefix) {
|
||||
void DirectoryReader::IndexDirectory(const Path &dir, const std::string &prefix, int depth) {
|
||||
// A symlink loop would otherwise recurse until the stack runs out.
|
||||
if (depth > 32) {
|
||||
return;
|
||||
}
|
||||
std::vector<File::FileInfo> files;
|
||||
File::GetFilesInDir(dir, &files);
|
||||
for (const File::FileInfo &file : files) {
|
||||
const std::string relative = prefix + file.name;
|
||||
if (file.isDirectory) {
|
||||
IndexDirectory(file.fullName, relative + "/");
|
||||
IndexDirectory(file.fullName, relative + "/", depth + 1);
|
||||
} else {
|
||||
lowerIndex_[ToLowerASCII(relative)] = relative;
|
||||
}
|
||||
@@ -44,7 +48,7 @@ bool DirectoryReader::FindIgnoringCase(std::string_view path, std::string *actua
|
||||
std::lock_guard<std::mutex> guard(indexLock_);
|
||||
if (!indexBuilt_) {
|
||||
// Only done on the first miss, so a directory where every name matches never pays for it.
|
||||
IndexDirectory(path_, "");
|
||||
IndexDirectory(path_, "", 0);
|
||||
indexBuilt_ = true;
|
||||
INFO_LOG(Log::IO, "Indexed %d files in %s to find names that differ in case", (int)lowerIndex_.size(), path_.c_str());
|
||||
}
|
||||
@@ -134,13 +138,22 @@ void DirectoryReader::ReleaseFile(VFSFileReference *vfsReference) {
|
||||
|
||||
VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) {
|
||||
DirectoryReaderFileReference *reference = (DirectoryReaderFileReference *)vfsReference;
|
||||
// On some platforms, directories can be opened as files. Their size is meaningless.
|
||||
if (File::IsDirectory(reference->path)) {
|
||||
return nullptr;
|
||||
}
|
||||
FILE *file = File::OpenCFile(reference->path, "rb");
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
File::Fseek(file, 0, SEEK_END);
|
||||
*size = File::Ftell(file);
|
||||
const int64_t fileSize = File::Ftell(file);
|
||||
File::Fseek(file, 0, SEEK_SET);
|
||||
if (fileSize < 0) {
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
}
|
||||
*size = (size_t)fileSize;
|
||||
DirectoryReaderOpenFile *openFile = new DirectoryReaderOpenFile();
|
||||
openFile->file = file;
|
||||
return openFile;
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
private:
|
||||
// Returns the path (relative to path_) of a file whose name matches path except for case.
|
||||
bool FindIgnoringCase(std::string_view path, std::string *actualPath);
|
||||
void IndexDirectory(const Path &dir, const std::string &prefix);
|
||||
void IndexDirectory(const Path &dir, const std::string &prefix, int depth);
|
||||
|
||||
Path path_;
|
||||
bool ignoreCase_ = false;
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
#include "Common/StringUtils.h"
|
||||
|
||||
// Sanity cap on a single zip entry's declared uncompressed size, so a corrupt or
|
||||
// malicious zip can't drive an implausible (or, near UINT64_MAX, wrapping) allocation.
|
||||
static constexpr uint64_t MAX_ZIP_ENTRY_SIZE = 1ULL << 32; // 4GB, generous for any real asset.
|
||||
// malicious zip can't drive an implausible allocation (which also has to fit in a
|
||||
// 32-bit size_t). Entries are read whole into memory; the largest real asset, an
|
||||
// uncompressed 8192x8192 replacement texture with mips, is under 512MB.
|
||||
static constexpr uint64_t MAX_ZIP_ENTRY_SIZE = 512ULL * 1024 * 1024;
|
||||
|
||||
ZipContainer::ZipContainer() noexcept : sourceData_(nullptr), zip_(nullptr) {}
|
||||
|
||||
@@ -186,8 +188,13 @@ uint8_t *ZipFileReader::ReadFile(std::string_view path, size_t *size) {
|
||||
return 0;
|
||||
}
|
||||
uint8_t *contents = new uint8_t[zstat.size + 1];
|
||||
zip_fread(file, contents, zstat.size);
|
||||
const zip_int64_t bytesRead = zip_fread(file, contents, zstat.size);
|
||||
zip_fclose(file);
|
||||
if (bytesRead < 0 || (uint64_t)bytesRead != zstat.size) {
|
||||
ERROR_LOG(Log::IO, "Error reading %s from ZIP (%lld of %llu bytes)", temp_path.c_str(), (long long)bytesRead, (unsigned long long)zstat.size);
|
||||
delete[] contents;
|
||||
return 0;
|
||||
}
|
||||
contents[zstat.size] = 0;
|
||||
|
||||
*size = zstat.size;
|
||||
@@ -402,6 +409,13 @@ VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size
|
||||
delete openFile;
|
||||
return nullptr;
|
||||
}
|
||||
// Callers allocate the returned size.
|
||||
if (zstat.size > MAX_ZIP_ENTRY_SIZE) {
|
||||
ERROR_LOG(Log::IO, "Zip entry %d claims an implausible size (%llu), refusing to open", reference->zi, (unsigned long long)zstat.size);
|
||||
lock_.unlock();
|
||||
delete openFile;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0);
|
||||
if (!openFile->zf) {
|
||||
@@ -429,15 +443,20 @@ void ZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) {
|
||||
size_t ZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {
|
||||
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
|
||||
_assert_(file);
|
||||
_dbg_assert_(file->zf != nullptr);
|
||||
return zip_fread(file->zf, buffer, length);
|
||||
if (!file->zf) {
|
||||
return 0;
|
||||
}
|
||||
// -1 on errors, like a bad CRC or a corrupt stream.
|
||||
const zip_int64_t bytesRead = zip_fread(file->zf, buffer, length);
|
||||
return bytesRead < 0 ? 0 : (size_t)bytesRead;
|
||||
}
|
||||
|
||||
void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {
|
||||
ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile;
|
||||
_assert_(file);
|
||||
_dbg_assert_(file->zf != nullptr);
|
||||
zip_fclose(file->zf);
|
||||
if (file->zf) {
|
||||
zip_fclose(file->zf);
|
||||
}
|
||||
file->zf = nullptr;
|
||||
vfsOpenFile = nullptr;
|
||||
lock_.unlock();
|
||||
|
||||
@@ -94,6 +94,18 @@ bool TempImage::LoadTextureLevelsFromFileData(const uint8_t *data, size_t size,
|
||||
switch (typeSuggestion) {
|
||||
case ImageFileType::ZIM:
|
||||
numLevels = LoadZIMPtr((const uint8_t *)data, size, width, height, &zimFlags, levels);
|
||||
if (numLevels <= 0) {
|
||||
numLevels = 0;
|
||||
return false;
|
||||
}
|
||||
// Everything below treats the data as four bytes per pixel.
|
||||
if ((zimFlags & ZIM_FORMAT_MASK) != ZIM_RGBA8888 || width[0] > maxWidth || height[0] > maxHeight) {
|
||||
ERROR_LOG(Log::IO, "Unsupported ZIM: format %d, %dx%d", zimFlags & ZIM_FORMAT_MASK, width[0], height[0]);
|
||||
free(levels[0]);
|
||||
levels[0] = nullptr;
|
||||
numLevels = 0;
|
||||
return false;
|
||||
}
|
||||
fmt = ZimToT3DFormat(zimFlags & ZIM_FORMAT_MASK);
|
||||
ConvertRGBA8888ToPremulAlpha((u32 *)levels[0], (const u32 *)levels[0], width[0] * height[0]);
|
||||
break;
|
||||
@@ -117,6 +129,22 @@ bool TempImage::LoadTextureLevelsFromFileData(const uint8_t *data, size_t size,
|
||||
case ImageFileType::JPEG:
|
||||
{
|
||||
int actual_components = 0;
|
||||
if (size > INT_MAX) {
|
||||
return false;
|
||||
}
|
||||
// Check the size before decoding.
|
||||
{
|
||||
jpgd::jpeg_decoder_mem_stream stream(data, (jpgd::uint)size);
|
||||
jpgd::jpeg_decoder decoder(&stream);
|
||||
if (decoder.get_error_code() != jpgd::JPGD_SUCCESS) {
|
||||
ERROR_LOG(Log::IO, "Bad JPEG header");
|
||||
return false;
|
||||
}
|
||||
if (decoder.get_width() <= 0 || decoder.get_height() <= 0 || decoder.get_width() > maxWidth || decoder.get_height() > maxHeight) {
|
||||
ERROR_LOG(Log::IO, "JPEG too large: %dx%d (max %dx%d)", decoder.get_width(), decoder.get_height(), maxWidth, maxHeight);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(data, (int)size, &width[0], &height[0], &actual_components, 4);
|
||||
if (jpegBuf) {
|
||||
numLevels = 1;
|
||||
|
||||
@@ -117,7 +117,7 @@ void TextDrawer::DrawString(DrawBuffer &target, const FontStyle &style, float sc
|
||||
} else {
|
||||
DataFormat texFormat;
|
||||
// Pick between the supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported)
|
||||
bool emoji = SupportsColorEmoji() && AnyEmojiInString(str.data(), str.length());
|
||||
bool emoji = SupportsColorEmoji() && AnyEmojiInString(str, str.length());
|
||||
if (emoji) {
|
||||
texFormat = Draw::DataFormat::R8G8B8A8_UNORM;
|
||||
} else if ((draw_->GetDataFormatSupport(Draw::DataFormat::R8_UNORM) & Draw::FMT_TEXTURE) != 0 && draw_->GetDeviceCaps().textureSwizzleSupported) {
|
||||
|
||||
@@ -32,6 +32,15 @@ void DoDeque(PointerWrap &p, std::deque<T> &x, T &default_val) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// See DoVector.
|
||||
if (p.mode == PointerWrap::MODE_READ && sizeof(T) > SerializeMinElemSize<T>()) {
|
||||
x.clear();
|
||||
for (u32 i = 0; i < deq_size && !p.Failed(); i++) {
|
||||
x.push_back(default_val);
|
||||
Do(p, x.back());
|
||||
}
|
||||
return;
|
||||
}
|
||||
x.resize(deq_size, default_val);
|
||||
u32 i;
|
||||
for (i = 0; i < deq_size; i++)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
// Templates for save state serialization. See Serializer.h.
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include "Common/Serialize/Serializer.h"
|
||||
@@ -134,6 +135,18 @@ void DoVector(PointerWrap &p, std::vector<T> &x, T &default_val) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Elements that aren't stored raw can serialize to a few bytes but be much bigger in memory, so
|
||||
// the check above still lets a corrupt state ask for gigabytes. Grow as the data arrives instead.
|
||||
if (p.mode == PointerWrap::MODE_READ && sizeof(T) > SerializeMinElemSize<T>()) {
|
||||
x.clear();
|
||||
for (u32 done = 0; done < vec_size && !p.Failed(); ) {
|
||||
const u32 n = std::min(vec_size - done, (u32)1024);
|
||||
x.resize(done + n, default_val);
|
||||
DoArray(p, &x[done], n);
|
||||
done += n;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (vec_size != x.size())
|
||||
x.resize(vec_size, default_val);
|
||||
if (vec_size > 0)
|
||||
|
||||
@@ -32,6 +32,15 @@ void DoList(PointerWrap &p, std::list<T> &x, T &default_val) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// See DoVector.
|
||||
if (p.mode == PointerWrap::MODE_READ && sizeof(T) > SerializeMinElemSize<T>()) {
|
||||
x.clear();
|
||||
for (u32 i = 0; i < list_size && !p.Failed(); i++) {
|
||||
x.push_back(default_val);
|
||||
Do(p, x.back());
|
||||
}
|
||||
return;
|
||||
}
|
||||
x.resize(list_size, default_val);
|
||||
|
||||
for (T &elem : x)
|
||||
|
||||
@@ -503,18 +503,22 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
|
||||
|
||||
if (header.Compress) {
|
||||
// Sanity cap on the decompressed size to avoid a giant allocation from
|
||||
// an attacker-controlled header field. Real savestates are well under this.
|
||||
if (header.UncompressedSize > 0x40000000) {
|
||||
// an attacker-controlled header field. Real savestates (even with 64MB of
|
||||
// RAM) are well under this.
|
||||
if (header.UncompressedSize > 0x10000000) {
|
||||
ERROR_LOG(Log::SaveState, "ChunkReader: UncompressedSize too large: %u", header.UncompressedSize);
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (SerializeCompressType(header.Compress) == SerializeCompressType::ZSTD) {
|
||||
u8 *uncomp_buffer = new u8[header.UncompressedSize];
|
||||
u8 *uncomp_buffer = (u8 *)malloc(header.UncompressedSize);
|
||||
if (!uncomp_buffer) {
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
if (!DecompressZstdFromFile(pFile, header.ExpectedSize, uncomp_buffer, header.UncompressedSize)) {
|
||||
ERROR_LOG(Log::SaveState, "ChunkReader: Failed to decompress file");
|
||||
delete [] uncomp_buffer;
|
||||
free(uncomp_buffer);
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
_buffer = uncomp_buffer;
|
||||
@@ -525,16 +529,20 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
|
||||
|
||||
// Uncompressed, or snappy (older states).
|
||||
sz = header.ExpectedSize;
|
||||
u8 *buffer = new u8[sz];
|
||||
if (!pFile.ReadBytes(buffer, sz))
|
||||
u8 *buffer = (u8 *)malloc(sz);
|
||||
if (!buffer || !pFile.ReadBytes(buffer, sz))
|
||||
{
|
||||
ERROR_LOG(Log::SaveState, "ChunkReader: Error reading file");
|
||||
delete [] buffer;
|
||||
free(buffer);
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
|
||||
if (header.Compress) {
|
||||
u8 *uncomp_buffer = new u8[header.UncompressedSize];
|
||||
u8 *uncomp_buffer = (u8 *)malloc(header.UncompressedSize);
|
||||
if (!uncomp_buffer) {
|
||||
free(buffer);
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
size_t uncomp_size = header.UncompressedSize;
|
||||
bool success = false;
|
||||
if (SerializeCompressType(header.Compress) == SerializeCompressType::SNAPPY) {
|
||||
@@ -545,19 +553,19 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
|
||||
}
|
||||
if (!success) {
|
||||
ERROR_LOG(Log::SaveState, "ChunkReader: Failed to decompress file");
|
||||
delete [] uncomp_buffer;
|
||||
delete [] buffer;
|
||||
free(uncomp_buffer);
|
||||
free(buffer);
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
if ((u32)uncomp_size != header.UncompressedSize) {
|
||||
ERROR_LOG(Log::SaveState, "Size mismatch: file: %u calc: %u", header.UncompressedSize, (u32)uncomp_size);
|
||||
delete [] uncomp_buffer;
|
||||
delete [] buffer;
|
||||
free(uncomp_buffer);
|
||||
free(buffer);
|
||||
return ERROR_BAD_FILE;
|
||||
}
|
||||
_buffer = uncomp_buffer;
|
||||
sz = uncomp_size;
|
||||
delete [] buffer;
|
||||
free(buffer);
|
||||
} else {
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ public:
|
||||
if (error == ERROR_NONE) {
|
||||
failureReason->clear();
|
||||
error = LoadPtr(ptr, sz, _class, failureReason);
|
||||
delete [] ptr;
|
||||
free(ptr);
|
||||
INFO_LOG(Log::SaveState, "ChunkReader: Done loading '%s'", filename.c_str());
|
||||
} else {
|
||||
WARN_LOG(Log::SaveState, "ChunkReader: Error found during load of '%s'", filename.c_str());
|
||||
@@ -359,6 +359,7 @@ public:
|
||||
|
||||
// The file half of Load() and Save(), for doing it away from the state itself. LoadFile hands back
|
||||
// a new[]'d buffer; SaveFile takes ownership of a malloc'd one (as from MeasureAndSavePtr).
|
||||
// The buffer is allocated with malloc (so the allocation can fail cleanly), free it with free().
|
||||
static Error LoadFile(const Path &filename, std::string *gitVersion, u8 *&buffer, size_t &sz);
|
||||
// Reads only the first size bytes of the state, decompressing no more than that. Not for snappy
|
||||
// compressed (old) states.
|
||||
|
||||
@@ -1469,8 +1469,13 @@ void CWCheatEngine::Run() {
|
||||
|
||||
// InterpretNextOp and ExecuteOp move i.
|
||||
for (size_t i = 0; i < cheat.lines.size(); ) {
|
||||
const size_t start = i;
|
||||
CheatOperation op = InterpretNextCwCheat(cheat, i);
|
||||
ExecuteOp(op, cheat, i);
|
||||
// A skip count near 2^32 wraps i on 32-bit hosts, which would run the same code forever.
|
||||
if (i <= start) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-27
@@ -16,6 +16,7 @@
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
@@ -83,19 +84,20 @@ public:
|
||||
return Fail();
|
||||
}
|
||||
int64_t SLEB() {
|
||||
int64_t result = 0;
|
||||
// Unsigned, so shifting into (and past) the sign bit is defined.
|
||||
uint64_t result = 0;
|
||||
int shift = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const uint8_t b = U8();
|
||||
if (!ok_)
|
||||
return 0;
|
||||
if (shift < 64)
|
||||
result |= (int64_t)(b & 0x7f) << shift;
|
||||
result |= (uint64_t)(b & 0x7f) << shift;
|
||||
shift += 7;
|
||||
if (!(b & 0x80)) {
|
||||
if (shift < 64 && (b & 0x40))
|
||||
result -= (int64_t)1 << shift;
|
||||
return result;
|
||||
result |= ~(uint64_t)0 << shift;
|
||||
return (int64_t)result;
|
||||
}
|
||||
}
|
||||
return Fail();
|
||||
@@ -147,6 +149,13 @@ enum {
|
||||
|
||||
// One .debug_line unit: header, then a bytecode program that walks a virtual machine whose output
|
||||
// rows are (address, file, line). See the DWARF spec, "Line Number Information".
|
||||
// Line 0 marks the end of a sequence, so real rows are at least 1.
|
||||
static u32 ClampLine(int64_t line) {
|
||||
return line > 0 ? (u32)std::min<int64_t>(line, UINT32_MAX) : 1;
|
||||
}
|
||||
|
||||
static constexpr size_t MAX_FILES = 1 << 16;
|
||||
|
||||
static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries,
|
||||
std::vector<std::string> *files, std::map<std::string, u32> *fileIndex, u32 moduleStart, u32 moduleSize, u32 addressDelta) {
|
||||
const uint16_t version = r.U16();
|
||||
@@ -158,6 +167,8 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
}
|
||||
|
||||
const uint32_t headerLength = r.U32();
|
||||
if ((u64)r.pos() + headerLength > unitEnd)
|
||||
return false;
|
||||
const size_t programStart = r.pos() + headerLength;
|
||||
|
||||
const uint8_t minInstLength = r.U8();
|
||||
@@ -192,6 +203,11 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
// Deduped across units so a header included by 500 files is stored once.
|
||||
auto it = fileIndex->find(name);
|
||||
if (it == fileIndex->end()) {
|
||||
// Each one costs far more memory than its bytes in the file.
|
||||
if (files->size() >= MAX_FILES) {
|
||||
unitFiles.push_back(0);
|
||||
continue;
|
||||
}
|
||||
it = fileIndex->insert({ name, (u32)files->size() }).first;
|
||||
files->push_back(name);
|
||||
}
|
||||
@@ -204,8 +220,12 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
|
||||
u32 address = 0;
|
||||
u32 file = 1;
|
||||
int line = 1;
|
||||
// 64 bits, so the file can't overflow it with line advances.
|
||||
int64_t line = 1;
|
||||
bool sawAddress = false;
|
||||
// Every one-byte special opcode can emit a row. After deduplication there's at most one per
|
||||
// address, so more than a couple per instruction is a file trying to exhaust memory.
|
||||
const size_t maxEntries = (size_t)moduleSize / 2 + 65536;
|
||||
|
||||
auto emit = [&](u32 lineNo) {
|
||||
// Rows before a DW_LNE_set_address belong to no real code, and anything that doesn't land
|
||||
@@ -213,6 +233,8 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
const u32 finalAddress = addressDelta + address;
|
||||
if (!sawAddress || finalAddress < moduleStart || finalAddress - moduleStart >= moduleSize)
|
||||
return;
|
||||
if (entries->size() >= maxEntries)
|
||||
return;
|
||||
LineEntry e;
|
||||
e.address = finalAddress;
|
||||
e.line = lineNo;
|
||||
@@ -227,10 +249,14 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
const uint8_t adjusted = op - opcodeBase;
|
||||
address += (adjusted / lineRange) * minInstLength;
|
||||
line += lineBase + (adjusted % lineRange);
|
||||
emit(line > 0 ? (u32)line : 1);
|
||||
emit(ClampLine(line));
|
||||
} else if (op == 0) {
|
||||
// Extended opcode, length-prefixed so unknown ones can be skipped.
|
||||
const uint64_t length = r.ULEB();
|
||||
// The length covers at least the sub-opcode. Check it before adding: a huge one wraps
|
||||
// back to this opcode, which then loops forever.
|
||||
if (!r.ok() || length == 0 || r.pos() > unitEnd || length > unitEnd - r.pos())
|
||||
return false;
|
||||
const size_t next = r.pos() + (size_t)length;
|
||||
const uint8_t sub = r.U8();
|
||||
if (sub == DW_LNE_end_sequence) {
|
||||
@@ -248,13 +274,13 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector<LineEntry> *entries
|
||||
} else {
|
||||
switch (op) {
|
||||
case DW_LNS_copy:
|
||||
emit(line > 0 ? (u32)line : 1);
|
||||
emit(ClampLine(line));
|
||||
break;
|
||||
case DW_LNS_advance_pc:
|
||||
address += (u32)r.ULEB() * minInstLength;
|
||||
break;
|
||||
case DW_LNS_advance_line:
|
||||
line += (int)r.SLEB();
|
||||
line = std::clamp<int64_t>(line + std::clamp<int64_t>(r.SLEB(), INT32_MIN, INT32_MAX), INT32_MIN, UINT32_MAX);
|
||||
break;
|
||||
case DW_LNS_set_file:
|
||||
file = (u32)r.ULEB();
|
||||
@@ -293,36 +319,46 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
if (data[0] != ELFMAG0 || data[1] != ELFMAG1 || data[2] != ELFMAG2 || data[3] != ELFMAG3)
|
||||
return 0;
|
||||
|
||||
const Elf32_Ehdr *header = (const Elf32_Ehdr *)data;
|
||||
// Copied out, since the offsets in the file needn't be aligned. Sums in 64 bits, so they can't
|
||||
// wrap a 32-bit size_t.
|
||||
Elf32_Ehdr headerCopy;
|
||||
memcpy(&headerCopy, data, sizeof(headerCopy));
|
||||
const Elf32_Ehdr *header = &headerCopy;
|
||||
if (header->e_shoff == 0 || header->e_shnum == 0 || header->e_shentsize < sizeof(Elf32_Shdr))
|
||||
return 0;
|
||||
if ((size_t)header->e_shoff + (size_t)header->e_shnum * header->e_shentsize > size)
|
||||
if ((u64)header->e_shoff + (u64)header->e_shnum * header->e_shentsize > size)
|
||||
return 0;
|
||||
if (header->e_shstrndx >= header->e_shnum)
|
||||
return 0;
|
||||
|
||||
auto section = [&](int i) {
|
||||
return (const Elf32_Shdr *)(data + header->e_shoff + (size_t)i * header->e_shentsize);
|
||||
Elf32_Shdr s;
|
||||
memcpy(&s, data + header->e_shoff + (size_t)i * header->e_shentsize, sizeof(s));
|
||||
return s;
|
||||
};
|
||||
|
||||
const Elf32_Shdr *shstr = section(header->e_shstrndx);
|
||||
if ((size_t)shstr->sh_offset + shstr->sh_size > size)
|
||||
const Elf32_Shdr shstr = section(header->e_shstrndx);
|
||||
if ((u64)shstr.sh_offset + shstr.sh_size > size)
|
||||
return 0;
|
||||
|
||||
const Elf32_Shdr *debugLine = nullptr;
|
||||
static const char debugLineName[] = ".debug_line";
|
||||
Elf32_Shdr debugLine{};
|
||||
bool foundDebugLine = false;
|
||||
for (int i = 0; i < header->e_shnum; i++) {
|
||||
const Elf32_Shdr *s = section(i);
|
||||
if (s->sh_name >= shstr->sh_size)
|
||||
const Elf32_Shdr s = section(i);
|
||||
// The name, with its terminator, has to fit in the string table.
|
||||
if (s.sh_name >= shstr.sh_size || shstr.sh_size - s.sh_name < sizeof(debugLineName))
|
||||
continue;
|
||||
const char *name = (const char *)data + shstr->sh_offset + s->sh_name;
|
||||
if (!strcmp(name, ".debug_line")) {
|
||||
const char *name = (const char *)data + shstr.sh_offset + s.sh_name;
|
||||
if (!memcmp(name, debugLineName, sizeof(debugLineName))) {
|
||||
debugLine = s;
|
||||
foundDebugLine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!debugLine || debugLine->sh_size == 0)
|
||||
if (!foundDebugLine || debugLine.sh_size == 0)
|
||||
return 0;
|
||||
if ((size_t)debugLine->sh_offset + debugLine->sh_size > size)
|
||||
if ((u64)debugLine.sh_offset + debugLine.sh_size > size)
|
||||
return 0;
|
||||
|
||||
ModuleLines mod;
|
||||
@@ -331,9 +367,9 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
mod.files.push_back("<unknown>");
|
||||
std::map<std::string, u32> fileIndex;
|
||||
|
||||
Reader r(data + debugLine->sh_offset, debugLine->sh_size);
|
||||
Reader r(data + debugLine.sh_offset, debugLine.sh_size);
|
||||
int skippedUnits = 0;
|
||||
while (r.ok() && !r.AtEnd(debugLine->sh_size)) {
|
||||
while (r.ok() && !r.AtEnd(debugLine.sh_size)) {
|
||||
const size_t unitStart = r.pos();
|
||||
const uint32_t unitLength = r.U32();
|
||||
if (!r.ok() || unitLength == 0)
|
||||
@@ -342,9 +378,9 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
// produces, and misreading it would walk off into nonsense.
|
||||
if (unitLength >= 0xfffffff0)
|
||||
break;
|
||||
const size_t unitEnd = unitStart + 4 + unitLength;
|
||||
if (unitEnd > debugLine->sh_size)
|
||||
if ((u64)unitStart + 4 + unitLength > debugLine.sh_size)
|
||||
break;
|
||||
const size_t unitEnd = unitStart + 4 + unitLength;
|
||||
|
||||
if (!ParseUnit(r, unitEnd, &mod.entries, &mod.files, &fileIndex, moduleStart, moduleSize, addressDelta))
|
||||
skippedUnits++;
|
||||
@@ -363,9 +399,16 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module
|
||||
std::stable_sort(mod.entries.begin(), mod.entries.end(), [](const LineEntry &a, const LineEntry &b) {
|
||||
return a.address < b.address;
|
||||
});
|
||||
mod.entries.erase(std::unique(mod.entries.begin(), mod.entries.end(), [](const LineEntry &a, const LineEntry &b) {
|
||||
return a.address == b.address;
|
||||
}), mod.entries.end());
|
||||
// (std::unique would keep the first.)
|
||||
size_t out = 0;
|
||||
for (size_t i = 0; i < mod.entries.size(); i++) {
|
||||
if (out > 0 && mod.entries[out - 1].address == mod.entries[i].address) {
|
||||
mod.entries[out - 1] = mod.entries[i];
|
||||
} else {
|
||||
mod.entries[out++] = mod.entries[i];
|
||||
}
|
||||
}
|
||||
mod.entries.resize(out);
|
||||
|
||||
const int count = (int)mod.entries.size();
|
||||
INFO_LOG(Log::Loader, "Line info: %d rows across %d files for module at %08x",
|
||||
|
||||
@@ -108,11 +108,10 @@ bool SymbolMap::LoadSymbolMap(const Path &filename) {
|
||||
if (p == NULL)
|
||||
break;
|
||||
|
||||
// Chop any newlines off.
|
||||
for (size_t i = strlen(line) - 1; i > 0; i--) {
|
||||
if (line[i] == '\r' || line[i] == '\n') {
|
||||
line[i] = '\0';
|
||||
}
|
||||
// Chop any newlines off. The line can be empty, if it starts with a NUL.
|
||||
size_t len = strlen(line);
|
||||
while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n')) {
|
||||
line[--len] = '\0';
|
||||
}
|
||||
|
||||
if (strlen(line) < 4 || sscanf(line, "%255s", temp) != 1)
|
||||
|
||||
@@ -213,7 +213,7 @@ void PSPGamedataInstallDialog::WriteSfoFile() {
|
||||
ParamSFOData sfoFile;
|
||||
std::string sfopath = GetGameDataInstallFileName(&request, SFO_FILENAME);
|
||||
std::vector<u8> sfoFileData;
|
||||
if (pspFileSystem.ReadEntireFile(sfopath, sfoFileData) >= 0) {
|
||||
if (pspFileSystem.ReadEntireFile(sfopath, sfoFileData, false, 1024 * 1024) >= 0) {
|
||||
sfoFile.ReadSFO(sfoFileData);
|
||||
}
|
||||
|
||||
|
||||
+90
-45
@@ -15,6 +15,9 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Data/Text/Demangle.h"
|
||||
#include "Common/File/DirListing.h"
|
||||
@@ -42,11 +45,15 @@ const char *ElfReader::GetSectionName(int section) const {
|
||||
return nullptr;
|
||||
}
|
||||
const char *ptr = (const char *)GetSectionDataPtr(header->e_shstrndx);
|
||||
|
||||
if (ptr)
|
||||
return ptr + nameOffset;
|
||||
else
|
||||
if (!ptr)
|
||||
return nullptr;
|
||||
// The name has to end before the file does.
|
||||
const size_t maxLen = size_ - ((size_t)nameOffset + stringsOffset);
|
||||
if (strnlen(ptr + nameOffset, maxLen) == maxLen) {
|
||||
ERROR_LOG(Log::Loader, "ELF: Unterminated name in section %d", section);
|
||||
return nullptr;
|
||||
}
|
||||
return ptr + nameOffset;
|
||||
}
|
||||
|
||||
void addrToHiLo(u32 addr, u16 &hi, s16 &lo)
|
||||
@@ -63,13 +70,51 @@ void addrToHiLo(u32 addr, u16 &hi, s16 &lo)
|
||||
}
|
||||
}
|
||||
|
||||
bool ElfReader::ConsumeRelocBudget(u64 bytes) {
|
||||
relocBytesProcessed_ += bytes;
|
||||
if (relocBytesProcessed_ > size_) {
|
||||
ERROR_LOG_REPORT(Log::Loader, "ELF: More relocation data than the file holds, ignoring the rest");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
|
||||
if (numRelocs <= 0)
|
||||
return true;
|
||||
if (!ConsumeRelocBudget((u64)numRelocs * sizeof(Elf32_Rel)))
|
||||
return false;
|
||||
|
||||
std::vector<u32> relocOps;
|
||||
relocOps.resize(numRelocs);
|
||||
|
||||
DEBUG_LOG(Log::Loader, "Loading %i relocations...", numRelocs);
|
||||
int numErrors = 0;
|
||||
|
||||
// A HI16 pairs with the next relocation that can serve as its LO16, skipping other HI16s.
|
||||
// Whether one can doesn't depend on the HI16, so find them all in one backward pass, rather
|
||||
// than scanning forward from every HI16 (quadratic, with a table of only HI16s).
|
||||
auto isLoCandidate = [&](int t) {
|
||||
int t_type = rels[t].r_info & 0xF;
|
||||
// In MotorStorm: Arctic Edge (US), these are sometimes R_MIPS_16 (instead of LO16.)
|
||||
// It appears the PSP takes any relocation that is not a HI16, but let's play it safe and
|
||||
// skip the others, we've only seen R_MIPS_16.
|
||||
if (t_type != R_MIPS_LO16 && t_type != R_MIPS_16)
|
||||
return false;
|
||||
// The candidate LO16 declares its own segment - use that rather than the HI16's,
|
||||
// which is what the mismatch warning further down is there to detect.
|
||||
int t_readwrite = (rels[t].r_info >> 8) & 0xff;
|
||||
if (!SegmentIsLoaded(t_readwrite))
|
||||
return false;
|
||||
return Memory::IsValidAddress(rels[t].r_offset + segmentVAddr[t_readwrite]);
|
||||
};
|
||||
std::vector<int> nextLo(numRelocs + 1, -1);
|
||||
for (int t = numRelocs - 1; t >= 0; t--) {
|
||||
nextLo[t] = isLoCandidate(t) ? t : nextLo[t + 1];
|
||||
}
|
||||
// Relocations up to here have already been logged as skipped by an earlier HI16.
|
||||
int loggedSkipsUpTo = 0;
|
||||
|
||||
{
|
||||
for (int r = 0; r < numRelocs; r++) {
|
||||
u32 info = rels[r].r_info;
|
||||
@@ -97,8 +142,8 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
|
||||
|
||||
addr += segmentVAddr[readwrite];
|
||||
|
||||
// It appears that misaligned relocations are allowed.
|
||||
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidAddress(addr)) {
|
||||
// It appears that misaligned relocations are allowed. We read and write 4 bytes either way.
|
||||
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidRange(addr, 4)) {
|
||||
if (numErrors < 10) {
|
||||
WARN_LOG_REPORT(Log::Loader, "Suspicious address %08x, skipping reloc, type = %d", addr, type);
|
||||
} else if (numErrors == 10) {
|
||||
@@ -128,7 +173,7 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
|
||||
}
|
||||
|
||||
addr += segmentVAddr[readwrite];
|
||||
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidAddress(addr)) {
|
||||
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidRange(addr, 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -164,31 +209,29 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
|
||||
u32 cur = (op & 0xFFFF) << 16;
|
||||
u16 hi = 0;
|
||||
bool found = false;
|
||||
for (int t = r + 1; t < numRelocs; t++) {
|
||||
int t_type = rels[t].r_info & 0xF;
|
||||
if (t_type == R_MIPS_HI16)
|
||||
const int t = nextLo[r + 1];
|
||||
const int skipEnd = t < 0 ? numRelocs : t;
|
||||
for (int s = std::max(r + 1, loggedSkipsUpTo); s < skipEnd; s++) {
|
||||
int s_type = rels[s].r_info & 0xF;
|
||||
int s_readwrite = (rels[s].r_info >> 8) & 0xff;
|
||||
if (s_type == R_MIPS_HI16 || !SegmentIsLoaded(s_readwrite))
|
||||
continue;
|
||||
|
||||
// The candidate LO16 declares its own segment - use that rather than the HI16's,
|
||||
// which is what the mismatch warning further down is there to detect.
|
||||
u32 skippedAddr = rels[s].r_offset + segmentVAddr[s_readwrite];
|
||||
if (s_type != R_MIPS_LO16 && s_type != R_MIPS_16) {
|
||||
// These exists in some popular games like Assassin's Creed: Bloodlines and GTA: VCS: (https://report.ppsspp.org/logs/kind/1187)
|
||||
ERROR_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d pair (instead of LO16) at %08x / %08x", s_type, addr, skippedAddr);
|
||||
} else {
|
||||
ERROR_LOG(Log::Loader, "Bad corrLoAddr %08x", skippedAddr);
|
||||
}
|
||||
}
|
||||
loggedSkipsUpTo = std::max(loggedSkipsUpTo, skipEnd);
|
||||
if (t >= 0) {
|
||||
int t_type = rels[t].r_info & 0xF;
|
||||
int t_readwrite = (rels[t].r_info >> 8) & 0xff;
|
||||
if (!SegmentIsLoaded(t_readwrite))
|
||||
continue;
|
||||
u32 corrLoAddr = rels[t].r_offset + segmentVAddr[t_readwrite];
|
||||
|
||||
// In MotorStorm: Arctic Edge (US), these are sometimes R_MIPS_16 (instead of LO16.)
|
||||
// It appears the PSP takes any relocation that is not a HI16.
|
||||
if (t_type != R_MIPS_LO16) {
|
||||
if (t_type != R_MIPS_16) {
|
||||
// Let's play it safe for now and skip. We've only seen this type.
|
||||
// These exists in some popular games like Assassin's Creed: Bloodlines and GTA: VCS: (https://report.ppsspp.org/logs/kind/1187)
|
||||
ERROR_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
|
||||
continue;
|
||||
} else {
|
||||
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d(16) pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
|
||||
}
|
||||
if (t_type == R_MIPS_16) {
|
||||
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d(16) pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
|
||||
}
|
||||
|
||||
// Should have matching index and segment info, according to llvm, which makes sense.
|
||||
if ((rels[t].r_info >> 8) != (rels[r].r_info >> 8)) {
|
||||
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/LO16 with mismatching r_info lo=%08x, hi=%08x", rels[t].r_info, rels[r].r_info);
|
||||
@@ -196,16 +239,11 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
|
||||
if (log) {
|
||||
DEBUG_LOG(Log::Loader, "Corresponding lo found at %08x", corrLoAddr);
|
||||
}
|
||||
if (Memory::IsValidAddress(corrLoAddr)) {
|
||||
s16 lo = (s16)relocOps[t];
|
||||
cur += lo;
|
||||
cur += relocateTo;
|
||||
addrToHiLo(cur, hi, lo);
|
||||
found = true;
|
||||
break;
|
||||
} else {
|
||||
ERROR_LOG(Log::Loader, "Bad corrLoAddr %08x", corrLoAddr);
|
||||
}
|
||||
s16 lo = (s16)relocOps[t];
|
||||
cur += lo;
|
||||
cur += relocateTo;
|
||||
addrToHiLo(cur, hi, lo);
|
||||
found = true;
|
||||
}
|
||||
if (found) {
|
||||
op = (op & 0xFFFF0000) | hi;
|
||||
@@ -289,6 +327,8 @@ void ElfReader::LoadRelocations2(int rel_seg)
|
||||
ERROR_LOG_REPORT(Log::Loader, "Rel2 segment extends past the end of the file");
|
||||
return;
|
||||
}
|
||||
if (!ConsumeRelocBudget(ph->p_filesz))
|
||||
return;
|
||||
end = buf+ph->p_filesz;
|
||||
|
||||
// Everything below reads forward from buf, so check there's something there each time. All of
|
||||
@@ -311,6 +351,12 @@ void ElfReader::LoadRelocations2(int rel_seg)
|
||||
while((1<<seg_bits)<rel_seg)
|
||||
seg_bits += 1;
|
||||
|
||||
// The three fields are packed into 16-bit commands, and the shifts below assume they fit.
|
||||
if (flag_bits + seg_bits + type_bits > 16) {
|
||||
ERROR_LOG_REPORT(Log::Loader, "Rel2: bad field sizes %d/%d/%d", flag_bits, seg_bits, type_bits);
|
||||
return;
|
||||
}
|
||||
|
||||
buf += 4;
|
||||
|
||||
// Both tables are prefixed by their own size, and are indexed by bitfields out of the command
|
||||
@@ -522,13 +568,12 @@ void ElfReader::LoadRelocations2(int rel_seg)
|
||||
|
||||
|
||||
int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
|
||||
DEBUG_LOG(Log::Loader,"String section: %i", header->e_shstrndx);
|
||||
|
||||
if (size_ < sizeof(Elf32_Ehdr)) {
|
||||
ERROR_LOG(Log::Loader, "Truncated ELF header, %d bytes", (int)size_);
|
||||
// Probably not the right error code.
|
||||
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
|
||||
}
|
||||
DEBUG_LOG(Log::Loader,"String section: %i", header->e_shstrndx);
|
||||
|
||||
if (header->e_ident[0] != ELFMAG0 || header->e_ident[1] != ELFMAG1
|
||||
|| header->e_ident[2] != ELFMAG2 || header->e_ident[3] != ELFMAG3)
|
||||
@@ -546,14 +591,14 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
|
||||
if (header->e_ident[EI_DATA] != ELFDATA2LSB)
|
||||
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
|
||||
|
||||
const size_t phdrEnd = header->e_phoff + sizeof(Elf32_Phdr) * GetNumSegments();
|
||||
const size_t shdrEnd = header->e_shoff + sizeof(Elf32_Shdr) * GetNumSections();
|
||||
const u64 phdrEnd = (u64)header->e_phoff + sizeof(Elf32_Phdr) * GetNumSegments();
|
||||
const u64 shdrEnd = (u64)header->e_shoff + sizeof(Elf32_Shdr) * GetNumSections();
|
||||
if (size_ < phdrEnd || size_ < shdrEnd) {
|
||||
// Say which table runs off the end and by how much. "Truncated ELF" on its own sends you
|
||||
// looking at the executable, when the usual cause is that we were handed fewer bytes than
|
||||
// the file really has - a short read, or a size clamped somewhere upstream.
|
||||
const char *which = size_ < phdrEnd ? "program header table" : "section header table";
|
||||
const size_t needed = size_ < phdrEnd ? phdrEnd : shdrEnd;
|
||||
const u64 needed = size_ < phdrEnd ? phdrEnd : shdrEnd;
|
||||
loadError_ = StringFromFormat(
|
||||
"Truncated ELF: %s ends at %d but only %d bytes are available (%d short), %d sections, %d segments",
|
||||
which, (int)needed, (int)size_, (int)(needed - size_), GetNumSections(), GetNumSegments());
|
||||
@@ -793,7 +838,7 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
|
||||
int numRelocs = s->sh_size / sizeof(Elf32_Rel);
|
||||
|
||||
Elf32_Rel *rels = (Elf32_Rel *)GetSectionDataPtr(i);
|
||||
if (GetSectionDataOffset(i) + sizeof(Elf32_Rel) * numRelocs > size_)
|
||||
if ((u64)GetSectionDataOffset(i) + sizeof(Elf32_Rel) * (u64)numRelocs > size_)
|
||||
rels = nullptr;
|
||||
|
||||
DEBUG_LOG(Log::Loader,"%s: Performing %i relocations on %s : offset = %08x", name, numRelocs, GetSectionName(sectionToModify), sections[i].sh_offset);
|
||||
@@ -845,7 +890,7 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
|
||||
int numRelocs = p->p_filesz / sizeof(Elf32_Rel);
|
||||
|
||||
Elf32_Rel *rels = (Elf32_Rel *)GetSegmentPtr(i);
|
||||
if (p->p_offset + p->p_filesz > size_)
|
||||
if ((u64)p->p_offset + p->p_filesz > size_)
|
||||
rels = nullptr;
|
||||
if (!rels || !LoadRelocations(rels, numRelocs)) {
|
||||
ERROR_LOG(Log::Loader, "LoadInto: Relocs failed, trying anyway (2)");
|
||||
|
||||
@@ -202,6 +202,10 @@ private:
|
||||
// array that callers are trusted to stay inside.
|
||||
std::vector<u32> segmentVAddr;
|
||||
size_t size_ = 0;
|
||||
// Relocation data processed so far. Several relocation sections or segments can point at the
|
||||
// same table, so this is capped at the file size to keep the total work linear.
|
||||
u64 relocBytesProcessed_ = 0;
|
||||
bool ConsumeRelocBudget(u64 bytes);
|
||||
u32 firstSegAlign = 0;
|
||||
std::string loadError_;
|
||||
};
|
||||
|
||||
+14
-13
@@ -146,7 +146,7 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
|
||||
|
||||
// The index table itself must fit entirely within the buffer before we
|
||||
// can dereference entries; otherwise indexTables[i] reads out of bounds.
|
||||
if (sizeof(Header) + (size_t)header->index_table_entries * sizeof(IndexTable) > size) {
|
||||
if (sizeof(Header) + (u64)header->index_table_entries * sizeof(IndexTable) > size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -172,15 +172,17 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
|
||||
|
||||
for (u32 i = 0; i < header->index_table_entries; i++)
|
||||
{
|
||||
size_t key_offset = header->key_table_start + indexTables[i].key_table_offset;
|
||||
if (key_offset >= size) {
|
||||
const u64 key_offset64 = (u64)header->key_table_start + indexTables[i].key_table_offset;
|
||||
if (key_offset64 >= size) {
|
||||
return false;
|
||||
}
|
||||
const size_t key_offset = (size_t)key_offset64;
|
||||
|
||||
size_t data_offset = header->data_table_start + indexTables[i].data_table_offset;
|
||||
if (data_offset >= size) {
|
||||
const u64 data_offset64 = (u64)header->data_table_start + indexTables[i].data_table_offset;
|
||||
if (data_offset64 >= size) {
|
||||
return false;
|
||||
}
|
||||
const size_t data_offset = (size_t)data_offset64;
|
||||
|
||||
std::string key = readStringCapped(key_offset, 0);
|
||||
if (key.empty())
|
||||
@@ -200,7 +202,7 @@ bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
|
||||
case 0x0004:
|
||||
// Special format UTF-8
|
||||
{
|
||||
if (data_offset + indexTables[i].param_len > size)
|
||||
if ((u64)data_offset + indexTables[i].param_len > size)
|
||||
continue;
|
||||
const u8 *utfdata = (const u8 *)(paramsfo + data_offset);
|
||||
VERBOSE_LOG(Log::Loader, "%s %s", key.c_str(), utfdata);
|
||||
@@ -242,7 +244,7 @@ int ParamSFOData::GetDataOffset(const u8 *paramsfo, size_t size, const char *dat
|
||||
WARN_LOG(Log::Loader, "Unexpected SFO header version: %08x", header->version);
|
||||
|
||||
// Both the index table and the key/data tables must fit in the buffer.
|
||||
if (sizeof(Header) + (size_t)header->index_table_entries * sizeof(IndexTable) > size)
|
||||
if (sizeof(Header) + (u64)header->index_table_entries * sizeof(IndexTable) > size)
|
||||
return -1;
|
||||
if (header->key_table_start > size || header->data_table_start > size)
|
||||
return -1;
|
||||
@@ -254,19 +256,18 @@ int ParamSFOData::GetDataOffset(const u8 *paramsfo, size_t size, const char *dat
|
||||
|
||||
for (u32 i = 0; i < header->index_table_entries; i++)
|
||||
{
|
||||
// In size_t throughout - these are u32s from the file, and mixing them with int meant the
|
||||
// bounds check below was done in whatever type the promotion landed on. ReadSFO does the
|
||||
// same arithmetic this way.
|
||||
size_t key_offset = header->key_table_start + indexTables[i].key_table_offset;
|
||||
// In u64 - these are u32s from the file, and the sums can wrap a 32-bit size_t.
|
||||
// ReadSFO does the same arithmetic this way.
|
||||
const u64 key_offset = (u64)header->key_table_start + indexTables[i].key_table_offset;
|
||||
if (key_offset >= size)
|
||||
continue;
|
||||
size_t data_offset = data_start + indexTables[i].data_table_offset;
|
||||
const u64 data_offset = (u64)data_start + indexTables[i].data_table_offset;
|
||||
if (data_offset >= size)
|
||||
continue;
|
||||
|
||||
const char *key = (const char *)(key_start + indexTables[i].key_table_offset);
|
||||
// Ensure the key string is NUL-terminated within the buffer before strcmp.
|
||||
if (strnlen(key, size - key_offset) == size - key_offset)
|
||||
if (strnlen(key, (size_t)(size - key_offset)) == size - key_offset)
|
||||
continue;
|
||||
if (!strcmp(key, dataName))
|
||||
{
|
||||
|
||||
@@ -972,7 +972,7 @@ static int pspDecryptType0(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
|
||||
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type0.prxHeader, sizeof(type0.prxHeader));
|
||||
decryptKirkHeaderType0(reinterpret_cast<u8*>(header), type0.kirkBlock, xorbuf, pti->code);
|
||||
|
||||
if (!KirkOutputFits(header, size))
|
||||
if (!KirkOutputFits(header, size - offset))
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
@@ -1031,7 +1031,7 @@ static int pspDecryptType1(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
|
||||
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type1.prxHeader, sizeof(type1.prxHeader));
|
||||
decryptKirkHeaderType0(reinterpret_cast<u8*>(header), type1.kirkBlock, xorbuf, pti->code);
|
||||
|
||||
if (!KirkOutputFits(header, size))
|
||||
if (!KirkOutputFits(header, size - offset))
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
@@ -1099,7 +1099,7 @@ static int pspDecryptType2(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
|
||||
decryptKirkHeader(reinterpret_cast<u8*>(header), type2.kirkHeader, xorbuf.cbegin()+0x10, pti->code);
|
||||
header->mode = 1;
|
||||
|
||||
if (!KirkOutputFits(header, size))
|
||||
if (!KirkOutputFits(header, size - offset))
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
@@ -1150,7 +1150,7 @@ static int pspFinishType5(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size
|
||||
decryptKirkHeader(reinterpret_cast<u8*>(header), type5.kirkHeader, xorbuf.cbegin()+0x10, code);
|
||||
header->mode = 1;
|
||||
|
||||
if (!KirkOutputFits(header, size))
|
||||
if (!KirkOutputFits(header, size - offset))
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
@@ -1275,7 +1275,7 @@ static int pspDecryptType6(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
|
||||
header->mode = 1;
|
||||
header->ecdsa_hash = 1;
|
||||
|
||||
if (!KirkOutputFits(header, size))
|
||||
if (!KirkOutputFits(header, size - offset))
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
@@ -1352,7 +1352,7 @@ static int pspDecryptType9(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
|
||||
// branch only writes the mode word and zeroes the rest of that region.
|
||||
header->ecdsa_hash = 0;
|
||||
|
||||
if (!KirkOutputFits(header, size))
|
||||
if (!KirkOutputFits(header, size - offset))
|
||||
{
|
||||
return -5;
|
||||
}
|
||||
@@ -1365,7 +1365,7 @@ static int pspDecryptType9(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 siz
|
||||
return decryptSize;
|
||||
}
|
||||
|
||||
int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
|
||||
static int pspDecryptPRXAnyType(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
|
||||
{
|
||||
// Every type below reads the tag at 0xD0, the size at 0xB0 and key data as far as 0x150, and
|
||||
// writes a KIRK header into outbuf at a fixed offset derived from sizeof(PSP_Header). Without
|
||||
@@ -1423,6 +1423,17 @@ int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u
|
||||
return pspDecryptType9(&kirk, inbuf, outbuf, size);
|
||||
}
|
||||
|
||||
int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
|
||||
{
|
||||
const int res = pspDecryptPRXAnyType(inbuf, outbuf, size, seed, fuseId);
|
||||
// The decrypted size comes from the header, and callers size their reads of outbuf by it.
|
||||
if (res > 0 && (u32)res > size) {
|
||||
ERROR_LOG(Log::Loader, "PRX claims a decrypted size of %d, larger than its %d bytes", res, (int)size);
|
||||
return -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool pspUnwrapDNASPGD(std::vector<u8> &data, const u8 *secureId, const u32 *fuseId)
|
||||
{
|
||||
if (data.size() < 0x90 || memcmp(data.data(), "\0PGD", 4) != 0) {
|
||||
|
||||
@@ -87,11 +87,15 @@ size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data,
|
||||
Prepare();
|
||||
size_t readSize;
|
||||
|
||||
if (absolutePos >= filesize_) {
|
||||
if (absolutePos < 0 || absolutePos >= filesize_) {
|
||||
bytes = 0;
|
||||
} else if (absolutePos + (s64)bytes >= filesize_) {
|
||||
bytes = (size_t)(filesize_ - absolutePos);
|
||||
}
|
||||
// The cache computes the last block as pos + bytes - 1.
|
||||
if (bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cache_ && cache_->IsValid() && (flags & Flags::HINT_UNCACHED) == 0) {
|
||||
readSize = cache_->ReadFromCache(absolutePos, bytes, data);
|
||||
@@ -357,6 +361,7 @@ bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
|
||||
|
||||
while (cacheSize_ > goal) {
|
||||
u16 minGeneration = generation_;
|
||||
bool evicted = false;
|
||||
|
||||
// We increment the iterator inside because we delete things inside.
|
||||
for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {
|
||||
@@ -377,6 +382,7 @@ bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
|
||||
info.generation = 0;
|
||||
info.hits = 0;
|
||||
--cacheSize_;
|
||||
evicted = true;
|
||||
|
||||
// TODO: Doing this in chunks might be a lot better.
|
||||
WriteIndexData(blockIndexLookup_[i], info);
|
||||
@@ -389,6 +395,12 @@ bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
|
||||
}
|
||||
}
|
||||
|
||||
// If we found nothing to evict and the next pass would look for the same generation,
|
||||
// it can't make progress either.
|
||||
if (!evicted && minGeneration == oldestGeneration_) {
|
||||
ERROR_LOG(Log::Loader, "Disk cache: Nothing left to evict, %d blocks counted", (int)cacheSize_);
|
||||
break;
|
||||
}
|
||||
// If we didn't find any, update to the lowest we did find.
|
||||
oldestGeneration_ = minGeneration;
|
||||
}
|
||||
@@ -548,6 +560,9 @@ bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {
|
||||
} else if (header.maxBlocks < MAX_BLOCKS_LOWER_BOUND || header.maxBlocks > MAX_BLOCKS_UPPER_BOUND) {
|
||||
// This means it's not in our safety bounds, reject.
|
||||
valid = false;
|
||||
} else if (header.blockSize != DEFAULT_BLOCK_SIZE) {
|
||||
// We only ever create these, and everything below divides by and allocates the block size.
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// If it's valid, retain the file pointer.
|
||||
@@ -600,6 +615,12 @@ void DiskCachingFileLoaderCache::LoadCacheIndex() {
|
||||
if (index_[i].block == INVALID_BLOCK) {
|
||||
continue;
|
||||
}
|
||||
// Two entries can't share a data block. Keeping both would serve one of them the wrong
|
||||
// data, and count a block that eviction can't find (which then never finishes).
|
||||
if (blockIndexLookup_[index_[i].block] != INVALID_INDEX) {
|
||||
index_[i].block = INVALID_BLOCK;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index_[i].generation < oldestGeneration_) {
|
||||
oldestGeneration_ = index_[i].generation;
|
||||
|
||||
@@ -89,6 +89,11 @@ void HTTPFileLoader::Prepare() {
|
||||
}
|
||||
if (size_pos != header.npos) {
|
||||
filesize_ = atoll(&header[size_pos]);
|
||||
// The caches size their indexes from this. Nothing we'd load is this big.
|
||||
if (filesize_ < 0 || filesize_ > (64LL << 30)) {
|
||||
ERROR_LOG(Log::Loader, "HTTP: Implausible Content-Length: %s", header.c_str());
|
||||
filesize_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startsWithNoCase(header, "Accept-Ranges:")) {
|
||||
|
||||
@@ -428,13 +428,13 @@ bool PS1EbootFileLoader::ParseMds(const Path &mdsPath, const std::string &data)
|
||||
const u8 *d = (const u8 *)data.data();
|
||||
const size_t size = data.size();
|
||||
auto read32 = [&](size_t offset) -> u32 {
|
||||
return offset + 4 <= size ? (d[offset] | (d[offset + 1] << 8) | (d[offset + 2] << 16) | ((u32)d[offset + 3] << 24)) : 0;
|
||||
return offset <= size && size - offset >= 4 ? (d[offset] | (d[offset + 1] << 8) | (d[offset + 2] << 16) | ((u32)d[offset + 3] << 24)) : 0;
|
||||
};
|
||||
if (size < 0x58 || memcmp(d, "MEDIA DESCRIPTOR", 16) != 0) {
|
||||
return false;
|
||||
}
|
||||
const u32 session = read32(0x50);
|
||||
if (session + 0x18 > size) {
|
||||
if ((size_t)session + 0x18 > size) {
|
||||
return false;
|
||||
}
|
||||
const u32 numBlocks = d[session + 0x0A];
|
||||
@@ -466,6 +466,9 @@ bool PS1EbootFileLoader::ParseMds(const Path &mdsPath, const std::string &data)
|
||||
if (file < 0) {
|
||||
// "*.mdf" is the .mds's own name.
|
||||
const u32 nameBlock = read32(b + 0x34);
|
||||
if ((size_t)nameBlock + 5 > size) {
|
||||
return false;
|
||||
}
|
||||
const u32 nameOffset = read32(nameBlock);
|
||||
std::string name;
|
||||
for (u32 p = nameOffset; p < size && d[p] != 0; p += d[nameBlock + 4] ? 2 : 1) {
|
||||
|
||||
@@ -88,6 +88,11 @@ size_t RamCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, F
|
||||
|
||||
void RamCachingFileLoader::InitCache() {
|
||||
std::lock_guard<std::mutex> guard(blocksMutex_);
|
||||
// The whole file has to fit in memory (and a size_t, which may be 32 bits).
|
||||
if (filesize_ <= 0 || (u64)filesize_ > (u64)(SIZE_MAX - BLOCK_SIZE) || (u64)filesize_ > (8ULL << 30)) {
|
||||
ERROR_LOG(Log::IO, "Can't cache a file of %lld bytes in RAM, will fall back to regular reads.", (long long)filesize_);
|
||||
return;
|
||||
}
|
||||
u32 blockCount = (u32)((filesize_ + BLOCK_SIZE - 1) >> BLOCK_SHIFT);
|
||||
// Overallocate for the last block.
|
||||
cache_ = (u8 *)malloc((size_t)blockCount << BLOCK_SHIFT);
|
||||
|
||||
@@ -74,6 +74,12 @@ bool ZipFileLoader::Initialize(int fileIndex) {
|
||||
}
|
||||
|
||||
_dbg_assert_(zstat.index == fileIndex);
|
||||
// The whole file is decompressed into memory. No disc image is anywhere near 4GB, and the
|
||||
// size has to fit in a (possibly 32-bit) size_t too.
|
||||
if (!(zstat.valid & ZIP_STAT_SIZE) || zstat.size > (4ULL << 30) || zstat.size > (u64)SIZE_MAX) {
|
||||
ERROR_LOG(Log::IO, "File index %d in ZIP has an implausible size (%llu)", fileIndex, (unsigned long long)zstat.size);
|
||||
return false;
|
||||
}
|
||||
dataFileSize_ = zstat.size;
|
||||
dataFile_ = zip_fopen_index(zipArchive_, zstat.index, ZIP_FL_UNCHANGED);
|
||||
if (!dataFile_) {
|
||||
|
||||
@@ -92,6 +92,10 @@ size_t BlobFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
|
||||
entry->second = fileLoader_->FileSize() + position;
|
||||
break;
|
||||
}
|
||||
// Reads take the position as a file offset.
|
||||
if (entry->second < 0) {
|
||||
entry->second = 0;
|
||||
}
|
||||
return (size_t)entry->second;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -99,7 +99,7 @@ static bool ParseUDFFileEntryExtent(FileLoader *fileLoader, u32 sector, UDFShort
|
||||
|
||||
u32 extAttrLen = ReadLE32(block.data() + 0xA8);
|
||||
u32 allocDescLen = ReadLE32(block.data() + 0xAC);
|
||||
u32 allocDescOffset = 0xB0 + extAttrLen;
|
||||
const u64 allocDescOffset = 0xB0 + (u64)extAttrLen;
|
||||
if (allocDescLen < 8 || allocDescOffset + 8 > block.size())
|
||||
return false;
|
||||
|
||||
@@ -110,11 +110,14 @@ static bool ParseUDFRootDirectory(FileLoader *fileLoader, u32 sector, u32 partit
|
||||
UDFShortAd extent{};
|
||||
if (!ParseUDFFileEntryExtent(fileLoader, sector, &extent))
|
||||
return false;
|
||||
if (extent.length == 0)
|
||||
// A root directory is a few KB, so this cap is very generous.
|
||||
if (extent.length == 0 || extent.length > 1024 * 1024)
|
||||
return false;
|
||||
|
||||
const u64 offset = ((u64)partitionStart + extent.position) * 2048;
|
||||
if (offset + extent.length > fileLoader->FileSize())
|
||||
return false;
|
||||
dirData->resize(extent.length);
|
||||
const u64 offset = (u64)(partitionStart + extent.position) * 2048;
|
||||
return fileLoader->ReadAt(offset, 1, dirData->size(), dirData->data()) == dirData->size();
|
||||
}
|
||||
|
||||
@@ -191,7 +194,8 @@ static bool FindUDFLayerFileEntrySectors(FileLoader *fileLoader, u32 rootSector,
|
||||
if (entryLen == 0 || offset + entryLen > dirData.size())
|
||||
break;
|
||||
|
||||
if (tag == 0x0101 && crcLen >= 20) {
|
||||
// The fixed part of a File Identifier Descriptor is 38 bytes.
|
||||
if (tag == 0x0101 && crcLen >= 22) {
|
||||
u8 fileIdLen = dirData[offset + 19];
|
||||
u16 implUseLen = ReadLE16(&dirData[offset + 36]);
|
||||
size_t nameOffset = offset + 38 + implUseLen;
|
||||
@@ -567,6 +571,10 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)
|
||||
return;
|
||||
}
|
||||
const u64 totalSize = hdr.total_bytes;
|
||||
if (totalSize > MAX_CSO_IMAGE_SIZE) {
|
||||
errorString_ = "Invalid CSO header (image is too large)";
|
||||
return;
|
||||
}
|
||||
// Compute the counts without overflowing the 64-bit total size, then
|
||||
// validate them before truncating to the 32-bit fields used by the CSO
|
||||
// block reader. In particular, numFrames + 1 must remain representable.
|
||||
@@ -739,6 +747,9 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
|
||||
}
|
||||
|
||||
bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
||||
if (count <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (count == 1) {
|
||||
return ReadBlock(minBlock, outPtr);
|
||||
}
|
||||
@@ -801,7 +812,18 @@ bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
||||
}
|
||||
|
||||
if (plain) {
|
||||
memcpy(outPtr, rawBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());
|
||||
// The frame's data may be shorter than a full frame (or the read came up short),
|
||||
// so only copy what's actually in the buffer.
|
||||
const u64 available = std::min(frameReadEnd, readBufferEnd) - frameReadPos;
|
||||
const u64 copyStart = (u64)frameBlockOffset * GetBlockSize();
|
||||
const u64 copyBytes = (u64)frameBlocks * GetBlockSize();
|
||||
const u64 copied = available > copyStart ? std::min(available - copyStart, copyBytes) : 0;
|
||||
if (copied) {
|
||||
memcpy(outPtr, rawBuffer + copyStart, (size_t)copied);
|
||||
}
|
||||
if (copied < copyBytes) {
|
||||
memset(outPtr + copied, 0, (size_t)(copyBytes - copied));
|
||||
}
|
||||
} else {
|
||||
z.avail_in = frameReadSize;
|
||||
z.next_out = frameBlocks == blocksPerFrame ? outPtr : zlibBuffer;
|
||||
@@ -980,14 +1002,15 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
|
||||
}
|
||||
|
||||
int lba = blockNumber - currentBlock_;
|
||||
if (lba >= 0 && lba < blockLBAs_){
|
||||
if (currentBlock_ >= 0 && lba >= 0 && lba < blockLBAs_){
|
||||
memcpy(outPtr, blockBuf_ + lba*2048, 2048);
|
||||
return true;
|
||||
}
|
||||
|
||||
int block = blockNumber / blockLBAs_;
|
||||
lba = blockNumber % blockLBAs_;
|
||||
currentBlock_ = block * blockLBAs_;
|
||||
// Only marked valid once blockBuf_ has been filled successfully.
|
||||
currentBlock_ = -1;
|
||||
|
||||
// blockNumber comes from the caller (ultimately from guest-controlled reads, e.g.
|
||||
// via /sce_lbn.../_size... on a raw sector open) and isn't otherwise guaranteed to be
|
||||
@@ -997,6 +1020,7 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
|
||||
}
|
||||
|
||||
if (table_[block].unk_1c != 0) {
|
||||
memset(outPtr, 0, 2048);
|
||||
if((u32)block == (numBlocks_ - 1))
|
||||
return true; // demos make by fake_np
|
||||
else
|
||||
@@ -1019,6 +1043,7 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
|
||||
|
||||
size_t readSize = fileLoader_->ReadAt(psarOffset+table_[block].offset, 1, table_[block].size, readBuf, flags);
|
||||
if (readSize != (size_t)table_[block].size){
|
||||
memset(outPtr, 0, 2048);
|
||||
if((u32)block==(numBlocks_-1))
|
||||
return true;
|
||||
else
|
||||
@@ -1047,6 +1072,7 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
|
||||
}
|
||||
}
|
||||
|
||||
currentBlock_ = block * blockLBAs_;
|
||||
memcpy(outPtr, blockBuf_+lba*2048, 2048);
|
||||
return true;
|
||||
}
|
||||
@@ -1091,10 +1117,13 @@ chd_file *OpenCHDThroughLoader(FileLoader *fileLoader, std::string *error) {
|
||||
coreFile->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {
|
||||
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
||||
FileLoader *loader = (FileLoader *)file->argp;
|
||||
if (size == 0) {
|
||||
return (size_t)0;
|
||||
}
|
||||
uint64_t totalSize = size * count;
|
||||
loader->ReadAt(coreFile->seekPos, totalSize, out_data);
|
||||
coreFile->seekPos += totalSize;
|
||||
return size * count;
|
||||
size_t bytesRead = loader->ReadAt(coreFile->seekPos, totalSize, out_data);
|
||||
coreFile->seekPos += bytesRead;
|
||||
return bytesRead / size;
|
||||
};
|
||||
coreFile->core.fclose = [](core_file *file) {
|
||||
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
||||
@@ -1173,6 +1202,15 @@ CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
||||
impl_->chd = file;
|
||||
impl_->header = chd_get_header(impl_->chd);
|
||||
|
||||
// We copy 2048 bytes out of each unit, and need at least one unit per hunk
|
||||
// (otherwise the block math below divides by zero or reads outside the hunk).
|
||||
if (impl_->header->unitbytes < 2048 || impl_->header->hunkbytes < impl_->header->unitbytes) {
|
||||
errorString_ = StringFromFormat("CHD has unsupported unit size %d (hunk size %d)", impl_->header->unitbytes, impl_->header->hunkbytes);
|
||||
chd_close(file);
|
||||
impl_->chd = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
readBuffer = new u8[impl_->header->hunkbytes];
|
||||
currentHunk = -1;
|
||||
blocksPerHunk = impl_->header->hunkbytes / impl_->header->unitbytes;
|
||||
@@ -1205,6 +1243,9 @@ bool CHDFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
|
||||
if (err != CHDERR_NONE) {
|
||||
ERROR_LOG(Log::Loader, "CHD read failed: %d %d %s", blockNumber, hunk, chd_error_string(err));
|
||||
NotifyReadError();
|
||||
currentHunk = -1;
|
||||
memset(outPtr, 0, GetBlockSize());
|
||||
return false;
|
||||
}
|
||||
currentHunk = hunk;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ protected:
|
||||
std::string errorString_;
|
||||
};
|
||||
|
||||
// A dual-layer UMD holds 1.8 GB, so this is generous. A CSO header can claim any size,
|
||||
// independent of how big the file is.
|
||||
constexpr u64 MAX_CSO_IMAGE_SIZE = 8ULL * 1024 * 1024 * 1024;
|
||||
|
||||
class CISOFileBlockDevice : public BlockDevice {
|
||||
public:
|
||||
CISOFileBlockDevice(FileLoader *fileLoader);
|
||||
|
||||
@@ -187,6 +187,13 @@ bool Compress(const Path &input, const Path &output, Preset preset, const Progre
|
||||
return false;
|
||||
}
|
||||
|
||||
// No PSP disc is anywhere near this. The size can come from a CSO header, which can claim
|
||||
// far more than the file holds, and the index below is sized from it.
|
||||
if (src.size > MAX_CSO_IMAGE_SIZE) {
|
||||
*error = "Input image is too large";
|
||||
return false;
|
||||
}
|
||||
|
||||
const PresetSettings settings = SettingsFor(preset);
|
||||
const uint32_t blockSize = settings.blockSize;
|
||||
const uint32_t sectorsPerBlock = blockSize / SECTOR_SIZE;
|
||||
|
||||
@@ -233,7 +233,17 @@ std::string ISOFileSystem::TreeEntry::BuildPath() {
|
||||
}
|
||||
|
||||
void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
|
||||
for (u32 secnum = root->startsector, endsector = root->startsector + (root->dirsize + 2047) / 2048; secnum < endsector; ++secnum) {
|
||||
// Real directories are a few KB. The size comes from the image, so cap it to keep a corrupt
|
||||
// one from making us parse (and allocate entries for) gigabytes of sectors.
|
||||
const u32 MAX_DIRECTORY_SIZE = 4 * 1024 * 1024;
|
||||
u32 dirsize = root->dirsize;
|
||||
if (dirsize > MAX_DIRECTORY_SIZE) {
|
||||
blockDevice->NotifyReadError();
|
||||
ERROR_LOG(Log::FileSystem, "Directory '%s' claims an implausible size (%u), truncating", root->name.c_str(), dirsize);
|
||||
dirsize = MAX_DIRECTORY_SIZE;
|
||||
}
|
||||
const u32 endsector = (u32)std::min<u64>((u64)root->startsector + (dirsize + 2047) / 2048, blockDevice->GetNumBlocks());
|
||||
for (u32 secnum = root->startsector; secnum < endsector; ++secnum) {
|
||||
u8 theSector[2048];
|
||||
if (!blockDevice->ReadBlock(secnum, theSector)) {
|
||||
blockDevice->NotifyReadError();
|
||||
@@ -252,9 +262,10 @@ void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
|
||||
break;
|
||||
|
||||
const int IDENTIFIER_OFFSET = 33;
|
||||
if (offset + IDENTIFIER_OFFSET + dir.identifierLength > 2048) {
|
||||
if (offset + IDENTIFIER_OFFSET > 2048 || offset + IDENTIFIER_OFFSET + dir.identifierLength > 2048) {
|
||||
blockDevice->NotifyReadError();
|
||||
ERROR_LOG(Log::FileSystem, "Directory entry crosses sectors, corrupt iso?");
|
||||
root->valid = true; // Prevents re-reading (and duplicating the children so far)
|
||||
return;
|
||||
}
|
||||
// dir.size (the record length actually consumed) must cover at least its
|
||||
@@ -265,6 +276,7 @@ void ISOFileSystem::ReadDirectory(TreeEntry *root) const {
|
||||
if (dir.size < IDENTIFIER_OFFSET + dir.identifierLength) {
|
||||
blockDevice->NotifyReadError();
|
||||
ERROR_LOG(Log::FileSystem, "Directory entry size too small, corrupt iso?");
|
||||
root->valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -522,7 +534,9 @@ int ISOFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outd
|
||||
u8 *out = Memory::GetPointerWriteRangeOrException(outdataPtr, size);
|
||||
|
||||
int blocks = size / blockDevice->GetBlockSize();
|
||||
blockDevice->ReadBlocks(block, blocks, out);
|
||||
if (blocks > 0) {
|
||||
blockDevice->ReadBlocks(block, blocks, out);
|
||||
}
|
||||
size -= blocks * blockDevice->GetBlockSize();
|
||||
out += blocks * blockDevice->GetBlockSize();
|
||||
|
||||
|
||||
@@ -616,7 +616,7 @@ size_t MetaFileSystem::SeekFile(u32 handle, s32 position, FileMove type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet) {
|
||||
int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet, size_t maxSize) {
|
||||
FileAccess access = FILEACCESS_READ;
|
||||
if (quiet) {
|
||||
access = (FileAccess)(access | FILEACCESS_PPSSPP_QUIET);
|
||||
@@ -626,8 +626,13 @@ int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8>
|
||||
return handle;
|
||||
|
||||
SeekFile(handle, 0, FILEMOVE_END);
|
||||
size_t dataSize = GetSeekPos(handle);
|
||||
const s64 fileSize = GetSeekPos(handle);
|
||||
SeekFile(handle, 0, FILEMOVE_BEGIN);
|
||||
if (fileSize < 0 || (u64)fileSize > maxSize) {
|
||||
CloseFile(handle);
|
||||
return SCE_KERNEL_ERROR_ERROR;
|
||||
}
|
||||
const size_t dataSize = (size_t)fileSize;
|
||||
data.resize(dataSize);
|
||||
|
||||
size_t result = ReadFile(handle, data.data(), dataSize);
|
||||
|
||||
@@ -151,7 +151,8 @@ public:
|
||||
u64 FreeDiskSpace(const std::string &path) override;
|
||||
|
||||
// Convenience helper - returns < 0 on failure.
|
||||
int ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet = false);
|
||||
// Fails (without allocating) if the file is larger than maxSize.
|
||||
int ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet = false, size_t maxSize = (size_t)-1);
|
||||
|
||||
void SetStartingDirectory(std::string_view dir) {
|
||||
std::lock_guard<std::recursive_mutex> guard(lock);
|
||||
|
||||
@@ -177,7 +177,9 @@ static int rc_bittree(LZRC_DECODE *rc, u8 *probs, int limit)
|
||||
*/
|
||||
static int rc_number(LZRC_DECODE *rc, u8 *prob, int n)
|
||||
{
|
||||
int i, number = 1;
|
||||
// Unsigned, since corrupt data can ask for more direct bits than fit (it then wraps).
|
||||
int i;
|
||||
u32 number = 1;
|
||||
|
||||
if(n>3){
|
||||
number = (number<<1)+rc_bit(rc, prob+3);
|
||||
@@ -210,7 +212,7 @@ static int rc_number(LZRC_DECODE *rc, u8 *prob, int n)
|
||||
}
|
||||
}
|
||||
|
||||
return number;
|
||||
return (int)number;
|
||||
}
|
||||
|
||||
|
||||
@@ -305,13 +307,15 @@ int lzrc_decompress(void *out, int out_len, void *in, int in_len)
|
||||
}
|
||||
|
||||
/* copy match bytes */
|
||||
if(match_dist>rc.out_ptr || match_dist<0){
|
||||
if(match_dist>rc.out_ptr || match_dist<=0){
|
||||
printf("match_dist out of range! %08x\n", match_dist);
|
||||
return -1;
|
||||
}
|
||||
match_src = rc.output+rc.out_ptr-match_dist;
|
||||
for(i=0; i<match_len+1; i++){
|
||||
rc_putbyte(&rc, *match_src++);
|
||||
if (rc.error)
|
||||
return -1;
|
||||
}
|
||||
rc_state = 6+((rc.out_ptr+1)&1);
|
||||
}
|
||||
|
||||
+12
-2
@@ -1048,8 +1048,13 @@ static u32 npdrmRead(FileNode *f, u8 *data, int size) {
|
||||
block = pgd->file_offset/pgd->block_size;
|
||||
offset = pgd->file_offset%pgd->block_size;
|
||||
|
||||
if (size > (int)pgd->data_size)
|
||||
size = (int)pgd->data_size;
|
||||
// Unsigned: data_size comes from the (forgeable) header, and a value over 2^31 turned the
|
||||
// clamp into a huge read. Only what's left of the file can be read, too.
|
||||
const u32 available = pgd->file_offset < pgd->data_size ? pgd->data_size - pgd->file_offset : 0;
|
||||
if (size < 0)
|
||||
size = 0;
|
||||
if ((u32)size > available)
|
||||
size = (int)available;
|
||||
remain_size = size;
|
||||
|
||||
KirkState *kirk = __ChnnlsvKirkState();
|
||||
@@ -3093,6 +3098,11 @@ int __IoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 out
|
||||
pspFileSystem.SeekFile(f->handle, (s32)f->pgd_offset, FILEMOVE_BEGIN);
|
||||
pspFileSystem.ReadFile(f->handle, pgd_header, 0x90);
|
||||
KirkState *kirk = __ChnnlsvKirkState();
|
||||
// Calling this again would otherwise leak the previous one.
|
||||
if (f->pgdInfo) {
|
||||
pgd_close(f->pgdInfo);
|
||||
f->pgdInfo = nullptr;
|
||||
}
|
||||
f->pgdInfo = pgd_open(kirk, pgd_header, 2, key_ptr);
|
||||
if (!f->pgdInfo) {
|
||||
f->npdrm = false;
|
||||
|
||||
@@ -1256,14 +1256,14 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
|
||||
return true;
|
||||
}
|
||||
|
||||
static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) {
|
||||
static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, const u8 *InBuffer, int InBufferLength) {
|
||||
int err;
|
||||
z_stream stream;
|
||||
u8 *outBufferPtr;
|
||||
|
||||
outBufferPtr = OutBuffer;
|
||||
stream.next_in = InBuffer;
|
||||
stream.avail_in = (uInt)OutBufferLength;
|
||||
stream.next_in = (Bytef *)InBuffer;
|
||||
stream.avail_in = (uInt)InBufferLength;
|
||||
stream.next_out = outBufferPtr;
|
||||
stream.avail_out = (uInt)OutBufferLength;
|
||||
stream.zalloc = (alloc_func)0;
|
||||
@@ -1629,7 +1629,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
|
||||
if (Read32(ptr) == SCE_MAGIC) { // "~SCE"
|
||||
const u32 headerSize = *(const u32_le*)(ptr + 4);
|
||||
if (headerSize < elfSize) {
|
||||
// Leave at least the magic read below.
|
||||
if (headerSize < elfSize && elfSize - headerSize >= 4) {
|
||||
// Parse and print the lib info
|
||||
parsePrxLibInfo(ptr, headerSize);
|
||||
|
||||
@@ -1646,11 +1647,13 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
DEBUG_LOG(Log::sceModule, "Decrypting ~PSP file");
|
||||
head = (const PSP_Header *)ptr;
|
||||
devkitVersion = head->devkitversion;
|
||||
// Not necessarily terminated.
|
||||
const std::string headModName(head->modname, strnlen(head->modname, sizeof(head->modname)));
|
||||
|
||||
bool wasDisabled;
|
||||
if (ShouldHLEModuleForLoad(head->modname, &wasDisabled)) {
|
||||
if (ShouldHLEModuleForLoad(headModName, &wasDisabled)) {
|
||||
int ver = (head->module_ver_hi << 8) | head->module_ver_lo;
|
||||
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x, crc %x", head->modname, ver, head->devkitversion, module->crc);
|
||||
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x, crc %x", headModName.c_str(), ver, head->devkitversion, module->crc);
|
||||
|
||||
// TODO: Don't conflate these!
|
||||
reportedModule = true;
|
||||
@@ -1658,7 +1661,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
}
|
||||
|
||||
if (wasDisabled) {
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, StringFromFormat("HLE for '%s' has been manually disabled", head->modname));
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, StringFromFormat("HLE for '%s' has been manually disabled", headModName.c_str()));
|
||||
}
|
||||
const u8 *in = ptr;
|
||||
const bool isCompressed = (head->comp_attribute & 1) != 0;
|
||||
@@ -1673,7 +1676,17 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
return nullptr;
|
||||
}
|
||||
const auto maxElfSize = std::max(head->elf_size, head->psp_size);
|
||||
newptr = new u8[maxElfSize];
|
||||
// elf_size isn't bounded by anything else. Nothing this big fits in PSP RAM.
|
||||
if (maxElfSize > 64 * 1024 * 1024) {
|
||||
*error_string = StringFromFormat("ELF/PRX corrupt, unreasonable size: %u", (u32)maxElfSize);
|
||||
module->Cleanup();
|
||||
kernelObjects.Destroy<PSPModule>(module->GetUID());
|
||||
error = SCE_KERNEL_ERROR_FILEERR;
|
||||
return nullptr;
|
||||
}
|
||||
// Zeroed, since decryption and decompression can fill less than all of it and the ELF
|
||||
// reader is handed the whole buffer.
|
||||
newptr = new u8[maxElfSize]();
|
||||
elfSize = maxElfSize;
|
||||
ptr = newptr;
|
||||
int decryptedSize = pspDecryptPRX(in, (u8*)ptr, head->psp_size, prxSeed, fuseId);
|
||||
@@ -1722,13 +1735,13 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
} else {
|
||||
scheme = "gzip";
|
||||
_dbg_assert_(Read32(ptr + 0x150) != ELF_MAGIC);
|
||||
outBytes = gzipDecompress((u8 *)ptr, maxElfSize, temp);
|
||||
outBytes = gzipDecompress((u8 *)ptr, maxElfSize, temp, decryptedSize);
|
||||
}
|
||||
free(temp);
|
||||
if (outBytes < 0) {
|
||||
// Bail out cleanly rather than falling through to parse whatever's left in the
|
||||
// buffer (still compressed, not a valid ELF) as if it were real code.
|
||||
*error_string = StringFromFormat("Module '%s' %s decompression failed", head->modname, scheme);
|
||||
*error_string = StringFromFormat("Module '%s' %s decompression failed", headModName.c_str(), scheme);
|
||||
delete [] newptr;
|
||||
module->Cleanup();
|
||||
kernelObjects.Destroy<PSPModule>(module->GetUID());
|
||||
@@ -1736,7 +1749,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
error = SCE_KERNEL_ERROR_FILEERR;
|
||||
return nullptr;
|
||||
}
|
||||
INFO_LOG(Log::sceModule, "'%s' is %s-compressed, decompressing (%d -> %d bytes, bufmax=%d).", head->modname, scheme, decryptedSize, outBytes, maxElfSize);
|
||||
INFO_LOG(Log::sceModule, "'%s' is %s-compressed, decompressing (%d -> %d bytes, bufmax=%d).", headModName.c_str(), scheme, decryptedSize, outBytes, maxElfSize);
|
||||
}
|
||||
|
||||
if (fakeLoadedModule) {
|
||||
@@ -1745,7 +1758,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
std::string elfFilename(KeepAfterLast(filename, '/'));
|
||||
if (elfFilename.empty() || startsWith(elfFilename, "sce_lbn")) {
|
||||
// Use the name from the header.
|
||||
elfFilename = head->modname;
|
||||
elfFilename = headModName;
|
||||
}
|
||||
DumpFileIfEnabled(ptr, (u32)elfSize, elfFilename.c_str(), DumpFileType::PRX);
|
||||
|
||||
@@ -1884,7 +1897,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
else
|
||||
modinfoaddr = reader.GetSegmentVaddr(0) + (reader.GetSegmentPaddr(0) & 0x7FFFFFFF) - reader.GetSegmentOffset(0);
|
||||
|
||||
if (!Memory::IsValidAddress(modinfoaddr)) {
|
||||
if (!Memory::IsValidRange(modinfoaddr, sizeof(PspModuleInfo))) {
|
||||
*error_string = StringFromFormat("Bad module info address %08x", modinfoaddr);
|
||||
ERROR_LOG(Log::sceModule, "Bad module info address %08x", modinfoaddr);
|
||||
delete[] newptr;
|
||||
@@ -1895,6 +1908,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
}
|
||||
|
||||
modinfo = (const PspModuleInfo *)Memory::GetPointerUnchecked(modinfoaddr);
|
||||
// Not necessarily terminated.
|
||||
const std::string modInfoName(modinfo->name, strnlen(modinfo->name, sizeof(modinfo->name)));
|
||||
|
||||
module->nm.nsegment = reader.GetNumSegments();
|
||||
module->nm.attribute = modinfo->moduleAttrs;
|
||||
@@ -1902,9 +1917,9 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
// name check is for firmware 1.50, whose vshmain.prx declares no attributes at all - Sony
|
||||
// only started setting PSP_MODULE_VSH_MODE in 1.52. Without it the whole VSH bootstrap
|
||||
// below was skipped and the shell ran with none of its support modules loaded.
|
||||
if ((module->nm.attribute & PSP_MODULE_VSH_MODE) != 0 || equals(modinfo->name, "vsh_module")) {
|
||||
if ((module->nm.attribute & PSP_MODULE_VSH_MODE) != 0 || modInfoName == "vsh_module") {
|
||||
g_runningVSH = true;
|
||||
INFO_LOG(Log::sceModule, "VSH mode module detected: %s", modinfo->name);
|
||||
INFO_LOG(Log::sceModule, "VSH mode module detected: %s", modInfoName.c_str());
|
||||
// The shell booted without a game: its drive starts out empty, for a disc to go in later.
|
||||
if (!pspFileSystem.GetSystem("disc0:")) {
|
||||
__UmdEject();
|
||||
@@ -1913,7 +1928,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
|
||||
// OK, even if it's an ELF module, it might be one we shouldn't fully load and execute!
|
||||
// This is seen with mpeg.prx in Tony Hawk's Underground 2, see #20568.
|
||||
if (ShouldHLEModuleForLoad(modinfo->name)) {
|
||||
if (ShouldHLEModuleForLoad(modInfoName)) {
|
||||
// We load it, but at least we don't run any part of it.
|
||||
module->isFake = true;
|
||||
}
|
||||
@@ -1988,7 +2003,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
module->libstub = modinfo->libstub;
|
||||
module->libstubend = modinfo->libstubend;
|
||||
|
||||
INFO_LOG(Log::Loader, "Module %s: %08x %08x %08x", modinfo->name, modinfo->gp, modinfo->libent, modinfo->libstub);
|
||||
INFO_LOG(Log::Loader, "Module %s: %08x %08x %08x", modInfoName.c_str(), modinfo->gp, modinfo->libent, modinfo->libstub);
|
||||
DEBUG_LOG(Log::Loader,"===================================================");
|
||||
|
||||
u32 firstImportStubAddr = 0;
|
||||
@@ -2098,16 +2113,24 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
|
||||
const u32_le *entPos = nullptr;
|
||||
const u32_le *entEnd = nullptr;
|
||||
const u32_le *entBegin = nullptr;
|
||||
// A module that wasn't relocated (cut off before its relocations, see LoadInto) still has
|
||||
// link-time addresses here.
|
||||
if (Memory::IsValidRange(modinfo->libent, modinfo->libentend - modinfo->libent)) {
|
||||
entPos = (const u32_le *)Memory::GetPointerUnchecked(modinfo->libent);
|
||||
entEnd = (const u32_le *)Memory::GetPointerUnchecked(modinfo->libentend);
|
||||
entBegin = entPos;
|
||||
} else {
|
||||
WARN_LOG(Log::Loader, "Garbage libent address %08x or end %08x, no exports", modinfo->libent, modinfo->libentend);
|
||||
}
|
||||
|
||||
for (int m = 0; entPos < entEnd; ++m) {
|
||||
const u32 entAddr = modinfo->libent + (u32)((const u8 *)entPos - (const u8 *)entBegin);
|
||||
// Read as a whole struct below, even when the entry is shorter.
|
||||
if (!Memory::IsValidRange(entAddr, sizeof(PspLibEntEntry))) {
|
||||
WARN_LOG_REPORT(Log::Loader, "Export entry at %08x runs off the end of memory", entAddr);
|
||||
break;
|
||||
}
|
||||
const PspLibEntEntry *ent = (const PspLibEntEntry *)entPos;
|
||||
entPos += ent->size;
|
||||
if (ent->size == 0) {
|
||||
@@ -2118,7 +2141,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
|
||||
const u32 variableCount = ent->size <= 4 ? ent->vcount : std::max((u32)ent->vcount , (u32)ent->vcountNew);
|
||||
const char *name;
|
||||
if (Memory::IsValidAddress(ent->name)) {
|
||||
if (ent->name != 0 && Memory::IsValidNullTerminatedString(ent->name)) {
|
||||
name = Memory::GetCharPointer(ent->name);
|
||||
} else if (ent->name == 0) {
|
||||
name = module->nm.name;
|
||||
@@ -2128,9 +2151,11 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
|
||||
INFO_LOG(Log::Loader, "Exporting ent %d named %s, %d funcs, %d vars, resident %08x", m, name, ent->fcount, ent->vcount, ent->resident);
|
||||
|
||||
if (!Memory::IsValidAddress(ent->resident)) {
|
||||
// The NIDs, then the addresses, one word each per function and variable.
|
||||
const u32 residentBytes = ((u32)ent->fcount + variableCount) * 8;
|
||||
if (!Memory::IsValidAddress(ent->resident) || !Memory::IsValidRange(ent->resident, residentBytes)) {
|
||||
if (ent->fcount + variableCount > 0) {
|
||||
WARN_LOG_REPORT(Log::Loader, "Invalid export resident address %08x", ent->resident);
|
||||
WARN_LOG_REPORT(Log::Loader, "Invalid export resident address %08x (%d funcs, %d vars)", ent->resident, ent->fcount, variableCount);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -2193,6 +2218,12 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
WARN_LOG(Log::Loader, "Invalid var %d, nid %08x, export address %08x", j, nid, exportAddr);
|
||||
continue;
|
||||
}
|
||||
// The thread parameter variables below are four words.
|
||||
const bool isThreadParam = nid == NID_MODULE_START_THREAD_PARAMETER || nid == NID_MODULE_STOP_THREAD_PARAMETER || nid == NID_MODULE_REBOOT_BEFORE_THREAD_PARAMETER;
|
||||
if ((isThreadParam && !Memory::IsValidRange(exportAddr, 16)) || (nid == NID_MODULE_SDK_VERSION && !Memory::IsValidRange(exportAddr, 4))) {
|
||||
WARN_LOG(Log::Loader, "Var %d, nid %08x, export address %08x runs off the end of memory", j, nid, exportAddr);
|
||||
continue;
|
||||
}
|
||||
|
||||
int size;
|
||||
switch (nid) {
|
||||
@@ -2265,8 +2296,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
|
||||
delete [] newptr;
|
||||
|
||||
if (!reportedModule && ShouldHLEModuleForLoad(modinfo->name)) {
|
||||
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion);
|
||||
if (!reportedModule && ShouldHLEModuleForLoad(modInfoName)) {
|
||||
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x", modInfoName.c_str(), modinfo->moduleVersion, devkitVersion);
|
||||
|
||||
if (!strcmp(modinfo->name, "sceMpeg_library")) {
|
||||
__MpegLoadModule(modinfo->moduleVersion, module->crc);
|
||||
@@ -2354,7 +2385,7 @@ SceUID KernelLoadModule(const std::string &filename, std::string *error_string,
|
||||
|
||||
u32 error = SCE_KERNEL_ERROR_ILLEGAL_OBJECT;
|
||||
u32 magic;
|
||||
PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, fromTop, error_string, &magic, filename, error);
|
||||
PSPModule *module = __KernelLoadELFFromPtr(buffer.data(), buffer.size(), 0, fromTop, error_string, &magic, filename, error);
|
||||
|
||||
if (module == nullptr)
|
||||
return error;
|
||||
@@ -2369,11 +2400,12 @@ static PSPModule *__KernelLoadModule(u8 *fileptr, size_t fileSize, SceKernelLMOp
|
||||
// PBP!
|
||||
u32_le version;
|
||||
memcpy(&version, fileptr + 4, 4);
|
||||
u32_le offset0, offsets[16];
|
||||
u32_le offset0, offsets[16]{};
|
||||
|
||||
memcpy(&offset0, fileptr + 8, 4);
|
||||
int numfiles = (offset0 - 8) / 4;
|
||||
if (numfiles <= 0 || numfiles > 16) {
|
||||
// offsets[5] and [6] are used below.
|
||||
if (numfiles < 7 || numfiles > 16) {
|
||||
*error_string = "Invalid PBP header - can't load";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -3686,11 +3718,13 @@ static u32 sceKernelLoadModuleByID(u32 id, u32 flags, u32 lmoptionPtr) {
|
||||
std::string error_string;
|
||||
pspFileSystem.SeekFile(handle, pos, FILEMOVE_BEGIN);
|
||||
PSPModule *module = nullptr;
|
||||
u8 *temp = new u8[size - pos];
|
||||
pspFileSystem.ReadFile(handle, temp, size - pos);
|
||||
// The game may have seeked past the end.
|
||||
const size_t remaining = size > pos ? size - pos : 0;
|
||||
u8 *temp = new u8[remaining]();
|
||||
const size_t bytesRead = pspFileSystem.ReadFile(handle, temp, remaining);
|
||||
|
||||
u32 magic;
|
||||
module = __KernelLoadELFFromPtr(temp, size - pos, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, "", error);
|
||||
module = __KernelLoadELFFromPtr(temp, std::min(bytesRead, remaining), 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, "", error);
|
||||
delete [] temp;
|
||||
|
||||
if (!module) {
|
||||
@@ -3805,6 +3839,9 @@ SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmo
|
||||
char fakeDebugFilename[512];
|
||||
snprintf(fakeDebugFilename, sizeof(fakeDebugFilename), "moduleByPtr_%08x_%d", bufPtr, (int)size);
|
||||
|
||||
if (!Memory::IsValidRange(bufPtr, size)) {
|
||||
return hleLogError(Log::Loader, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "Bad module buffer %08x (%d bytes)", bufPtr, size);
|
||||
}
|
||||
module = __KernelLoadELFFromPtr(Memory::GetPointerOrException(bufPtr), size, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, fakeDebugFilename, error);
|
||||
|
||||
if (!module) {
|
||||
|
||||
@@ -678,6 +678,10 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
|
||||
// is quite convenient and makes it easy to add shared heuristics.
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
const char *fn = zip_get_name(z, i, 0);
|
||||
if (!fn) {
|
||||
// A corrupt central directory entry.
|
||||
continue;
|
||||
}
|
||||
|
||||
zip_stat_t stat{};
|
||||
zip_stat_index(z, i, 0, &stat);
|
||||
|
||||
+2
-1
@@ -143,7 +143,8 @@ bool LoadParamSFOFromDisc() {
|
||||
PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());
|
||||
if (fileInfo.exists) {
|
||||
std::vector<u8> paramsfo;
|
||||
pspFileSystem.ReadEntireFile(sfoPath, paramsfo);
|
||||
// A real PARAM.SFO is a KB or two.
|
||||
pspFileSystem.ReadEntireFile(sfoPath, paramsfo, false, 1024 * 1024);
|
||||
if (g_paramSFO.ReadSFO(paramsfo)) {
|
||||
g_paramSFORaw = g_paramSFO;
|
||||
return true;
|
||||
|
||||
+9
-3
@@ -15,6 +15,7 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
@@ -482,8 +483,11 @@ uint32_t ReplayApplyDiskRead(void *data, uint32_t readSize, uint32_t dataSize, b
|
||||
{
|
||||
auto item = ReplayNextDisk(ReplayAction::FILE_READ, t);
|
||||
if (item && item->data.size() <= dataSize) {
|
||||
memcpy(data, &item->data[0], item->data.size());
|
||||
return item->info.result;
|
||||
if (!item->data.empty()) {
|
||||
memcpy(data, item->data.data(), item->data.size());
|
||||
}
|
||||
// The game sizes its next steps by this, so don't claim more than was copied.
|
||||
return std::min((uint32_t)item->info.result, (uint32_t)item->data.size());
|
||||
}
|
||||
return readSize;
|
||||
}
|
||||
@@ -492,7 +496,9 @@ uint32_t ReplayApplyDiskRead(void *data, uint32_t readSize, uint32_t dataSize, b
|
||||
{
|
||||
ReplayItem item = ReplayItemHeader(ReplayAction::FILE_READ, t, readSize);
|
||||
item.data.resize(readSize);
|
||||
memcpy(&item.data[0], data, readSize);
|
||||
if (readSize) {
|
||||
memcpy(item.data.data(), data, readSize);
|
||||
}
|
||||
replayItems.push_back(item);
|
||||
return readSize;
|
||||
}
|
||||
|
||||
+2
-1
@@ -122,7 +122,8 @@ enum class OperationType {
|
||||
struct PendingRead {
|
||||
std::atomic<bool> done{ false };
|
||||
CChunkFileReader::Error error = CChunkFileReader::ERROR_NONE;
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
// From LoadFile, so malloc'd.
|
||||
std::unique_ptr<u8, decltype(&free)> buffer{ nullptr, &free };
|
||||
size_t size = 0;
|
||||
std::string gitVersion;
|
||||
// "PPSSPP <version>" from the header, for the failure message.
|
||||
|
||||
@@ -179,7 +179,9 @@ int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *err
|
||||
}
|
||||
case FACT_CHUNK_MAGIC:
|
||||
{
|
||||
track->endSample = Read32(buffer, offset);
|
||||
if (chunkSize >= 4) {
|
||||
track->endSample = Read32(buffer, offset);
|
||||
}
|
||||
if (chunkSize >= 8) {
|
||||
track->firstSampleOffset = Read32(buffer, offset + 4);
|
||||
}
|
||||
@@ -417,6 +419,10 @@ int ParseWaveAT3(const u8 *data, u32 dataLength, TrackInfo *track) {
|
||||
}
|
||||
const u32 chunkID = Read32(data, &offset);
|
||||
const int chunkSize = RoundUpToEven(Read32(data, &offset));
|
||||
// A negative size would point back at this chunk's own header, looping forever.
|
||||
if (chunkSize < 0) {
|
||||
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
|
||||
}
|
||||
const int nextOffset = offset + chunkSize;
|
||||
// We allow the data chunk to be bigger than readSize, as we may not have read the whole file.
|
||||
if ((u32)(offset + chunkSize) > dataLength && chunkID != DATA_CHUNK_MAGIC) {
|
||||
@@ -634,7 +640,8 @@ static u32 ParseAA3Headers(u32 readSize, AA3Info *info, u32 fileSize, const u8 *
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if ((u32)offset + 0x22 > readSize) {
|
||||
// The header fields read below go up to offset + 0x24.
|
||||
if ((u32)offset + 0x24 > readSize) {
|
||||
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
|
||||
}
|
||||
if (Parse3BytesBE(aa3Data, &offset) != 0x454133) {
|
||||
|
||||
@@ -912,7 +912,8 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
|
||||
return false;
|
||||
}
|
||||
std::string fn = fnPtr;
|
||||
size_t nameOffset = fn.rfind('/');
|
||||
// Zips made on Windows can use either separator.
|
||||
size_t nameOffset = fn.find_last_of("/\\");
|
||||
if (nameOffset == std::string::npos) {
|
||||
nameOffset = 0;
|
||||
} else {
|
||||
@@ -934,6 +935,13 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
|
||||
name = name.substr(2);
|
||||
}
|
||||
|
||||
// The name is used as a filename in the destination, so mustn't be able to point outside it.
|
||||
if (!IsSafePathComponent(name)) {
|
||||
ERROR_LOG(Log::IO, "Bad ISO filename in zip: '%s'", fn.c_str());
|
||||
SetInstallError(I18NCat::INSTALLZIP, "Zip archive corrupt");
|
||||
return false;
|
||||
}
|
||||
|
||||
Path outputISOFilename = destDir;
|
||||
if (outputISOFilename.empty()) {
|
||||
outputISOFilename = Path(g_Config.currentDirectory);
|
||||
@@ -952,8 +960,12 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
|
||||
|
||||
installProgress_ = 1.0f;
|
||||
InstallDone();
|
||||
ResetInstallError();
|
||||
return true;
|
||||
if (success) {
|
||||
ResetInstallError();
|
||||
} else {
|
||||
SetInstallError(I18NCat::INSTALLZIP, "Installation failed");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GameManager::InstallZippedPS1Disc(struct zip *z, const std::vector<std::string> &files, const Path &destDir) {
|
||||
|
||||
@@ -697,6 +697,11 @@ int PSARReader::NextEntry(std::string *error) {
|
||||
|
||||
const u32 chunkSize = ReadU32(block_.data() + 0x104);
|
||||
const u32 expandedSize = ReadU32(block_.data() + 0x108);
|
||||
// pos_ is a u32, so a huge chunk would wrap it back to an earlier entry and loop forever.
|
||||
if (chunkSize > limit_ - std::min((size_t)pos_, limit_)) {
|
||||
*error = StringFromFormat("Entry '%s' runs past the end of the archive (%u bytes)", entryName_.c_str(), chunkSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (expandedSize == 0) {
|
||||
entryIsDirectory_ = true;
|
||||
|
||||
@@ -187,7 +187,7 @@ bool PkgReader::Open(FileLoader *loader, std::string *error) {
|
||||
info_.contentId = contentId;
|
||||
|
||||
const s64 fileSize = loader->FileSize();
|
||||
if (fileSize < 0 || (u64)fileSize < totalSize || dataOffset_ + dataSize_ > (u64)fileSize) {
|
||||
if (fileSize < 0 || (u64)fileSize < totalSize || dataOffset_ > (u64)fileSize || dataSize_ > (u64)fileSize - dataOffset_) {
|
||||
*error = "PKG file is truncated";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -553,6 +553,12 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
|
||||
ERROR_LOG(Log::TexReplacement, "Could not load texture replacement info: %s - unsupported format %s", filename.c_str(), magic.c_str());
|
||||
}
|
||||
|
||||
// The sizes come straight from the file headers, and everything below sizes buffers from them.
|
||||
if (good && (level.w <= 0 || level.h <= 0 || level.w > 8192 || level.h > 8192)) {
|
||||
ERROR_LOG(Log::TexReplacement, "Replacement texture %s has a bad size: %dx%d", filename.c_str(), level.w, level.h);
|
||||
good = false;
|
||||
}
|
||||
|
||||
// TODO: We no longer really need to have a split in this function, the upper and lower parts can be merged now.
|
||||
|
||||
if (good && mipLevel != 0) {
|
||||
@@ -723,19 +729,22 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
|
||||
bool result = transcoder.get_image_level_info(levelInfo, i, 0, 0);
|
||||
_dbg_assert_(result);
|
||||
|
||||
size_t dataSizeBytes = levelInfo.m_total_blocks * blockSize;
|
||||
size_t dataSizeBytes = (size_t)levelInfo.m_total_blocks * blockSize;
|
||||
size_t outputSize = levelInfo.m_total_blocks;
|
||||
size_t outputPitch = levelInfo.m_num_blocks_x;
|
||||
// Support transcoded-to-RGBA8888 images too.
|
||||
if (!bc) {
|
||||
dataSizeBytes = levelInfo.m_orig_width * levelInfo.m_orig_height * 4;
|
||||
outputSize = levelInfo.m_orig_width * levelInfo.m_orig_height;
|
||||
dataSizeBytes = (size_t)levelInfo.m_orig_width * levelInfo.m_orig_height * 4;
|
||||
outputSize = (size_t)levelInfo.m_orig_width * levelInfo.m_orig_height;
|
||||
outputPitch = levelInfo.m_orig_width;
|
||||
}
|
||||
out.resize(dataSizeBytes);
|
||||
|
||||
transcodeState.clear();
|
||||
transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, level.h, -1, -1, &transcodeState);
|
||||
if (!result || dataSizeBytes == 0 || !transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, levelInfo.m_orig_height, -1, -1, &transcodeState)) {
|
||||
WARN_LOG(Log::TexReplacement, "KTX2: Failed to transcode level %d of %s", i, filename.c_str());
|
||||
std::fill(out.begin(), out.end(), 0);
|
||||
}
|
||||
level.w = levelInfo.m_orig_width;
|
||||
level.h = levelInfo.m_orig_height;
|
||||
if (i != 0)
|
||||
@@ -822,15 +831,15 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
|
||||
// We only use the first level for now.
|
||||
int numLevels = LoadZIMPtr(&zim[0], fileSize, w, h, &f, image);
|
||||
if (numLevels > 0) {
|
||||
if (w[0] > level.w || h[0] > level.h) {
|
||||
if (w[0] > level.w || h[0] > level.h || (f & ZIM_FORMAT_MASK) != ZIM_RGBA8888) {
|
||||
ERROR_LOG(Log::TexReplacement, "Texture replacement changed since header read: %s", filename.c_str());
|
||||
free(image[0]);
|
||||
return LoadLevelResult::LOAD_ERROR;
|
||||
}
|
||||
|
||||
out.resize(level.w * level.h * 4);
|
||||
out.resize((size_t)level.w * level.h * 4);
|
||||
if (w[0] == level.w) {
|
||||
memcpy(&out[0], image[0], level.w * 4 * level.h);
|
||||
memcpy(&out[0], image[0], (size_t)level.w * 4 * h[0]);
|
||||
} else {
|
||||
for (int y = 0; y < h[0]; ++y) {
|
||||
memcpy(&out[level.w * 4 * y], image[0] + w[0] * 4 * y, w[0] * 4);
|
||||
|
||||
@@ -367,6 +367,9 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri
|
||||
}
|
||||
options->Get("skipLastDXT1Blocks128x64", &skipLastDXT1Blocks128x64_);
|
||||
options->Get("skipLastDXT1Blocks128x128", &skipLastDXT1Blocks128x128_);
|
||||
// These are subtracted from the hashed size, so can't be more than the blocks there are.
|
||||
skipLastDXT1Blocks128x64_ = std::clamp(skipLastDXT1Blocks128x64_, 0, (128 / 4) * (64 / 4));
|
||||
skipLastDXT1Blocks128x128_ = std::clamp(skipLastDXT1Blocks128x128_, 0, (128 / 4) * (128 / 4));
|
||||
if (reduceHash_ && textureHash_ == ReplacedTextureHash::QUICK) {
|
||||
reduceHash_ = false;
|
||||
ERROR_LOG(Log::TexReplacement, "Texture Replacement: reduceHash option requires safer hash, use xxh32 or xxh64 instead.");
|
||||
@@ -635,11 +638,12 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, bool swizzled
|
||||
}
|
||||
|
||||
// Hack for Yu Gi Oh texture hashing problem. See issue #19714
|
||||
if (skipLastDXT1Blocks128x64_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 64) {
|
||||
// (reduceHash can make sizeInRAM smaller than all the blocks, so check it can't underflow.)
|
||||
if (skipLastDXT1Blocks128x64_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 64 && (u32)(8 * skipLastDXT1Blocks128x64_) < (u32)sizeInRAM) {
|
||||
// Skip the last few blocks as specified.
|
||||
sizeInRAM -= 8 * skipLastDXT1Blocks128x64_;
|
||||
}
|
||||
if (skipLastDXT1Blocks128x128_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 128) {
|
||||
if (skipLastDXT1Blocks128x128_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 128 && (u32)(8 * skipLastDXT1Blocks128x128_) < (u32)sizeInRAM) {
|
||||
sizeInRAM -= 8 * skipLastDXT1Blocks128x128_;
|
||||
}
|
||||
|
||||
|
||||
+20
-5
@@ -2,6 +2,7 @@
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
|
||||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/UI/Root.h"
|
||||
@@ -126,19 +127,25 @@ bool WavData::Read(RIFFReader &file_) {
|
||||
int playCount;
|
||||
};
|
||||
|
||||
if (numLoops > 0 && smplData.size() >= 36 + sizeof(AtracLoopInfo) * numLoops) {
|
||||
// Divided, since the product wraps a 32-bit size_t.
|
||||
if (numLoops > 0 && smplData.size() >= 36 && (smplData.size() - 36) / sizeof(AtracLoopInfo) >= (size_t)numLoops) {
|
||||
AtracLoopInfo *loops = (AtracLoopInfo *)&smplData[36];
|
||||
int samplesPerFrame = codec == PSP_CODEC_AT3PLUS ? 2048 : 1024;
|
||||
|
||||
for (int i = 0; i < numLoops; ++i) {
|
||||
// Only seen forward loops, so let's ignore others.
|
||||
if (loops[i].type != 0)
|
||||
// Only seen forward loops, so let's ignore others. Also ones that don't go forwards,
|
||||
// which would point playback before the data.
|
||||
if (loops[i].type != 0 || loops[i].startSample < 0 || loops[i].endSample <= loops[i].startSample)
|
||||
continue;
|
||||
|
||||
// We ignore loop interpolation (fraction) and play count for now.
|
||||
raw_offset_loop_start = (loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame;
|
||||
const s64 loopStart = (s64)(loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame;
|
||||
const s64 loopEnd = (s64)(loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame;
|
||||
if (loopEnd > INT_MAX)
|
||||
continue;
|
||||
raw_offset_loop_start = (int)loopStart;
|
||||
loop_start_offset = loops[i].startSample % samplesPerFrame;
|
||||
raw_offset_loop_end = (loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame;
|
||||
raw_offset_loop_end = (int)loopEnd;
|
||||
loop_end_offset = loops[i].endSample % samplesPerFrame;
|
||||
|
||||
if (loops[i].playCount == 0) {
|
||||
@@ -200,6 +207,14 @@ bool WavData::Read(RIFFReader &file_) {
|
||||
ERROR_LOG(Log::Audio, "Could not descend into RIFF file.");
|
||||
return false;
|
||||
}
|
||||
// The loop points came from the smpl chunk, so they have to land in the data. The sample
|
||||
// offsets (within a frame) are always positive, since the loop samples were checked above.
|
||||
if (raw_offset_loop_start < 0 || raw_offset_loop_end > raw_data_size || raw_offset_loop_start > raw_offset_loop_end) {
|
||||
raw_offset_loop_start = 0;
|
||||
raw_offset_loop_end = 0;
|
||||
loop_start_offset = 0;
|
||||
loop_end_offset = 0;
|
||||
}
|
||||
sample_rate = samplesPerSec;
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -298,7 +298,7 @@ bool ImposeArt::Parse(const std::vector<uint8_t> &file, int pspLanguage) {
|
||||
const uint32_t w = r.U32(rec + 16);
|
||||
const uint32_t h = r.U32(rec + 20);
|
||||
const uint32_t offset = r.U32(rec + 24);
|
||||
if (!r.ok || palette >= NUM_PALETTES || w == 0 || h == 0 || x + w > PSP_W || y + h > PSP_H || (w * h + 1) / 2 != size) {
|
||||
if (!r.ok || palette >= NUM_PALETTES || w == 0 || h == 0 || w > PSP_W || x > PSP_W - w || h > PSP_H || y > PSP_H - h || (w * h + 1) / 2 != size) {
|
||||
return false;
|
||||
}
|
||||
const size_t pixels = block + TEXT_RECORDS + (size_t)offset;
|
||||
|
||||
+3
-1
@@ -448,7 +448,9 @@ void StoreScreen::ParseListing(const std::string &json) {
|
||||
// (Path::operator/ does no ".." normalization); on platforms/builds where
|
||||
// the store index isn't fetched over HTTPS, a MITM'd or compromised
|
||||
// response could otherwise point "launch" at an arbitrary host path.
|
||||
if (HasParentDirComponent(file))
|
||||
// It's also uninstalled (deleted recursively) by name, so "." or "/" would be the
|
||||
// whole GAME directory.
|
||||
if (HasParentDirComponent(file) || !IsSafePathComponent(file))
|
||||
continue;
|
||||
e.file = file;
|
||||
entries_.push_back(e);
|
||||
|
||||
+8
-2
@@ -3188,12 +3188,18 @@ namespace jpgd {
|
||||
*height = image_height;
|
||||
*actual_comps = decoder.get_num_components();
|
||||
|
||||
// PPSSPP: Nothing we decode is anywhere near this, and the header alone (from a game, or
|
||||
// a file) could otherwise make us allocate gigabytes.
|
||||
if (image_width <= 0 || image_height <= 0 || image_width > 8192 || image_height > 8192)
|
||||
return nullptr;
|
||||
|
||||
if (decoder.begin_decoding() != JPGD_SUCCESS)
|
||||
return nullptr;
|
||||
|
||||
const int dst_bpl = image_width * req_comps;
|
||||
|
||||
uint8* pImage_data = (uint8*)jpgd_malloc(dst_bpl * image_height);
|
||||
// PPSSPP: In size_t, the int product overflows for large (up to 32768x32768) images.
|
||||
uint8* pImage_data = (uint8*)jpgd_malloc((size_t)dst_bpl * image_height);
|
||||
if (!pImage_data)
|
||||
return nullptr;
|
||||
|
||||
@@ -3207,7 +3213,7 @@ namespace jpgd {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint8* pDst = pImage_data + y * dst_bpl;
|
||||
uint8* pDst = pImage_data + (size_t)y * dst_bpl;
|
||||
|
||||
if (((req_comps == 1) && (decoder.get_num_components() == 1)) || ((req_comps == 4) && (decoder.get_num_components() == 3)))
|
||||
memcpy(pDst, pScan_line, dst_bpl);
|
||||
|
||||
@@ -154,7 +154,8 @@ int sceDrmBBMacUpdate(KirkState *kirk, MAC_KEY *mkey, u8 *buf, int size)
|
||||
int retv = 0, ksize, p, type;
|
||||
u8 *kbuf;
|
||||
|
||||
if(mkey->pad_size>16){
|
||||
// The key can live in guest memory (sceDrmBBMacUpdate), so pad_size can be anything.
|
||||
if(mkey->pad_size<0 || mkey->pad_size>16){
|
||||
retv = 0x80510302;
|
||||
goto _exit;
|
||||
}
|
||||
@@ -204,7 +205,7 @@ int sceDrmBBMacFinal(KirkState *kirk, MAC_KEY *mkey, u8 *buf, u8 *vkey)
|
||||
u8 *kbuf, tmp[16], tmp1[16];
|
||||
u32 t0, v0, v1;
|
||||
|
||||
if(mkey->pad_size>16)
|
||||
if(mkey->pad_size<0 || mkey->pad_size>16)
|
||||
return 0x80510302;
|
||||
|
||||
code = (mkey->type==2)? 0x3A : 0x38;
|
||||
@@ -609,6 +610,8 @@ PGD_DESC *pgd_open(KirkState *kirk, u8 *pgd_buf, int pgd_flag, u8 *pgd_vkey)
|
||||
//DEBUG_LOG(Log::HLE, "Open PGD ...");
|
||||
|
||||
pgd = (PGD_DESC*)malloc(sizeof(PGD_DESC));
|
||||
if (!pgd)
|
||||
return NULL;
|
||||
memset(pgd, 0, sizeof(PGD_DESC));
|
||||
|
||||
pgd->key_index = *(u32*)(pgd_buf+4);
|
||||
|
||||
@@ -403,6 +403,13 @@ double ScreenshotComparer::Compare(const Path &screenshotFilename) {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
// Everything below reads w_ x h_ pixels from it.
|
||||
if (width < (int)w_ || height < (int)h_) {
|
||||
error_ = StringFromFormat("Screenshot is too small (%dx%d, expected %dx%d): %s", width, height, (int)w_, (int)h_, screenshotFilename.ToVisualString().c_str());
|
||||
free(reference_);
|
||||
reference_ = nullptr;
|
||||
return -1.0f;
|
||||
}
|
||||
referenceStride_ = width;
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user