[Scummvm-git-logs] scummvm master -> 13eb6f3be096392b48681a072e36ea6df75ea020

sev- noreply at scummvm.org
Tue Feb 28 00:58:21 UTC 2023


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

Summary:
4d5ec3c73c GRAPHICS: Rename DownscaleAllByHalfARM to downscaleAllByHalfARM
a99d59551e GRAPHICS: Add 2x downscaler for CLUT images
90f8c24265 ENGINES: Allow to specify several supported resolutions
7dba71d671 PLUMBERS: Remove need for highres
35d72e4487 PLUMBERS: Use smaller cursor on lower resolutions
573bd18cd3 PLUMBERS: Mark 3DO version as supported
13eb6f3be0 DREAMWEB: Don't use 640x480 if USE_HIGHRES is false


Commit: 4d5ec3c73caa90048f09c17cbdd40a1175a2f6d5
    https://github.com/scummvm/scummvm/commit/4d5ec3c73caa90048f09c17cbdd40a1175a2f6d5
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
GRAPHICS: Rename DownscaleAllByHalfARM to downscaleAllByHalfARM

Changed paths:
    graphics/scaler/downscaler.cpp
    graphics/scaler/downscalerARM.s


diff --git a/graphics/scaler/downscaler.cpp b/graphics/scaler/downscaler.cpp
index e107fdb7a52..5991df1fdd4 100644
--- a/graphics/scaler/downscaler.cpp
+++ b/graphics/scaler/downscaler.cpp
@@ -25,7 +25,7 @@ int gBitFormat = 565;
 
 #ifdef USE_ARM_SCALER_ASM
 extern "C" {
-	void DownscaleAllByHalfARM(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height, int mask, int round);
+	void downscaleAllByHalfARM(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height, int mask, int round);
 }
 
 void DownscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) {
@@ -34,7 +34,7 @@ void DownscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uin
 	static const int redbluegreenMasks[] = { 0x03E07C1F, 0x07E0F81F };
 
 	const int maskUsed = (gBitFormat == 565);
-	DownscaleAllByHalfARM(srcPtr, srcPitch, dstPtr, dstPitch, width, height, redbluegreenMasks[maskUsed], roundingconstants[maskUsed]);
+	downscaleAllByHalfARM(srcPtr, srcPitch, dstPtr, dstPitch, width, height, redbluegreenMasks[maskUsed], roundingconstants[maskUsed]);
 }
 
 #else
diff --git a/graphics/scaler/downscalerARM.s b/graphics/scaler/downscalerARM.s
index 1df83f035dc..8fe73b4f6a4 100644
--- a/graphics/scaler/downscalerARM.s
+++ b/graphics/scaler/downscalerARM.s
@@ -21,7 +21,7 @@
 
 	.text
 
-	.global	DownscaleAllByHalfARM
+	.global	downscaleAllByHalfARM
 
 	@ ARM implementation of DownscaleAllByHalf scaler.
 	@ Scales a width x height block of 16bpp pixels from srcPtr to
@@ -29,7 +29,7 @@
 	@ lines. redblueMask and round allow for one routine to do both
 	@ 565 and 555 formats.
 	.align 2
-DownscaleAllByHalfARM:
+downscaleAllByHalfARM:
 	@ r0 = srcPtr
 	@ r1 = srcPitch
 	@ r2 = dstPtr


Commit: a99d59551ebb89884547fa36bc3a865ed8ad7991
    https://github.com/scummvm/scummvm/commit/a99d59551ebb89884547fa36bc3a865ed8ad7991
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
GRAPHICS: Add 2x downscaler for CLUT images

Changed paths:
    graphics/module.mk
    graphics/scaler/downscaler.cpp
    graphics/scaler/downscaler.h


diff --git a/graphics/module.mk b/graphics/module.mk
index 1e63b227526..59e1ed4f08b 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -42,6 +42,7 @@ MODULE_OBJS := \
 	primitives.o \
 	renderer.o \
 	scalerplugin.o \
+	scaler/downscaler.o \
 	scaler/thumbnail_intern.o \
 	screen.o \
 	scaler/normal.o \
@@ -57,6 +58,11 @@ MODULE_OBJS := \
 	wincursor.o \
 	yuv_to_rgb.o
 
+ifdef USE_ARM_SCALER_ASM
+MODULE_OBJS += \
+	scaler/downscalerARM.o
+endif
+
 ifdef USE_TINYGL
 MODULE_OBJS += \
 	tinygl/api.o \
@@ -95,7 +101,6 @@ MODULE_OBJS += \
 	scaler/dotmatrix.o \
 	scaler/sai.o \
 	scaler/pm.o \
-	scaler/downscaler.o \
 	scaler/scale2x.o \
 	scaler/scale3x.o \
 	scaler/scalebit.o \
@@ -103,7 +108,6 @@ MODULE_OBJS += \
 
 ifdef USE_ARM_SCALER_ASM
 MODULE_OBJS += \
-	scaler/downscalerARM.o \
 	scaler/scale2xARM.o \
 	scaler/Normal2xARM.o
 endif
diff --git a/graphics/scaler/downscaler.cpp b/graphics/scaler/downscaler.cpp
index 5991df1fdd4..8f1dab82f14 100644
--- a/graphics/scaler/downscaler.cpp
+++ b/graphics/scaler/downscaler.cpp
@@ -18,17 +18,18 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
+#include "common/debug.h"
 #include "graphics/scaler/downscaler.h"
 #include "graphics/scaler/intern.h"
 
-int gBitFormat = 565;
+namespace Graphics {
 
 #ifdef USE_ARM_SCALER_ASM
 extern "C" {
 	void downscaleAllByHalfARM(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height, int mask, int round);
 }
 
-void DownscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) {
+void downscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height, int gBitFormat) {
 	// Rounding constants and masks used for different pixel formats
 	static const int roundingconstants[] = { 0x00200802, 0x00201002 };
 	static const int redbluegreenMasks[] = { 0x03E07C1F, 0x07E0F81F };
@@ -40,7 +41,7 @@ void DownscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uin
 #else
 
 template<typename ColorMask>
-void DownscaleAllByHalfTemplate(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) {
+void downscaleAllByHalfTemplate(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) {
 	uint8 *work;
 	uint16 srcPitch16 = (uint16)(srcPitch / sizeof(uint16));
 
@@ -62,11 +63,82 @@ void DownscaleAllByHalfTemplate(const uint8 *srcPtr, uint32 srcPitch, uint8 *dst
 	}
 }
 
-void DownscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) {
+void downscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height, int gBitFormat) {
 	if (gBitFormat == 565)
-		DownscaleAllByHalfTemplate<Graphics::ColorMasks<565> >(srcPtr, srcPitch, dstPtr, dstPitch, width, height);
+		downscaleAllByHalfTemplate<Graphics::ColorMasks<565> >(srcPtr, srcPitch, dstPtr, dstPitch, width, height);
 	else
-		DownscaleAllByHalfTemplate<Graphics::ColorMasks<555> >(srcPtr, srcPitch, dstPtr, dstPitch, width, height);
+		downscaleAllByHalfTemplate<Graphics::ColorMasks<555> >(srcPtr, srcPitch, dstPtr, dstPitch, width, height);
 }
 
 #endif
+
+namespace {
+uint32 getBrightness(byte col, const byte *pal) {
+	return pal[3 * col] * pal[3 * col] + pal[3 * col + 1] * pal[3 * col + 1] + pal[3 * col + 2] * pal[3 * col + 2];
+}
+
+void downscaleCLUT8ByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int maxX, int maxY, const byte *palette) {
+	const byte *src1 = srcPtr;
+	const byte *src2 = srcPtr + srcPitch;
+	byte *dst = dstPtr;
+	int32 srcSkip = 2 * srcPitch - maxX * 2;
+	int32 dstSkip = dstPitch - maxX;
+	for (int y = 0; y < maxY; y++) {
+		for (int x = 0; x < maxX; x++) {
+			// Choose the brightest pixel. Writing often is bright on
+			// a dark background, so this preserves text as much as we can.
+			byte colors[4] = {
+				*src1++,
+				*src1++,
+				*src2++,
+				*src2++
+			};
+
+			byte col = colors[0];
+			uint32 bri = getBrightness(col, palette);
+			for (uint i = 1; i < 4; i++) {
+				uint32 nbri = getBrightness(colors[i], palette);
+				if (nbri > bri) {
+					bri = nbri;
+					col = colors[i];
+				}
+			}
+
+			*dst++ = col;
+		}
+		src1 += srcSkip;
+		src2 += srcSkip;
+		dst += dstSkip;
+	}
+}
+}
+
+void downscaleSurfaceByHalf(Surface *out, const Surface *in, const byte *palette) {
+	if (in->format.isCLUT8() && out->format.isCLUT8() && palette != nullptr) {
+		downscaleCLUT8ByHalf((const byte *) in->getBasePtr(0, 0), in->pitch, (byte *) out->getBasePtr(0, 0), out->pitch,
+				     MIN<int>(out->w, in->w / 2), MIN<int>(out->h, in->h / 2), palette);
+		return;
+	}
+
+	if ((in->format == PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)
+	     && out->format == PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0))
+	    || (in->format == PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0)
+		&& out->format == PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0))) {
+		downscaleAllByHalf((const byte *) in->getBasePtr(0, 0), in->pitch, (byte *) out->getBasePtr(0, 0), out->pitch,
+				   MIN<int>(in->w, out->w * 2), MIN<int>(in->h, out->h * 2), 565);
+		return;
+	}
+
+	if ((in->format == PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)
+	     && out->format == PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0))
+	    || (in->format == PixelFormat(2, 5, 5, 5, 0, 0, 5, 10, 0)
+		&& out->format == PixelFormat(2, 5, 5, 5, 0, 0, 5, 10, 0))) {
+		downscaleAllByHalf((const byte *) in->getBasePtr(0, 0), in->pitch, (byte *) out->getBasePtr(0, 0), out->pitch,
+				   MIN<int>(in->w, out->w * 2), MIN<int>(in->h, out->h * 2), 555);
+		return;
+	}
+
+	error("downscaleCLUT8ByHalf(): Unsupported downscale format %s->%s", in->format.toString().c_str(), out->format.toString().c_str());
+}
+
+}  // end of namespace Graphics
diff --git a/graphics/scaler/downscaler.h b/graphics/scaler/downscaler.h
index f75c7f6530f..c781572084d 100644
--- a/graphics/scaler/downscaler.h
+++ b/graphics/scaler/downscaler.h
@@ -23,12 +23,18 @@
 #define GRAPHICS_SCALER_DOWNSCALER_H
 
 #include "common/scummsys.h"
+#include "graphics/surface.h"
 
+namespace Graphics {
 /**
  * This filter (down)scales the source image by a factor of 1/2.
  * For example, a 320x200 image is scaled to 160x100.
  */
-extern void DownscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr,
-							   uint32 dstPitch, int width, int height);
+extern void downscaleAllByHalf(const uint8 *srcPtr, uint32 srcPitch, uint8 *dstPtr,
+			       uint32 dstPitch, int width, int height, int gBits = 565);
+
+void downscaleSurfaceByHalf(Surface *out, const Surface *in, const byte *palette);
+
+}
 
 #endif


Commit: 90f8c24265aaaf207d2a505025a72708485f71a5
    https://github.com/scummvm/scummvm/commit/90f8c24265aaaf207d2a505025a72708485f71a5
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
ENGINES: Allow to specify several supported resolutions

This is useful in order to fallback resolutions even if supported
resolution is not known at compile time.

Changed paths:
    engines/engine.cpp
    engines/util.h
    graphics/mode.h


diff --git a/engines/engine.cpp b/engines/engine.cpp
index d2da357cae5..dc93286b88a 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -310,37 +310,53 @@ void initGraphicsModes(const Graphics::ModeList &modes) {
 	g_system->initSizeHint(modes);
 }
 
