mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-29 15:56:37 +02:00
Merge pull request #22247 from hrydgard/kl4e
Implement KL4E/KL3E decompression, so firmware modules that use it can load
This commit is contained in:
@@ -693,6 +693,8 @@ add_library(Core STATIC
|
||||
Util/BlockAllocator.h
|
||||
Util/PPGeDraw.cpp
|
||||
Util/PPGeDraw.h
|
||||
Util/KL4E.cpp
|
||||
Util/KL4E.h
|
||||
Util/PSARUnpack.cpp
|
||||
Util/PSARUnpack.h
|
||||
Util/RecentFiles.cpp
|
||||
|
||||
@@ -921,6 +921,7 @@
|
||||
<ClCompile Include="Util\PathUtil.cpp" />
|
||||
<ClCompile Include="Util\PortManager.cpp" />
|
||||
<ClCompile Include="Util\PPGeDraw.cpp" />
|
||||
<ClCompile Include="Util\KL4E.cpp" />
|
||||
<ClCompile Include="Util\PSARUnpack.cpp" />
|
||||
<ClCompile Include="..\ext\xxhash.c">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization>
|
||||
@@ -1289,6 +1290,7 @@
|
||||
<ClInclude Include="Util\PathUtil.h" />
|
||||
<ClInclude Include="Util\PortManager.h" />
|
||||
<ClInclude Include="Util\PPGeDraw.h" />
|
||||
<ClInclude Include="Util\KL4E.h" />
|
||||
<ClInclude Include="Util\PSARUnpack.h" />
|
||||
<ClInclude Include="..\ext\xxhash.h" />
|
||||
<ClInclude Include="Util\RecentFiles.h" />
|
||||
|
||||
@@ -363,6 +363,9 @@
|
||||
<ClCompile Include="Util\PPGeDraw.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util\KL4E.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Util\PSARUnpack.cpp">
|
||||
<Filter>Util</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "Core/ELF/ElfReader.h"
|
||||
#include "Core/ELF/PBPReader.h"
|
||||
#include "Core/ELF/PrxDecrypter.h"
|
||||
#include "Core/Util/KL4E.h"
|
||||
#include "Core/FileSystems/FileSystem.h"
|
||||
#include "Core/FileSystems/MetaFileSystem.h"
|
||||
#include "Core/Util/BlockAllocator.h"
|
||||
@@ -1273,7 +1274,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, StringFromFormat("HLE for '%s' has been manually disabled", head->modname));
|
||||
}
|
||||
const u8 *in = ptr;
|
||||
const auto isGzip = head->comp_attribute & 1;
|
||||
const bool isCompressed = (head->comp_attribute & 1) != 0;
|
||||
// Kind of odd.
|
||||
u32 size = head->psp_size;
|
||||
if (size > elfSize) {
|
||||
@@ -1311,22 +1312,36 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// decompress if required.
|
||||
if (isGzip) {
|
||||
_dbg_assert_(Read32(ptr + 0x150) != ELF_MAGIC);
|
||||
|
||||
// decompress if required. comp_attribute bit 0 says "compressed"; which scheme is then
|
||||
// decided by the payload's own magic - gzip, or Sony's KL4E/KL3E. (JPCSP instead reads
|
||||
// bits 8-11 of comp_attribute, but the magic is right there and can't disagree.)
|
||||
if (isCompressed) {
|
||||
// Can't decompress in place so we need a temporary buffer.
|
||||
u8 *temp = (u8 *)malloc(decryptedSize);
|
||||
_assert_msg_(temp != nullptr, "Failed to allocate gzip decompression buffer (decryptedSize: %d)", decryptedSize);
|
||||
_assert_msg_(temp != nullptr, "Failed to allocate decompression buffer (decryptedSize: %d)", decryptedSize);
|
||||
memcpy(temp, ptr, decryptedSize);
|
||||
int outBytes = gzipDecompress((u8 *)ptr, maxElfSize, temp);
|
||||
|
||||
bool isKL3E = false;
|
||||
int outBytes;
|
||||
const char *scheme;
|
||||
if (IsKL4EMagic(temp, decryptedSize, &isKL3E)) {
|
||||
scheme = isKL3E ? "KL3E" : "KL4E";
|
||||
// The decompressor is handed the stream header, i.e. past the four-byte magic.
|
||||
outBytes = DecompressKL4E((u8 *)ptr, maxElfSize, temp + 4, (size_t)decryptedSize - 4, nullptr, isKL3E);
|
||||
if (outBytes < 0) {
|
||||
// A PSP error code, not a byte count.
|
||||
outBytes = -1;
|
||||
}
|
||||
} else {
|
||||
scheme = "gzip";
|
||||
_dbg_assert_(Read32(ptr + 0x150) != ELF_MAGIC);
|
||||
outBytes = gzipDecompress((u8 *)ptr, maxElfSize, temp);
|
||||
}
|
||||
free(temp);
|
||||
if (outBytes < 0) {
|
||||
// Not necessarily actually gzip - some kd/ system modules (and possibly VSH
|
||||
// modules) use KL4E compression instead, which we don't support decompressing.
|
||||
// Bail out cleanly here 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' decompression failed", head->modname);
|
||||
// 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);
|
||||
delete [] newptr;
|
||||
module->Cleanup();
|
||||
kernelObjects.Destroy<PSPModule>(module->GetUID());
|
||||
@@ -1334,7 +1349,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
|
||||
error = SCE_KERNEL_ERROR_FILEERR;
|
||||
return nullptr;
|
||||
}
|
||||
INFO_LOG(Log::sceModule, "gzip is enabled in '%s', decompressing (%d -> %d bytes, bufmax=%d).", head->modname, decryptedSize, outBytes, maxElfSize);
|
||||
INFO_LOG(Log::sceModule, "'%s' is %s-compressed, decompressing (%d -> %d bytes, bufmax=%d).", head->modname, scheme, decryptedSize, outBytes, maxElfSize);
|
||||
}
|
||||
|
||||
if (fakeLoadedModule) {
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
// Copyright (c) 2026- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
// KL4E / KL3E decompression. See docs/KL4E.md.
|
||||
//
|
||||
// LZ77 tokens - "emit this literal" or "repeat N bytes from M back" - where every bit is coded
|
||||
// with an adaptive binary arithmetic coder rather than written directly. Structurally close to
|
||||
// LZMA. KL3E differs from KL4E in exactly one constant (kPowLimitLong).
|
||||
//
|
||||
// Written from the format description, not ported from existing code.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Core/HLE/ErrorCodes.h"
|
||||
#include "Core/Util/KL4E.h"
|
||||
namespace {
|
||||
|
||||
// Adaptation profiles. A probability is the chance the next bit is a 1, out of 256: on a 0 it
|
||||
// just decays, on a 1 it decays and then gains the bonus back, so it climbs toward a ceiling of
|
||||
// roughly (bonus << decay).
|
||||
const int kDecayNormal = 3, kBonusNormal = 31;
|
||||
const int kDecayFlag = 4, kBonusFlag = 15;
|
||||
|
||||
// Table sizes. Each is exactly large enough for the highest index its indexing scheme can
|
||||
// produce - except copyDistProbs, see the bounds check in the distance decode.
|
||||
const int kLitProbsSize = 2040; // 8 literal contexts * 255
|
||||
const int kCopyCountBitsProbsSize = 64; // 8 states * 8 unary steps
|
||||
const int kCopyCountProbsSize = 256;
|
||||
const int kCopyDistBitsProbsSize = 304;
|
||||
const int kCopyDistProbsSize = 144;
|
||||
|
||||
// The doubling walk that sizes a distance code stops once it reaches this. The single constant
|
||||
// that separates the two formats.
|
||||
const int kPowLimitKL4E = 256;
|
||||
const int kPowLimitKL3E = 128;
|
||||
// Short matches use a tighter limit in both formats.
|
||||
const int kPowLimitShort = 64;
|
||||
|
||||
struct ArithDecoder {
|
||||
const u8 *in;
|
||||
const u8 *inEnd;
|
||||
u32 range;
|
||||
u32 code;
|
||||
bool overrun;
|
||||
|
||||
u8 NextByte() {
|
||||
if (in >= inEnd) {
|
||||
// The format carries no length and relies on the stream terminating itself, so a
|
||||
// truncated file would otherwise read forever. Feed zeroes and remember.
|
||||
overrun = true;
|
||||
return 0;
|
||||
}
|
||||
return *in++;
|
||||
}
|
||||
|
||||
// One bit against an adaptive probability.
|
||||
int ReadBit(u8 *prob, int decay, int bonus) {
|
||||
u32 bound;
|
||||
const u32 p = *prob;
|
||||
if ((range >> 24) == 0) {
|
||||
// Renormalize. The bound uses the range from *before* the shift.
|
||||
code = (code << 8) + NextByte();
|
||||
bound = range * p;
|
||||
range <<= 8;
|
||||
} else {
|
||||
bound = (range >> 8) * p;
|
||||
}
|
||||
const u32 decayed = p - (p >> decay);
|
||||
if (code >= bound) {
|
||||
code -= bound;
|
||||
range -= bound;
|
||||
*prob = (u8)decayed;
|
||||
return 0;
|
||||
}
|
||||
range = bound;
|
||||
*prob = (u8)(decayed + bonus);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A bit the encoder judged incompressible: no probability, no adaptation.
|
||||
int ReadBitUniform() {
|
||||
if ((range >> 24) == 0) {
|
||||
code = (code << 8) + NextByte();
|
||||
range <<= 7;
|
||||
} else {
|
||||
range >>= 1;
|
||||
}
|
||||
if (code >= range) {
|
||||
code -= range;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Same, skipping the refill check. Only valid straight after a read that did refill, which
|
||||
// is how runs of uniform bits are emitted.
|
||||
int ReadBitUniformNoNorm() {
|
||||
range >>= 1;
|
||||
if (code >= range) {
|
||||
code -= range;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Eight bits into an accumulator that starts at 1, so the count is implicit. The context is
|
||||
// a mix of the previous byte and the output position's alignment; `shift` slides the window
|
||||
// between the two, the same choice LZMA's lc makes.
|
||||
u32 ReadLiteral(u8 *litProbs, int outPos, u32 prevByte, int shift) {
|
||||
const u32 ctx = ((u32)(outPos & 7) << 8) | (prevByte & 0xFF);
|
||||
u8 *probs = litProbs + ((ctx >> shift) & 7) * 255 - 1;
|
||||
u32 acc = 1;
|
||||
while (acc < 0x100) {
|
||||
acc = (acc << 1) | ReadBit(&probs[acc], kDecayNormal, kBonusNormal);
|
||||
}
|
||||
return acc & 0xFF;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsKL4EMagic(const u8 *data, size_t size, bool *isKL3E) {
|
||||
if (size < 4) {
|
||||
return false;
|
||||
}
|
||||
if (!memcmp(data, "KL4E", 4)) {
|
||||
if (isKL3E) {
|
||||
*isKL3E = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!memcmp(data, "KL3E", 4)) {
|
||||
if (isKL3E) {
|
||||
*isKL3E = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int DecompressKL4E(u8 *out, int outSize, const u8 *in, size_t inSize, const u8 **end, bool isKL3E) {
|
||||
if (!out || !in || outSize <= 0 || inSize < 5) {
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
// --- five-byte stream header ---
|
||||
const u8 flags = in[0];
|
||||
// Big-endian, which almost nothing else on the PSP is.
|
||||
const u32 headerWord = ((u32)in[1] << 24) | ((u32)in[2] << 16) | ((u32)in[3] << 8) | in[4];
|
||||
|
||||
if (flags & 0x80) {
|
||||
// Stored, not compressed: the header word is a length and the bytes follow verbatim.
|
||||
// A payload that exactly fills the output buffer is rejected, not accepted.
|
||||
if (headerWord >= (u32)outSize) {
|
||||
return SCE_KERNEL_ERROR_INVALID_SIZE;
|
||||
}
|
||||
if (5 + (size_t)headerWord > inSize) {
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
memcpy(out, in + 5, headerWord);
|
||||
if (end) {
|
||||
*end = in + 5 + headerWord;
|
||||
}
|
||||
return (int)headerWord;
|
||||
}
|
||||
|
||||
// bits 4-3 pick the starting probability, bits 2-0 the literal context selector.
|
||||
const u8 seed = (u8)(0x80 - (((flags >> 3) & 3) << 4));
|
||||
const int shift = flags & 7;
|
||||
|
||||
u8 litProbs[kLitProbsSize];
|
||||
u8 copyCountBitsProbs[kCopyCountBitsProbsSize];
|
||||
u8 copyCountProbs[kCopyCountProbsSize];
|
||||
u8 copyDistBitsProbs[kCopyDistBitsProbsSize];
|
||||
u8 copyDistProbs[kCopyDistProbsSize];
|
||||
memset(litProbs, seed, sizeof(litProbs));
|
||||
memset(copyCountBitsProbs, seed, sizeof(copyCountBitsProbs));
|
||||
memset(copyCountProbs, seed, sizeof(copyCountProbs));
|
||||
memset(copyDistBitsProbs, seed, sizeof(copyDistBitsProbs));
|
||||
memset(copyDistProbs, seed, sizeof(copyDistProbs));
|
||||
|
||||
ArithDecoder dec;
|
||||
dec.in = in + 5;
|
||||
dec.inEnd = in + inSize;
|
||||
dec.range = 0xFFFFFFFF;
|
||||
dec.code = headerWord;
|
||||
dec.overrun = false;
|
||||
|
||||
const int powLimitLong = isKL3E ? kPowLimitKL3E : kPowLimitKL4E;
|
||||
|
||||
// outPos indexes the byte being produced. The loop head advances it, which is why a match
|
||||
// only advances it by copyCount after writing copyCount + 1 bytes.
|
||||
int outPos = 0;
|
||||
u32 prevByte = 0;
|
||||
|
||||
// An index into copyCountBitsProbs that persists across tokens - the "state". The unary walk
|
||||
// below moves it in steps of 8 and does not put it back, so its low three bits are what the
|
||||
// length code's context uses.
|
||||
int countBitsIdx = 0;
|
||||
|
||||
// The first literal has no flag bit in front of it.
|
||||
prevByte = dec.ReadLiteral(litProbs, outPos, prevByte, shift);
|
||||
out[0] = (u8)prevByte;
|
||||
|
||||
while (true) {
|
||||
outPos++;
|
||||
|
||||
if (dec.overrun) {
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (dec.ReadBit(©CountBitsProbs[countBitsIdx], kDecayFlag, kBonusFlag) == 0) {
|
||||
// Literal.
|
||||
countBitsIdx = countBitsIdx > 0 ? countBitsIdx - 1 : 0;
|
||||
if (outPos >= outSize) {
|
||||
return SCE_KERNEL_ERROR_INVALID_SIZE;
|
||||
}
|
||||
prevByte = dec.ReadLiteral(litProbs, outPos, prevByte, shift);
|
||||
out[outPos] = (u8)prevByte;
|
||||
continue;
|
||||
}
|
||||
|
||||
// A match. First, how many bits the length code uses, as a unary run stepping the index
|
||||
// by 8 so the low three bits stay put.
|
||||
u32 copyCount = 1;
|
||||
int copyCountBits = -1;
|
||||
while (copyCountBits < 6) {
|
||||
countBitsIdx += 8;
|
||||
if (countBitsIdx >= kCopyCountBitsProbsSize) {
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
if (!dec.ReadBit(©CountBitsProbs[countBitsIdx], kDecayFlag, kBonusFlag)) {
|
||||
break;
|
||||
}
|
||||
copyCountBits++;
|
||||
}
|
||||
|
||||
// The length, and with it the distance code's parameters: the bit that lands in the
|
||||
// length's low position also selects them, for short matches.
|
||||
int powLimit = kPowLimitShort;
|
||||
int distBase = copyCountBits;
|
||||
if (copyCountBits >= 0) {
|
||||
const int offset = (copyCountBits << 5)
|
||||
| (((outPos & 3) << (copyCountBits + 3)) & 0x18)
|
||||
| (countBitsIdx & 7);
|
||||
u8 *probs = copyCountProbs + offset;
|
||||
|
||||
// High bits: two adaptive, then uniform for anything longer.
|
||||
if (copyCountBits >= 3) {
|
||||
copyCount = 2 + dec.ReadBit(probs + 24, kDecayNormal, kBonusNormal);
|
||||
if (copyCountBits > 3) {
|
||||
copyCount = (copyCount << 1) | dec.ReadBit(probs + 24, kDecayNormal, kBonusNormal);
|
||||
if (copyCountBits > 4) {
|
||||
copyCount = (copyCount << 1) | dec.ReadBitUniform();
|
||||
}
|
||||
for (int i = 5; i < copyCountBits; i++) {
|
||||
copyCount = (copyCount << 1) | dec.ReadBitUniformNoNorm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copyCount <<= 1;
|
||||
if (dec.ReadBit(probs, kDecayNormal, kBonusNormal)) {
|
||||
copyCount |= 1;
|
||||
if (copyCountBits <= 0) {
|
||||
powLimit = powLimitLong;
|
||||
distBase = 56 + copyCountBits;
|
||||
}
|
||||
} else if (copyCountBits <= 0) {
|
||||
powLimit = kPowLimitShort;
|
||||
distBase = copyCountBits;
|
||||
}
|
||||
|
||||
if (copyCountBits > 0) {
|
||||
copyCount = (copyCount << 1) | dec.ReadBit(probs + 8, kDecayNormal, kBonusNormal);
|
||||
if (copyCountBits != 1) {
|
||||
copyCount <<= 1;
|
||||
if (dec.ReadBit(probs + 16, kDecayNormal, kBonusNormal)) {
|
||||
copyCount++;
|
||||
// 0xFF ends the stream. There is no length in the header and no
|
||||
// terminator byte; this is the only normal way out of the loop.
|
||||
if (copyCount == 0xFF) {
|
||||
if (end) {
|
||||
*end = dec.in;
|
||||
}
|
||||
return outPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
powLimit = powLimitLong;
|
||||
distBase = 56 + copyCountBits;
|
||||
}
|
||||
}
|
||||
|
||||
// How many bits the distance code uses, by a doubling walk. curPow only picks up the
|
||||
// extra 8 when the walk continues, which is what makes the deepest reachable index fit
|
||||
// copyDistBitsProbs exactly.
|
||||
u32 copyDist = 0;
|
||||
bool distanceIsZero = false;
|
||||
int curPow = 8;
|
||||
int copyDistBits = 0;
|
||||
while (true) {
|
||||
const int probIndex = distBase + curPow - 7;
|
||||
if (probIndex < 0 || probIndex >= kCopyDistBitsProbsSize) {
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
u8 *prob = ©DistBitsProbs[probIndex];
|
||||
curPow <<= 1;
|
||||
copyDistBits = curPow - powLimit;
|
||||
if (!dec.ReadBit(prob, kDecayNormal, kBonusNormal)) {
|
||||
if (copyDistBits >= 0) {
|
||||
if (copyDistBits != 0) {
|
||||
copyDistBits -= 8;
|
||||
break;
|
||||
}
|
||||
// No distance bits at all: a match against the byte just emitted, i.e. a run.
|
||||
distanceIsZero = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
curPow += 8;
|
||||
if (copyDistBits >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!distanceIsZero) {
|
||||
// The distance value. Same shape as the length: two adaptive high bits, uniform in
|
||||
// the middle, then three adaptive low bits whose +1/-1 adjustments keep the code
|
||||
// ranges contiguous across bit counts.
|
||||
if (copyDistBits < 0 || copyDistBits + 3 >= kCopyDistProbsSize) {
|
||||
// Only reachable from a stream encoding a distance far larger than any real
|
||||
// module needs - the hardware routine reads past its own table here.
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
u8 *probs = copyDistProbs + copyDistBits;
|
||||
int readBits = copyDistBits / 8;
|
||||
|
||||
if (readBits < 3) {
|
||||
copyDist = 1;
|
||||
} else {
|
||||
copyDist = 2 + dec.ReadBit(probs + 3, kDecayNormal, kBonusNormal);
|
||||
if (readBits > 3) {
|
||||
copyDist = (copyDist << 1) | dec.ReadBit(probs + 3, kDecayNormal, kBonusNormal);
|
||||
if (readBits > 4) {
|
||||
copyDist = (copyDist << 1) | dec.ReadBitUniform();
|
||||
readBits--;
|
||||
}
|
||||
while (readBits > 4) {
|
||||
copyDist = (copyDist << 1) + dec.ReadBitUniformNoNorm();
|
||||
readBits--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copyDist <<= 1;
|
||||
if (dec.ReadBit(probs, kDecayNormal, kBonusNormal)) {
|
||||
if (readBits > 0) {
|
||||
copyDist++;
|
||||
}
|
||||
} else if (readBits <= 0) {
|
||||
copyDist--;
|
||||
}
|
||||
|
||||
if (readBits > 0) {
|
||||
copyDist <<= 1;
|
||||
if (dec.ReadBit(probs + 1, kDecayNormal, kBonusNormal)) {
|
||||
if (readBits != 1) {
|
||||
copyDist++;
|
||||
}
|
||||
} else if (readBits == 1) {
|
||||
copyDist--;
|
||||
}
|
||||
if (readBits != 1) {
|
||||
copyDist <<= 1;
|
||||
if (!dec.ReadBit(probs + 2, kDecayNormal, kBonusNormal)) {
|
||||
copyDist--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (copyDist >= (u32)outPos) {
|
||||
return SCE_KERNEL_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
const int copyLen = (int)copyCount + 1;
|
||||
// The hardware routine bounds-checks only the literal path, so a crafted stream can
|
||||
// write up to 255 bytes past the end of the output buffer here. Check it.
|
||||
if (copyLen > outSize - outPos) {
|
||||
return SCE_KERNEL_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Forward, byte at a time - overlap is legal and is how runs are encoded.
|
||||
const u8 *src = out + outPos - copyDist - 1;
|
||||
for (int i = 0; i < copyLen; i++) {
|
||||
out[outPos + i] = src[i];
|
||||
}
|
||||
prevByte = out[outPos + (int)copyCount];
|
||||
outPos += (int)copyCount;
|
||||
countBitsIdx = 6 + (outPos & 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
// KL4E / KL3E - Sony's LZ77-with-arithmetic-coding scheme for compressed PSP executables, the
|
||||
// alternative to gzip inside a ~PSP module. KL3E is the same bitstream with one constant changed.
|
||||
//
|
||||
// See docs/KL4E.md for the format.
|
||||
|
||||
// Decompresses one KL4E/KL3E stream. `in` points at the five-byte stream header, i.e. *past* the
|
||||
// four-byte "KL4E"/"KL3E" magic. `inSize` bounds reads so a truncated file can't run off the end.
|
||||
//
|
||||
// Returns the number of bytes written, or a negative PSP error code:
|
||||
// SCE_KERNEL_ERROR_INVALID_SIZE (0x80000104) output didn't fit
|
||||
// SCE_KERNEL_ERROR_INVALID_FORMAT (0x80000108) stream is malformed
|
||||
//
|
||||
// If `end` is non-null it receives a pointer just past the last input byte consumed. (The PSP's
|
||||
// own routine reports the last byte consumed for stored streams and one past for compressed ones;
|
||||
// this is deliberately consistent instead, since nothing in PPSSPP depends on the quirk.)
|
||||
int DecompressKL4E(u8 *out, int outSize, const u8 *in, size_t inSize, const u8 **end, bool isKL3E);
|
||||
|
||||
// True if the buffer starts with a "KL4E" or "KL3E" magic. Sets *isKL3E when it does.
|
||||
bool IsKL4EMagic(const u8 *data, size_t size, bool *isKL3E);
|
||||
@@ -336,6 +336,7 @@
|
||||
<ClInclude Include="..\..\Core\Util\DisArm64.h" />
|
||||
<ClInclude Include="..\..\Core\Util\GameManager.h" />
|
||||
<ClInclude Include="..\..\Core\Util\PPGeDraw.h" />
|
||||
<ClInclude Include="..\..\Core\Util\KL4E.h" />
|
||||
<ClInclude Include="..\..\Core\Util\PSARUnpack.h" />
|
||||
<ClInclude Include="..\..\Core\WaveFile.h" />
|
||||
<ClInclude Include="..\..\ext\cityhash\city.h" />
|
||||
@@ -655,6 +656,7 @@
|
||||
<ClCompile Include="..\..\Core\Util\DisArm64.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\GameManager.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\PPGeDraw.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\KL4E.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\PSARUnpack.cpp" />
|
||||
<ClCompile Include="..\..\Core\WaveFile.cpp" />
|
||||
<ClCompile Include="..\..\ext\cityhash\city.cpp" />
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
<ClCompile Include="..\..\Core\Util\DisArm64.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\GameManager.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\PPGeDraw.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\KL4E.cpp" />
|
||||
<ClCompile Include="..\..\Core\Util\PSARUnpack.cpp" />
|
||||
<ClCompile Include="..\..\Core\WaveFile.cpp" />
|
||||
<ClCompile Include="..\..\ext\cityhash\city.cpp" />
|
||||
|
||||
@@ -811,6 +811,7 @@ EXEC_AND_LIB_FILES := \
|
||||
$(SRC)/Core/Util/GameManager.cpp \
|
||||
$(SRC)/Core/Util/BlockAllocator.cpp \
|
||||
$(SRC)/Core/Util/PPGeDraw.cpp \
|
||||
$(SRC)/Core/Util/KL4E.cpp \
|
||||
$(SRC)/Core/Util/PSARUnpack.cpp \
|
||||
$(SRC)/Core/Util/RecentFiles.cpp \
|
||||
$(SRC)/Core/Util/VideoPlayer.cpp \
|
||||
|
||||
@@ -900,6 +900,7 @@ SOURCES_CXX += \
|
||||
$(COREDIR)/Util/BlockAllocator.cpp \
|
||||
$(COREDIR)/Util/MemStick.cpp \
|
||||
$(COREDIR)/Util/PPGeDraw.cpp \
|
||||
$(COREDIR)/Util/KL4E.cpp \
|
||||
$(COREDIR)/Util/PSARUnpack.cpp \
|
||||
$(COREDIR)/Util/RecentFiles.cpp \
|
||||
$(COREDIR)/Util/AudioFormat.cpp \
|
||||
|
||||
Reference in New Issue
Block a user