diff --git a/rar/rar50.c b/rar/rar50.c index 99dac1f..61984b1 100644 --- a/rar/rar50.c +++ b/rar/rar50.c @@ -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; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 022b55a..302712d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 diff --git a/tests/data/rar50_m3.bin b/tests/data/rar50_m3.bin new file mode 100644 index 0000000..e6eef46 Binary files /dev/null and b/tests/data/rar50_m3.bin differ diff --git a/tests/data/rar50_m4.bin b/tests/data/rar50_m4.bin new file mode 100644 index 0000000..1296f84 Binary files /dev/null and b/tests/data/rar50_m4.bin differ diff --git a/tests/data/rar50_m5.bin b/tests/data/rar50_m5.bin new file mode 100644 index 0000000..ed53918 Binary files /dev/null and b/tests/data/rar50_m5.bin differ diff --git a/tests/rar/rar.cpp b/tests/rar/rar.cpp index 073a9c4..5257db9 100644 --- a/tests/rar/rar.cpp +++ b/tests/rar/rar.cpp @@ -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); +}