[Scummvm-git-logs] scummvm master -> 747e839f2ebb7e4bdd0619b9a7c89fe693d1e3c6
neuromancer
noreply at scummvm.org
Thu Jun 11 22:06:49 UTC 2026
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
7051bf01f8 SCUMM RA2: avoid invalid decoding in L2
747e839f2e SCUMM: RA: fix train corruption in L2
Commit: 7051bf01f893bf81051b84c4ae2c8d6e84b08291
https://github.com/scummvm/scummvm/commit/7051bf01f893bf81051b84c4ae2c8d6e84b08291
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-12T00:06:32+02:00
Commit Message:
SCUMM RA2: avoid invalid decoding in L2
Changed paths:
engines/scumm/insane/rebel2/rebel.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 4d1f76af1fd..24810696bf1 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -537,6 +537,7 @@ public:
// Get current handler ID (8, 25, 38 etc.) for SMUSH player to query
int getHandler() const { return _rebelHandler; }
+ int getHandler25GrdSpriteMode() const { return _grdSpriteMode; }
bool isHiRes() const;
void iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32 setupsan12,
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 1dec1a12192..aa2af155c65 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -33,6 +33,8 @@
#include "scumm/scumm.h"
#include "scumm/scumm_v7.h"
+#include "scumm/smush/codec37.h"
+#include "scumm/smush/codec47.h"
#include "scumm/smush/smush_font.h"
#include "scumm/smush/rebel/smush_multi_font.h"
#include "scumm/smush/rebel/codec_ra2.h"
@@ -60,6 +62,14 @@ bool isRebel2GameplayActive(Insane *insane) {
return static_cast<InsaneRebel2 *>(insane)->getHandler() != 0;
}
+bool isRebel2Handler25CorridorMode(Insane *insane) {
+ if (insane == nullptr)
+ return false;
+
+ InsaneRebel2 *rebel2 = static_cast<InsaneRebel2 *>(insane);
+ return rebel2->getHandler() == 25 && rebel2->getHandler25GrdSpriteMode() == 3;
+}
+
static void scaleNativeViewportToHiRes(byte *dst, int dstPitch, int dstWidth, int dstHeight,
const byte *src, int srcPitch, int srcWidth, int srcHeight, int scrollX, int scrollY) {
if (!dst || !src || dstPitch <= 0 || dstWidth < 640 || dstHeight < 400 ||
@@ -165,8 +175,13 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_ra2FrameSourceSkipX = 0;
_ra2FrameSourceSkipY = 0;
_ra2FrameObjectOriginalWidth = 0;
+ _ra2FrameObjectOriginalHeight = 0;
_ra2FrameObjectSurfaceWidth = 0;
_ra2FrameObjectSurfaceHeight = 0;
+ _ra2DeltaBlocksWidth = 0;
+ _ra2DeltaBlocksHeight = 0;
+ _ra2DeltaGlyphsWidth = 0;
+ _ra2DeltaGlyphsHeight = 0;
_ra2LowResVideoBuffer = nullptr;
_ra2LowResVideoBufferSize = 0;
_ra2NativeFrameNeedsClear = false;
@@ -854,6 +869,7 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
*/
void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int width, int height) {
_ra2FrameObjectOriginalWidth = width;
+ _ra2FrameObjectOriginalHeight = height;
_ra2FrameObjectSurfaceWidth = width;
_ra2FrameObjectSurfaceHeight = height;
@@ -1002,17 +1018,36 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
if (needsSpecialBuffer) {
// Frame is larger than the native target - need special buffer.
if (_specialBuffer == nullptr || bufSize > _specialBufferSize) {
+ byte *oldDst = _dst;
+ const int oldWidth = _width;
+ const int oldHeight = _height;
+ int oldPitch = oldWidth;
+ if (oldDst != _specialBuffer && oldDst != _ra2LowResVideoBuffer)
+ oldPitch = _vm->_screenWidth;
+ const bool oldTargetIsNative = oldDst == _specialBuffer || oldDst == _ra2LowResVideoBuffer ||
+ (oldWidth <= 320 && oldHeight <= 200);
+
byte *newSpecialBuffer = (byte *)malloc(bufSize);
if (newSpecialBuffer == nullptr) {
warning("SmushPlayerRebel2::ra2SelectFrameBuffer: Failed to allocate %d bytes for FOBJ %dx%d",
bufSize, width, height);
return false;
}
+ memset(newSpecialBuffer, 0, bufSize);
+
+ if (oldTargetIsNative && oldDst != nullptr && oldWidth > 0 && oldHeight > 0 && oldPitch > 0) {
+ const int copyWidth = MIN<int>(surfaceWidth, MIN<int>(oldWidth, oldPitch));
+ const int copyHeight = MIN<int>(surfaceHeight, oldHeight);
+ for (int y = 0; y < copyHeight; y++) {
+ memcpy(newSpecialBuffer + y * surfaceWidth,
+ oldDst + y * oldPitch,
+ copyWidth);
+ }
+ }
+
free(_specialBuffer);
_specialBuffer = newSpecialBuffer;
_specialBufferSize = bufSize;
- // Zero-fill the new buffer to avoid garbage in areas not written by FOBJ codec
- memset(_specialBuffer, 0, bufSize);
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Allocated new _specialBuffer %dx%d for FOBJ %dx%d (%d bytes)",
surfaceWidth, surfaceHeight, width, height, bufSize);
}
@@ -1046,6 +1081,122 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
return true;
}
+/**
+ * Decode RA2 full-frame delta FOBJ streams into a tight/padded scratch frame,
+ * then place the visible rectangle at the FOBJ coordinates.
+ */
+bool SmushPlayerRebel2::ra2DecodePlacedDeltaCodec(int codec, const uint8 *src, int left, int top,
+ int width, int height, int pitch, int dataSize) {
+ if (!src || !_dst || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
+ return true;
+
+ const int visibleWidth = (_ra2FrameObjectOriginalWidth > 0) ? _ra2FrameObjectOriginalWidth : width;
+ const int visibleHeight = (_ra2FrameObjectOriginalHeight > 0) ? _ra2FrameObjectOriginalHeight : height;
+ if (visibleWidth <= 0 || visibleHeight <= 0)
+ return true;
+
+ const int blockSize = (codec == SMUSH_CODEC_DELTA_BLOCKS) ? 4 : 8;
+ const int decodeWidth = (visibleWidth + blockSize - 1) & ~(blockSize - 1);
+ const int decodeHeight = (visibleHeight + blockSize - 1) & ~(blockSize - 1);
+ const int64 decodeSize64 = (int64)decodeWidth * decodeHeight;
+ if (decodeSize64 <= 0 || decodeSize64 > INT_MAX) {
+ warning("SmushPlayerRebel2::ra2DecodePlacedDeltaCodec: invalid codec %d frame size %dx%d",
+ codec, decodeWidth, decodeHeight);
+ return true;
+ }
+
+ byte *deltaFrame = (byte *)malloc((size_t)decodeSize64);
+ if (deltaFrame == nullptr) {
+ warning("SmushPlayerRebel2::ra2DecodePlacedDeltaCodec: failed to allocate %d-byte scratch frame",
+ (int)decodeSize64);
+ return true;
+ }
+
+ if (codec == SMUSH_CODEC_DELTA_BLOCKS) {
+ if (_deltaBlocksCodec != nullptr &&
+ (_ra2DeltaBlocksWidth != decodeWidth || _ra2DeltaBlocksHeight != decodeHeight)) {
+ delete _deltaBlocksCodec;
+ _deltaBlocksCodec = nullptr;
+ _ra2DeltaBlocksWidth = 0;
+ _ra2DeltaBlocksHeight = 0;
+ }
+ if (_deltaBlocksCodec == nullptr) {
+ _deltaBlocksCodec = new SmushDeltaBlocksDecoder(decodeWidth, decodeHeight);
+ _ra2DeltaBlocksWidth = decodeWidth;
+ _ra2DeltaBlocksHeight = decodeHeight;
+ }
+ _deltaBlocksCodec->decode(deltaFrame, src);
+ } else {
+ if (_deltaGlyphsCodec != nullptr &&
+ (_ra2DeltaGlyphsWidth != decodeWidth || _ra2DeltaGlyphsHeight != decodeHeight)) {
+ delete _deltaGlyphsCodec;
+ _deltaGlyphsCodec = nullptr;
+ _ra2DeltaGlyphsWidth = 0;
+ _ra2DeltaGlyphsHeight = 0;
+ }
+ if (_deltaGlyphsCodec == nullptr) {
+ _deltaGlyphsCodec = new SmushDeltaGlyphsDecoder(decodeWidth, decodeHeight);
+ _ra2DeltaGlyphsWidth = decodeWidth;
+ _ra2DeltaGlyphsHeight = decodeHeight;
+ }
+ _deltaGlyphsCodec->decode(deltaFrame, src);
+ }
+
+ int srcX = CLIP<int>(_ra2FrameSourceSkipX, 0, visibleWidth);
+ int srcY = CLIP<int>(_ra2FrameSourceSkipY, 0, visibleHeight);
+ int copyWidth = MIN<int>(width, visibleWidth - srcX);
+ int copyHeight = MIN<int>(height, visibleHeight - srcY);
+ int dstX = left;
+ int dstY = top;
+
+ if (dstX < 0) {
+ const int skip = -dstX;
+ srcX += skip;
+ copyWidth -= skip;
+ dstX = 0;
+ }
+ if (dstY < 0) {
+ const int skip = -dstY;
+ srcY += skip;
+ copyHeight -= skip;
+ dstY = 0;
+ }
+
+ const int dstHeight = (_height > 0) ? _height : _vm->_screenHeight;
+ if (dstX + copyWidth > pitch)
+ copyWidth = pitch - dstX;
+ if (dstY + copyHeight > dstHeight)
+ copyHeight = dstHeight - dstY;
+
+ const bool transparentZero = codec == SMUSH_CODEC_DELTA_BLOCKS &&
+ visibleWidth == 350 && visibleHeight == 200 &&
+ isRebel2Handler25CorridorMode(_insane);
+
+ if (copyWidth > 0 && copyHeight > 0) {
+ for (int y = 0; y < copyHeight; y++) {
+ byte *dstLine = _dst + (dstY + y) * pitch + dstX;
+ const byte *srcLine = deltaFrame + (srcY + y) * decodeWidth + srcX;
+
+ if (transparentZero) {
+ for (int x = 0; x < copyWidth; x++) {
+ if (srcLine[x] != 0)
+ dstLine[x] = srcLine[x];
+ }
+ } else {
+ memcpy(dstLine, srcLine, copyWidth);
+ }
+ }
+ }
+
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2DecodePlacedDeltaCodec: codec=%d pos=(%d,%d) visible=%dx%d decode=%dx%d copy=%dx%d skip=(%d,%d) transparent=%d",
+ codec, left, top, visibleWidth, visibleHeight, decodeWidth, decodeHeight,
+ MAX<int>(0, copyWidth), MAX<int>(0, copyHeight), _ra2FrameSourceSkipX, _ra2FrameSourceSkipY,
+ transparentZero ? 1 : 0);
+
+ free(deltaFrame);
+ return true;
+}
+
/**
* Dispatch to RA2-specific codec functions.
* Returns true if the codec was handled, false for standard codecs.
@@ -1255,6 +1406,7 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
// RLE data. The original FOBJ dispatcher applies FUN_00424510 offsets
// to the destination coordinates passed to FUN_0042cba0; it does not
// advance the compressed source by clipped scanlines.
+ _ra2FrameSourceSkipY = sourceSkipY;
if (srcSkipY)
*srcSkipY = 0;
} else if (srcSkipY) {
@@ -1264,8 +1416,11 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
}
bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int left, int top, int width, int height, int pitch, int dataSize, uint8 param, uint16 parm2) {
+ if (isRebel2FullFrameDeltaCodec(codec))
+ return ra2DecodePlacedDeltaCodec(codec, src, left, top, width, height, pitch, dataSize);
+
// Handle RA2-specific codecs (21, 23, 44, 45); return false for standard
- // codecs (RLE, uncompressed, codec 37/47) so the base class decodes them.
+ // codecs (RLE) so the base class decodes them.
return ra2DecodeCodec(codec, src, left, top, width, height, pitch, dataSize);
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index cd5d2351776..c3c87e8ea75 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -74,6 +74,8 @@ private:
bool ra2EnsureLowResVideoBuffer();
void ra2ClearCurrentTarget();
bool ra2IsHighResMode() const;
+ bool ra2DecodePlacedDeltaCodec(int codec, const uint8 *src, int left, int top,
+ int width, int height, int pitch, int dataSize);
bool ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
int width, int height, int pitch, int dataSize);
void ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b);
@@ -94,8 +96,13 @@ private:
int _ra2FrameSourceSkipX;
int _ra2FrameSourceSkipY;
int _ra2FrameObjectOriginalWidth;
+ int _ra2FrameObjectOriginalHeight;
int _ra2FrameObjectSurfaceWidth;
int _ra2FrameObjectSurfaceHeight;
+ int _ra2DeltaBlocksWidth;
+ int _ra2DeltaBlocksHeight;
+ int _ra2DeltaGlyphsWidth;
+ int _ra2DeltaGlyphsHeight;
byte *_ra2LowResVideoBuffer;
int _ra2LowResVideoBufferSize;
bool _ra2NativeFrameNeedsClear;
Commit: 747e839f2ebb7e4bdd0619b9a7c89fe693d1e3c6
https://github.com/scummvm/scummvm/commit/747e839f2ebb7e4bdd0619b9a7c89fe693d1e3c6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-12T00:06:32+02:00
Commit Message:
SCUMM: RA: fix train corruption in L2
Changed paths:
engines/scumm/smush/rebel/smush_player_ra2.cpp
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index aa2af155c65..e44fe21ba11 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -934,6 +934,7 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
const bool highRes = ra2IsHighResMode();
const bool gameplayActive = isRebel2GameplayActive(_insane);
const bool fullFrameDelta = isRebel2FullFrameDeltaCodec(codec);
+ const int64 currentSurfaceSize64 = (int64)_width * _height;
if (!gameplayActive && fobjSize64 <= nativeScreenSize) {
if (highRes) {
@@ -958,6 +959,22 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
return true;
}
+ if (gameplayActive && !fullFrameDelta &&
+ isRebel2Handler25CorridorMode(_insane) &&
+ _dst == _specialBuffer && _specialBuffer != nullptr &&
+ _width == 350 && _height == 200 &&
+ _ra2FrameObjectSurfaceWidth <= _width &&
+ _ra2FrameObjectSurfaceHeight <= _height &&
+ currentSurfaceSize64 > 0 && currentSurfaceSize64 <= _specialBufferSize) {
+ if (_ra2NativeFrameNeedsClear) {
+ ra2ClearCurrentTarget();
+ _ra2NativeFrameNeedsClear = false;
+ }
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using current Handler25 corridor _specialBuffer %dx%d for FOBJ %dx%d",
+ _width, _height, width, height);
+ return true;
+ }
+
const bool oversizedNative = fobjSize64 > nativeScreenSize ||
width > 320 || height > 200 ||
_ra2FrameObjectSurfaceWidth > 320 || _ra2FrameObjectSurfaceHeight > 200;
@@ -973,7 +990,6 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
}
- const int64 currentSurfaceSize64 = (int64)_width * _height;
if (highRes && gameplayActive && !useGameplaySurface && !oversizedNative &&
width > 0 && height > 0 &&
_dst == _specialBuffer && _specialBuffer != nullptr &&
More information about the Scummvm-git-logs
mailing list