[Scummvm-git-logs] scummvm master -> 6fff0ec9b765675c06790b570a8342696c05f48a

bluegr noreply at scummvm.org
Thu Apr 10 05:11:13 UTC 2025


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
6fff0ec9b7 SCI: Merge and cleanup LZW decompressors


Commit: 6fff0ec9b765675c06790b570a8342696c05f48a
    https://github.com/scummvm/scummvm/commit/6fff0ec9b765675c06790b570a8342696c05f48a
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-10T08:11:09+03:00

Commit Message:
SCI: Merge and cleanup LZW decompressors

Changed paths:
    engines/sci/resource/decompressor.cpp
    engines/sci/resource/decompressor.h


diff --git a/engines/sci/resource/decompressor.cpp b/engines/sci/resource/decompressor.cpp
index e94c210e965..d6ace94c6ec 100644
--- a/engines/sci/resource/decompressor.cpp
+++ b/engines/sci/resource/decompressor.cpp
@@ -140,33 +140,22 @@ int16 DecompressorHuffman::getc2() {
 //-------------------------------
 // LZW Decompressor for SCI0/01/1
 //-------------------------------
-void DecompressorLZW::init(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
-	Decompressor::init(src, dest, nPacked, nUnpacked);
 
-	_numbits = 9;
-	_curtoken = 0x102;
-	_endtoken = 0x1ff;
-}
-
-int DecompressorLZW::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked,
-								uint32 nUnpacked) {
+int DecompressorLZW::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
 	byte *buffer = nullptr;
 
 	switch (_compression) {
 	case kCompLZW:	// SCI0 LZW compression
-		return unpackLZW(src, dest, nPacked, nUnpacked);
-		break;
 	case kCompLZW1: // SCI01/1 LZW compression
-		return unpackLZW1(src, dest, nPacked, nUnpacked);
-		break;
+		return unpackLZW(src, dest, nPacked, nUnpacked);
 	case kCompLZW1View:
 		buffer = new byte[nUnpacked];
-		unpackLZW1(src, buffer, nPacked, nUnpacked);
+		unpackLZW(src, buffer, nPacked, nUnpacked);
 		reorderView(buffer, dest);
 		break;
 	case kCompLZW1Pic:
 		buffer = new byte[nUnpacked];
-		unpackLZW1(src, buffer, nPacked, nUnpacked);
+		unpackLZW(src, buffer, nPacked, nUnpacked);
 		reorderPic(buffer, dest, nUnpacked);
 		break;
 	default:
@@ -176,172 +165,80 @@ int DecompressorLZW::unpack(Common::ReadStream *src, byte *dest, uint32 nPacked,
 	return 0;
 }
 
-int DecompressorLZW::unpackLZW(Common::ReadStream *src, byte *dest, uint32 nPacked,
-								uint32 nUnpacked) {
+// Decompresses SCI0 LZW and SCI01/1 LZW, depending on _compression value.
+//
+// SCI0:    LSB-first.
+// SCI01/1: MSB-first, code size is increased one code earlier than necessary.
+//          This is known as an "early change" bug in LZW implementations.
+int DecompressorLZW::unpackLZW(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
 	init(src, dest, nPacked, nUnpacked);
 
-	uint16 token; // The last received value
-	uint16 tokenlastlength = 0;
-
-	uint16 *tokenlist = (uint16 *)malloc(4096 * sizeof(uint16)); // pointers to dest[]
-	uint16* tokenlengthlist = (uint16 *)malloc(4096 * sizeof(uint16)); // char length of each token
-	if (!tokenlist || !tokenlengthlist) {
-		free(tokenlist);
-		free(tokenlengthlist);
+	uint16 codeBitLength = 9;
+	uint16 tableSize = 258;
+	uint16 codeLimit = (_compression == kCompLZW) ? 512 : 511;
 
-		error("[DecompressorLZW::unpackLZW] Cannot allocate token memory buffers");
-	}
+	// LZW table
+	uint16 *stringOffsets = new uint16[4096]; // 0-257: unused
+	uint16 *stringLengths = new uint16[4096]; // 0-257: unused
 
 	while (!isFinished()) {
-		token = getBitsLSB(_numbits);
-
-		if (token == 0x101) {
-			free(tokenlist);
-			free(tokenlengthlist);
+		uint16 code = (_compression == kCompLZW) ?
+		              getBitsLSB(codeBitLength) :
+		              getBitsMSB(codeBitLength);
 
-			return 0; // terminator
+		if (code >= tableSize) {
+			warning("LZW code %x exceeds table size %x", code, tableSize);
+			break;
 		}
 
-		if (token == 0x100) { // reset command
-			_numbits = 9;
-			_endtoken = 0x1FF;
-			_curtoken = 0x0102;
-		} else {
-			if (token > 0xff) {
-				if (token >= _curtoken) {
-					warning("unpackLZW: Bad token %x", token);
-
-					free(tokenlist);
-					free(tokenlengthlist);
-
-					return SCI_ERROR_DECOMPRESSION_ERROR;
-				}
-				tokenlastlength = tokenlengthlist[token] + 1;
-				if (_dwWrote + tokenlastlength > _szUnpacked) {
-					// For me this seems a normal situation, It's necessary to handle it
-					warning("unpackLZW: Trying to write beyond the end of array(len=%d, destctr=%d, tok_len=%d)",
-					        _szUnpacked, _dwWrote, tokenlastlength);
-					for (int i = 0; _dwWrote < _szUnpacked; i++)
-						putByte(dest[tokenlist[token] + i]);
-				} else
-					for (int i = 0; i < tokenlastlength; i++)
-						putByte(dest[tokenlist[token] + i]);
-			} else {
-				tokenlastlength = 1;
-				if (_dwWrote >= _szUnpacked)
-					warning("unpackLZW: Try to write single byte beyond end of array");
-				else
-					putByte(token);
-			}
-			if (_curtoken > _endtoken && _numbits < 12) {
-				_numbits++;
-				_endtoken = (_endtoken << 1) + 1;
-			}
-			if (_curtoken <= _endtoken) {
-				tokenlist[_curtoken] = _dwWrote - tokenlastlength;
-				tokenlengthlist[_curtoken] = tokenlastlength;
-				_curtoken++;
-			}
-
+		if (code == 257) { // terminator
+			break;
 		}
-	}
-
-	free(tokenlist);
-	free(tokenlengthlist);
-
-	return _dwWrote == _szUnpacked ? 0 : SCI_ERROR_DECOMPRESSION_ERROR;
-}
 
-int DecompressorLZW::unpackLZW1(Common::ReadStream *src, byte *dest, uint32 nPacked,
-								uint32 nUnpacked) {
-	init(src, dest, nPacked, nUnpacked);
-
-	byte *stak = (byte *)malloc(0x1014);
-	uint32 tokensSize = 0x1004 * sizeof(Tokenlist);
-	Tokenlist *tokens = (Tokenlist *)malloc(tokensSize);
-	if (!stak || !tokens) {
-		free(stak);
-		free(tokens);
-
-		error("[DecompressorLZW::unpackLZW1] Cannot allocate decompression buffers");
-	}
-
-	memset(tokens, 0, tokensSize);
-
-	byte lastchar = 0;
-	uint16 stakptr = 0, lastbits = 0;
+		if (code == 256) { // reset command
+			codeBitLength = 9;
+			tableSize = 258;
+			codeLimit = (_compression == kCompLZW) ? 512 : 511;
+			continue;
+		}
 
-	byte decryptstart = 0;
-	uint16 bitstring;
-	uint16 token;
-	bool bExit = false;
+		uint16 newStringOffset = _dwWrote;
+		if (code <= 255) {
+			// Code is a literal byte
+			putByte(code);
+		} else {
+			// Code is a table index
 
-	while (!isFinished() && !bExit) {
-		switch (decryptstart) {
-		case 0:
-			bitstring = getBitsMSB(_numbits);
-			if (bitstring == 0x101) {// found end-of-data signal
-				bExit = true;
-				continue;
+			// Boundary check included because the previous decompressor had a
+			// comment saying it's "a normal situation" for a string to attempt
+			// to write beyond the destination. I have not seen this occur.
+			for (int i = 0; i < stringLengths[code] && !isFinished(); i++) {
+				putByte(dest[stringOffsets[code] + i]);
 			}
-			putByte(bitstring);
-			lastbits = bitstring;
-			lastchar = (bitstring & 0xff);
-			decryptstart = 1;
-			break;
+		}
 
-		case 1:
-			bitstring = getBitsMSB(_numbits);
-			if (bitstring == 0x101) { // found end-of-data signal
-				bExit = true;
-				continue;
-			}
-			if (bitstring == 0x100) { // start-over signal
-				_numbits = 9;
-				_curtoken = 0x102;
-				_endtoken = 0x1ff;
-				decryptstart = 0;
-				continue;
-			}
+		// Stop adding to the table once it is full
+		if (tableSize >= 4096) {
+			continue;
+		}
 
-			token = bitstring;
-			if (token >= _curtoken) { // index past current point
-				token = lastbits;
-				stak[stakptr++] = lastchar;
-			}
-			while ((token > 0xff) && (token < 0x1004)) { // follow links back in data
-				stak[stakptr++] = tokens[token].data;
-				token = tokens[token].next;
-			}
-			lastchar = stak[stakptr++] = token & 0xff;
-			// put stack in buffer
-			while (stakptr > 0) {
-				putByte(stak[--stakptr]);
-				if (_dwWrote == _szUnpacked) {
-					bExit = true;
-					continue;
-				}
-			}
-			// put token into record
-			if (_curtoken <= _endtoken) {
-				tokens[_curtoken].data = lastchar;
-				tokens[_curtoken].next = lastbits;
-				_curtoken++;
-				if (_curtoken == _endtoken && _numbits < 12) {
-					_numbits++;
-					_endtoken = (_endtoken << 1) + 1;
-				}
+		// Increase code size once a bit limit has been reached
+		if (tableSize == codeLimit && codeBitLength < 12) {
+			codeBitLength++;
+			codeLimit = 1 << codeBitLength;
+			if (_compression != kCompLZW) {
+				codeLimit--;
 			}
-			lastbits = bitstring;
-			break;
-
-		default:
-			break;
 		}
+
+		// Append code to table
+		stringOffsets[tableSize] = newStringOffset;
+		stringLengths[tableSize] = _dwWrote - newStringOffset + 1;
+		tableSize++;
 	}
 
-	free(stak);
-	free(tokens);
+	delete[] stringOffsets;
+	delete[] stringLengths;
 
 	return _dwWrote == _szUnpacked ? 0 : SCI_ERROR_DECOMPRESSION_ERROR;
 }
@@ -414,6 +311,12 @@ int DecompressorLZW::getRLEsize(byte *rledata, int dsize) {
 	return size;
 }
 
+enum {
+	PIC_OPX_EMBEDDED_VIEW = 1,
+	PIC_OPX_SET_PALETTE = 2,
+	PIC_OP_OPX = 0xfe
+};
+
 void DecompressorLZW::reorderPic(byte *src, byte *dest, int dsize) {
 	uint16 view_size, view_start, cdata_size;
 	int i;
diff --git a/engines/sci/resource/decompressor.h b/engines/sci/resource/decompressor.h
index e3ec6fc0bf3..8d16b4fb88e 100644
--- a/engines/sci/resource/decompressor.h
+++ b/engines/sci/resource/decompressor.h
@@ -142,26 +142,15 @@ protected:
 };
 
 /**
- * LZW-like decompressor for SCI01/SCI1.
- * TODO: Needs clean-up of post-processing fncs
+ * LZW decompressor for SCI0/01/1
+ * TODO: Clean-up post-processing functions
  */
 class DecompressorLZW : public Decompressor {
 public:
-	DecompressorLZW(int nCompression) : _numbits(0), _curtoken(0), _endtoken(0) {
-		_compression = nCompression;
-	}
-	void init(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) override;
+	DecompressorLZW(ResourceCompression compression) : _compression(compression) {}
 	int unpack(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) override;
 
 protected:
-	enum {
-		PIC_OPX_EMBEDDED_VIEW = 1,
-		PIC_OPX_SET_PALETTE = 2,
-		PIC_OP_OPX = 0xfe
-	};
-	// unpacking procedures
-	// TODO: unpackLZW and unpackLZW1 are similar and should be merged
-	int unpackLZW1(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
 	int unpackLZW(Common::ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
 
 	// functions to post-process view and pic resources
@@ -171,14 +160,7 @@ protected:
 	int getRLEsize(byte *rledata, int dsize);
 	void buildCelHeaders(byte **seeker, byte **writer, int celindex, int *cc_lengths, int max);
 
-	// decompressor data
-	struct Tokenlist {
-		byte data;
-		uint16 next;
-	};
-	uint16 _numbits;
-	uint16 _curtoken, _endtoken;
-	int _compression;
+	ResourceCompression _compression;
 };
 
 /**




More information about the Scummvm-git-logs mailing list