-void initGraphics(int width, int height, const Graphics::PixelFormat *format) {
-
-	g_system->beginGFXTransaction();
+/**
+ * Inits any of the modes in "modes". "modes" is in the order of preference.
+ * Return value is index in modes of resulting mode.
+ */
+int initGraphicsAny(const Graphics::ModeWithFormatList &modes) {
+	int candidate = -1;
+	OSystem::TransactionError gfxError = OSystem::kTransactionSizeChangeFailed;
+	int last_width = 0, last_height = 0;
 
+	for (candidate = 0; candidate < (int)modes.size(); candidate++) {
+		g_system->beginGFXTransaction();
 		initCommonGFX();
 #ifdef USE_RGB_COLOR
-		if (format)
-			g_system->initSize(width, height, format);
+		if (modes[candidate].hasFormat)
+			g_system->initSize(modes[candidate].width, modes[candidate].height, &modes[candidate].format);
 		else {
 			Graphics::PixelFormat bestFormat = g_system->getSupportedFormats().front();
-			g_system->initSize(width, height, &bestFormat);
+			g_system->initSize(modes[candidate].width, modes[candidate].height, &bestFormat);
 		}
 #else
-		g_system->initSize(width, height);
+		g_system->initSize(modes[candidate].width, modes[candidate].height);
 #endif
+		last_width = modes[candidate].width;
+		last_height = modes[candidate].height;
 
-	OSystem::TransactionError gfxError = g_system->endGFXTransaction();
+		gfxError = g_system->endGFXTransaction();
 
-	if (!splash && !GUI::GuiManager::instance()._launched)
-		splashScreen();
+		if (!splash && !GUI::GuiManager::instance()._launched)
+			splashScreen();
 
-	if (gfxError == OSystem::kTransactionSuccess)
-		return;
+		if (gfxError == OSystem::kTransactionSuccess)
+			return candidate;
+
+		// If error is related to resolution, continue
+		if (gfxError & (OSystem::kTransactionSizeChangeFailed | OSystem::kTransactionFormatNotSupported))
+			continue;
+
+		break;
+	}
 
 	// Error out on size switch failure
 	if (gfxError & OSystem::kTransactionSizeChangeFailed) {
 		Common::U32String message;
-		message = Common::U32String::format(_("Could not switch to resolution '%dx%d'."), width, height);
+		message = Common::U32String::format(_("Could not switch to resolution '%dx%d'."), last_width, last_height);
 
 		GUIErrorMessage(message);
-		error("Could not switch to resolution '%dx%d'.", width, height);
+		error("Could not switch to resolution '%dx%d'.", last_width, last_height);
 	}
 
 	// Just show warnings then these occur:
@@ -383,6 +399,14 @@ void initGraphics(int width, int height, const Graphics::PixelFormat *format) {
 		GUI::MessageDialog dialog(_("Could not apply filtering setting."));
 		dialog.runModal();
 	}
+
+	return candidate;
+}
+
+void initGraphics(int width, int height, const Graphics::PixelFormat *format) {
+	Graphics::ModeWithFormatList modes;
+	modes.push_back(Graphics::ModeWithFormat(width, height, format));
+	initGraphicsAny(modes);
 }
 
 /**
diff --git a/engines/util.h b/engines/util.h
index d84ddbc4822..1310f451e57 100644
--- a/engines/util.h
+++ b/engines/util.h
@@ -79,5 +79,11 @@ void initGraphics(int width, int height, const Common::List<Graphics::PixelForma
  * @overload
  */
 void initGraphics3d(int width, int height);
+
+/**
+ * Inits any of the modes in "modes". "modes" is in the order of preference.
+ * Return value is index in modes of resulting mode.
+ */
+int initGraphicsAny(const Graphics::ModeWithFormatList &modes);
 /** @} */
 #endif
diff --git a/graphics/mode.h b/graphics/mode.h
index 9e16ddd216d..28cc12c5fe1 100644
--- a/graphics/mode.h
+++ b/graphics/mode.h
@@ -44,6 +44,28 @@ struct Mode {
 
 typedef Common::Array<Mode> ModeList;
 
+/**
+ * Represents a hardware video mode with pixel format.
+ */
+struct ModeWithFormat {
+	int16 width; ///< The width in pixels
+	int16 height; ///< The height in pixels
+	bool hasFormat; ///< Whether pixel format is valid
+	Graphics::PixelFormat format; ///< Pixel format
+
+	ModeWithFormat(const int16 w, const int16 h) :
+		width(w), height(h), hasFormat(false) {}
+	ModeWithFormat(const int16 w, const int16 h, const Graphics::PixelFormat &f) :
+		width(w), height(h), hasFormat(true), format(f) {}
+	ModeWithFormat(const int16 w, const int16 h, const Graphics::PixelFormat *f) :
+		width(w), height(h), hasFormat(f != nullptr) {
+		if (f != nullptr)
+			format = *f;
+	}
+};
+
+typedef Common::Array<ModeWithFormat> ModeWithFormatList;
+
 }
 
 #endif


Commit: 7dba71d671e352b3c7e0d971d3601c31decb3f1c
    https://github.com/scummvm/scummvm/commit/7dba71d671e352b3c7e0d971d3601c31decb3f1c
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
PLUMBERS: Remove need for highres

Downscale images to 320x240 if highres is not supported.

Changed paths:
  A engines/plumbers/3do.cpp
  A engines/plumbers/POTFILES
  A engines/plumbers/windows.cpp
    engines/plumbers/configure.engine
    engines/plumbers/metaengine.cpp
    engines/plumbers/module.mk
    engines/plumbers/plumbers.cpp
    engines/plumbers/plumbers.h


diff --git a/engines/plumbers/3do.cpp b/engines/plumbers/3do.cpp
new file mode 100644
index 00000000000..f83a406c5ff
--- /dev/null
+++ b/engines/plumbers/3do.cpp
@@ -0,0 +1,702 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "plumbers/plumbers.h"
+#include "plumbers/console.h"
+
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+#include "audio/audiostream.h"
+
+#include "common/debug.h"
+#include "common/debug-channels.h"
+#include "common/error.h"
+#include "common/events.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "common/timer.h"
+
+#include "engines/util.h"
+
+#include "graphics/cursorman.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/palette.h"
+#include "graphics/surface.h"
+
+#include "image/cel_3do.h"
+
+#include "video/3do_decoder.h"
+
+namespace Plumbers {
+// TODO(3do):
+// * effect when changing between scenes
+// * 3do boot logo
+// * return to previous scene
+// * hide cursor
+
+namespace {
+// TODO: discover correct offsets
+Common::Point getMikeStart(uint num, uint total) {
+	if (total == 2)
+		return Common::Point(140 * num + 10, 120 * num + 10);
+	return Common::Point(60 * num + 20, 70 * num + 20);
+}
+
+// TODO: discover correct offsets
+Common::Point getMikeSize(uint total) {
+	if (total == 2)
+		return Common::Point(80, 100);
+	return Common::Point(80, 60);
+}
+
+void makeMikeDecision(Scene &scene, uint num) {
+	scene._bitmapNum = 0;
+	scene._startBitmap = 0;
+	scene._decisionChoices = num;
+	scene._waveFilename = "";
+	scene._style = Scene::STYLE_DECISION_MIKE;
+
+	Common::Point sz = getMikeSize(num);
+
+	for (uint i = 0; i < num; i++) {
+		Common::Point ms = getMikeStart(i, num);
+		scene._choices[i]._region = Common::Rect(ms.x, ms.y, ms.x + 2 * sz.x, ms.y + sz.y);
+	}
+}
+
+void makeVideo(Scene &scene, const Common::String &videoName, const Common::String &nextScene) {
+	scene._bitmapNum = 0;
+	scene._startBitmap = 0;
+	scene._decisionChoices = 1;
+	scene._waveFilename = "";
+	scene._style = Scene::STYLE_VIDEO;
+	scene._sceneName = videoName;
+	scene._decisionBitmap = "";
+	scene._choices[0]._sceneName = nextScene;
+}
+
+const struct {
+	const char *from;
+	struct {
+		const char *scene;
+		int points;
+	} to[kMaxChoice];
+} tungraph[] = {
+	{
+		"dec13",
+		{
+			{"sc15", -10000},
+			{"sc17", -10000},
+			{"sc20", -50000}
+		}
+	},
+	{
+		"dec16",
+		{
+			{"dec13", -20000},
+			{"restart", 0}
+		}
+	},
+	{
+
+		"dec18",
+		{
+			{"dec13", -30000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec20",
+		{
+			{"sc21", -90000},
+			{"sc22", -90000}
+		}
+	},
+	{
+		"dec22",
+		{
+			{"dec20", -90000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec23",
+		{
+			{"sc24", 50000},
+			{"sc28", -50000}
+		}
+	},
+	{
+		"dec25",
+		{
+			{"sc26", -75000},
+			{"sc27", -90000}
+		}
+	},
+	{
+		"dec26",
+		{
+			{"dec25", -75000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec28",
+		{
+			{"dec23", -75000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec29",
+		{
+			{"sc30", -20000},
+			{"sc31", 90000}
+		}
+	},
+	{
+		"dec30",
+		{
+			{"sc32", 0},
+			{"restart", 0},
+			{"end", 0}
+		}
+	},
+	{
+		"dec31",
+		{
+			{"dec29", -20000},
+			{"end", 0}
+		}
+
+	}
+};
+
+static const Common::KeyCode cheatKbd[] = {
+	Common::KEYCODE_UP,
+	Common::KEYCODE_DOWN,
+	Common::KEYCODE_RIGHT,
+	Common::KEYCODE_LEFT,
+	Common::KEYCODE_DOWN,
+	Common::KEYCODE_RIGHT,
+	Common::KEYCODE_RETURN
+};
+
+static const Common::JoystickButton cheatJoy[] = {
+	Common::JOYSTICK_BUTTON_DPAD_UP,
+	Common::JOYSTICK_BUTTON_DPAD_DOWN,
+	Common::JOYSTICK_BUTTON_DPAD_RIGHT,
+	Common::JOYSTICK_BUTTON_DPAD_LEFT,
+	Common::JOYSTICK_BUTTON_DPAD_DOWN,
+	Common::JOYSTICK_BUTTON_DPAD_RIGHT,
+	Common::JOYSTICK_BUTTON_X
+};
+}  // end of anonymous namespace
+
+PlumbersGame3DO::PlumbersGame3DO(OSystem *syst, const ADGameDescription *gameDesc) :
+	PlumbersGame(syst, gameDesc), _ctrlHelpImage(nullptr), _cheatEnabled(false), _cheatFSM(0), _leftShoulderPressed(false),
+	_hiLite(-1), _mouseHiLite(-1), _kbdHiLite(-1) {
+}
+
+void PlumbersGame3DO::readTables() {
+	Common::File file;
+	if (!file.open("launchme"))
+		error("sReadTables(): Error reading launchme file");
+
+	initTables();
+
+	file.seek(0x1ec08);
+
+	Common::HashMap<Common::String, int> imgCounter, firstImg;
+
+	uint bitmapCtr = 0;
+
+	for (; bitmapCtr < 287; bitmapCtr++) {
+		char buf[16];
+		file.read(buf, 16);
+		_bitmaps[bitmapCtr]._filename = Common::String(buf);
+		_bitmaps[bitmapCtr]._duration = (file.readSint32BE() * 1000) / 60;
+		Common::String scene = Common::String(buf).substr(0, 4);
+		scene.toLowercase();
+		imgCounter[scene]++;
+		if (!firstImg.contains(scene))
+			firstImg[scene] = bitmapCtr;
+	}
+
+	file.seek(0x205d0);
+
+	for (; bitmapCtr < 704; bitmapCtr++) {
+		char buf[16];
+		file.read(buf, 16);
+		_bitmaps[bitmapCtr]._filename = Common::String(buf);
+		_bitmaps[bitmapCtr]._duration = (file.readSint32BE() * 1000) / 60;
+		Common::String scene = Common::String(buf).substr(0, 4);
+		scene.toLowercase();
+		imgCounter[scene]++;
+		if (!firstImg.contains(scene))
+			firstImg[scene] = bitmapCtr;
+	}
+
+	uint scPtr = 0;
+	makeVideo(_scenes[scPtr++], "kirinweaver", "janp1weaver");
+	makeVideo(_scenes[scPtr++], "janp1weaver", "janp2weaver");
+	makeVideo(_scenes[scPtr++], "janp2weaver", "janp3weaver");
+	makeVideo(_scenes[scPtr++], "janp3weaver", "titleweaver");
+	makeVideo(_scenes[scPtr++], "titleweaver", "miketest/sc00");
+
+	makeMikeDecision(_scenes[scPtr], 2);
+	_scenes[scPtr]._sceneName = "miketest/sc00";
+	_scenes[scPtr]._decisionBitmap = "DEC00";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc01";
+	_scenes[scPtr++]._choices[1]._sceneName = "miketest/sc07a";
+
+	for (uint scNo = 1; scNo <= 13; scNo++, scPtr++) {
+		Common::String imgScene = scNo == 5 ?
+			"sc44" : Common::String::format("sc%02d", scNo);
+		_scenes[scPtr]._bitmapNum = imgCounter[imgScene];
+		_scenes[scPtr]._startBitmap = firstImg[imgScene];
+		_scenes[scPtr]._sceneName = scNo == 5 ? "miketest/sc04a" : Common::String::format("miketest/sc%02d", scNo);
+		_scenes[scPtr]._waveFilename = Common::String::format("DIA%02d.aiff", scNo == 5 ? 4 : scNo);
+		_scenes[scPtr]._style = Scene::STYLE_PC;
+		_scenes[scPtr]._decisionChoices = 1;
+		switch(scNo) {
+		case 4:
+		case 5:
+			_scenes[scPtr]._choices[0]._sceneName = "miketest/sc06";
+			break;
+		case 11:
+			_scenes[scPtr]._choices[0]._sceneName = "miketest/sc13";
+			break;
+		case 13:
+			_scenes[scPtr]._choices[0]._sceneName = "tuntest/dec/dec13";
+			break;
+		case 7:
+		case 8:
+		case 12:
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("miketest/sc%02da", scNo);
+			break;
+		default:
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("miketest/sc%02d", scNo + 1);
+			break;
+		}
+	}
+
+	makeMikeDecision(_scenes[scPtr], 3);
+	_scenes[scPtr]._sceneName = "miketest/sc07a";
+	_scenes[scPtr]._decisionBitmap = "DEC07";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc08";
+	_scenes[scPtr]._choices[0]._points = -10000;
+	_scenes[scPtr]._choices[1]._sceneName = "miketest/sc11";
+	_scenes[scPtr]._choices[1]._points = 10000;
+	_scenes[scPtr]._choices[2]._sceneName = "miketest/sc12";
+	_scenes[scPtr]._choices[2]._points = -20000;
+	scPtr++;
+
+	makeMikeDecision(_scenes[scPtr], 2);
+	_scenes[scPtr]._sceneName = "miketest/sc08a";
+	_scenes[scPtr]._decisionBitmap = "DEC08";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc09";
+	_scenes[scPtr]._choices[0]._points = 0;
+	_scenes[scPtr]._choices[1]._sceneName = "miketest/sc09";
+	_scenes[scPtr]._choices[1]._points = 10000;
+	scPtr++;
+
+	makeMikeDecision(_scenes[scPtr], 2);
+	_scenes[scPtr]._sceneName = "miketest/sc12a";
+	_scenes[scPtr]._decisionBitmap = "DEC12";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc07a";
+	_scenes[scPtr]._choices[0]._points = 0;
+	_scenes[scPtr]._choices[1]._sceneName = "restart";
+	_scenes[scPtr]._choices[1]._points = 0;
+	scPtr++;
+
+	for (uint scNo = 15; scNo <= 32; scNo++) {
+		// there is no sc19
+		if (scNo == 19)
+			continue;
+		Common::String imgScene = Common::String::format("sc%02d", scNo);
+		_scenes[scPtr]._bitmapNum = imgCounter[imgScene];
+		_scenes[scPtr]._startBitmap = firstImg[imgScene];
+		_scenes[scPtr]._sceneName = Common::String::format("tuntest/sc%02d", scNo);
+		_scenes[scPtr]._waveFilename = Common::String::format("sc%02d.aiff", scNo);
+		_scenes[scPtr]._style = Scene::STYLE_PC;
+		_scenes[scPtr]._decisionChoices = 1;
+		if (scNo == 32)
+			_scenes[scPtr]._choices[0]._sceneName = "end";
+		else if (scNo == 16 || scNo == 18 || scNo == 20 || scNo == 22 || scNo == 23 || scNo == 25
+		    || scNo == 26 || scNo == 28 || scNo == 29 || scNo == 30 || scNo == 31)
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("tuntest/dec/dec%02d", scNo);
+		else
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("tuntest/sc%02d", scNo + 1);
+		scPtr++;
+	}
+
+	file.seek(0x20290);
+
+	for (int i = 0; i < 26; i++) {
+		char buf[16];
+		file.read(buf, 16);
+		uint32 x = file.readUint32BE();
+		uint32 y = file.readUint32BE();
+		uint32 w = file.readUint32BE();
+		uint32 h = file.readUint32BE();
+		Common::String shortName = Common::String(buf, 5);
+		Common::String sceneName = "tuntest/dec/" + shortName;
+		if (i == 0 || _scenes[scPtr - 1]._sceneName != sceneName) {
+			_scenes[scPtr]._bitmapNum = 0;
+			_scenes[scPtr]._startBitmap = 0;
+			_scenes[scPtr]._decisionChoices = 0;
+			_scenes[scPtr]._waveFilename = "";
+			_scenes[scPtr]._style = Scene::STYLE_DECISION_TUN;
+			_scenes[scPtr]._sceneName = sceneName;
+			_scenes[scPtr]._decisionBitmap = shortName;
+			scPtr++;
+		}
+
+		Scene &scene = _scenes[scPtr - 1];
+		assert(scene._decisionChoices < kMaxChoice);
+		scene._choices[scene._decisionChoices]._region = Common::Rect(x, y, x + w, y + h);
+		for (uint j = 0 ; j < ARRAYSIZE(tungraph); j++) {
+			if (shortName == tungraph[j].from) {
+				Common::String target = tungraph[j].to[scene._decisionChoices].scene;
+				if (target[0] == 's')
+					scene._choices[scene._decisionChoices]._sceneName = "tuntest/" + target;
+				else
+					scene._choices[scene._decisionChoices]._sceneName = "tuntest/dec/" + target;
+				scene._choices[scene._decisionChoices]._points = tungraph[j].to[scene._decisionChoices].points;
+				break;
+			}
+		}
+		scene._decisionChoices++;
+	}
+
+	_totScene = scPtr;
+}
+
+void PlumbersGame3DO::loadMikeDecision(const Common::String &dirname, const Common::String &baseFilename, uint num) {
+	Common::String baseName = dirname + "/" + baseFilename;
+	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, baseName.c_str());
+	Graphics::Surface *surf = new Graphics::Surface();
+	surf->create(_screenW, _screenH, Graphics::PixelFormat(2, 5, 5, 5, 1, 10,  5,  0, 15));
+
+	delete _compositeSurface;
+	_compositeSurface = nullptr;
+
+	for (uint i = 0; i < num; i++) {
+		Common::Point p = getMikeStart(i, num);
+		Common::Point sz = getMikeSize(num);
+		Common::File fileP;
+		Common::String nameP = Common::String::format("%s%dP.CEL", baseName.c_str(), i + 1);
+		if (!fileP.open(nameP))
+			error("unable to load image %s", nameP.c_str());
+
+		_image->loadStream(fileP);
+		surf->copyRectToSurface(*_image->getSurface(), p.x, p.y,
+					Common::Rect(0, 0, sz.x, sz.y));
+
+		Common::File fileW;
+		Common::String nameW = Common::String::format("%s%dW.CEL", baseName.c_str(), i + 1);
+		if (!fileW.open(nameW))
+			error("unable to load image %s", nameW.c_str());
+
+		_image->loadStream(fileW);
+		surf->copyRectToSurface(*_image->getSurface(), p.x + sz.x, p.y,
+					Common::Rect(0, 0, sz.x, sz.y));
+	}
+
+	_compositeSurface = surf;
+
+	Common::File fileCtrl;
+	if (fileCtrl.open(dirname + "/CONTROLHELP.CEL"))
+		_ctrlHelpImage->loadStream(fileCtrl);
+}
+
+void PlumbersGame3DO::postSceneBitmaps() {
+	if (_scenes[_curSceneIdx]._style == Scene::STYLE_VIDEO) {
+		_videoDecoder = new Video::ThreeDOMovieDecoder();
+		_curChoice = 0;
+		if (!_videoDecoder->loadFile(_scenes[_curSceneIdx]._sceneName)) {
+			_actions.push(ChangeScene);
+			return;
+		}
+		_videoDecoder->start();
+		return;
+	}
+
+	if (_scenes[_curSceneIdx]._decisionChoices == 1) {
+		_curChoice = 0;
+		_actions.push(ChangeScene);
+		return;
+	}
+	
+	_showScoreFl = true;
+	_leftButtonDownFl = true;
+	_setDurationFl = false;
+	if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_MIKE) {
+		loadMikeDecision(_scenes[_curSceneIdx]._sceneName, _scenes[_curSceneIdx]._decisionBitmap,
+				 _scenes[_curSceneIdx]._decisionChoices);
+		_hiLite = 0;
+		_kbdHiLite = 0;
+		updateHiLite();
+	} else if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_TUN) {
+		loadImage(_scenes[_curSceneIdx]._sceneName + ".cel");
+		_hiLite = 0;
+		_kbdHiLite = 0;
+		updateHiLite();
+		Common::File fileCtrl;
+		if (fileCtrl.open("tuntest/dec/controlhelp.cel"))
+			_ctrlHelpImage->loadStream(fileCtrl);
+	} else {
+		loadImage(_scenes[_curSceneIdx]._sceneName + "/" + _scenes[_curSceneIdx]._decisionBitmap);
+		_hiLite = -1;
+		_kbdHiLite = -1;
+	}
+
+	_mouseHiLite = getMouseHiLite();
+}
+
+void PlumbersGame3DO::startGraphics() {
+	_image = new Image::Cel3DODecoder();
+	_ctrlHelpImage = new Image::Cel3DODecoder();
+	_screenW = 320;
+	_screenH = 240;
+	Graphics::PixelFormat pf(2, 5, 5, 5, 1, 10,  5,  0, 15);
+	initGraphics(_screenW, _screenH, &pf);
+}
+
+void PlumbersGame3DO::blitImage(Graphics::Surface *screen) {
+	const Graphics::Surface *surface;
+	bool ctrlHelp = false;
+	if (_leftShoulderPressed && _leftButtonDownFl && _ctrlHelpImage) {
+		surface = _ctrlHelpImage->getSurface();
+		ctrlHelp = true;
+	} else if (_videoDecoder)
+		surface = _videoDecoder->decodeNextFrame();
+	else if (_compositeSurface)
+		surface = _compositeSurface;
+	else
+		surface = _image->getSurface();
+
+	Graphics::Surface modSurf;
+	bool modded = false;
+
+	if (_hiLite >= 0 && _leftButtonDownFl && !ctrlHelp) {
+		Graphics::PixelFormat pf(2, 5, 5, 5, 1, 10,  5,  0, 15);
+		modSurf.create(surface->w, surface->h, pf);
+		modSurf.copyRectToSurface(*surface, 0, 0, Common::Rect(0, 0, surface->w, surface->h));
+		const Common::Rect rec = _scenes[_curSceneIdx]._choices[_hiLite]._region;
+
+		for (int y = rec.top; y <= rec.bottom; y++) {
+			uint16 *p = (uint16 *) modSurf.getPixels() + modSurf.w * y + rec.left;
+			for (int x = rec.left; x < rec.right; x++, p++) {
+				uint r, g, b;
+				r = (*p >> 10) & 0x1f;
+				g = (*p >> 5) & 0x1f;
+				b = (*p >> 0) & 0x1f;
+				// TODO: figure out the correct multipliers
+				r = MIN<int>(3 * r / 2, 0x1f);
+				g = MIN<int>(3 * g / 2, 0x1f);
+				b = MIN<int>(3 * b / 2, 0x1f);
+				*p = (*p & 0x8000) | (r << 10) | (g << 5) | (b);
+			}
+		}
+		modded = true;
+	}
+
+	blitImageSurface(screen, modded ? &modSurf : surface);
+}
+
+void PlumbersGame3DO::skipVideo() {
+	if (_scenes[_curSceneIdx]._sceneName == "janp1weaver"
+	    || _scenes[_curSceneIdx]._sceneName == "janp2weaver") {
+		// Skip janp2weaver and janp3weaver
+		_curSceneIdx = getSceneNumb("titleweaver");
+		_actions.push(ShowScene);
+	} else {
+		_actions.push(ChangeScene);
+	}
+	_videoDecoder->close();
+	delete _videoDecoder;
+	_videoDecoder = nullptr;
+}
+
+void PlumbersGame3DO::joyUp() {
+	int decNum = _scenes[_curSceneIdx]._decisionChoices;
+	if (!_leftButtonDownFl)
+		return;
+	_kbdHiLite = _kbdHiLite < 0 ? 0 : (_kbdHiLite + decNum - 1) % decNum;
+	_hiLite = _kbdHiLite;
+	updateHiLite();
+}
+
+void PlumbersGame3DO::joyDown() {
+	if (!_leftButtonDownFl)
+		return;
+	int decNum = _scenes[_curSceneIdx]._decisionChoices;
+	_kbdHiLite = _kbdHiLite < 0 ? 0 : (_kbdHiLite + 1) % decNum;
+	_hiLite = _kbdHiLite;
+	updateHiLite();
+}
+
+void PlumbersGame3DO::joyA() {
+	if (_kbdHiLite < 0 || !_leftButtonDownFl)
+		return;
+	debugC(5, kDebugGeneral, "Accepting enter press with choice = %d", _kbdHiLite);
+	_curChoice = _kbdHiLite;
+	_totScore += _scenes[_curSceneIdx]._choices[_kbdHiLite]._points;
+	_actions.push(ChangeScene);
+	_leftButtonDownFl = false;
+}
+
+void PlumbersGame3DO::handleEvent(const Common::Event &event) {
+	switch (event.type) {
+	case Common::EVENT_JOYBUTTON_DOWN:
+		if (_videoDecoder) {
+			if (_cheatFSM < ARRAYSIZE(cheatJoy) && event.joystick.button == cheatJoy[_cheatFSM]) {
+				_cheatFSM++;
+				if (_cheatFSM == ARRAYSIZE(cheatJoy)) {
+					debugC(1, kDebugGeneral, "Cheat enabled");
+					_cheatEnabled = true;
+				}
+			} else if (event.joystick.button == cheatJoy[0])
+				_cheatFSM = 1;
+			else
+				_cheatFSM = 0;
+		}
+		if (_videoDecoder && (event.joystick.button == Common::JOYSTICK_BUTTON_A ||
+				      event.joystick.button == Common::JOYSTICK_BUTTON_B ||
+				      event.joystick.button == Common::JOYSTICK_BUTTON_X)) {
+			skipVideo();
+			return;
+		}
+		if (event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_UP ||
+		    event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_LEFT) {
+			joyUp();
+			return;
+		}
+		if (event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_DOWN ||
+		    event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_RIGHT) {
+			joyDown();
+			return;
+		}
+		if (event.joystick.button == Common::JOYSTICK_BUTTON_A) {
+			joyA();
+			return;
+		}
+		if (event.joystick.button == Common::JOYSTICK_BUTTON_LEFT_SHOULDER) {
+			_leftShoulderPressed = true;
+			if (_leftButtonDownFl && _ctrlHelpImage)
+				_actions.push(Redraw);
+			return;
+		}
+		break;
+	case Common::EVENT_JOYBUTTON_UP:
+		if (event.joystick.button == Common::JOYSTICK_BUTTON_LEFT_SHOULDER) {
+			_leftShoulderPressed = false;
+			if (_leftButtonDownFl && _ctrlHelpImage)
+				_actions.push(Redraw);
+			return;
+		}
+		break;
+	case Common::EVENT_KEYDOWN:
+		if (_videoDecoder) {
+			if (_cheatFSM < ARRAYSIZE(cheatKbd) && event.kbd.keycode == cheatKbd[_cheatFSM]) {
+				_cheatFSM++;
+				if (_cheatFSM == ARRAYSIZE(cheatKbd)) {
+					debugC(1, kDebugGeneral, "Cheat enabled");
+					_cheatEnabled = true;
+				}
+			} else if (event.kbd.keycode == cheatKbd[0])
+				_cheatFSM = 1;
+			else
+				_cheatFSM = 0;
+		}
+		if (event.kbd.keycode == Common::KEYCODE_SPACE && _videoDecoder) {
+			skipVideo();
+			return;
+		}
+		if ((event.kbd.keycode == Common::KEYCODE_UP ||
+		     event.kbd.keycode == Common::KEYCODE_LEFT)) {
+			joyUp();
+			return;
+		}
+		if ((event.kbd.keycode == Common::KEYCODE_DOWN ||
+		     event.kbd.keycode == Common::KEYCODE_RIGHT)) {
+			joyDown();
+			return;
+		}
+		if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+			joyA();
+			return;
+		}
+		if (event.kbd.keycode == Common::KEYCODE_q) {
+			_leftShoulderPressed = true;
+			if (_leftButtonDownFl && _ctrlHelpImage)
+				_actions.push(Redraw);
+			return;
+		}
+		break;
+	case Common::EVENT_KEYUP:
+		if (event.kbd.keycode == Common::KEYCODE_q) {
+			_leftShoulderPressed = false;
+			if (_leftButtonDownFl && _ctrlHelpImage)
+				_actions.push(Redraw);
+		}
+		break;
+	default:
+		break;
+	}
+	PlumbersGame::handleEvent(event);
+}
+
+int PlumbersGame3DO::getSceneNumb(const Common::String &sName) {
+	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, sName.c_str());
+	if (sName == "miketest/sc04" && _cheatEnabled)
+	        return PlumbersGame::getSceneNumb("miketest/sc04a");
+
+	return PlumbersGame::getSceneNumb(sName);
+}
+
+void PlumbersGame3DO::updateHiLite() {
+	_actions.push(Redraw);
+	if (_hiLite < 0)
+		return;
+	if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_MIKE) {
+		playSound(Common::String::format("%s/%s%dS.Aiff",
+						 _scenes[_curSceneIdx]._sceneName.c_str(),
+						 _scenes[_curSceneIdx]._decisionBitmap.c_str(), _hiLite + 1));
+	} else if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_TUN) {
+		playSound(Common::String::format("%s%c.aiff", _scenes[_curSceneIdx]._sceneName.c_str(), _hiLite + 'a'));
+	}
+}
+
+void PlumbersGame3DO::preActions() {
+	if (_leftButtonDownFl) {
+		int nh = getMouseHiLite();
+		if (nh != _mouseHiLite) {
+			_mouseHiLite = nh;
+			_hiLite = _mouseHiLite;
+			updateHiLite();
+		}
+	}
+}
+}
diff --git a/engines/plumbers/POTFILES b/engines/plumbers/POTFILES
new file mode 100644
index 00000000000..d2f6832465b
--- /dev/null
+++ b/engines/plumbers/POTFILES
@@ -0,0 +1 @@
+engines/plumbers/metaengine.cpp
diff --git a/engines/plumbers/configure.engine b/engines/plumbers/configure.engine
index 7df809b57ad..4a309abcfb4 100644
--- a/engines/plumbers/configure.engine
+++ b/engines/plumbers/configure.engine
@@ -1,3 +1,3 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine plumbers "Plumbers Don't Wear Ties" yes "" "" "highres 16bit"
+add_engine plumbers "Plumbers Don't Wear Ties" yes
diff --git a/engines/plumbers/metaengine.cpp b/engines/plumbers/metaengine.cpp
index 7357952b5d5..ead531071e1 100644
--- a/engines/plumbers/metaengine.cpp
+++ b/engines/plumbers/metaengine.cpp
@@ -21,7 +21,10 @@
 
 #include "base/plugins.h"
 
+#include "common/translation.h"
+
 #include "engines/advancedDetector.h"
+#include "gui/message.h"
 
 #include "plumbers/plumbers.h"
 
@@ -40,7 +43,17 @@ class PlumbersMetaEngine : public AdvancedMetaEngine {
 };
 
 Common::Error PlumbersMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
-	*engine = new Plumbers::PlumbersGame(syst, desc);
+	if (desc->platform == Common::kPlatform3DO) {
+#ifdef USE_RGB_COLOR
+		*engine = new Plumbers::PlumbersGame3DO(syst, desc);
+#else
+		 // I18N: Plumbers is the title of the game. 3DO is the name of platform
+		GUI::MessageDialog dialog(_("3DO Plumbers requires RGB support."));
+		dialog.runModal();
+		return Common::kUnsupportedColorMode;
+#endif
+	} else
+		*engine = new Plumbers::PlumbersGameWindows(syst, desc);
 	return Common::kNoError;
 }
 
diff --git a/engines/plumbers/module.mk b/engines/plumbers/module.mk
index 63fde172d33..11111cad180 100644
--- a/engines/plumbers/module.mk
+++ b/engines/plumbers/module.mk
@@ -3,7 +3,12 @@ MODULE := engines/plumbers
 MODULE_OBJS = \
 	plumbers.o \
 	console.o \
-	metaengine.o
+	metaengine.o \
+	windows.o
+
+ifdef USE_RGB_COLOR
+MODULE_OBJS += 3do.o
+endif
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_PLUMBERS), DYNAMIC_PLUGIN)
diff --git a/engines/plumbers/plumbers.cpp b/engines/plumbers/plumbers.cpp
index 998619752b5..cfa44385bd7 100644
--- a/engines/plumbers/plumbers.cpp
+++ b/engines/plumbers/plumbers.cpp
@@ -40,38 +40,14 @@
 #include "graphics/font.h"
 #include "graphics/fontman.h"
 #include "graphics/palette.h"
