Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions code/cstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ CStreamClass::CStreamClass(void) :
IsWriting(false),
CurOffset(0),
DataBuffer(new unsigned char[BUFFER_SIZE]),
StreamBuffer(new unsigned char[BUFFER_SIZE]),
LZODictionary(new unsigned char[BUFFER_SIZE])
StreamBuffer(new unsigned char[COMP_BUFFER_SIZE]),
// The dictionary holds pointer-wide entries, so its byte size follows the target.
LZODictionary(new unsigned char[LZO1X_MEM_COMPRESS])
{
BlockHead.CompSize = BUFFER_SIZE - 1;
}
Expand Down Expand Up @@ -490,7 +491,9 @@ HRESULT CStreamClass::Compress(void *in_buffer, ULONG length)
HRESULT hr;
unsigned int out_len = length;
lzo1x_1_compress((lzo_byte *)in_buffer, length, (lzo_byte *)StreamBuffer, &out_len, (lzo_byte *)LZODictionary);
BlockHead.UncompSize = BUFFER_SIZE;
// The final block of a stream is usually partial, and the reader takes this
// header as the number of bytes the block expands to.
BlockHead.UncompSize = length;
length = 0;
BlockHead.CompSize = out_len;

Expand Down
4 changes: 4 additions & 0 deletions code/cstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class CStreamClass : public IStream, public ILinkStream

enum {
BUFFER_SIZE = 64*1024,

// A block that will not compress comes out bigger than it went in, up to
// the LZO worst case, and the compressed side has to hold that.
COMP_BUFFER_SIZE = BUFFER_SIZE + BUFFER_SIZE / 16 + 64 + 3,
};

private:
Expand Down
6 changes: 3 additions & 3 deletions code/lzopipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ int LZOPipe::Put(void const * source, int slen)

