[Scummvm-git-logs] scummvm master -> dfe56394d7ba482b244fb9d897646b3c091c2502

Scorpeg noreply at scummvm.org
Wed Jul 15 12:54:56 UTC 2026


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

Summary:
62d91f3c63 VIDEO: Add decoder for older 4XM IDCT and error diffusion
b1e1d311d4 PHOENIXVR: Fix script loader and detection
dfe56394d7 VIDEO: Fix raw 4XM chroma quant table


Commit: 62d91f3c63e1cc536fb09cb18d0313a5842004aa
    https://github.com/scummvm/scummvm/commit/62d91f3c63e1cc536fb09cb18d0313a5842004aa
Author: Scorp (scorp at mrs.mn)
Date: 2026-07-15T15:54:37+03:00

Commit Message:
VIDEO: Add decoder for older 4XM IDCT and error diffusion

Changed paths:
    video/4xm_decoder.cpp
    video/4xm_utils.cpp
    video/4xm_utils.h


diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index e617edc2152..afe2a679fba 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -62,6 +62,7 @@ enum Raw4XMChunkId {
 	kRaw4XMFile = 0xfb814000,
 	kRaw4XMFrameContainer = 0xfb814100,
 	kRaw4XMFullFrame = 0xfb814210,
+	kRaw4XMFullFrameAlt = 0xfb814211,
 	kRaw4XMDeltaFrame = 0xfb814220,
 	kRaw4XMCompressedAudio = 0xfb814230,
 	kRaw4XMCachedFrame = 0xfb814240,
@@ -188,6 +189,456 @@ struct Raw4XMDeltaDecoder {
 	}
 };
 
