[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