if (Counter == BlockSize) {
unsigned int len = sizeof (Buffer2);
char *dictionary = new char [64*1024];
char *dictionary = new char [LZO1X_MEM_COMPRESS];
lzo1x_1_compress ((unsigned char*)Buffer, BlockSize, (unsigned char*)Buffer2, &len, dictionary);
delete [] dictionary;
BlockHeader.CompCount = (unsigned short)len;
Expand All @@ -213,7 +213,7 @@ int LZOPipe::Put(void const * source, int slen)
*/
while (slen >= BlockSize) {
unsigned int len = 0;//sizeof (Buffer2);
char *dictionary = new char [64*1024];
char *dictionary = new char [LZO1X_MEM_COMPRESS];
lzo1x_1_compress ((unsigned char*)source, BlockSize, (unsigned char*)Buffer2, &len, dictionary);
delete [] dictionary;
source = ((char *)source) + BlockSize;
Expand Down Expand Up @@ -300,7 +300,7 @@ int LZOPipe::Flush(void)
** compress the partial block and output normally.
*/
unsigned int len = 0;//sizeof (Buffer2);
char *dictionary = new char [64*1024];
char *dictionary = new char [LZO1X_MEM_COMPRESS];
lzo1x_1_compress ((unsigned char*)Buffer, Counter, (unsigned char *)Buffer2, &len, dictionary);
delete [] dictionary;
BlockHeader.CompCount = (unsigned short)len;
Expand Down
2 changes: 1 addition & 1 deletion code/lzostraw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ int LZOStraw::Get(void * destbuf, int slen)
} else {
BlockHeader.UncompCount = (unsigned short)BASECLASS::Get(Buffer, BlockSize);
if (BlockHeader.UncompCount == 0) break;
char *dictionary = new char [64*1024];
char *dictionary = new char [LZO1X_MEM_COMPRESS];
unsigned int length = sizeof (Buffer2) - sizeof (BlockHeader);
lzo1x_1_compress ((unsigned char*)Buffer, BlockHeader.UncompCount, (unsigned char*)(&Buffer2[sizeof(BlockHeader)]), &length, dictionary);
BlockHeader.CompCount = (unsigned short)length;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_subdirectory(gamedirs)
add_subdirectory(logstress)
add_subdirectory(cpudetect)
add_subdirectory(lzocomp)
33 changes: 33 additions & 0 deletions tests/lzocomp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# The compression sources are compiled straight into the harness. They live outside code/
# so that the recursive glob building OpenTS cannot pick this target's entry point up.
add_executable(LzoComp
"${CMAKE_CURRENT_SOURCE_DIR}/lzocomp.cpp"
"${CMAKE_SOURCE_DIR}/code/lzo1x_c.cpp"
"${CMAKE_SOURCE_DIR}/code/lzo1x_d.cpp"
"${CMAKE_SOURCE_DIR}/code/lzopipe.cpp"
"${CMAKE_SOURCE_DIR}/code/lzostraw.cpp"
"${CMAKE_SOURCE_DIR}/code/pipe.cpp"
"${CMAKE_SOURCE_DIR}/code/straw.cpp"
)

target_compile_features(LzoComp PRIVATE cxx_std_20)

target_include_directories(LzoComp PRIVATE
"${CMAKE_SOURCE_DIR}/code"
"${CMAKE_CURRENT_SOURCE_DIR}"
)

target_compile_definitions(LzoComp PRIVATE WIN32 _WINDOWS _MBCS NOMINMAX)

target_compile_options(LzoComp PRIVATE
$<$<CONFIG:Debug>:/MTd /EHsc /Zc:__cplusplus>
$<$<CONFIG:Release>:/MT /EHsc /Zc:__cplusplus>
)

target_link_libraries(LzoComp PRIVATE kernel32 user32 shell32)

set_target_properties(LzoComp PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

add_test(NAME lzocomp COMMAND LzoComp)
159 changes: 159 additions & 0 deletions tests/lzocomp/lzocomp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*******************************************************************************
* O P E N T S
*******************************************************************************
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright 2026 OpenTS contributors
*
* See LICENSE.md for applicable additional terms and warranty disclaimers.
******************************************************************************/

// Round-trips data through LZOPipe and LZOStraw, the compression path save games
// travel. The compressor's dictionary holds pointers, so its byte size differs
// between 32-bit and LP64 targets; an undersized allocation corrupts the stream
// this test would then fail to expand. Needs no game data.

#include <cstdio>
#include <cstring>
#include <vector>

#include "lzopipe.h"
#include "lzostraw.h"

namespace {

int Failures = 0;

unsigned int Seed = 0;

unsigned int Next_Random(void)
{
Seed = Seed * 1103515245u + 12345u;
return(Seed >> 8);
}


// Collects everything a pipe chain emits.
class CapturePipe : public Pipe
{
public:
std::vector<unsigned char> Data;

virtual int Put(void const * source, int slen) override
{
unsigned char const * bytes = (unsigned char const *)source;
Data.insert(Data.end(), bytes, bytes + slen);
return(slen);
}
};


// Serves a fixed buffer to a straw chain.
class MemoryStraw : public Straw
{
public:
MemoryStraw(unsigned char const * data, int length) : Data(data), Length(length), Offset(0) {}

virtual int Get(void * buffer, int slen) override
{
int count = (slen < Length - Offset) ? slen : (Length - Offset);
if (count > 0) {
memcpy(buffer, Data + Offset, count);
Offset += count;
}
return(count);
}

private:
unsigned char const * Data;
int Length;
int Offset;
};


void Fill_Source(std::vector<unsigned char> & source, int shape, int size, unsigned int seed)
{
Seed = seed;
source.resize((size_t)size);

for (int i = 0; i < size; i++) {
switch (shape) {
case 0:
source[(size_t)i] = (unsigned char)Next_Random();
break;

case 1:
source[(size_t)i] = (unsigned char)(i & 0x0F);
break;

case 2:
source[(size_t)i] = (unsigned char)((Next_Random() % 8 == 0) ? Next_Random() : 0x55);
break;

default:
source[(size_t)i] = 0;
break;
}
}
}


void Check_Roundtrip(int shape, int size, unsigned int seed)
{
std::vector<unsigned char> source;
Fill_Source(source, shape, size, seed);

CapturePipe captured;
{
LZOPipe compressor(LZOPipe::COMPRESS);
compressor.Put_To(captured);
int offset = 0;
while (offset < size) {
int chunk = (size - offset < 977) ? (size - offset) : 977;
compressor.Put(&source[(size_t)offset], chunk);
offset += chunk;
}
compressor.End();
}

MemoryStraw stored(captured.Data.data(), (int)captured.Data.size());
LZOStraw expander(LZOStraw::DECOMPRESS);
expander.Get_From(stored);

std::vector<unsigned char> expanded((size_t)size, 0);
int got = 0;
while (got < size) {
int step = expander.Get(&expanded[(size_t)got], size - got);
if (step <= 0) {
break;
}
got += step;
}

if (got != size || memcmp(source.data(), expanded.data(), (size_t)size) != 0) {
printf("FAIL shape %d size %d: %d of %d bytes back, %s\n", shape, size, got, size,
(got == size) ? "content differs" : "stream ended short");
Failures++;
}
}

} // namespace


int main(void)
{
int const sizes[] = { 1, 100, 8191, 8192, 8193, 65536, 250000 };

for (int shape = 0; shape < 3; shape++) {
for (int size : sizes) {
Check_Roundtrip(shape, size, 0x1234u + (unsigned int)shape);
}
}

if (Failures > 0) {
printf("lzocomp: %d failures\n", Failures);
return(1);
}

printf("lzocomp: all round trips match\n");
return(0);
}