+#include "graphics/scaler/downscaler.h"
 #include "graphics/surface.h"
 
-#include "image/bmp.h"
-#include "image/cel_3do.h"
-
-#include "video/3do_decoder.h"
-
 namespace Plumbers {
 
-static const Common::KeyCode cheatKbd[] = {
-	Common::KEYCODE_UP,
-	Common::KEYCODE_DOWN,
-	Common::KEYCODE_RIGHT,
-	Common::KEYCODE_LEFT,
-	Common::KEYCODE_DOWN,
-	Common::KEYCODE_RIGHT,
-	Common::KEYCODE_RETURN
-};
-
-static const Common::JoystickButton cheatJoy[] = {
-	Common::JOYSTICK_BUTTON_DPAD_UP,
-	Common::JOYSTICK_BUTTON_DPAD_DOWN,
-	Common::JOYSTICK_BUTTON_DPAD_RIGHT,
-	Common::JOYSTICK_BUTTON_DPAD_LEFT,
-	Common::JOYSTICK_BUTTON_DPAD_DOWN,
-	Common::JOYSTICK_BUTTON_DPAD_RIGHT,
-	Common::JOYSTICK_BUTTON_X
-};
-
 PlumbersGame::PlumbersGame(OSystem *syst, const ADGameDescription *gameDesc) :
 		Engine(syst), _gameDescription(gameDesc), _console(nullptr), _image(nullptr),
-		_compositeSurface(nullptr), _videoDecoder(nullptr), _ctrlHelpImage(nullptr) {
+		_compositeSurface(nullptr), _videoDecoder(nullptr), _quit(false) {
 	_timerInstalled = false;
 	_showScoreFl = false;
 	_setDurationFl = false;
@@ -83,8 +59,6 @@ PlumbersGame::PlumbersGame(OSystem *syst, const ADGameDescription *gameDesc) :
 	_curChoice = 0;
 	_totScene = -1;
 	_totScore = 0;
-	_cheatEnabled = false;
-	_cheatFSM = 0;
 }
 
 PlumbersGame::~PlumbersGame() {
@@ -117,82 +91,35 @@ static const byte cursorPalette[] = {
 	0xff, 0xff, 0xff   // White
 };
 
-// TODO(3do):
-// * effect when changing between scenes
-// * 3do boot logo
-// * return to previous scene
-// * hide cursor
+void PlumbersGame::handleEvent(const Common::Event &event) {
+	switch (event.type) {
+	case Common::EVENT_QUIT:
+	case Common::EVENT_RETURN_TO_LAUNCHER:
+		_quit = true;
+		break;
 
-void PlumbersGame::updateHiLite() {
-	_actions.push(Redraw);
-	if (_hiLite < 0)
-		return;
-	if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_MIKE) {
-		playSound(Common::String::format("%s/%s%dS.Aiff",
-						 _scenes[_curSceneIdx]._sceneName.c_str(),
-						 _scenes[_curSceneIdx]._decisionBitmap.c_str(), _hiLite + 1));
-	} else if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_TUN) {
-		playSound(Common::String::format("%s%c.aiff", _scenes[_curSceneIdx]._sceneName.c_str(), _hiLite + 'a'));
-	}
-}
-
-void PlumbersGame::joyUp() {
-	int decNum = _scenes[_curSceneIdx]._decisionChoices;
-	if (!_leftButtonDownFl || !_hiLiteEnabled)
-		return;
-	_kbdHiLite = _kbdHiLite < 0 ? 0 : (_kbdHiLite + decNum - 1) % decNum;
-	_hiLite = _kbdHiLite;
-	updateHiLite();
-}
-
-void PlumbersGame::joyDown() {
-	if (!_leftButtonDownFl || !_hiLiteEnabled)
-		return;
-	int decNum = _scenes[_curSceneIdx]._decisionChoices;
-	_kbdHiLite = _kbdHiLite < 0 ? 0 : (_kbdHiLite + 1) % decNum;
-	_hiLite = _kbdHiLite;
-	updateHiLite();
-}
-
-void PlumbersGame::joyA() {
-	if (_kbdHiLite < 0 || !_leftButtonDownFl || !_hiLiteEnabled)
-		return;
-	debugC(5, kDebugGeneral, "Accepting enter press with choice = %d", _kbdHiLite);
-	_curChoice = _kbdHiLite;
-	_totScore += _scenes[_curSceneIdx]._choices[_kbdHiLite]._points;
-	_actions.push(ChangeScene);
-	_leftButtonDownFl = false;
-}
+	case Common::EVENT_LBUTTONDOWN:
+		if (_leftButtonDownFl) {
+			_curChoice = getMouseHiLite();
 
-void PlumbersGame::skipVideo() {
-	if (_scenes[_curSceneIdx]._sceneName == "janp1weaver"
-	    || _scenes[_curSceneIdx]._sceneName == "janp2weaver") {
-		// Skip janp2weaver and janp3weaver
-		_curSceneIdx = getSceneNumb("titleweaver");
-		_actions.push(ShowScene);
-	} else {
-		_actions.push(ChangeScene);
+			if (_curChoice >= 0 && _curChoice < _scenes[_curSceneIdx]._decisionChoices) {
+				debugC(5, kDebugGeneral, "Accepting mouse click with choice = %d", _curChoice);
+				_totScore += _scenes[_curSceneIdx]._choices[_curChoice]._points;
+				_actions.push(ChangeScene);
+				_leftButtonDownFl = false;
+			}
+		} else if (_console->_allowSkip && _timerInstalled) {
+			// Allows to skip speech by skipping wait delay
+			onTimer(this);
+		}
+		break;
+	default:
+		break;
 	}
-	_videoDecoder->close();
-	delete _videoDecoder;
-	_videoDecoder = nullptr;
 }
 
-
 Common::Error PlumbersGame::run() {
-	if (getPlatform() == Common::kPlatform3DO) {
-		_image = new Image::Cel3DODecoder();
-		_ctrlHelpImage = new Image::Cel3DODecoder();
-		_screenW = 320;
-		_screenH = 240;
-		Graphics::PixelFormat pf(2, 5, 5, 5, 1, 10,  5,  0, 15);
-		initGraphics(_screenW, _screenH, &pf);
-	} else {
-		_image = new Image::BitmapDecoder();
-		_screenW = 640;
-		_screenH = 480;
-		initGraphics(_screenW, _screenH);
-	}
+	startGraphics();
 
 	_console = new Console();
 	setDebugger(_console);
@@ -201,142 +128,26 @@ Common::Error PlumbersGame::run() {
 	CursorMan.replaceCursorPalette(cursorPalette, 0, 3);
 	CursorMan.showMouse(true);
 
-	if (getPlatform() == Common::kPlatform3DO)
-		readTables3DO("launchme");
-	else
-		readTablesPC("game.bin");
+	readTables();
 
 	_showScoreFl = false;
 	_leftButtonDownFl = false;
 	_endGameFl = false;
-	// PC uses a palette, so we don't do highlighting.
-	// Original does the same
-	_hiLiteEnabled = getPlatform() == Common::kPlatform3DO;
 	_totScore = 0;
 	_curSceneIdx = _prvSceneIdx = 0;
 	_curChoice = 0;
-	_kbdHiLite = -1;
-	_mouseHiLite = -1;
-	_hiLite = -1;
-	_mouseHiLite = -1;
-	_kbdHiLite = -1;
-	_leftShoulderPressed = false;
 	_actions.clear();
 	_actions.push(ShowScene);
 
-	bool quit = false;
+	_quit = false;
 
-	while (!quit && !_endGameFl) {
+	while (!_quit && !_endGameFl) {
 		Common::Event event;
 		while (g_system->getEventManager()->pollEvent(event)) {
-			switch (event.type) {
-			case Common::EVENT_QUIT:
-			case Common::EVENT_RETURN_TO_LAUNCHER:
-				quit = true;
-				break;
-
-			case Common::EVENT_LBUTTONDOWN:
-				if (_leftButtonDownFl) {
-					_curChoice = getMouseHiLite();
-
-					if (_curChoice >= 0 && _curChoice < _scenes[_curSceneIdx]._decisionChoices) {
-						debugC(5, kDebugGeneral, "Accepting mouse click with choice = %d", _curChoice);
-						_totScore += _scenes[_curSceneIdx]._choices[_curChoice]._points;
-						_actions.push(ChangeScene);
-						_leftButtonDownFl = false;
-					}
-				} else if (_console->_allowSkip && _timerInstalled) {
-					// Allows to skip speech by skipping wait delay
-					onTimer(this);
-				}
-				break;
-			case Common::EVENT_JOYBUTTON_DOWN:
-				if (_videoDecoder) {
-					if (_cheatFSM < ARRAYSIZE(cheatJoy) && event.joystick.button == cheatJoy[_cheatFSM]) {
-						_cheatFSM++;
-						if (_cheatFSM == ARRAYSIZE(cheatJoy)) {
-							debugC(1, kDebugGeneral, "Cheat enabled");
-							_cheatEnabled = true;
-						}
-					} else if (event.joystick.button == cheatJoy[0])
-						_cheatFSM = 1;
-					else
-						_cheatFSM = 0;
-				}
-				if (_videoDecoder && (event.joystick.button == Common::JOYSTICK_BUTTON_A ||
-						      event.joystick.button == Common::JOYSTICK_BUTTON_B ||
-						      event.joystick.button == Common::JOYSTICK_BUTTON_X)) {
-					skipVideo();
-				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_UP ||
-					   event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_LEFT) {
-					joyUp();
-				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_DOWN ||
-					   event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_RIGHT) {
-					joyDown();
-				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_A) {
-					joyA();
-				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_LEFT_SHOULDER) {
-					_leftShoulderPressed = true;
-					if (_leftButtonDownFl && _ctrlHelpImage)
-						_actions.push(Redraw);
-				}
-				break;
-			case Common::EVENT_JOYBUTTON_UP:
-				if (event.joystick.button == Common::JOYSTICK_BUTTON_LEFT_SHOULDER) {
-					_leftShoulderPressed = false;
-					if (_leftButtonDownFl && _ctrlHelpImage)
-						_actions.push(Redraw);
-				}
-				break;
-			case Common::EVENT_KEYDOWN:
-				if (_videoDecoder) {
-					if (_cheatFSM < ARRAYSIZE(cheatKbd) && event.kbd.keycode == cheatKbd[_cheatFSM]) {
-						_cheatFSM++;
-						if (_cheatFSM == ARRAYSIZE(cheatKbd)) {
-							debugC(1, kDebugGeneral, "Cheat enabled");
-							_cheatEnabled = true;
-						}
-					} else if (event.kbd.keycode == cheatKbd[0])
-						_cheatFSM = 1;
-					else
-						_cheatFSM = 0;
-				}
-				if (event.kbd.keycode == Common::KEYCODE_SPACE && _videoDecoder) {
-					skipVideo();
-				} else if ((event.kbd.keycode == Common::KEYCODE_UP ||
-					    event.kbd.keycode == Common::KEYCODE_LEFT)) {
-					joyUp();
-				} else if ((event.kbd.keycode == Common::KEYCODE_DOWN ||
-					    event.kbd.keycode == Common::KEYCODE_RIGHT)) {
-					joyDown();
-				} else if (event.kbd.keycode == Common::KEYCODE_RETURN) {
-					joyA();
-				} else if (event.kbd.keycode == Common::KEYCODE_q) {
-					_leftShoulderPressed = true;
-					if (_leftButtonDownFl && _ctrlHelpImage)
-						_actions.push(Redraw);
-				}
-				break;
-			case Common::EVENT_KEYUP:
-				if (event.kbd.keycode == Common::KEYCODE_q) {
-					_leftShoulderPressed = false;
-					if (_leftButtonDownFl && _ctrlHelpImage)
-						_actions.push(Redraw);
-				}
-				break;
-			default:
-				break;
-			}
+			handleEvent(event);
 		}
 
-		if (_leftButtonDownFl) {
-			int nh = getMouseHiLite();
-			if (nh != _mouseHiLite) {
-				_mouseHiLite = nh;
-				_hiLite = _mouseHiLite;
-				updateHiLite();
-			}
-		}
+		preActions();
 
 		while (!_actions.empty()) {
 			switch (_actions.pop()) {
@@ -392,56 +203,18 @@ void PlumbersGame::loadImage(const Common::String &name) {
 	_compositeSurface = nullptr;
 }
 
-// TODO: discover correct offsets
-Common::Point getMikeStart(uint num, uint total) {
-	if (total == 2)
-		return Common::Point(140 * num + 10, 120 * num + 10);
-	return Common::Point(60 * num + 20, 70 * num + 20);
-}
-
-// TODO: discover correct offsets
-Common::Point getMikeSize(uint total) {
-	if (total == 2)
-		return Common::Point(80, 100);
-	return Common::Point(80, 60);
-}
+void PlumbersGame::blitImageSurface(Graphics::Surface *screen, const Graphics::Surface *surface) {
+	int w = CLIP<int>(surface->w, 0, _screenW);
+	int h = CLIP<int>(surface->h, 0, _screenH);
 
-void PlumbersGame::loadMikeDecision(const Common::String &dirname, const Common::String &baseFilename, uint num) {
-	Common::String baseName = dirname + "/" + baseFilename;
-	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, baseName.c_str());
-	Graphics::Surface *surf = new Graphics::Surface();
-	surf->create(_screenW, _screenH, Graphics::PixelFormat(2, 5, 5, 5, 1, 10,  5,  0, 15));
+	int x = (_screenW - w) / 2;
+	int y = (_screenH - h) / 2;
 
-	delete _compositeSurface;
-	_compositeSurface = nullptr;
-
-	for (uint i = 0; i < num; i++) {
-		Common::Point p = getMikeStart(i, num);
-		Common::Point sz = getMikeSize(num);
-		Common::File fileP;
-		Common::String nameP = Common::String::format("%s%dP.CEL", baseName.c_str(), i + 1);
-		if (!fileP.open(nameP))
-			error("unable to load image %s", nameP.c_str());
-
-		_image->loadStream(fileP);
-		surf->copyRectToSurface(*_image->getSurface(), p.x, p.y,
-					Common::Rect(0, 0, sz.x, sz.y));
-
-		Common::File fileW;
-		Common::String nameW = Common::String::format("%s%dW.CEL", baseName.c_str(), i + 1);
-		if (!fileW.open(nameW))
-			error("unable to load image %s", nameW.c_str());
-
-		_image->loadStream(fileW);
-		surf->copyRectToSurface(*_image->getSurface(), p.x + sz.x, p.y,
-					Common::Rect(0, 0, sz.x, sz.y));
-	}
-
-	_compositeSurface = surf;
+	screen->copyRectToSurface(*surface, x, y, Common::Rect(0, 0, w, h));
+}
 
-	Common::File fileCtrl;
-	if (fileCtrl.open(dirname + "/CONTROLHELP.CEL"))
-		_ctrlHelpImage->loadStream(fileCtrl);
+void PlumbersGame::blitImage(Graphics::Surface *screen) {
+	blitImageSurface(screen, _compositeSurface ? _compositeSurface : _image->getSurface());
 }
 
 void PlumbersGame::drawScreen() {
@@ -457,51 +230,7 @@ void PlumbersGame::drawScreen() {
 		Graphics::Surface *screen = g_system->lockScreen();
 		screen->fillRect(Common::Rect(0, 0, g_system->getWidth(), g_system->getHeight()), 0);
 
-		const Graphics::Surface *surface;
-		bool ctrlHelp = false;
-		if (_leftShoulderPressed && _leftButtonDownFl && _ctrlHelpImage) {
-			surface = _ctrlHelpImage->getSurface();
-			ctrlHelp = true;
-		} else if (_videoDecoder)
-			surface = _videoDecoder->decodeNextFrame();
-		else if (_compositeSurface)
-			surface = _compositeSurface;
-		else
-			surface = _image->getSurface();
-
-		Graphics::Surface modSurf;
-		bool modded = false;
-
-		if (_hiLiteEnabled && _hiLite >= 0 && _leftButtonDownFl && !ctrlHelp) {
-			Graphics::PixelFormat pf(2, 5, 5, 5, 1, 10,  5,  0, 15);
-			modSurf.create(surface->w, surface->h, pf);
-			modSurf.copyRectToSurface(*surface, 0, 0, Common::Rect(0, 0, surface->w, surface->h));
-			const Common::Rect rec = _scenes[_curSceneIdx]._choices[_hiLite]._region;
-
-			for (int y = rec.top; y <= rec.bottom; y++) {
-				uint16 *p = (uint16 *) modSurf.getPixels() + modSurf.w * y + rec.left;
-				for (int x = rec.left; x < rec.right; x++, p++) {
-					uint r, g, b;
-					r = (*p >> 10) & 0x1f;
-					g = (*p >> 5) & 0x1f;
-					b = (*p >> 0) & 0x1f;
-					// TODO: figure out the correct multipliers
-					r = MIN<int>(3 * r / 2, 0x1f);
-					g = MIN<int>(3 * g / 2, 0x1f);
-					b = MIN<int>(3 * b / 2, 0x1f);
-					*p = (*p & 0x8000) | (r << 10) | (g << 5) | (b);
-				}
-			}
-			modded = true;
-		}
-
-		int w = CLIP<int>(surface->w, 0, _screenW);
-		int h = CLIP<int>(surface->h, 0, _screenH);
-
-		int x = (_screenW - w) / 2;
-		int y = (_screenH - h) / 2;
-
-		screen->copyRectToSurface(modded ? modSurf : *surface, x, y, Common::Rect(0, 0, w, h));
+		blitImage(screen);
 
 		if (_showScoreFl) {
 			Common::String score = Common::String::format("Your Score is: %ld", _totScore);
@@ -532,7 +261,7 @@ void PlumbersGame::playSound(const Common::String &name) {
 		error("unable to load sound %s", name.c_str());
 
 	Audio::AudioStream *stream;
-	if (name.hasSuffix(".aiff") || name.hasSuffix(".Aiff"))
+	if (name.hasSuffixIgnoreCase(".aiff"))
 		stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
 	else
 		stream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
@@ -569,43 +298,7 @@ void PlumbersGame::updateScene() {
 	debugC(2, kDebugGeneral, "%s : %d", __FUNCTION__, _curBitmapIdx);
 	_curBitmapIdx++;
 	if (_curBitmapIdx >= _scenes[_curSceneIdx]._startBitmap + _scenes[_curSceneIdx]._bitmapNum) {
-		if (_scenes[_curSceneIdx]._style == Scene::STYLE_VIDEO) {
-			_videoDecoder = new Video::ThreeDOMovieDecoder();
-			_curChoice = 0;
-			if (!_videoDecoder->loadFile(_scenes[_curSceneIdx]._sceneName)) {
-				_actions.push(ChangeScene);
-				return;
-			}
-			_videoDecoder->start();
-		} else if (_scenes[_curSceneIdx]._decisionChoices == 1) {
-			_curChoice = 0;
-			_actions.push(ChangeScene);
-		} else {
-			_showScoreFl = true;
-			_leftButtonDownFl = true;
-			_setDurationFl = false;
-			if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_MIKE) {
-				loadMikeDecision(_scenes[_curSceneIdx]._sceneName, _scenes[_curSceneIdx]._decisionBitmap,
-					_scenes[_curSceneIdx]._decisionChoices);
-				_hiLite = 0;
-				_kbdHiLite = 0;
-				updateHiLite();
-			} else if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_TUN) {
-				loadImage(_scenes[_curSceneIdx]._sceneName + ".cel");
-				_hiLite = 0;
-				_kbdHiLite = 0;
-				updateHiLite();
-				Common::File fileCtrl;
-				if (fileCtrl.open("tuntest/dec/controlhelp.cel"))
-					_ctrlHelpImage->loadStream(fileCtrl);
-			} else {
-				loadImage(_scenes[_curSceneIdx]._sceneName + "/" + _scenes[_curSceneIdx]._decisionBitmap);
-				_hiLite = -1;
-				_kbdHiLite = -1;
-			}
-
-			_mouseHiLite = getMouseHiLite();
-		}
+		postSceneBitmaps();
 	} else {
 		loadImage(_scenes[_curSceneIdx]._sceneName  + "/" + _bitmaps[_curBitmapIdx]._filename);
 		_setDurationFl = true;
@@ -678,353 +371,8 @@ void PlumbersGame::initTables() {
 	}
 }
 
-void PlumbersGame::readTablesPC(const Common::String &fileName) {
-	Common::File file;
-	if (!file.open(fileName))
-		error("sReadTables(): Error reading BIN file");
-
-	initTables();
-
-	_totScore = file.readSint32LE();
-	file.skip(10);
-	_totScene = file.readSint16LE();
-	file.skip(6);
-
-	char buf[kMaxName];
-	for (int i = 0; i < kMaxScene; i++) {
-		_scenes[i]._bitmapNum = file.readSint16LE();
-		_scenes[i]._startBitmap = file.readSint16LE();
-		_scenes[i]._decisionChoices = file.readSint16LE();
-		file.read(buf, kMaxName);
-		_scenes[i]._sceneName = Common::String(buf);
-		file.read(buf, kMaxName);
-		_scenes[i]._waveFilename = Common::String(buf);
-		file.read(buf, kMaxName);
-		_scenes[i]._decisionBitmap = Common::String(buf);
-		_scenes[i]._style = Scene::STYLE_PC;
-
-		for (int j = 0; j < kMaxChoice; j++) {
-			_scenes[i]._choices[j]._points = file.readSint32LE();
-			_scenes[i]._choices[j]._sceneName = Common::String::format("SC%02d", file.readSint16LE());
-			_scenes[i]._choices[j]._skipScene = file.readSint16LE();
-			int left = file.readSint16LE();
-			int top = file.readSint16LE();
-			int right = file.readSint16LE();
-			int bottom = file.readSint16LE();
-			_scenes[i]._choices[j]._region = Common::Rect(left, top, right, bottom);
-		}
-	}
-
-	for (int i = 0; i < kMaxBitmaps; i++) {
-		_bitmaps[i]._duration = file.readSint16LE() * 100;
-		file.read(buf, kMaxName);
-		_bitmaps[i]._filename = Common::String(buf);
-	}
-}
-
-static void makeMikeDecision(Scene &scene, uint num) {
-	scene._bitmapNum = 0;
-	scene._startBitmap = 0;
-	scene._decisionChoices = num;
-	scene._waveFilename = "";
-	scene._style = Scene::STYLE_DECISION_MIKE;
-
-	Common::Point sz = getMikeSize(num);
-
-	for (uint i = 0; i < num; i++) {
-		Common::Point ms = getMikeStart(i, num);
-		scene._choices[i]._region = Common::Rect(ms.x, ms.y, ms.x + 2 * sz.x, ms.y + sz.y);
-	}
-}
-
-static void makeVideo(Scene &scene, const Common::String &videoName, const Common::String &nextScene) {
-	scene._bitmapNum = 0;
-	scene._startBitmap = 0;
-	scene._decisionChoices = 1;
-	scene._waveFilename = "";
-	scene._style = Scene::STYLE_VIDEO;
-	scene._sceneName = videoName;
-	scene._decisionBitmap = "";
-	scene._choices[0]._sceneName = nextScene;
-}
-
-static const struct {
-	const char *from;
-	struct {
-		const char *scene;
-		int points;
-	} to[kMaxChoice];
-} tungraph[] = {
-	{
-		"dec13",
-		{
-			{"sc15", -10000},
-			{"sc17", -10000},
-			{"sc20", -50000}
-		}
-	},
-	{
-		"dec16",
-		{
-			{"dec13", -20000},
-			{"restart", 0}
-		}
-	},
-	{
-
-		"dec18",
-		{
-			{"dec13", -30000},
-			{"restart", 0}
-		}
-	},
-	{
-		"dec20",
-		{
-			{"sc21", -90000},
-			{"sc22", -90000}
-		}
-	},
-	{
-		"dec22",
-		{
-			{"dec20", -90000},
-			{"restart", 0}
-		}
-	},
-	{
-		"dec23",
-		{
-			{"sc24", 50000},
-			{"sc28", -50000}
-		}
-	},
-	{
-		"dec25",
-		{
-			{"sc26", -75000},
-			{"sc27", -90000}
-		}
-	},
-	{
-		"dec26",
-		{
-			{"dec25", -75000},
-			{"restart", 0}
-		}
-	},
-	{
-		"dec28",
-		{
-			{"dec23", -75000},
-			{"restart", 0}
-		}
-	},
-	{
-		"dec29",
-		{
-			{"sc30", -20000},
-			{"sc31", 90000}
-		}
-	},
-	{
-		"dec30",
-		{
-			{"sc32", 0},
-			{"restart", 0},
-			{"end", 0}
-		}
-	},
-	{
-		"dec31",
-		{
-			{"dec29", -20000},
-			{"end", 0}
-		}
-
-	}
-};
-
-void PlumbersGame::readTables3DO(const Common::String &fileName) {
-	Common::File file;
-	if (!file.open(fileName))
-		error("sReadTables(): Error reading launchme file");
-
-	initTables();
-
-	file.seek(0x1ec08);
-
-	Common::HashMap<Common::String, int> imgCounter, firstImg;
-
-	uint bitmapCtr = 0;
-
-	for (; bitmapCtr < 287; bitmapCtr++) {
-		char buf[16];
-		file.read(buf, 16);
-		_bitmaps[bitmapCtr]._filename = Common::String(buf);
-		_bitmaps[bitmapCtr]._duration = (file.readSint32BE() * 1000) / 60;
-		Common::String scene = Common::String(buf).substr(0, 4);
-		scene.toLowercase();
-		imgCounter[scene]++;
-		if (!firstImg.contains(scene))
-			firstImg[scene] = bitmapCtr;
-	}
-
-	file.seek(0x205d0);
-
-	for (; bitmapCtr < 704; bitmapCtr++) {
-		char buf[16];
-		file.read(buf, 16);
-		_bitmaps[bitmapCtr]._filename = Common::String(buf);
-		_bitmaps[bitmapCtr]._duration = (file.readSint32BE() * 1000) / 60;
-		Common::String scene = Common::String(buf).substr(0, 4);
-		scene.toLowercase();
-		imgCounter[scene]++;
-		if (!firstImg.contains(scene))
-			firstImg[scene] = bitmapCtr;
-	}
-
-	uint scPtr = 0;
-	makeVideo(_scenes[scPtr++], "kirinweaver", "janp1weaver");
-	makeVideo(_scenes[scPtr++], "janp1weaver", "janp2weaver");
-	makeVideo(_scenes[scPtr++], "janp2weaver", "janp3weaver");
-	makeVideo(_scenes[scPtr++], "janp3weaver", "titleweaver");
-	makeVideo(_scenes[scPtr++], "titleweaver", "miketest/sc00");
-
-	makeMikeDecision(_scenes[scPtr], 2);
-	_scenes[scPtr]._sceneName = "miketest/sc00";
-	_scenes[scPtr]._decisionBitmap = "DEC00";
-	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc01";
-	_scenes[scPtr++]._choices[1]._sceneName = "miketest/sc07a";
-
-	for (uint scNo = 1; scNo <= 13; scNo++, scPtr++) {
-		Common::String imgScene = scNo == 5 ?
-			"sc44" : Common::String::format("sc%02d", scNo);
-		_scenes[scPtr]._bitmapNum = imgCounter[imgScene];
-		_scenes[scPtr]._startBitmap = firstImg[imgScene];
-		_scenes[scPtr]._sceneName = scNo == 5 ? "miketest/sc04a" : Common::String::format("miketest/sc%02d", scNo);
-		_scenes[scPtr]._waveFilename = Common::String::format("DIA%02d.aiff", scNo == 5 ? 4 : scNo);
-		_scenes[scPtr]._style = Scene::STYLE_PC;
-		_scenes[scPtr]._decisionChoices = 1;
-		switch(scNo) {
-		case 4:
-		case 5:
-			_scenes[scPtr]._choices[0]._sceneName = "miketest/sc06";
-			break;
-		case 11:
-			_scenes[scPtr]._choices[0]._sceneName = "miketest/sc13";
-			break;
-		case 13:
-			_scenes[scPtr]._choices[0]._sceneName = "tuntest/dec/dec13";
-			break;
-		case 7:
-		case 8:
-		case 12:
-			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("miketest/sc%02da", scNo);
-			break;
-		default:
-			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("miketest/sc%02d", scNo + 1);
-			break;
-		}
-	}
-
-	makeMikeDecision(_scenes[scPtr], 3);
-	_scenes[scPtr]._sceneName = "miketest/sc07a";
-	_scenes[scPtr]._decisionBitmap = "DEC07";
-	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc08";
-	_scenes[scPtr]._choices[0]._points = -10000;
-	_scenes[scPtr]._choices[1]._sceneName = "miketest/sc11";
-	_scenes[scPtr]._choices[1]._points = 10000;
-	_scenes[scPtr]._choices[2]._sceneName = "miketest/sc12";
-	_scenes[scPtr]._choices[2]._points = -20000;
-	scPtr++;
-
-	makeMikeDecision(_scenes[scPtr], 2);
-	_scenes[scPtr]._sceneName = "miketest/sc08a";
-	_scenes[scPtr]._decisionBitmap = "DEC08";
-	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc09";
-	_scenes[scPtr]._choices[0]._points = 0;
-	_scenes[scPtr]._choices[1]._sceneName = "miketest/sc09";
-	_scenes[scPtr]._choices[1]._points = 10000;
-	scPtr++;
-
-	makeMikeDecision(_scenes[scPtr], 2);
-	_scenes[scPtr]._sceneName = "miketest/sc12a";
-	_scenes[scPtr]._decisionBitmap = "DEC12";
-	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc07a";
-	_scenes[scPtr]._choices[0]._points = 0;
-	_scenes[scPtr]._choices[1]._sceneName = "restart";
-	_scenes[scPtr]._choices[1]._points = 0;
-	scPtr++;
-
-	for (uint scNo = 15; scNo <= 32; scNo++) {
-		// there is no sc19
-		if (scNo == 19)
-			continue;
-		Common::String imgScene = Common::String::format("sc%02d", scNo);
-		_scenes[scPtr]._bitmapNum = imgCounter[imgScene];
-		_scenes[scPtr]._startBitmap = firstImg[imgScene];
-		_scenes[scPtr]._sceneName = Common::String::format("tuntest/sc%02d", scNo);
-		_scenes[scPtr]._waveFilename = Common::String::format("sc%02d.aiff", scNo);
-		_scenes[scPtr]._style = Scene::STYLE_PC;
-		_scenes[scPtr]._decisionChoices = 1;
-		if (scNo == 32)
-			_scenes[scPtr]._choices[0]._sceneName = "end";
-		else if (scNo == 16 || scNo == 18 || scNo == 20 || scNo == 22 || scNo == 23 || scNo == 25
-		    || scNo == 26 || scNo == 28 || scNo == 29 || scNo == 30 || scNo == 31)
-			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("tuntest/dec/dec%02d", scNo);
-		else
-			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("tuntest/sc%02d", scNo + 1);
-		scPtr++;
-	}
-
-	file.seek(0x20290);
-
-	for (int i = 0; i < 26; i++) {
-		char buf[16];
-		file.read(buf, 16);
-		uint32 x = file.readUint32BE();
-		uint32 y = file.readUint32BE();
-		uint32 w = file.readUint32BE();
-		uint32 h = file.readUint32BE();
-		Common::String shortName = Common::String(buf).substr(0, 5);
-		Common::String sceneName = "tuntest/dec/" + shortName;
-		if (i == 0 || _scenes[scPtr - 1]._sceneName != sceneName) {
-			_scenes[scPtr]._bitmapNum = 0;
-			_scenes[scPtr]._startBitmap = 0;
-			_scenes[scPtr]._decisionChoices = 0;
-			_scenes[scPtr]._waveFilename = "";
-			_scenes[scPtr]._style = Scene::STYLE_DECISION_TUN;
-			_scenes[scPtr]._sceneName = sceneName;
-			_scenes[scPtr]._decisionBitmap = shortName;
-			scPtr++;
-		}
-
-		Scene &scene = _scenes[scPtr - 1];
-		assert(scene._decisionChoices < kMaxChoice);
-		scene._choices[scene._decisionChoices]._region = Common::Rect(x, y, x + w, y + h);
-		for (uint j = 0 ; j < ARRAYSIZE(tungraph); j++) {
-			if (shortName == tungraph[j].from) {
-				Common::String target = tungraph[j].to[scene._decisionChoices].scene;
-				if (target[0] == 's')
-					scene._choices[scene._decisionChoices]._sceneName = "tuntest/" + target;
-				else
-					scene._choices[scene._decisionChoices]._sceneName = "tuntest/dec/" + target;
-				scene._choices[scene._decisionChoices]._points = tungraph[j].to[scene._decisionChoices].points;
-				break;
-			}
-		}
-		scene._decisionChoices++;
-	}
-
-	_totScene = scPtr;
-}
-
 int PlumbersGame::getSceneNumb(const Common::String &sName) {
 	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, sName.c_str());
-	if (sName == "miketest/sc04" && _cheatEnabled)
-	        return getSceneNumb("miketest/sc04a");
-
 	for (int sCurScene = 0; sCurScene < _totScene; sCurScene++) {
 		if (sName == _scenes[sCurScene]._sceneName)
 			return sCurScene;
diff --git a/engines/plumbers/plumbers.h b/engines/plumbers/plumbers.h
index 3aff5af0855..787390449ed 100644
--- a/engines/plumbers/plumbers.h
+++ b/engines/plumbers/plumbers.h
@@ -24,6 +24,7 @@
 
 #include "engines/engine.h"
 
+#include "common/events.h"
 #include "common/platform.h"
 #include "common/queue.h"
 #include "common/rect.h"
@@ -31,14 +32,11 @@
 
 #include "audio/mixer.h"
 
-#include "video/3do_decoder.h"
+#include "video/video_decoder.h"
+#include "image/image_decoder.h"
 
 struct ADGameDescription;
 
-namespace Image {
-class ImageDecoder;
-}
-
 namespace Graphics {
 struct Surface;
 }
@@ -88,10 +86,23 @@ public:
 	const char *getGameId() const;
 	Common::Platform getPlatform() const;
 
-private:
+protected:
+	virtual void readTables() = 0;
+	virtual void postSceneBitmaps() = 0;
+	virtual bool handlePlatformJoyButton(int button) { return false; }
+	virtual bool handlePlatformKeyDown(int button) { return false; }
+	virtual void loadImage(const Common::String &name);
+	virtual void startGraphics() = 0;
+	void blitImageSurface(Graphics::Surface *screen, const Graphics::Surface *surface);
+	virtual void blitImage(Graphics::Surface *screen);
+	virtual void handleEvent(const Common::Event &event);
+	virtual int getSceneNumb(const Common::String &sName);
+	virtual void preActions() {}
+
 	static const int kMaxName = 13 + 1;
 	static const int kMaxBitmaps = 2000;
 	static const int kMaxScene = 100;
+	void initTables();
 
 	struct {
 		int  _duration;
@@ -99,11 +110,12 @@ private:
 	} _bitmaps[kMaxBitmaps];
 
 	Scene _scenes[kMaxScene];
+	int	 _totScene;
+	long _totScore;
 
 	Image::ImageDecoder *_image;
-	Image::ImageDecoder *_ctrlHelpImage;
 	Console *_console;
-
+	Video::VideoDecoder *_videoDecoder;
 	bool _showScoreFl;
 	bool _setDurationFl;
 	bool _leftButtonDownFl;
@@ -112,16 +124,8 @@ private:
 	int	 _curSceneIdx, _prvSceneIdx;
 	int	 _curBitmapIdx;
 	int	 _curChoice;
-	int	 _totScene;
-	long _totScore;
 	int _screenW, _screenH;
-	int _kbdHiLite;
-	int _mouseHiLite;
-	int _hiLite;
-	bool _hiLiteEnabled;
-	bool _cheatEnabled;
-	int _cheatFSM;
-	bool _leftShoulderPressed;
+	bool _quit;
 
 	enum Action {
 		Redraw,
@@ -134,13 +138,9 @@ private:
 	Common::Queue<Action> _actions;
 	Graphics::Surface *_compositeSurface;
 
-	void loadImage(const Common::String &name);
-	void loadMikeDecision(const Common::String &dirname, const Common::String &baseFilename, uint num);
 	void drawScreen();
-	void updateHiLite();
 
 	Audio::SoundHandle _soundHandle;
-	Video::ThreeDOMovieDecoder *_videoDecoder;
 
 	void playSound(const Common::String &name);
 	void stopSound();
@@ -152,17 +152,53 @@ private:
 	void processTimer();
 	static void onTimer(void *arg);
 
-	void initTables();
-	void readTablesPC(const Common::String &fileName);
-		void readTables3DO(const Common::String &fileName);
-	int getSceneNumb(const Common::String &sName);
 	int getMouseHiLite();
+};
+
+class PlumbersGame3DO : public PlumbersGame {
+public:
+	PlumbersGame3DO(OSystem *syst, const ADGameDescription *gameDesc);
+
+protected:
+	void readTables() override;
+	void postSceneBitmaps() override;
+	void startGraphics() override;
+	void handleEvent(const Common::Event &event) override;
+	void blitImage(Graphics::Surface *screen) override;
+	int getSceneNumb(const Common::String &sName) override;
+	void preActions() override;
 
+private:
+	void skipVideo();
+	void loadMikeDecision(const Common::String &dirname, const Common::String &baseFilename, uint num);
 	void joyUp();
 	void joyDown();
 	void joyA();
-	void skipVideo();
+	void updateHiLite();
+
+	bool _cheatEnabled;
+	int _cheatFSM;
+	bool _leftShoulderPressed;
+	int _kbdHiLite;
+	int _mouseHiLite;
+	int _hiLite;
+	Image::ImageDecoder *_ctrlHelpImage;
 };
+
+class PlumbersGameWindows : public PlumbersGame {
+public:
+	PlumbersGameWindows(OSystem *syst, const ADGameDescription *gameDesc);
+
+protected:
+	void readTables() override;
+	void postSceneBitmaps() override;
+	void loadImage(const Common::String &name) override;
+	void startGraphics() override;
+
+private:
+	bool _halfSize;
+};
+
 } // End of namespace Plumbers
 
 #endif
diff --git a/engines/plumbers/windows.cpp b/engines/plumbers/windows.cpp
new file mode 100644
index 00000000000..2882fc6e957
--- /dev/null
+++ b/engines/plumbers/windows.cpp
@@ -0,0 +1,144 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "plumbers/plumbers.h"
+#include "plumbers/console.h"
+
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+#include "audio/audiostream.h"
+
+#include "common/debug.h"
+#include "common/debug-channels.h"
+#include "common/error.h"
+#include "common/events.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "common/timer.h"
+
+#include "engines/util.h"
+
+#include "graphics/cursorman.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/palette.h"
+#include "graphics/surface.h"
+#include "graphics/scaler/downscaler.h"
+
+#include "image/bmp.h"
+
+namespace Plumbers {
+PlumbersGameWindows::PlumbersGameWindows(OSystem *syst, const ADGameDescription *gameDesc) : PlumbersGame(syst, gameDesc), _halfSize(false) {
+}
+
+void PlumbersGameWindows::loadImage(const Common::String &name) {
+	PlumbersGame::loadImage(name);
+
+	if (_halfSize) {
+		_compositeSurface = new Graphics::Surface();
+		const Graphics::Surface *inSurf = _image->getSurface();
+		_compositeSurface->create(_screenW, _screenH, inSurf->format);
+		Graphics::downscaleSurfaceByHalf(_compositeSurface, inSurf, _image->getPalette());
+	}
+}
+
+void PlumbersGameWindows::startGraphics() {
+	_image = new Image::BitmapDecoder();
+
+	Graphics::ModeWithFormatList modes = {
+		// First try for a 640x480 mode
+		Graphics::ModeWithFormat(640, 480),
+		// System doesn't support it, so fall back on 320x240 mode
+		Graphics::ModeWithFormat(320, 240),
+	};
+
+	int modeIdx = initGraphicsAny(modes);
+
+	if (modeIdx == 0) {
+		_screenW = 640;
+		_screenH = 480;
+	} else {
+		_halfSize = true;
+		_screenW = 320;
+		_screenH = 240;
+	}
+}
+
+void PlumbersGameWindows::readTables() {
+	Common::File file;
+	if (!file.open("game.bin"))
+		error("sReadTables(): Error reading BIN file");
+
+	initTables();
+
+	_totScore = file.readSint32LE();
+	file.skip(10);
+	_totScene = file.readSint16LE();
+	file.skip(6);
+
+	char buf[kMaxName];
+	for (int i = 0; i < kMaxScene; i++) {
+		_scenes[i]._bitmapNum = file.readSint16LE();
+		_scenes[i]._startBitmap = file.readSint16LE();
+		_scenes[i]._decisionChoices = file.readSint16LE();
+		file.read(buf, kMaxName);
+		_scenes[i]._sceneName = Common::String(buf);
+		file.read(buf, kMaxName);
+		_scenes[i]._waveFilename = Common::String(buf);
+		file.read(buf, kMaxName);
+		_scenes[i]._decisionBitmap = Common::String(buf);
+		_scenes[i]._style = Scene::STYLE_PC;
+
+		for (int j = 0; j < kMaxChoice; j++) {
+			_scenes[i]._choices[j]._points = file.readSint32LE();
+			_scenes[i]._choices[j]._sceneName = Common::String::format("SC%02d", file.readSint16LE());
+			_scenes[i]._choices[j]._skipScene = file.readSint16LE();
+			int left = file.readSint16LE();
+			int top = file.readSint16LE();
+			int right = file.readSint16LE();
+			int bottom = file.readSint16LE();
+			if (_halfSize)
+				_scenes[i]._choices[j]._region = Common::Rect(left / 2, top / 2, right / 2, bottom / 2);
+			else
+				_scenes[i]._choices[j]._region = Common::Rect(left, top, right, bottom);
+		}
+	}
+
+	for (int i = 0; i < kMaxBitmaps; i++) {
+		_bitmaps[i]._duration = file.readSint16LE() * 100;
+		file.read(buf, kMaxName);
+		_bitmaps[i]._filename = Common::String(buf);
+	}
+}
+
+void PlumbersGameWindows::postSceneBitmaps() {
+	if (_scenes[_curSceneIdx]._decisionChoices == 1) {
+		_curChoice = 0;
+		_actions.push(ChangeScene);
+		return;
+	}
+
+	_showScoreFl = true;
+	_leftButtonDownFl = true;
+	_setDurationFl = false;
+	loadImage(_scenes[_curSceneIdx]._sceneName + "/" + _scenes[_curSceneIdx]._decisionBitmap);
+}
+}  // End of namespace Plumbers


Commit: 35d72e4487fccbc9271e82e8313327e72b2e94eb
    https://github.com/scummvm/scummvm/commit/35d72e4487fccbc9271e82e8313327e72b2e94eb
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
PLUMBERS: Use smaller cursor on lower resolutions

Changed paths:
    engines/plumbers/plumbers.cpp


diff --git a/engines/plumbers/plumbers.cpp b/engines/plumbers/plumbers.cpp
index cfa44385bd7..2753844a9b5 100644
--- a/engines/plumbers/plumbers.cpp
+++ b/engines/plumbers/plumbers.cpp
@@ -85,6 +85,21 @@ static const byte MOUSECURSOR_SCI[] = {
 	0,0,0,0,0,0,1,2,2,1,0
 };
 
+static const byte MOUSECURSOR_AMIGA[] = {
+	1,1,0,0,0,0,0,0,
+	1,2,1,0,0,0,0,0,
+	1,2,2,1,0,0,0,0,
+	1,2,2,2,1,0,0,0,
+	1,2,2,2,2,1,0,0,
+	1,2,2,2,2,2,1,0,
+	1,1,1,2,2,1,1,0,
+	0,0,0,1,2,1,0,0,
+	0,0,0,1,2,2,1,0,
+	0,0,0,0,1,2,1,0,
+	0,0,0,0,1,2,2,1,
+	0,0,0,0,0,1,1,0,
+};
+
 static const byte cursorPalette[] = {
 	0, 0, 0,           // Black / Transparent
 	0x80, 0x80, 0x80,  // Gray
@@ -124,7 +139,10 @@ Common::Error PlumbersGame::run() {
 	_console = new Console();
 	setDebugger(_console);
 
-	CursorMan.replaceCursor(MOUSECURSOR_SCI, 11, 16, 0, 0, 0);
+	if (_screenW > 320)
+		CursorMan.replaceCursor(MOUSECURSOR_SCI, 11, 16, 0, 0, 0);
+	else
+		CursorMan.replaceCursor(MOUSECURSOR_AMIGA, 8, 12, 0, 0, 0);
 	CursorMan.replaceCursorPalette(cursorPalette, 0, 3);
 	CursorMan.showMouse(true);
 


Commit: 573bd18cd3ff6a9e4a165d272d94207049a8eeda
    https://github.com/scummvm/scummvm/commit/573bd18cd3ff6a9e4a165d272d94207049a8eeda
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
PLUMBERS: Mark 3DO version as supported

Changed paths:
    engines/plumbers/detection.cpp


diff --git a/engines/plumbers/detection.cpp b/engines/plumbers/detection.cpp
index 5a2b03863dc..844494eebec 100644
--- a/engines/plumbers/detection.cpp
+++ b/engines/plumbers/detection.cpp
@@ -57,7 +57,7 @@ static const ADGameDescription gameDescriptions[] = {
 		AD_ENTRY1s("launchme", "d3ab77d1a8a2289422a0f51e7aa91821", 143300),
 		Common::EN_ANY,
 		Common::kPlatform3DO,
-		ADGF_UNSTABLE,
+		ADGF_NO_FLAGS,
 		GUIO1(GUIO_NOMIDI)
 	},
 


Commit: 13eb6f3be096392b48681a072e36ea6df75ea020
    https://github.com/scummvm/scummvm/commit/13eb6f3be096392b48681a072e36ea6df75ea020
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T01:58:13+01:00

Commit Message:
DREAMWEB: Don't use 640x480 if USE_HIGHRES is false

Otherwise it leads to immediate crash on low-resolution devices.

Changed paths:
    engines/dreamweb/titles.cpp
    engines/dreamweb/vgagrafx.cpp


diff --git a/engines/dreamweb/titles.cpp b/engines/dreamweb/titles.cpp
index d6f1ff126ef..5dcf5ccb19c 100644
--- a/engines/dreamweb/titles.cpp
+++ b/engines/dreamweb/titles.cpp
@@ -27,6 +27,21 @@
 
 namespace DreamWeb {
 
+namespace {
+void initTitlesGfx() {
+	Graphics::ModeWithFormatList modes = {
+#ifdef USE_HIGHRES
+		// First try for a 640x480 mode
+		Graphics::ModeWithFormat(640, 480),
+#endif
+		// System doesn't support it, so fall back on 320x240 mode
+		Graphics::ModeWithFormat(320, 240),
+	};
+
+	initGraphicsAny(modes);
+}
+}
+
 void DreamWebEngine::endGame() {
 	loadTempText("T83");
 	monkSpeaking();
@@ -148,7 +163,7 @@ void DreamWebEngine::bibleQuote() {
 		break;
 	}
 
-	initGraphics(640, 480);
+	initTitlesGfx();
 
 	showPCX("I00");
 	fadeScreenUps();
@@ -342,7 +357,7 @@ void DreamWebEngine::realCredits() {
 	_sound->loadRoomsSample(_roomsSample);
 	_sound->volumeSet(0);
 
-	initGraphics(640, 480);
+	initTitlesGfx();
 	hangOn(35);
 
 	showPCX("I01");
diff --git a/engines/dreamweb/vgagrafx.cpp b/engines/dreamweb/vgagrafx.cpp
index 07b0c7954b9..a0523bfd5d9 100644
--- a/engines/dreamweb/vgagrafx.cpp
+++ b/engines/dreamweb/vgagrafx.cpp
@@ -23,6 +23,7 @@
 #include "common/file.h"
 #include "engines/util.h"
 #include "graphics/surface.h"
+#include "graphics/scaler/downscaler.h"
 #include "image/pcx.h"
 
 namespace DreamWeb {
@@ -176,12 +177,17 @@ void DreamWebEngine::showPCX(const Common::String &suffix) {
 	}
 
 	Graphics::Surface *s = g_system->lockScreen();
-	s->fillRect(Common::Rect(640, 480), 0);
+	s->fillRect(Common::Rect(s->w, s->h), 0);
 	const Graphics::Surface *pcxSurface = pcx.getSurface();
 	if (pcxSurface->format.bytesPerPixel != 1)
 		error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel);
-	for (uint16 y = 0; y < pcxSurface->h; y++)
-		memcpy((byte *)s->getBasePtr(0, y), pcxSurface->getBasePtr(0, y), pcxSurface->w);
+	if (pcxSurface->w >= s->w * 2)
+		Graphics::downscaleSurfaceByHalf(s, pcxSurface, _mainPal);
+	else {
+		int limitW = MIN(pcxSurface->w, s->w);
+		for (uint16 y = 0; y < pcxSurface->h; y++)
+			memcpy((byte *)s->getBasePtr(0, y), pcxSurface->getBasePtr(0, y), limitW);
+	}
 	g_system->unlockScreen();
 }
 




More information about the Scummvm-git-logs mailing list