+struct Raw4XMNibbleDctReader {
+	const byte *byteStream = nullptr;
+	const byte *nibbleStream = nullptr;
+	uint32 byteSize = 0;
+	uint32 nibbleSize = 0;
+	uint32 bytePos = 0;
+	uint32 nibbleWordPos = 1;
+	uint32 nibbleBitsLeft = 8;
+	uint32 nibbleWord = 0;
+
+	Raw4XMNibbleDctReader(const byte *data, uint32 size) {
+		if (size < 8)
+			return;
+
+		const uint32 nibbleOffset = READ_LE_UINT32(data);
+		if (nibbleOffset + 4 > size)
+			return;
+
+		byteStream = data + 4;
+		byteSize = nibbleOffset;
+		nibbleStream = data + nibbleOffset + 4;
+		nibbleSize = size - nibbleOffset - 4;
+		nibbleWord = nibbleSize >= 4 ? READ_LE_UINT32(nibbleStream) : 0;
+	}
+
+	bool valid() const {
+		return byteStream && nibbleStream;
+	}
+
+	byte readByte() {
+		return bytePos < byteSize ? byteStream[bytePos++] : 0;
+	}
+
+	int8 readSignedByte() {
+		return (int8)readByte();
+	}
+
+	uint32 readNibble() {
+		uint32 result = nibbleWord & 0xf;
+		if (--nibbleBitsLeft == 0) {
+			const uint32 off = nibbleWordPos * 4;
+			nibbleWord = off + 4 <= nibbleSize ? READ_LE_UINT32(nibbleStream + off) : 0;
+			++nibbleWordPos;
+			nibbleBitsLeft = 8;
+		} else {
+			nibbleWord >>= 4;
+		}
+		return result;
+	}
+};
+
+struct Raw4XMNativeDeltaReader {
+	const byte *data = nullptr;
+	uint32 size = 0;
+	FourXM::RawDeltaReader dctReader;
+	const byte *addStream = nullptr;
+	const byte *byteStream = nullptr;
+	uint32 addSize = 0;
+	uint32 byteSize = 0;
+	uint32 controlPos = 0x24;
+	uint32 controlWord = 0;
+	uint32 controlBitsLeft = 32;
+	uint32 addPos = 0;
+	uint32 bytePos = 0;
+
+	Raw4XMNativeDeltaReader(const byte *ptr, uint32 len) : data(ptr), size(len) {
+		if (size < 0x24)
+			return;
+
+		const uint32 addOffset = READ_LE_UINT32(data + 8);
+		const uint32 byteOffset = READ_LE_UINT32(data + 12);
+		const uint32 signedByteOffset = READ_LE_UINT32(data + 16);
+		const uint32 nibbleOffset = READ_LE_UINT32(data + 24);
+		if (0x20 + addOffset > size || 0x20 + byteOffset > size || 0x20 + signedByteOffset > size || 0x20 + nibbleOffset > size)
+			return;
+
+		controlWord = READ_LE_UINT32(data + 0x20);
+		addStream = data + 0x20 + addOffset;
+		byteStream = data + 0x20 + byteOffset;
+		addSize = size - (0x20 + addOffset);
+		byteSize = size - (0x20 + byteOffset);
+		dctReader = FourXM::RawDeltaReader(data, size, 0x20, addOffset, byteOffset, signedByteOffset, nibbleOffset, 0x20);
+	}
+
+	bool valid() const {
+		return addStream && byteStream && dctReader.valid();
+	}
+
+	bool readBit() {
+		const bool result = (controlWord & 0x80000000) != 0;
+		controlWord <<= 1;
+		if (--controlBitsLeft == 0) {
+			controlWord = controlPos + 4 <= size ? READ_LE_UINT32(data + controlPos) : 0;
+			controlPos += 4;
+			controlBitsLeft = 32;
+		}
+		return result;
+	}
+
+	byte readByteIndex() {
+		return bytePos < byteSize ? byteStream[bytePos++] : 0;
+	}
+
+	uint16 readAddWord() {
+		const uint32 off = addPos * 2;
+		++addPos;
+		return off + 2 <= addSize ? READ_LE_UINT16(addStream + off) : 0;
+	}
+};
+
+static const int kRaw4XMDctOrder[64] = {
+	0, 1, 8, 16, 9, 2, 3, 10,
+	17, 24, 32, 25, 18, 11, 4, 5,
+	12, 19, 26, 33, 40, 48, 41, 34,
+	27, 20, 13, 6, 7, 14, 21, 28,
+	35, 42, 49, 56, 57, 50, 43, 36,
+	29, 22, 15, 23, 30, 37, 44, 51,
+	58, 59, 52, 45, 38, 31, 39, 46,
+	53, 60, 61, 54, 47, 55, 62, 63};
+
+static const int kRaw4XMDctQuant[5][64] = {
+	{32, 31, 26, 38, 48, 63, 55, 34, 33, 46, 51, 62, 72, 126, 90, 42,
+	 37, 47, 55, 74, 105, 117, 98, 40, 33, 55, 68, 80, 120, 161, 102, 40,
+	 36, 61, 97, 132, 136, 171, 111, 42, 38, 76, 113, 118, 127, 128, 96, 40,
+	 53, 96, 110, 111, 111, 103, 70, 30, 40, 70, 68, 64, 62, 43, 31, 15},
+	{64, 61, 52, 75, 96, 126, 110, 67, 67, 92, 101, 124, 144, 253, 180, 84,
+	 73, 94, 109, 147, 209, 234, 195, 81, 66, 111, 135, 160, 240, 322, 204, 80,
+	 72, 122, 193, 263, 272, 343, 223, 85, 75, 153, 226, 237, 255, 257, 192, 80,
+	 106, 192, 221, 221, 223, 206, 141, 60, 79, 141, 137, 127, 124, 87, 62, 30},
+	{8, 12, 16, 28, 50, 39, 27, 14, 12, 20, 24, 54, 69, 54, 37, 19,
+	 16, 24, 48, 76, 65, 51, 35, 18, 28, 54, 76, 68, 58, 46, 31, 16,
+	 50, 69, 65, 58, 50, 39, 27, 14, 39, 54, 51, 46, 39, 31, 21, 11,
+	 27, 37, 35, 31, 27, 21, 14, 7, 14, 19, 18, 16, 14, 11, 7, 4},
+	{16, 25, 31, 55, 99, 78, 54, 27, 25, 40, 47, 108, 137, 108, 74, 38,
+	 31, 47, 96, 152, 129, 102, 70, 36, 55, 108, 152, 137, 116, 91, 63, 32,
+	 99, 137, 129, 116, 99, 78, 54, 27, 78, 108, 102, 91, 78, 61, 42, 21,
+	 54, 74, 70, 63, 54, 42, 29, 15, 27, 38, 36, 32, 27, 21, 15, 8},
+	{128, 122, 105, 151, 192, 251, 221, 135, 133, 185, 203, 248, 289, 506, 360, 168,
+	 146, 188, 219, 295, 418, 468, 390, 161, 132, 222, 270, 320, 480, 643, 407, 161,
+	 144, 244, 387, 527, 544, 685, 446, 170, 151, 305, 452, 473, 509, 514, 384, 160,
+	 212, 384, 441, 443, 446, 412, 281, 121, 159, 282, 274, 254, 247, 173, 123, 60}};
+
+struct Raw4XMDctToken {
+	byte zeroes;
+	int8 values[2];
+	byte valueCount;
+};
+
+static const Raw4XMDctToken kRaw4XMDctTokens[8] = {
+	{64, {0, 0}, 0},
+	{5, {0, 0}, 0},
+	{1, {1, 0}, 1},
+	{1, {-1, 0}, 1},
+	{2, {1, 0}, 1},
+	{1, {0, -1}, 2},
+	{2, {0, 1}, 2},
+	{3, {-1, 0}, 1}};
+
+static int raw4XMSign2(byte value) {
+	return (value & 2) ? (value | ~3) : (value + 1);
+}
+
+static int raw4XMSign4(byte value) {
+	return (value & 8) ? (value | ~15) : (value + 1);
+}
+
+static void readRaw4XMDctToken(Raw4XMNibbleDctReader &reader, int coeff[64], int &index) {
+	auto zero = [&](int count) {
+		for (int i = 0; i < count && index < 64; ++i, ++index)
+			coeff[kRaw4XMDctOrder[index]] = 0;
+	};
+	auto write = [&](int value) {
+		if (index < 64)
+			coeff[kRaw4XMDctOrder[index++]] = value;
+	};
+
+	const byte code = reader.readNibble();
+	if (code <= 7) {
+		const Raw4XMDctToken &token = kRaw4XMDctTokens[code];
+		zero(code == 0 ? 64 - index : token.zeroes);
+		for (byte i = 0; i < token.valueCount; ++i)
+			write(token.values[i]);
+		return;
+	}
+
+	switch (code) {
+	case 8: {
+		const byte header = reader.readNibble();
+		const byte pair = reader.readNibble();
+		zero((header >> 2) + 1);
+		write(raw4XMSign2(pair >> 2));
+		write(raw4XMSign2(pair & 3));
+		if ((header & 3) != 0) {
+			const byte extra = reader.readNibble();
+			write(raw4XMSign2(extra >> 2));
+			if ((header & 3) > 1)
+				write(raw4XMSign2(extra & 3));
+			if ((header & 3) > 2)
+				write(raw4XMSign2(reader.readNibble() >> 2));
+		}
+		break;
+	}
+	case 9: {
+		const byte header = reader.readNibble();
+		zero((header >> 2) + 1);
+		for (int i = 0; i <= (header & 3); ++i)
+			write(raw4XMSign4(reader.readNibble()));
+		break;
+	}
+	case 10: {
+		const byte value = reader.readNibble();
+		write(raw4XMSign2(value >> 2));
+		write(raw4XMSign2(value & 3));
+		break;
+	}
+	case 11:
+	case 12:
+	case 13:
+		for (int i = 0; i < code - 10; ++i)
+			write(raw4XMSign4(reader.readNibble()));
+		break;
+	case 14:
+		write(0);
+		break;
+	case 15:
+		write((int8)reader.readByte());
+		break;
+	}
+}
+
+static void readRaw4XMDctCoefficients(Raw4XMNibbleDctReader &reader, int coeffs[6][64]) {
+	for (int block = 0; block < 6; ++block) {
+		int index = 1;
+		Common::fill(coeffs[block], coeffs[block] + 64, 0);
+		coeffs[block][0] = (int8)reader.readByte();
+		while (index <= 63)
+			readRaw4XMDctToken(reader, coeffs[block], index);
+	}
+}
+
+static void inverseRaw4XMDct1D(int *base, int step, int shift) {
+	static const int kSqrt2 = 0x16a0a;
+	static const int kC6 = 0x1d907;
+	static const int kC2 = -0x29cf6;
+	static const int kC4 = 0x11518;
+
+	const int in0 = base[0 * step];
+	const int in1 = base[1 * step];
+	const int in2 = base[2 * step];
+	const int in3 = base[3 * step];
+	const int in4 = base[4 * step];
+	const int in5 = base[5 * step];
+	const int in6 = base[6 * step];
+	const int in7 = base[7 * step];
+
+	const int even04Sum = in0 + in4;
+	const int even04Diff = in0 - in4;
+	const int even26Sum = in2 + in6;
+	const int evenSum = even04Sum + even26Sum;
+	const int evenDiff = even04Sum - even26Sum;
+	const int even26Rot = ((in2 - in6) * kSqrt2 >> 16) - even26Sum;
+	const int midA = even26Rot + even04Diff;
+	const int midB = even04Diff - even26Rot;
+
+	const int odd35Sum = in3 + in5;
+	const int odd35Diff = in5 - in3;
+	const int odd17Sum = in7 + in1;
+	const int odd17Diff = in1 - in7;
+	const int oddSum = odd17Sum + odd35Sum;
+	const int oddRot = (odd17Diff + odd35Diff) * kC6 >> 16;
+	const int oddA = ((odd35Diff * kC2 >> 16) - oddSum) + oddRot;
+	const int oddB = ((odd17Sum - odd35Sum) * kSqrt2 >> 16) - oddA;
+	const int oddC = ((odd17Diff * kC4 >> 16) - oddRot) + oddB;
+
+	base[0 * step] = (oddSum + evenSum) >> shift;
+	base[7 * step] = (evenSum - oddSum) >> shift;
+	base[1 * step] = (oddA + midA) >> shift;
+	base[6 * step] = (midA - oddA) >> shift;
+	base[5 * step] = (midB - oddB) >> shift;
+	base[4 * step] = (oddC + evenDiff) >> shift;
+	base[2 * step] = (oddB + midB) >> shift;
+	base[3 * step] = (evenDiff - oddC) >> shift;
+}
+
+static void inverseRaw4XMDct(const int coeff[64], int out[64], const int quant[64]) {
+	for (int i = 0; i < 64; ++i)
+		out[i] = coeff[i] * quant[i];
+
+	for (int x = 0; x < 8; ++x)
+		inverseRaw4XMDct1D(out + x, 8, 0);
+	for (int y = 0; y < 8; ++y)
+		inverseRaw4XMDct1D(out + y * 8, 1, 6);
+}
+
+static void writeRaw4XMDctRgbPixel(byte *dst, int yValue, int cb, int cr) {
+	const int blue = yValue + cb * 2;
+	const int green = yValue - ((cb + cr) >> 1);
+	const int red = yValue + cr;
+	dst[0] = CLIP(blue, 0, 0xff);
+	dst[1] = CLIP(green, 0, 0xff);
+	dst[2] = CLIP(red, 0, 0xff);
+}
+
+static void writeRaw4XMDctTileRgb(byte *rgb, int stride, int dstX, int dstY, const int planes[6][64]) {
+	auto getY = [&](int index) {
+		return planes[index >> 6][index & 0x3f];
+	};
+
+	int yIndex = 0;
+	for (int block = 0; block < 4; ++block) {
+		const int blockX = block & 1;
+		const int blockY = block >> 1;
+		int chromaIndex = (blockX + blockY * 8) * 4;
+		int pixelOffset = ((dstY + blockY * 8) * stride + dstX + blockX * 8) * 4;
+		for (int y = 0; y < 4; ++y) {
+			int rowPixelOffset = pixelOffset;
+			for (int x = 0; x < 4; ++x) {
+				const int cb = planes[4][chromaIndex];
+				const int cr = planes[5][chromaIndex];
+				writeRaw4XMDctRgbPixel(rgb + rowPixelOffset, getY(yIndex) + 0x80, cb, cr);
+				writeRaw4XMDctRgbPixel(rgb + rowPixelOffset + 4, getY(yIndex + 1) + 0x80, cb, cr);
+				writeRaw4XMDctRgbPixel(rgb + rowPixelOffset + stride * 4, getY(yIndex + 8) + 0x80, cb, cr);
+				writeRaw4XMDctRgbPixel(rgb + rowPixelOffset + stride * 4 + 4, getY(yIndex + 9) + 0x80, cb, cr);
+				yIndex += 2;
+				rowPixelOffset += 8;
+				++chromaIndex;
+			}
+			pixelOffset += stride * 8;
+			chromaIndex += 4;
+			yIndex += 8;
+		}
+	}
+}
+
+static byte quantizeRaw4XMDitherComponent(int32 value) {
+	value = CLIP<int32>(value, 0, 0xff0000);
+	int component = (value >> 16) & 0xff;
+	component = MIN(((component + 4) >> 3), 0x1f);
+	return component << 3;
+}
+
+static void clampRaw4XMDitherPixel(int32 *pixel) {
+	pixel[-2] = CLIP<int32>(pixel[-2], 0, 0xff0000);
+	pixel[-1] = CLIP<int32>(pixel[-1], 0, 0xff0000);
+	pixel[0] = CLIP<int32>(pixel[0], 0, 0xff0000);
+}
+
+static void quantizeRaw4XMDitherPixel(int32 *pixel, int32 diff[3]) {
+	clampRaw4XMDitherPixel(pixel);
+	const byte blue = quantizeRaw4XMDitherComponent(pixel[-2]);
+	const byte green = quantizeRaw4XMDitherComponent(pixel[-1]);
+	const byte red = quantizeRaw4XMDitherComponent(pixel[0]);
+	diff[0] = pixel[-2] - (blue << 16);
+	diff[1] = pixel[-1] - (green << 16);
+	diff[2] = pixel[0] - (red << 16);
+	pixel[-2] = blue << 16;
+	pixel[-1] = green << 16;
+	pixel[0] = red << 16;
+}
+
+static void diffuseRaw4XMDither(int32 *pixel, int32 *below, const int pixelNeighbor, const int belowA, const int belowB, const int belowC, const int32 diff[3]) {
+	static const int kWeights[4] = {7, 5, 3, 1};
+	int32 *targets[4] = {
+		pixel + pixelNeighbor,
+		below + belowA,
+		below + belowB,
+		below + belowC};
+
+	for (int target = 0; target < 4; ++target) {
+		for (int c = 0; c < 3; ++c)
+			targets[target][c] += (diff[c] * kWeights[target]) >> 4;
+	}
+}
+
+static void ditherRaw4XMRgbTo555(const byte *rgb, uint16 *dst, int width, int height) {
+	Common::Array<int32> error;
+	error.resize(width * height * 3 + width * 3 + 16);
+	for (int i = 0; i < width * height; ++i) {
+		error[i * 3] = rgb[i * 4] << 16;
+		error[i * 3 + 1] = rgb[i * 4 + 1] << 16;
+		error[i * 3 + 2] = rgb[i * 4 + 2] << 16;
+	}
+
+	for (int y = 0; y < height - 1; ++y) {
+		if ((y & 1) == 0) {
+			int32 *pixel = error.begin() + y * width * 3 + 2;
+			int32 *below = error.begin() + (y + 1) * width * 3 + 2;
+			for (int x = 0; x < width - 1; ++x) {
+				int32 diff[3];
+				quantizeRaw4XMDitherPixel(pixel, diff);
+				diffuseRaw4XMDither(pixel, below, 1, -2, -5, 1, diff);
+				pixel += 3;
+				below += 3;
+			}
+		} else {
+			int32 *pixel = error.begin() + y * width * 3 + (width - 2) * 3 + 2;
+			int32 *below = error.begin() + (y + 1) * width * 3 + (width - 2) * 3 + 2;
+			for (int x = 0; x < width - 1; ++x) {
+				int32 diff[3];
+				quantizeRaw4XMDitherPixel(pixel, diff);
+				diffuseRaw4XMDither(pixel, below, -5, -2, 1, -5, diff);
+				pixel -= 3;
+				below -= 3;
+			}
+		}
+	}
+
+	for (int i = 0; i < width * height; ++i) {
+		int32 *pixel = error.begin() + i * 3 + 2;
+		clampRaw4XMDitherPixel(pixel);
+		const byte blue = quantizeRaw4XMDitherComponent(pixel[-2]);
+		const byte green = quantizeRaw4XMDitherComponent(pixel[-1]);
+		const byte red = quantizeRaw4XMDitherComponent(pixel[0]);
+		dst[i] = ((red >> 3) << 10) | ((green >> 3) << 5) | (blue >> 3);
+	}
+}
+
+static bool applyRaw4XMFullDct(const byte *data, uint32 size, uint16 *dst, int width, int height, bool altTables) {
+	Raw4XMNibbleDctReader reader(data, size);
+	if (!reader.valid() || width <= 0 || height <= 0)
+		return false;
+
+	Common::Array<byte> rgb;
+	rgb.resize(width * height * 4);
+
+	for (int y = 0; y < height; y += 16) {
+		for (int x = 0; x < width; x += 16) {
+			const byte flags = reader.readByte();
+			int coeffs[6][64];
+			int planes[6][64];
+			readRaw4XMDctCoefficients(reader, coeffs);
+			for (int i = 0; i < 4; ++i)
+				inverseRaw4XMDct(coeffs[i], planes[i], kRaw4XMDctQuant[(flags & (1 << i)) ? 1 : 0]);
+			if (altTables) {
+				inverseRaw4XMDct(coeffs[4], planes[4], kRaw4XMDctQuant[(flags & 0x10) ? 4 : 1]);
+				inverseRaw4XMDct(coeffs[5], planes[5], kRaw4XMDctQuant[(flags & 0x20) ? 4 : 1]);
+			} else {
+				inverseRaw4XMDct(coeffs[4], planes[4], kRaw4XMDctQuant[(flags & 0x10) ? 3 : 2]);
+				inverseRaw4XMDct(coeffs[5], planes[5], kRaw4XMDctQuant[(flags & 0x20) ? 3 : 2]);
+			}
+
+			if (x + 16 <= width && y + 16 <= height)
+				writeRaw4XMDctTileRgb(rgb.begin(), width, x, y, planes);
+		}
+	}
+
+	ditherRaw4XMRgbTo555(rgb.begin(), dst, width, height);
+	return true;
+}
+
 static bool applyRaw4XMDelta(const byte *data, uint32 size, uint16 *dst, const uint16 *src,
 							 int width, int height, const Common::Array<int> &fullOffsets, const Common::Array<int> &expOffsets) {
 	Raw4XMDeltaDecoder decoder(data, size, dst, src, width, height, fullOffsets, expOffsets);
@@ -209,6 +660,115 @@ static bool applyRaw4XMDelta(const byte *data, uint32 size, uint16 *dst, const u
 	return true;
 }
 
+struct Raw4XMNativeDeltaDecoder {
+	Raw4XMNativeDeltaReader reader;
+	uint16 *dst = nullptr;
+	const uint16 *src = nullptr;
+	int frameWidth = 0;
+	int frameHeight = 0;
+	const Common::Array<int> &motionOffsets;
+
+	Raw4XMNativeDeltaDecoder(const byte *data, uint32 size, uint16 *dstPixels, const uint16 *srcPixels,
+							 int width, int height, const Common::Array<int> &offsets) : reader(data, size), dst(dstPixels), src(srcPixels), frameWidth(width), frameHeight(height), motionOffsets(offsets) {}
+
+	bool copyBlock(int dstOffset, int blockWidth, int blockHeight, bool add) {
+		const byte motionIndex = reader.readByteIndex();
+		const uint16 addValue = add ? reader.readAddWord() : 0;
+		if (dstOffset < 0 || dstOffset + frameWidth * (blockHeight - 1) + blockWidth > frameWidth * frameHeight)
+			return true;
+
+		int srcOffset = dstOffset;
+		if (motionIndex < motionOffsets.size())
+			srcOffset += motionOffsets[motionIndex];
+		if (srcOffset < 0 || srcOffset + frameWidth * (blockHeight - 1) + blockWidth > frameWidth * frameHeight)
+			return true;
+
+		for (int y = 0; y < blockHeight; ++y) {
+			const uint16 *srcRow = src + srcOffset + y * frameWidth;
+			uint16 *dstRow = dst + dstOffset + y * frameWidth;
+			if (!add) {
+				for (int x = 0; x < blockWidth; ++x)
+					dstRow[x] = srcRow[x];
+			} else if (blockWidth == 1) {
+				dstRow[0] = (uint16)(srcRow[0] + addValue);
+			} else {
+				const uint32 addPair = addValue | ((uint32)addValue << 16);
+				for (int x = 0; x < blockWidth; x += 2) {
+					const uint32 srcPair = srcRow[x] | ((uint32)srcRow[x + 1] << 16);
+					const uint32 dstPair = (srcPair & 0x7fff7fff) + addPair;
+					dstRow[x] = (uint16)dstPair;
+					dstRow[x + 1] = (uint16)(dstPair >> 16);
+				}
+			}
+		}
+		return true;
+	}
+
+	bool dctBlock(int dstOffset) {
+		int coeffs[3][64];
+		int blocks[3][64] = {};
+		const byte scaleY = reader.dctReader.readNibble();
+		const byte scaleCb = reader.dctReader.readNibble();
+		const byte scaleCr = reader.dctReader.readNibble();
+		FourXM::readRawCoefficients(reader.dctReader, coeffs);
+		FourXM::transformRawCoefficients(coeffs[0], blocks[0], scaleY, false);
+		FourXM::transformRawCoefficients(coeffs[1], blocks[1], scaleCb, true);
+		FourXM::transformRawCoefficients(coeffs[2], blocks[2], scaleCr, true);
+		FourXM::writeRawDctBlock(blocks[0], blocks[1], blocks[2], dst + dstOffset, frameWidth);
+		return true;
+	}
+
+	bool splitVertical(int dstOffset, int blockWidth, int blockHeight) {
+		const int topHeight = blockHeight / 2;
+		decodeBlock(dstOffset, blockWidth, topHeight, false);
+		decodeBlock(dstOffset + topHeight * frameWidth, blockWidth, blockHeight - topHeight, false);
+		return true;
+	}
+
+	bool splitHorizontal(int dstOffset, int blockWidth, int blockHeight) {
+		const int leftWidth = blockWidth / 2;
+		decodeBlock(dstOffset, leftWidth, blockHeight, false);
+		decodeBlock(dstOffset + leftWidth, blockWidth - leftWidth, blockHeight, false);
+		return true;
+	}
+
+	bool decodeBlock(int dstOffset, int blockWidth, int blockHeight, bool allowDct) {
+		if (!reader.readBit())
+			return copyBlock(dstOffset, blockWidth, blockHeight, false);
+
+		const bool canSplitVertical = blockHeight > 1 && (blockWidth > 1 || blockHeight > 2);
+		const bool canSplitHorizontal = blockWidth > 1 && (blockHeight > 1 || blockWidth > 2);
+		if (canSplitVertical && !reader.readBit())
+			return splitVertical(dstOffset, blockWidth, blockHeight);
+		if (canSplitHorizontal && !reader.readBit())
+			return splitHorizontal(dstOffset, blockWidth, blockHeight);
+		if (!reader.readBit())
+			return true;
+
+		if (!allowDct)
+			return copyBlock(dstOffset, blockWidth, blockHeight, true);
+		if (!reader.readBit())
+			return copyBlock(dstOffset, blockWidth, blockHeight, true);
+		if (blockWidth == 8 && blockHeight == 8)
+			return dctBlock(dstOffset);
+		return true;
+	}
+};
+
+static bool applyRaw4XMNativeDelta(const byte *data, uint32 size, uint16 *dst, const uint16 *src,
+								   int width, int height, const Common::Array<int> &motionOffsets) {
+	Raw4XMNativeDeltaDecoder decoder(data, size, dst, src, width, height, motionOffsets);
+	if (!decoder.reader.valid() || width <= 0 || height <= 0)
+		return false;
+
+	for (int y = 0; y < height; y += 8) {
+		for (int x = 0; x < width; x += 8)
+			decoder.decodeBlock(y * width + x, MIN(8, width - x), MIN(8, height - y), true);
+	}
+
+	return true;
+}
+
 static Raw4XMCacheEntry &raw4XMFindCacheEntry(Common::Array<Raw4XMCacheEntry> &cache, int32 frame) {
 	for (uint i = 0; i < cache.size(); ++i) {
 		if (cache[i].frame == frame)
@@ -449,6 +1009,7 @@ class FourXMDecoder::FourXMRawVideoTrack : public FixedRateVideoTrack {
 	Common::Array<int> _fullMotionOffsets;
 	Common::Array<int> _expMotionOffsets;
 	Common::Array<Raw4XMCacheEntry> _cache;
+	bool _nativeRawStream = false;
 
 public:
 	FourXMRawVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h) {
@@ -510,10 +1071,15 @@ public:
 private:
 	Common::Rational getFrameRate() const override { return _frameRate; }
 
-	bool decodeFullFrame(const byte *payload, uint32 payloadSize) {
-		if (payloadSize < _w * _h * 2)
-			return false;
+	bool decodeFullFrame(const byte *payload, uint32 payloadSize, bool altTables) {
+		const uint32 rawFrameSize = _w * _h * 2;
+		if (payloadSize < rawFrameSize) {
+			const bool decoded = applyRaw4XMFullDct(payload, payloadSize, (uint16 *)_frame->getPixels(), _w, _h, altTables);
+			_nativeRawStream = decoded;
+			return decoded;
+		}
 
+		_nativeRawStream = false;
 		Common::MemoryReadStream fullFrameStream(payload, payloadSize);
 
 		for (uint y = 0; y < _h; ++y) {
@@ -562,19 +1128,28 @@ private:
 
 			const byte *subPayload = payload + pos + 8;
 			const uint32 subPayloadSize = subChunkSize - 8;
-			if (subChunkId == kRaw4XMFullFrame) {
-				changed = decodeFullFrame(subPayload, subPayloadSize) || changed;
+			if (subChunkId == kRaw4XMFullFrame || subChunkId == kRaw4XMFullFrameAlt) {
+				changed = decodeFullFrame(subPayload, subPayloadSize, subChunkId == kRaw4XMFullFrameAlt) || changed;
 			} else if (subChunkId == kRaw4XMDeltaFrame) {
-				changed = applyRaw4XMDelta(subPayload, subPayloadSize, (uint16 *)_frame->getPixels(),
-										   (const uint16 *)_previousFrame->getPixels(), _w, _h,
-										   _fullMotionOffsets, _expMotionOffsets) ||
-						  changed;
+				if (_nativeRawStream) {
+					changed = applyRaw4XMNativeDelta(subPayload, subPayloadSize, (uint16 *)_frame->getPixels(),
+													 (const uint16 *)_previousFrame->getPixels(), _w, _h,
+													 _expMotionOffsets) ||
+							  changed;
+				} else {
+					changed = applyRaw4XMDelta(subPayload, subPayloadSize, (uint16 *)_frame->getPixels(),
+											   (const uint16 *)_previousFrame->getPixels(), _w, _h,
+											   _fullMotionOffsets, _expMotionOffsets) ||
+							  changed;
+				}
 			} else if (subChunkId == kRaw4XMCachedFrame) {
 				changed = decodeCachedFrame(subPayload, subPayloadSize, currentFrame) || changed;
 			} else if (subChunkId == kRaw4XMCompressedAudio && _dec->_rawAudio) {
 				_dec->_rawAudio->queueADPCM(subPayload, subPayloadSize);
 			} else if (subChunkId == kRaw4XMRawAudio && _dec->_rawAudio) {
 				_dec->_rawAudio->queueRaw(subPayload, subPayloadSize);
+			} else {
+				// Known ignored marker: 0xfb814260 wraps original audio-group filtering.
 			}
 
 			pos += subChunkSize;
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 7ee78a7f138..62e33fb1b93 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -172,12 +172,26 @@ RawDeltaReader::RawDeltaReader(const byte *ptr, uint32 len) : data(ptr), size(le
 	mode = READ_LE_UINT32(data);
 	pairOffset = READ_LE_UINT32(data + 8) + 0x18;
 	byteOffset = READ_LE_UINT32(data + 12) + 0x18;
+	signedByteOffset = byteOffset;
 	nibbleOffset = READ_LE_UINT32(data + 16) + 0x18;
 	controlWord = READ_LE_UINT32(data + 0x18);
 	if (nibbleOffset + 4 <= size)
 		nibbleWord = READ_LE_UINT32(data + nibbleOffset);
 }
 
+RawDeltaReader::RawDeltaReader(const byte *ptr, uint32 len, uint32 streamBase, uint32 pairOff, uint32 byteOff,
+							   uint32 signedByteOff, uint32 nibbleOff, uint32 controlOff) : data(ptr), size(len) {
+	pairOffset = streamBase + pairOff;
+	byteOffset = streamBase + byteOff;
+	signedByteOffset = streamBase + signedByteOff;
+	nibbleOffset = streamBase + nibbleOff;
+	separateSignedByteStream = true;
+	if (controlOff + 4 <= size)
+		controlWord = READ_LE_UINT32(data + controlOff);
+	if (nibbleOffset + 4 <= size)
+		nibbleWord = READ_LE_UINT32(data + nibbleOffset);
+}
+
 bool RawDeltaReader::valid() const {
 	return size >= 0x18;
 }
@@ -208,7 +222,12 @@ byte RawDeltaReader::readByteIndex() {
 }
 
 int8 RawDeltaReader::readSignedByte() {
-	return (int8)readByteIndex();
+	if (!separateSignedByteStream)
+		return (int8)readByteIndex();
+
+	uint32 off = signedByteOffset + signedByteIndex;
+	++signedByteIndex;
+	return off < size ? (int8)data[off] : 0;
 }
 
 uint32 RawDeltaReader::readNibble() {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index b18839b5abb..53d97479b17 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -40,9 +40,15 @@ struct RawDeltaReader {
 	uint32 nibbleWord = 0;
 	uint32 pairOffset = 0;
 	uint32 byteOffset = 0;
+	uint32 signedByteOffset = 0;
 	uint32 nibbleOffset = 0;
+	uint32 signedByteIndex = 0;
+	bool separateSignedByteStream = false;
 
+	RawDeltaReader() = default;
 	RawDeltaReader(const byte *ptr, uint32 len);
+	RawDeltaReader(const byte *ptr, uint32 len, uint32 streamBase, uint32 pairOff, uint32 byteOff,
+				   uint32 signedByteOff, uint32 nibbleOff, uint32 controlOff);
 
 	bool valid() const;
 	uint32 readControl2();


Commit: b1e1d311d4e00172290656b48b3aaaacc8085fb2
    https://github.com/scummvm/scummvm/commit/b1e1d311d4e00172290656b48b3aaaacc8085fb2
Author: Scorp (scorp at mrs.mn)
Date: 2026-07-15T15:54:37+03:00

Commit Message:
PHOENIXVR: Fix script loader and detection

Changed paths:
    engines/phoenixvr/detection_tables.h
    engines/phoenixvr/phoenixvr.cpp


diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index 6bb198976ef..69392200810 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -313,8 +313,8 @@ const ADGameDescription gameDescriptions[] = {
 	},
 
 	{"dracula1",
-		nullptr,
-		AD_ENTRY2s("script.pak", "6998262fcce9cb5d3cc8a555f8ee024a", 265,
+		"Retail version",
+		AD_ENTRY2s("Install/script.pak", "6998262fcce9cb5d3cc8a555f8ee024a", 265,
 				   "Interface.vr", "47099471f31f794be8a7e5a3c382bf62", 104304),
 		Common::IT_ITA,
 		Common::kPlatformWindows,
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9082e122ba8..1abc043bd0d 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -416,13 +416,14 @@ bool PhoenixVREngine::setNextLevel() {
 void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
 	debug("setNextScript %s", nextScript.c_str());
 	_contextScript = nextScript;
-	if (nextScript.find('\\') == nextScript.npos) {
+	const Common::String scriptPath = removeDrive(nextScript);
+	if (scriptPath.find('\\') == scriptPath.npos) {
 		// simple filename, e.g. "script.lst"
-		_nextScript = nextScript;
+		_nextScript = scriptPath;
 		return;
 	}
 
-	auto nextPath = Common::Path(removeDrive(nextScript), '\\');
+	auto nextPath = Common::Path(scriptPath, '\\');
 	_currentScriptPath = nextPath.getParent();
 	debug("changed script directory to %s", _currentScriptPath.toString().c_str());
 	_nextScript = nextPath.getLastComponent().toString();


Commit: dfe56394d7ba482b244fb9d897646b3c091c2502
    https://github.com/scummvm/scummvm/commit/dfe56394d7ba482b244fb9d897646b3c091c2502
Author: Scorp (scorp at mrs.mn)
Date: 2026-07-15T15:54:37+03:00

Commit Message:
VIDEO: Fix raw 4XM chroma quant table

Changed paths:
    video/4xm_decoder.cpp


diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index afe2a679fba..2951ad9af39 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -327,7 +327,7 @@ static const int kRaw4XMDctQuant[5][64] = {
 	 99, 137, 129, 116, 99, 78, 54, 27, 78, 108, 102, 91, 78, 61, 42, 21,
 	 54, 74, 70, 63, 54, 42, 29, 15, 27, 38, 36, 32, 27, 21, 15, 8},
 	{128, 122, 105, 151, 192, 251, 221, 135, 133, 185, 203, 248, 289, 506, 360, 168,
-	 146, 188, 219, 295, 418, 468, 390, 161, 132, 222, 270, 320, 480, 643, 407, 161,
+	 146, 188, 219, 295, 418, 468, 390, 161, 132, 222, 270, 321, 480, 643, 407, 161,
 	 144, 244, 387, 527, 544, 685, 446, 170, 151, 305, 452, 473, 509, 514, 384, 160,
 	 212, 384, 441, 443, 446, 412, 281, 121, 159, 282, 274, 254, 247, 173, 123, 60}};
 




More information about the Scummvm-git-logs mailing list