Enhance RAR decompression logic to handle filter boundaries and improve output management

This commit is contained in:
2026-04-16 13:53:58 +01:00
parent c3806e163b
commit bbeff6f3c6
6 changed files with 166 additions and 13 deletions

View File

@@ -495,24 +495,36 @@ AARU_EXPORT int AARU_CALL rar50_decompress(const uint8_t *in_buf, size_t in_len,
/* Main decompression loop */
while(ctx.out_pos < ctx.out_size)
{
int64_t target = (int64_t)ctx.out_size;
int64_t pos = (int64_t)ctx.out_pos;
/* If a filter is pending, stop expansion at its start */
if(ctx.filters) target = ctx.filters->start;
int64_t nextfilterstart = (int64_t)ctx.out_size;
if(ctx.filters) nextfilterstart = ctx.filters->start;
/* Also limit to out_size */
if(target > (int64_t)ctx.out_size) target = (int64_t)ctx.out_size;
if(pos == nextfilterstart)
{
/* We're at a filter boundary — expand through the filter data */
int64_t target = nextfilterstart + (int64_t)ctx.filters->length;
/* Expand LZSS data up to target */
if(expand_to_position(&ctx, target) != 0) goto fail;
if(expand_to_position(&ctx, target) != 0) goto fail;
if(flush_output(&ctx, ctx.lzss.position) != 0) goto fail;
}
else
{
/* Expand up to the next filter or end of output, with window protection */
int64_t target = pos + 0x40000;
int64_t winedge = rar_lzss_next_window_edge(&ctx.lzss, pos);
if(target > winedge) target = winedge;
if(target > nextfilterstart) target = nextfilterstart;
if(target > (int64_t)ctx.out_size) target = (int64_t)ctx.out_size;
/* Flush available data to out_buf (applying filters) */
if(flush_output(&ctx, ctx.lzss.position) != 0) goto fail;
if(expand_to_position(&ctx, target) != 0) goto fail;
if(flush_output(&ctx, ctx.lzss.position) != 0) goto fail;
/* If we're stuck (no progress), either we finished or there's an error */
if(ctx.lzss.position <= (int64_t)ctx.out_pos && ctx.islastblock &&
rar_bs_bit_offset(&ctx.bs) >= ctx.blockbitend)
break;
/* If no progress was made, check if we're truly done */
if(ctx.lzss.position <= pos && ctx.islastblock &&
rar_bs_bit_offset(&ctx.bs) >= ctx.blockbitend)
break;
}
}
*out_len = ctx.out_pos;

View File

@@ -138,6 +138,15 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/rar30_default.bin
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/rar50_default.bin
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/rar50_m3.bin
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/rar50_m4.bin
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/rar50_m5.bin
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/)
# 'Google_Tests_run' is the target name
# 'test1.cpp tests2.cpp' are source files with tests
add_executable(tests_run apple_rle.cpp crc32.c crc32.h adc.cpp bzip2.cpp lzip.cpp lzfse.cpp zstd.cpp lzma.cpp flac.cpp lz4.cpp

BIN
tests/data/rar50_m3.bin Normal file

Binary file not shown.

BIN
tests/data/rar50_m4.bin Normal file

Binary file not shown.

BIN
tests/data/rar50_m5.bin Normal file

Binary file not shown.

View File

@@ -206,3 +206,135 @@ TEST_F(rar50Fixture, rar50_decompress)
free(outBuf);
EXPECT_EQ(crc, (uint32_t)EXPECTED_CRC32);
}
/* ── RAR 5.0 method 3/4/5 (Calgary obj2, with filters) ── */
#define RAR50_OBJ2_CRC32 0x3ae33007
#define RAR50_OBJ2_ORIGSIZE 246814
#define RAR50_OBJ2_WINSIZE 1048576 /* 1MB dictionary */
class rar50M3Fixture : public ::testing::Test
{
protected:
uint8_t *buffer;
size_t srcLen;
void SetUp() override
{
char cwd[PATH_MAX];
getcwd(cwd, PATH_MAX);
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/data/rar50_m3.bin", cwd);
FILE *f = fopen(path, "rb");
ASSERT_NE(f, nullptr) << "Cannot open " << path;
fseek(f, 0, SEEK_END);
srcLen = (size_t)ftell(f);
fseek(f, 0, SEEK_SET);
buffer = (uint8_t *)malloc(srcLen);
ASSERT_NE(buffer, nullptr);
fread(buffer, 1, srcLen, f);
fclose(f);
}
void TearDown() override { free(buffer); }
};
TEST_F(rar50M3Fixture, rar50_m3_decompress)
{
size_t destLen = RAR50_OBJ2_ORIGSIZE;
uint8_t *outBuf = (uint8_t *)malloc(RAR50_OBJ2_ORIGSIZE);
ASSERT_NE(outBuf, nullptr);
int err = rar50_decompress(buffer, srcLen, outBuf, &destLen, RAR50_OBJ2_WINSIZE);
EXPECT_EQ(err, 0);
EXPECT_EQ(destLen, (size_t)RAR50_OBJ2_ORIGSIZE);
uint32_t crc = crc32_data(outBuf, RAR50_OBJ2_ORIGSIZE);
free(outBuf);
EXPECT_EQ(crc, (uint32_t)RAR50_OBJ2_CRC32);
}
class rar50M4Fixture : public ::testing::Test
{
protected:
uint8_t *buffer;
size_t srcLen;
void SetUp() override
{
char cwd[PATH_MAX];
getcwd(cwd, PATH_MAX);
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/data/rar50_m4.bin", cwd);
FILE *f = fopen(path, "rb");
ASSERT_NE(f, nullptr) << "Cannot open " << path;
fseek(f, 0, SEEK_END);
srcLen = (size_t)ftell(f);
fseek(f, 0, SEEK_SET);
buffer = (uint8_t *)malloc(srcLen);
ASSERT_NE(buffer, nullptr);
fread(buffer, 1, srcLen, f);
fclose(f);
}
void TearDown() override { free(buffer); }
};
TEST_F(rar50M4Fixture, rar50_m4_decompress)
{
size_t destLen = RAR50_OBJ2_ORIGSIZE;
uint8_t *outBuf = (uint8_t *)malloc(RAR50_OBJ2_ORIGSIZE);
ASSERT_NE(outBuf, nullptr);
int err = rar50_decompress(buffer, srcLen, outBuf, &destLen, RAR50_OBJ2_WINSIZE);
EXPECT_EQ(err, 0);
EXPECT_EQ(destLen, (size_t)RAR50_OBJ2_ORIGSIZE);
uint32_t crc = crc32_data(outBuf, RAR50_OBJ2_ORIGSIZE);
free(outBuf);
EXPECT_EQ(crc, (uint32_t)RAR50_OBJ2_CRC32);
}
class rar50M5Fixture : public ::testing::Test
{
protected:
uint8_t *buffer;
size_t srcLen;
void SetUp() override
{
char cwd[PATH_MAX];
getcwd(cwd, PATH_MAX);
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/data/rar50_m5.bin", cwd);
FILE *f = fopen(path, "rb");
ASSERT_NE(f, nullptr) << "Cannot open " << path;
fseek(f, 0, SEEK_END);
srcLen = (size_t)ftell(f);
fseek(f, 0, SEEK_SET);
buffer = (uint8_t *)malloc(srcLen);
ASSERT_NE(buffer, nullptr);
fread(buffer, 1, srcLen, f);
fclose(f);
}
void TearDown() override { free(buffer); }
};
TEST_F(rar50M5Fixture, rar50_m5_decompress)
{
size_t destLen = RAR50_OBJ2_ORIGSIZE;
uint8_t *outBuf = (uint8_t *)malloc(RAR50_OBJ2_ORIGSIZE);
ASSERT_NE(outBuf, nullptr);
int err = rar50_decompress(buffer, srcLen, outBuf, &destLen, RAR50_OBJ2_WINSIZE);
EXPECT_EQ(err, 0);
EXPECT_EQ(destLen, (size_t)RAR50_OBJ2_ORIGSIZE);
uint32_t crc = crc32_data(outBuf, RAR50_OBJ2_ORIGSIZE);
free(outBuf);
EXPECT_EQ(crc, (uint32_t)RAR50_OBJ2_CRC32);
}