aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--README48
-rw-r--r--backends/graphics/opengl/opengl-graphics.cpp84
-rw-r--r--backends/graphics/opengl/opengl-graphics.h17
-rw-r--r--engines/sci/graphics/screen.cpp3
-rw-r--r--engines/sword25/util/double_serialization.cpp76
-rw-r--r--engines/sword25/util/double_serialization.h37
-rw-r--r--engines/zvision/configure.engine2
-rw-r--r--engines/zvision/core/console.cpp62
-rw-r--r--engines/zvision/core/console.h1
-rw-r--r--engines/zvision/detection.cpp51
-rw-r--r--engines/zvision/file/save_manager.cpp105
-rw-r--r--engines/zvision/file/save_manager.h12
-rw-r--r--engines/zvision/file/search_manager.cpp67
-rw-r--r--engines/zvision/file/search_manager.h3
-rw-r--r--engines/zvision/graphics/cursors/cursor.cpp2
-rw-r--r--engines/zvision/graphics/cursors/cursor_manager.cpp7
-rw-r--r--engines/zvision/graphics/render_manager.cpp57
-rw-r--r--engines/zvision/graphics/render_manager.h11
-rw-r--r--engines/zvision/scripting/actions.cpp78
-rw-r--r--engines/zvision/scripting/controls/save_control.cpp4
-rw-r--r--engines/zvision/scripting/scr_file_handling.cpp2
-rw-r--r--engines/zvision/scripting/script_manager.cpp2
-rw-r--r--engines/zvision/scripting/script_manager.h1
-rw-r--r--engines/zvision/sound/zork_raw.cpp22
-rw-r--r--engines/zvision/text/string_manager.cpp3
-rw-r--r--engines/zvision/text/subtitles.cpp9
-rw-r--r--engines/zvision/text/subtitles.h2
-rw-r--r--engines/zvision/video/video.cpp9
-rw-r--r--engines/zvision/zvision.cpp66
-rw-r--r--engines/zvision/zvision.h42
-rw-r--r--video/module.mk1
-rw-r--r--video/mpegps_decoder.cpp732
-rw-r--r--video/mpegps_decoder.h189
34 files changed, 1451 insertions, 358 deletions
diff --git a/NEWS b/NEWS
index d66eb55d0e..45b0afd2d7 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,8 @@ For a more comprehensive changelog of the latest experimental code, see:
1.8.0 (????-??-??)
New Games:
- Added support for Sfinx.
+ - Added support for Zork Nemesis: The Forbidden Lands.
+ - Added support for Zork: Grand Inquisitor.
General:
- Updated Munt MT-32 emulation code to version 1.5.0.
diff --git a/README b/README
index e5a4229afd..3e2cacc05b 100644
--- a/README
+++ b/README
@@ -373,6 +373,10 @@ SCI Games by Sierra Entertainment:
Wintermute Games:
Chivalry is Not Dead [chivalry]
+ZVISION Games by Activision:
+ Zork Nemesis: The Forbidden Lands [znemesis]
+ Zork: Grand Inquisitor [zgi]
+
SCUMM Games by Humongous Entertainment:
Backyard Baseball [baseball]
Backyard Baseball 2001 [baseball2001]
@@ -1344,6 +1348,7 @@ Engines which currently support returning to the Launcher are:
TOUCHE
TSAGE
TUCKER
+ ZVISION
5.5) Hotkeys:
@@ -1508,6 +1513,26 @@ other games.
t - Switch between 'Voice only',
'Voice and Text' and 'Text only'
+ Zork: Grand Inquisitor
+ Ctrl-s - Save
+ Ctrl-r - Restore
+ Ctrl-q - Quit
+ Ctrl-p - Preferences
+ F1 - Help
+ F5 - Inventory
+ F6 - Spellbook
+ F7 - Score
+ F8 - Put away current object/forget spell
+ F9 - Extract coin (must have the coin bag)
+ Space - Skips movies
+
+ Zork Nemesis: The Forbidden Lands
+ Ctrl-s - Save
+ Ctrl-r - Restore
+ Ctrl-q - Quit
+ Ctrl-p - Preferences
+ Space - Skips movies
+
Note that using Ctrl-f or Ctrl-g is not recommended: games can crash
when being run faster than their normal speed, as scripts will lose
synchronisation.
@@ -1627,6 +1652,7 @@ Where 'xxx' is exact the saved game slot (ie 001) under ScummVM
TOUCHE
TSAGE
TUCKER
+ ZVISION
--save-slot/-x:
@@ -1659,6 +1685,7 @@ Where 'xxx' is exact the saved game slot (ie 001) under ScummVM
TOUCHE
TSAGE
TUCKER
+ ZVISION
7.0) Music and Sound:
@@ -2340,6 +2367,27 @@ The 7th Guest adds the following non-standard keyword:
normal speed, to avoid music synchronization
issues
+Zork Nemesis: The Forbidden Lands adds the following non-standard keywords:
+
+ originalsaveload bool If true, the original save/load screens are
+ used instead of the enhanced ScummVM ones
+ doublefps bool If true, game FPS are increased from 30 to 60
+ venusenabled bool If true, the in-game Venus help system is
+ enabled
+ noanimwhileturning bool If true, animations are disabled while turning
+ in panoramic mode
+
+Zork: Grand Inquisitor adds the following non-standard keywords:
+
+ originalsaveload bool If true, the original save/load screens are
+ used instead of the enhanced ScummVM ones
+ doublefps bool If true, game FPS are increased from 30 to 60
+ noanimwhileturning bool If true, animations are disabled while turning
+ in panoramic mode
+ mpegmovies bool If true, the hires MPEG movies are used in the
+ DVD version of the game, instead of the lowres
+ AVI ones
+
8.2) Custom game options that can be toggled via the GUI
---- ---------------------------------------------------
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index c455c4ce2e..5821856c30 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -49,9 +49,9 @@ OpenGLGraphicsManager::OpenGLGraphicsManager()
_displayWidth(0), _displayHeight(0), _defaultFormat(), _defaultFormatAlpha(),
_gameScreen(nullptr), _gameScreenShakeOffset(0), _overlay(nullptr),
_overlayVisible(false), _cursor(nullptr),
- _cursorX(0), _cursorY(0), _cursorHotspotX(0), _cursorHotspotY(0), _cursorHotspotXScaled(0),
- _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0), _cursorKeyColor(0),
- _cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false)
+ _cursorX(0), _cursorY(0), _cursorDisplayX(0),_cursorDisplayY(0), _cursorHotspotX(0), _cursorHotspotY(0),
+ _cursorHotspotXScaled(0), _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0),
+ _cursorKeyColor(0), _cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false)
#ifdef USE_OSD
, _osdAlpha(0), _osdFadeStartTime(0), _osd(nullptr)
#endif
@@ -351,7 +351,7 @@ void OpenGLGraphicsManager::updateScreen() {
return;
}
- // Clear the screen buffer
+ // Clear the screen buffer.
GLCALL(glClear(GL_COLOR_BUFFER_BIT));
const GLfloat shakeOffset = _gameScreenShakeOffset * (GLfloat)_displayHeight / _gameScreen->getHeight();
@@ -370,12 +370,42 @@ void OpenGLGraphicsManager::updateScreen() {
// visible.
const GLfloat cursorOffset = _overlayVisible ? 0 : shakeOffset;
- _cursor->draw(_cursorX - _cursorHotspotXScaled, _cursorY - _cursorHotspotYScaled + cursorOffset,
+ _cursor->draw(_cursorDisplayX - _cursorHotspotXScaled,
+ _cursorDisplayY - _cursorHotspotYScaled + cursorOffset,
_cursorWidthScaled, _cursorHeightScaled);
}
+ // Fourth step: Draw black borders around the game screen when no overlay
+ // is visible. This makes sure that the mouse cursor etc. is only drawn
+ // in the actual game screen area in this case.
+ if (!_overlayVisible) {
+ GLCALL(glColor4f(0.0f, 0.0f, 0.0f, 1.0f));
+
+ GLCALL(glDisable(GL_TEXTURE_2D));
+ GLCALL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
+
+ // Top border.
+ drawRect(0, 0, _outputScreenWidth, _displayY);
+
+ // Left border.
+ drawRect(0, 0, _displayX, _outputScreenHeight);
+
+ // Bottom border.
+ const int y = _displayY + _displayHeight;
+ drawRect(0, y, _outputScreenWidth, _outputScreenHeight - y);
+
+ // Right border.
+ const int x = _displayX + _displayWidth;
+ drawRect(x, 0, _outputScreenWidth - x, _outputScreenHeight);
+
+ GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
+ GLCALL(glEnable(GL_TEXTURE_2D));
+
+ GLCALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f));
+ }
+
#ifdef USE_OSD
- // Fourth step: Draw the OSD.
+ // Fifth step: Draw the OSD.
if (_osdAlpha > 0) {
Common::StackLock lock(_osdMutex);
@@ -435,10 +465,16 @@ int16 OpenGLGraphicsManager::getOverlayHeight() {
void OpenGLGraphicsManager::showOverlay() {
_overlayVisible = true;
+
+ // Update cursor position.
+ setMousePosition(_cursorX, _cursorY);
}
void OpenGLGraphicsManager::hideOverlay() {
_overlayVisible = false;
+
+ // Update cursor position.
+ setMousePosition(_cursorX, _cursorY);
}
Graphics::PixelFormat OpenGLGraphicsManager::getOverlayFormat() const {
@@ -892,8 +928,8 @@ void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) {
const int16 width = _gameScreen->getWidth();
const int16 height = _gameScreen->getHeight();
- x = (x * width) / _displayWidth;
- y = (y * height) / _displayHeight;
+ x = (x * width) / (int)_displayWidth;
+ y = (y * height) / (int)_displayHeight;
// Make sure we only supply valid coordinates.
x = CLIP<int16>(x, 0, width - 1);
@@ -901,6 +937,19 @@ void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) {
}
}
+void OpenGLGraphicsManager::setMousePosition(int x, int y) {
+ _cursorX = x;
+ _cursorY = y;
+
+ if (_overlayVisible) {
+ _cursorDisplayX = x;
+ _cursorDisplayY = y;
+ } else {
+ _cursorDisplayX = CLIP<int>(x, _displayX, _displayX + _displayWidth - 1);
+ _cursorDisplayY = CLIP<int>(y, _displayY, _displayY + _displayHeight - 1);
+ }
+}
+
Texture *OpenGLGraphicsManager::createTexture(const Graphics::PixelFormat &format, bool wantAlpha) {
GLenum glIntFormat, glFormat, glType;
if (format.bytesPerPixel == 1) {
@@ -1046,6 +1095,9 @@ void OpenGLGraphicsManager::recalculateDisplayArea() {
// We center the screen in the middle for now.
_displayX = (_outputScreenWidth - _displayWidth ) / 2;
_displayY = (_outputScreenHeight - _displayHeight) / 2;
+
+ // Update the cursor position to adjust for new display area.
+ setMousePosition(_cursorX, _cursorY);
}
void OpenGLGraphicsManager::updateCursorPalette() {
@@ -1163,4 +1215,20 @@ void OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const
delete[] pixels;
}
+void OpenGLGraphicsManager::drawRect(GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
+ if (w < 0 || h < 0) {
+ return;
+ }
+
+ const GLfloat vertices[4*2] = {
+ x, y,
+ x + w, y,
+ x, y + h,
+ x + w, y + h
+ };
+ GLCALL(glVertexPointer(2, GL_FLOAT, 0, vertices));
+
+ GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
+}
+
} // End of namespace OpenGL
diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h
index dde21533b0..cec970e0cc 100644
--- a/backends/graphics/opengl/opengl-graphics.h
+++ b/backends/graphics/opengl/opengl-graphics.h
@@ -155,7 +155,7 @@ protected:
* @param x X coordinate in physical coordinates.
* @param y Y coordinate in physical coordinates.
*/
- void setMousePosition(int x, int y) { _cursorX = x; _cursorY = y; }
+ void setMousePosition(int x, int y);
/**
* Query the mouse position in physical coordinates.
@@ -394,6 +394,16 @@ private:
int _cursorY;
/**
+ * X coordinate used for drawing the cursor.
+ */
+ int _cursorDisplayX;
+
+ /**
+ * Y coordinate used for drawing the cursor.
+ */
+ int _cursorDisplayY;
+
+ /**
* The X offset for the cursor hotspot in unscaled coordinates.
*/
int _cursorHotspotX;
@@ -454,6 +464,11 @@ private:
*/
byte _cursorPalette[3 * 256];
+ /**
+ * Draws a rectangle
+ */
+ void drawRect(GLfloat x, GLfloat y, GLfloat w, GLfloat h);
+
#ifdef USE_OSD
//
// OSD
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index 2f95bf7751..5a3b30f7ef 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -81,7 +81,8 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
case GID_LSL1:
case GID_LSL5:
case GID_SQ1:
- _width = 190;
+ _scriptHeight = 190;
+ break;
default:
break;
}
diff --git a/engines/sword25/util/double_serialization.cpp b/engines/sword25/util/double_serialization.cpp
index a34eb0fbff..73da296e40 100644
--- a/engines/sword25/util/double_serialization.cpp
+++ b/engines/sword25/util/double_serialization.cpp
@@ -65,80 +65,4 @@ double decodeDouble(SerializedDouble value) {
return ((value.signAndSignificandTwo & 0x80000000) == 0x80000000) ? -returnValue : returnValue;
}
-#if 0
-
-// Why these are needed?
-
-uint64 encodeDouble_64(double value) {
- // Split the value into its significand and exponent
- int exponent;
- double significand = frexp(value, &exponent);
-
- // Shift the significand into the integer range
- double shiftedsignificand = ldexp(abs(significand), 53);
-
- // Combine everything using the IEEE standard
- uint64 uintsignificand = (uint64)shiftedsignificand;
- return ((uint64)(value < 0 ? 1 : 0) << 63) | // Sign
- ((uint64)(exponent + 1023) << 52) | // Exponent stored as an offset to 1023
- (uintsignificand & 0x000FFFFFFFFFFFFFLL); // significand with MSB inferred
-}
-
-double decodeDouble_64(uint64 value) {
- // Expand the exponent and significand
- int exponent = (int)((value >> 52) & 0x7FF) - 1023;
- double expandedsignificand = (double)(0x10000000000000LL /* Inferred MSB */ | (value & 0x000FFFFFFFFFFFFFLL));
-
- // Deflate the significand
- int temp;
- double significand = frexp(expandedsignificand, &temp);
-
- // Re-calculate the actual double
- double returnValue = ldexp(significand, exponent);
-
- // Check the sign bit and return
- return ((value & 0x8000000000000000LL) == 0x8000000000000000LL) ? -returnValue : returnValue;
-}
-
-CompactSerializedDouble encodeDouble_Compact(double value) {
- // Split the value into its significand and exponent
- int exponent;
- double significand = frexp(value, &exponent);
-
- // Shift the the first part of the significand into the integer range
- double shiftedsignificandPart = ldexp(abs(significand), 32);
- uint32 significandOne = uint32(floor(shiftedsignificandPart));
-
- // Shift the remainder of the significand into the integer range
- shiftedsignificandPart -= significandOne;
- uint32 significandTwo = (uint32)(ldexp(shiftedsignificandPart, 21));
-
- CompactSerializedDouble returnValue;
- returnValue.signAndSignificandOne = ((uint32)(value < 0 ? 1 : 0) << 31) | // Sign
- (significandOne & 0x7FFFFFFF); // significandOne with MSB inferred
- // Exponent stored as an offset to 1023
- returnValue.exponentAndSignificandTwo = ((uint32)(exponent + 1023) << 21) | significandTwo;
-
- return returnValue;
-}
-
-double decodeDouble_Compact(CompactSerializedDouble value) {
- // Expand the exponent and the parts of the significand
- int exponent = (int)(value.exponentAndSignificandTwo >> 21) - 1023;
- double expandedsignificandOne = (double)(0x80000000 /* Inferred MSB */ | (value.signAndSignificandOne & 0x7FFFFFFF));
- double expandedsignificandTwo = (double)(value.exponentAndSignificandTwo & 0x1FFFFF);
-
- // Deflate the significand
- double shiftedsignificand = ldexp(expandedsignificandTwo, -21);
- double significand = ldexp(expandedsignificandOne + shiftedsignificand, -32);
-
- // Re-calculate the actual double
- double returnValue = ldexp(significand, exponent);
-
- // Check the sign bit and return
- return ((value.signAndSignificandOne & 0x80000000) == 0x80000000) ? -returnValue : returnValue;
-}
-
-#endif
-
} // End of namespace Sword25
diff --git a/engines/sword25/util/double_serialization.h b/engines/sword25/util/double_serialization.h
index a910a66f20..af58d03c17 100644
--- a/engines/sword25/util/double_serialization.h
+++ b/engines/sword25/util/double_serialization.h
@@ -56,43 +56,6 @@ SerializedDouble encodeDouble(double value);
*/
double decodeDouble(SerializedDouble value);
-#if 0
-/**
- * Encodes a double as a uint64
- *
- * Does NOT support denormalized numbers. Does NOT support NaN, or Inf
- *
- * @param value The value to encode
- * @return The encoded value
- */
-uint64 encodeDouble_64(double value);
-/**
- * Decodes a previously encoded double
- *
- * @param value The value to decode
- * @return The decoded value
- */
-double decodeDouble_64(uint64 value);
-
-/**
- * Encodes a double as two uint32
- *
- * Does NOT support denormalized numbers. Does NOT support NaN, or Inf
- *
- * @param value The value to encode
- * @return The encoded value
- */
-CompactSerializedDouble encodeDouble_Compact(double value);
-/**
- * Decodes a previously encoded double
- *
- * @param value The value to decode
- * @return The decoded value
- */
-double decodeDouble_Compact(CompactSerializedDouble value);
-
-#endif
-
} // End of namespace Sword25
#endif
diff --git a/engines/zvision/configure.engine b/engines/zvision/configure.engine
index 02e31943af..38a5959995 100644
--- a/engines/zvision/configure.engine
+++ b/engines/zvision/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 zvision "ZVision" no "" "" "freetype2 16bit"
+add_engine zvision "ZVision" yes "" "" "freetype2 16bit"
diff --git a/engines/zvision/core/console.cpp b/engines/zvision/core/console.cpp
index b5e542d777..f39c06b57e 100644
--- a/engines/zvision/core/console.cpp
+++ b/engines/zvision/core/console.cpp
@@ -53,6 +53,7 @@ Console::Console(ZVision *engine) : GUI::Debugger(), _engine(engine) {
registerCmd("location", WRAP_METHOD(Console, cmdLocation));
registerCmd("dumpfile", WRAP_METHOD(Console, cmdDumpFile));
registerCmd("dumpfiles", WRAP_METHOD(Console, cmdDumpFiles));
+ registerCmd("dumpimage", WRAP_METHOD(Console, cmdDumpImage));
}
bool Console::cmdLoadVideo(int argc, const char **argv) {
@@ -262,11 +263,70 @@ bool Console::cmdDumpFiles(int argc, const char **argv) {
debugPrintf("Dumping %s\n", fileName.c_str());
in = iter->_value.arch->createReadStreamForMember(iter->_value.name);
- dumpFile(in, fileName.c_str());
+ if (in)
+ dumpFile(in, fileName.c_str());
delete in;
}
return true;
}
+bool Console::cmdDumpImage(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Use %s <TGA/TGZ name> to dump a ZVision TGA/TGZ image into a regular BMP image\n", argv[0]);
+ return true;
+ }
+
+ Common::String fileName = argv[1];
+ if (!fileName.hasSuffix(".tga")) {
+ debugPrintf("%s is not an image file", argv[1]);
+ }
+
+ Common::File f;
+ if (!_engine->getSearchManager()->openFile(f, argv[1])) {
+ warning("File not found: %s", argv[1]);
+ return true;
+ }
+
+ Graphics::Surface surface;
+ _engine->getRenderManager()->readImageToSurface(argv[1], surface, false);
+
+ // Open file
+ Common::DumpFile out;
+
+ fileName.setChar('b', fileName.size() - 3);
+ fileName.setChar('m', fileName.size() - 2);
+ fileName.setChar('p', fileName.size() - 1);
+
+ out.open(fileName);
+
+ // Write BMP header
+ out.writeByte('B');
+ out.writeByte('M');
+ out.writeUint32LE(surface.h * surface.pitch + 54);
+ out.writeUint32LE(0);
+ out.writeUint32LE(54);
+ out.writeUint32LE(40);
+ out.writeUint32LE(surface.w);
+ out.writeUint32LE(surface.h);
+ out.writeUint16LE(1);
+ out.writeUint16LE(16);
+ out.writeUint32LE(0);
+ out.writeUint32LE(0);
+ out.writeUint32LE(0);
+ out.writeUint32LE(0);
+ out.writeUint32LE(0);
+ out.writeUint32LE(0);
+
+ // Write pixel data to BMP
+ out.write(surface.getPixels(), surface.pitch * surface.h);
+
+ out.flush();
+ out.close();
+
+ surface.free();
+
+ return true;
+}
+
} // End of namespace ZVision
diff --git a/engines/zvision/core/console.h b/engines/zvision/core/console.h
index a7bd88ebc3..ffce87869f 100644
--- a/engines/zvision/core/console.h
+++ b/engines/zvision/core/console.h
@@ -47,6 +47,7 @@ private:
bool cmdLocation(int argc, const char **argv);
bool cmdDumpFile(int argc, const char **argv);
bool cmdDumpFiles(int argc, const char **argv);
+ bool cmdDumpImage(int argc, const char **argv);
};
} // End of namespace ZVision
diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp
index 1eaff83413..545ebe35d4 100644
--- a/engines/zvision/detection.cpp
+++ b/engines/zvision/detection.cpp
@@ -59,6 +59,7 @@ namespace ZVision {
#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2
#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3
#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4
+#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5
static const ZVisionGameDescription gameDescriptions[] = {
@@ -77,6 +78,40 @@ static const ZVisionGameDescription gameDescriptions[] = {
},
{
+ // Zork Nemesis French version
+ {
+ "znemesis",
+ 0,
+ {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873},
+ {"NEMESIS.STR", 0, "333bcb17bbb7f57cae742fbbe44f56f3", 9219},
+ AD_LISTEND
+ },
+ Common::FR_FRA,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Nemesis German version
+ {
+ "znemesis",
+ 0,
+ {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873},
+ {"NEMESIS.STR", 0, "3d1a12b907751653866cffc6d4dfb331", 9505},
+ AD_LISTEND
+ },
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ },
+ GID_NEMESIS
+ },
+
+ {
// Zork Nemesis English demo version
{
"znemesis",
@@ -113,7 +148,7 @@ static const ZVisionGameDescription gameDescriptions[] = {
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
- GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING)
+ GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES)
},
GID_GRANDINQUISITOR
},
@@ -160,7 +195,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
GAMEOPTION_DOUBLE_FPS,
{
_s("Double FPS"),
- _s("Halve the update delay"),
+ _s("Increase game FPS from 30 to 60"),
"doublefps",
false
}
@@ -186,6 +221,16 @@ static const ADExtraGuiOptionsMap optionsList[] = {
}
},
+ {
+ GAMEOPTION_USE_HIRES_MPEG_MOVIES,
+ {
+ _s("Use the hires MPEG movies"),
+ _s("Use the hires MPEG movies of the DVD version, instead of the lowres AVI ones"),
+ "mpegmovies",
+ true
+ }
+ },
+
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
@@ -236,7 +281,7 @@ Common::Error ZVision::ZVision::loadGameState(int slot) {
}
Common::Error ZVision::ZVision::saveGameState(int slot, const Common::String &desc) {
- _saveManager->saveGame(slot, desc);
+ _saveManager->saveGame(slot, desc, false);
return Common::kNoError;
}
diff --git a/engines/zvision/file/save_manager.cpp b/engines/zvision/file/save_manager.cpp
index 042fafd38e..5e96e4ab5e 100644
--- a/engines/zvision/file/save_manager.cpp
+++ b/engines/zvision/file/save_manager.cpp
@@ -69,7 +69,7 @@ bool SaveManager::scummVMSaveLoadDialog(bool isSave) {
return false;
if (isSave) {
- saveGame(slot, desc);
+ saveGame(slot, desc, false);
return true;
} else {
Common::ErrorCode result = loadGame(slot).getCode();
@@ -77,46 +77,34 @@ bool SaveManager::scummVMSaveLoadDialog(bool isSave) {
}
}
-void SaveManager::saveGame(uint slot, const Common::String &saveName) {
- Common::SaveFileManager *saveFileManager = g_system->getSavefileManager();
- Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot));
-
- writeSaveGameHeader(file, saveName);
+void SaveManager::saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer) {
+ if (!_tempSave && useSaveBuffer)
+ return;
- _engine->getScriptManager()->serialize(file);
-
- file->finalize();
- delete file;
-
- _lastSaveTime = g_system->getMillis();
-}
-
-void SaveManager::saveGame(uint slot, const Common::String &saveName, Common::MemoryWriteStreamDynamic *stream) {
Common::SaveFileManager *saveFileManager = g_system->getSavefileManager();
Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot));
- writeSaveGameHeader(file, saveName);
+ writeSaveGameHeader(file, saveName, useSaveBuffer);
- file->write(stream->getData(), stream->size());
+ if (useSaveBuffer)
+ file->write(_tempSave->getData(), _tempSave->size());
+ else
+ _engine->getScriptManager()->serialize(file);
file->finalize();
delete file;
- _lastSaveTime = g_system->getMillis();
-}
-
-void SaveManager::saveGameBuffered(uint slot, const Common::String &saveName) {
- if (_tempSave) {
- saveGame(slot, saveName, _tempSave);
+ if (useSaveBuffer)
flushSaveBuffer();
- }
+
+ _lastSaveTime = g_system->getMillis();
}
void SaveManager::autoSave() {
- saveGame(0, "Auto save");
+ saveGame(0, "Auto save", false);
}
-void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName) {
+void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer) {
file->writeUint32BE(SAVEGAME_ID);
// Write version
@@ -126,8 +114,11 @@ void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::S
file->writeString(saveName);
file->writeByte(0);
- // Create a thumbnail and save it
- Graphics::saveThumbnail(*file);
+ // Save the game thumbnail
+ if (useSaveBuffer)
+ file->write(_tempThumbnail->getData(), _tempThumbnail->size());
+ else
+ Graphics::saveThumbnail(*file);
// Write out the save date/time
TimeDate td;
@@ -139,39 +130,25 @@ void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::S
file->writeSint16LE(td.tm_min);
}
-Common::Error SaveManager::loadGame(uint slot) {
- Common::SeekableReadStream *saveFile = getSlotFile(slot);
- if (saveFile == 0) {
- return Common::kPathDoesNotExist;
- }
-
- // Read the header
- SaveGameHeader header;
- if (!readSaveGameHeader(saveFile, header)) {
- return Common::kUnknownError;
- }
-
- ScriptManager *scriptManager = _engine->getScriptManager();
- // Update the state table values
- scriptManager->deserialize(saveFile);
-
- delete saveFile;
- if (header.thumbnail)
- delete header.thumbnail;
-
- return Common::kNoError;
-}
-
-Common::Error SaveManager::loadGame(const Common::String &saveName) {
- Common::File *saveFile = _engine->getSearchManager()->openFile(saveName);
- if (saveFile == NULL) {
- saveFile = new Common::File;
- if (!saveFile->open(saveName)) {
- delete saveFile;
- return Common::kPathDoesNotExist;
+Common::Error SaveManager::loadGame(int slot) {
+ Common::SeekableReadStream *saveFile = NULL;
+
+ if (slot >= 0) {
+ saveFile = getSlotFile(slot);
+ } else {
+ Common::File *saveFile = _engine->getSearchManager()->openFile("r.svr");
+ if (!saveFile) {
+ saveFile = new Common::File;
+ if (!saveFile->open("r.svr")) {
+ delete saveFile;
+ return Common::kPathDoesNotExist;
+ }
}
}
+ if (!saveFile)
+ return Common::kPathDoesNotExist;
+
// Read the header
SaveGameHeader header;
if (!readSaveGameHeader(saveFile, header)) {
@@ -272,18 +249,20 @@ Common::SeekableReadStream *SaveManager::getSlotFile(uint slot) {
}
void SaveManager::prepareSaveBuffer() {
- if (_tempSave)
- delete _tempSave;
+ delete _tempThumbnail;
+ _tempThumbnail = new Common::MemoryWriteStreamDynamic;
+ Graphics::saveThumbnail(*_tempThumbnail);
+ delete _tempSave;
_tempSave = new Common::MemoryWriteStreamDynamic;
-
_engine->getScriptManager()->serialize(_tempSave);
}
void SaveManager::flushSaveBuffer() {
- if (_tempSave)
- delete _tempSave;
+ delete _tempThumbnail;
+ _tempThumbnail = NULL;
+ delete _tempSave;
_tempSave = NULL;
}
diff --git a/engines/zvision/file/save_manager.h b/engines/zvision/file/save_manager.h
index fc8db67566..9e816373ea 100644
--- a/engines/zvision/file/save_manager.h
+++ b/engines/zvision/file/save_manager.h
@@ -48,7 +48,7 @@ struct SaveGameHeader {
class SaveManager {
public:
- SaveManager(ZVision *engine) : _engine(engine), _tempSave(NULL), _lastSaveTime(0) {}
+ SaveManager(ZVision *engine) : _engine(engine), _tempSave(NULL), _tempThumbnail(NULL), _lastSaveTime(0) {}
~SaveManager() {
flushSaveBuffer();
}
@@ -67,6 +67,7 @@ private:
SAVE_VERSION = 1
};
+ Common::MemoryWriteStreamDynamic *_tempThumbnail;
Common::MemoryWriteStreamDynamic *_tempSave;
public:
@@ -83,17 +84,14 @@ public:
* @param slot The save slot this save pertains to. Must be [1, 20]
* @param saveName The internal name for this save. This is NOT the name of the actual save file.
*/
- void saveGame(uint slot, const Common::String &saveName);
- void saveGame(uint slot, const Common::String &saveName, Common::MemoryWriteStreamDynamic *stream);
- void saveGameBuffered(uint slot, const Common::String &saveName);
+ void saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer);
/**
* Loads the state data from the save file that slot references. Uses
* ZVision::generateSaveFileName(slot) to get the save file name.
*
* @param slot The save slot to load. Must be [1, 20]
*/
- Common::Error loadGame(uint slot);
- Common::Error loadGame(const Common::String &saveName);
+ Common::Error loadGame(int slot);
Common::SeekableReadStream *getSlotFile(uint slot);
bool readSaveGameHeader(Common::SeekableReadStream *in, SaveGameHeader &header);
@@ -102,7 +100,7 @@ public:
void flushSaveBuffer();
bool scummVMSaveLoadDialog(bool isSave);
private:
- void writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName);
+ void writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer);
};
} // End of namespace ZVision
diff --git a/engines/zvision/file/search_manager.cpp b/engines/zvision/file/search_manager.cpp
index ec250ff648..9f709dd0a1 100644
--- a/engines/zvision/file/search_manager.cpp
+++ b/engines/zvision/file/search_manager.cpp
@@ -62,19 +62,6 @@ SearchManager::~SearchManager() {
_archList.clear();
}
-void SearchManager::addPatch(const Common::String &src, const Common::String &dst) {
- Common::String lowerCaseName = dst;
- lowerCaseName.toLowercase();
-
- SearchManager::MatchList::iterator it = _files.find(lowerCaseName);
-
- if (it != _files.end()) {
- lowerCaseName = src;
- lowerCaseName.toLowercase();
- _files[lowerCaseName] = it->_value;
- }
-}
-
void SearchManager::addFile(const Common::String &name, Common::Archive *arch) {
bool addArch = true;
Common::List<Common::Archive *>::iterator it = _archList.begin();
@@ -147,9 +134,10 @@ bool SearchManager::hasFile(const Common::String &name) {
return false;
}
-void SearchManager::loadZix(const Common::String &name) {
+bool SearchManager::loadZix(const Common::String &name) {
Common::File file;
- file.open(name);
+ if (!file.open(name))
+ return false;
Common::String line;
@@ -160,7 +148,7 @@ void SearchManager::loadZix(const Common::String &name) {
}
if (file.eos())
- return;
+ error("Corrupt ZIX file: %s", name.c_str());
Common::Array<Common::Archive *> archives;
@@ -169,42 +157,37 @@ void SearchManager::loadZix(const Common::String &name) {
line.trim();
if (line.matchString("----------*", true))
break;
- else if (line.matchString("DIR:*", true) || line.matchString("CD0:*", true) || line.matchString("CD1:*", true)) {
+ else if (line.matchString("DIR:*", true) || line.matchString("CD0:*", true) || line.matchString("CD1:*", true) || line.matchString("CD2:*", true)) {
+ Common::Archive *arc;
+
Common::String path(line.c_str() + 5);
+ for (uint i = 0; i < path.size(); i++)
+ if (path[i] == '\\')
+ path.setChar('/', i);
+
+ // Check if NEMESIS.ZIX/MEDIUM.ZIX refers to the znemesis folder, and
+ // check the game root folder instead
+ if (path.hasPrefix("znemesis/"))
+ path = Common::String(path.c_str() + 9);
+
// Check if INQUIS.ZIX refers to the ZGI folder, and check the game
// root folder instead
- if (path.hasPrefix("zgi\\"))
+ if (path.hasPrefix("zgi/"))
path = Common::String(path.c_str() + 4);
+ if (path.hasPrefix("zgi_e/"))
+ path = Common::String(path.c_str() + 6);
- Common::Archive *arc;
- char tempPath[128];
- strcpy(tempPath, path.c_str());
- for (uint i = 0; i < path.size(); i++)
- if (tempPath[i] == '\\')
- tempPath[i] = '/';
-
- path = Common::String(tempPath);
if (path.size() && path[0] == '.')
path.deleteChar(0);
if (path.size() && path[0] == '/')
path.deleteChar(0);
+ if (path.size() && path.hasSuffix("/"))
+ path.deleteLastChar();
- if (path.matchString("*.zfs", true))
+ if (path.matchString("*.zfs", true)) {
arc = new ZfsArchive(path);
- else {
- if (path.size()) {
- if (path[path.size() - 1] == '\\' || path[path.size() - 1] == '/')
- path.deleteLastChar();
- if (path.size())
- for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end(); ++it)
- if (path.equalsIgnoreCase(*it)) {
- path = *it;
- break;
- }
- }
-
+ } else {
path = Common::String::format("%s/%s", _root.c_str(), path.c_str());
-
arc = new Common::FSDirectory(path);
}
archives.push_back(arc);
@@ -212,7 +195,7 @@ void SearchManager::loadZix(const Common::String &name) {
}
if (file.eos())
- return;
+ error("Corrupt ZIX file: %s", name.c_str());
while (!file.eos()) {
line = file.readLine();
@@ -225,6 +208,8 @@ void SearchManager::loadZix(const Common::String &name) {
}
}
}
+
+ return true;
}
void SearchManager::addDir(const Common::String &name) {
diff --git a/engines/zvision/file/search_manager.h b/engines/zvision/file/search_manager.h
index b9ed02ec13..0d0ab14d31 100644
--- a/engines/zvision/file/search_manager.h
+++ b/engines/zvision/file/search_manager.h
@@ -39,13 +39,12 @@ public:
void addFile(const Common::String &name, Common::Archive *arch);
void addDir(const Common::String &name);
- void addPatch(const Common::String &src, const Common::String &dst);
Common::File *openFile(const Common::String &name);
bool openFile(Common::File &file, const Common::String &name);
bool hasFile(const Common::String &name);
- void loadZix(const Common::String &name);
+ bool loadZix(const Common::String &name);
struct Node {
Common::String name;
diff --git a/engines/zvision/graphics/cursors/cursor.cpp b/engines/zvision/graphics/cursors/cursor.cpp
index f32c68645d..2c011668ac 100644
--- a/engines/zvision/graphics/cursors/cursor.cpp
+++ b/engines/zvision/graphics/cursors/cursor.cpp
@@ -43,7 +43,7 @@ ZorkCursor::ZorkCursor(ZVision *engine, const Common::String &fileName)
_hotspotY(0) {
Common::File file;
if (!engine->getSearchManager()->openFile(file, fileName))
- return;
+ error("Cursor file %s does not exist", fileName.c_str());
uint32 magic = file.readUint32BE();
if (magic != MKTAG('Z', 'C', 'R', '1')) {
diff --git a/engines/zvision/graphics/cursors/cursor_manager.cpp b/engines/zvision/graphics/cursors/cursor_manager.cpp
index c364426bad..1e048efedf 100644
--- a/engines/zvision/graphics/cursors/cursor_manager.cpp
+++ b/engines/zvision/graphics/cursors/cursor_manager.cpp
@@ -37,7 +37,7 @@ const char *CursorManager::_cursorNames[NUM_CURSORS] = { "active", "arrow", "bac
"hright", "hup", "idle", "leftarrow", "rightarrow", "suggest_surround", "suggest_tilt", "turnaround", "zuparrow"
};
-const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac001.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr",
+const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac011.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr",
"g0gac091.zcr", "g0gac101.zcr", "g0gac011.zcr", "g0gac111.zcr", "g0gac121.zcr", "g0gac131.zcr", "g0gac141.zcr", "g0gac151.zcr", "g0gac161.zcr"
};
@@ -55,6 +55,11 @@ CursorManager::CursorManager(ZVision *engine, const Graphics::PixelFormat pixelF
for (int i = 0; i < NUM_CURSORS; i++) {
if (_engine->getGameId() == GID_NEMESIS) {
Common::String name;
+ if (i == 1) {
+ // Cursors "arrowa.zcr" and "arrowb.zcr" are missing
+ _cursors[i][0] = _cursors[i][1] = ZorkCursor();
+ continue;
+ }
name = Common::String::format("%sa.zcr", _zNemCursorFileNames[i]);
_cursors[i][0] = ZorkCursor(_engine, name); // Up cursor
name = Common::String::format("%sb.zcr", _zNemCursorFileNames[i]);
diff --git a/engines/zvision/graphics/render_manager.cpp b/engines/zvision/graphics/render_manager.cpp
index a1cc8ac53c..4a43e09b07 100644
--- a/engines/zvision/graphics/render_manager.cpp
+++ b/engines/zvision/graphics/render_manager.cpp
@@ -42,28 +42,25 @@ namespace ZVision {
RenderManager::RenderManager(ZVision *engine, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat, bool doubleFPS)
: _engine(engine),
_system(engine->_system),
- _workingWidth(workingWindow.width()),
- _workingHeight(workingWindow.height()),
- _screenCenterX(_workingWidth / 2),
- _screenCenterY(_workingHeight / 2),
+ _screenCenterX(_workingWindow.width() / 2),
+ _screenCenterY(_workingWindow.height() / 2),
_workingWindow(workingWindow),
_pixelFormat(pixelFormat),
_backgroundWidth(0),
_backgroundHeight(0),
_backgroundOffset(0),
- _renderTable(_workingWidth, _workingHeight),
- _doubleFPS(doubleFPS) {
+ _renderTable(_workingWindow.width(), _workingWindow.height()),
+ _doubleFPS(doubleFPS),
+ _subid(0) {
- _backgroundSurface.create(_workingWidth, _workingHeight, _pixelFormat);
- _effectSurface.create(_workingWidth, _workingHeight, _pixelFormat);
- _warpedSceneSurface.create(_workingWidth, _workingHeight, _pixelFormat);
+ _backgroundSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat);
+ _effectSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat);
+ _warpedSceneSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat);
_menuSurface.create(windowWidth, workingWindow.top, _pixelFormat);
- _subtitleSurface.create(windowWidth, windowHeight - workingWindow.bottom, _pixelFormat);
-
+
_menuArea = Common::Rect(0, 0, windowWidth, workingWindow.top);
- _subtitleArea = Common::Rect(0, workingWindow.bottom, windowWidth, windowHeight);
- _subid = 0;
+ initSubArea(windowWidth, windowHeight, workingWindow);
}
RenderManager::~RenderManager() {
@@ -83,7 +80,7 @@ void RenderManager::renderSceneToScreen() {
// If we have graphical effects, we apply them using a temporary buffer
if (!_effects.empty()) {
bool copied = false;
- Common::Rect windowRect(_workingWidth, _workingHeight);
+ Common::Rect windowRect(_workingWindow.width(), _workingWindow.height());
for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) {
Common::Rect rect = (*it)->getRegion();
@@ -121,7 +118,7 @@ void RenderManager::renderSceneToScreen() {
if (!_backgroundSurfaceDirtyRect.isEmpty()) {
_renderTable.mutateImage(&_warpedSceneSurface, in);
out = &_warpedSceneSurface;
- outWndDirtyRect = Common::Rect(_workingWidth, _workingHeight);
+ outWndDirtyRect = Common::Rect(_workingWindow.width(), _workingWindow.height());
}
} else {
out = in;
@@ -590,7 +587,7 @@ void RenderManager::prepareBackground() {
if (state == RenderTable::PANORAMA) {
// Calculate the visible portion of the background
- Common::Rect viewPort(_workingWidth, _workingHeight);
+ Common::Rect viewPort(_workingWindow.width(), _workingWindow.height());
viewPort.translate(-(_screenCenterX - _backgroundOffset), 0);
Common::Rect drawRect = _backgroundDirtyRect;
drawRect.clip(viewPort);
@@ -635,7 +632,7 @@ void RenderManager::prepareBackground() {
}
} else if (state == RenderTable::TILT) {
// Tilt doesn't allow wrapping, so we just do a simple clip
- Common::Rect viewPort(_workingWidth, _workingHeight);
+ Common::Rect viewPort(_workingWindow.width(), _workingWindow.height());
viewPort.translate(0, -(_screenCenterY - _backgroundOffset));
Common::Rect drawRect = _backgroundDirtyRect;
drawRect.clip(viewPort);
@@ -655,7 +652,7 @@ void RenderManager::prepareBackground() {
// Clear the dirty rect since everything is clean now
_backgroundDirtyRect = Common::Rect();
- _backgroundSurfaceDirtyRect.clip(_workingWidth, _workingHeight);
+ _backgroundSurfaceDirtyRect.clip(_workingWindow.width(), _workingWindow.height());
}
void RenderManager::clearMenuSurface() {
@@ -687,6 +684,15 @@ void RenderManager::renderMenuToScreen() {
}
}
+void RenderManager::initSubArea(uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow) {
+ _workingWindow = workingWindow;
+
+ _subtitleSurface.free();
+
+ _subtitleSurface.create(windowWidth, windowHeight - workingWindow.bottom, _pixelFormat);
+ _subtitleArea = Common::Rect(0, workingWindow.bottom, windowWidth, windowHeight);
+}
+
uint16 RenderManager::createSubArea(const Common::Rect &area) {
_subid++;
@@ -791,8 +797,8 @@ Common::Rect RenderManager::transformBackgroundSpaceRectToScreenSpace(const Comm
if (state == RenderTable::PANORAMA) {
if (_backgroundOffset < _screenCenterX) {
- Common::Rect rScreen(_screenCenterX + _backgroundOffset, _workingHeight);
- Common::Rect lScreen(_workingWidth - rScreen.width(), _workingHeight);
+ Common::Rect rScreen(_screenCenterX + _backgroundOffset, _workingWindow.height());
+ Common::Rect lScreen(_workingWindow.width() - rScreen.width(), _workingWindow.height());
lScreen.translate(_backgroundWidth - lScreen.width(), 0);
lScreen.clip(src);
rScreen.clip(src);
@@ -802,8 +808,8 @@ Common::Rect RenderManager::transformBackgroundSpaceRectToScreenSpace(const Comm
tmp.translate(_screenCenterX - _backgroundOffset, 0);
}
} else if (_backgroundWidth - _backgroundOffset < _screenCenterX) {
- Common::Rect rScreen(_screenCenterX - (_backgroundWidth - _backgroundOffset), _workingHeight);
- Common::Rect lScreen(_workingWidth - rScreen.width(), _workingHeight);
+ Common::Rect rScreen(_screenCenterX - (_backgroundWidth - _backgroundOffset), _workingWindow.height());
+ Common::Rect lScreen(_workingWindow.width() - rScreen.width(), _workingWindow.height());
lScreen.translate(_backgroundWidth - lScreen.width(), 0);
lScreen.clip(src);
rScreen.clip(src);
@@ -1172,4 +1178,11 @@ void RenderManager::rotateTo(int16 _toPos, int16 _time) {
_engine->startClock();
}
+void RenderManager::upscaleRect(Common::Rect &rect) {
+ rect.top = rect.top * HIRES_WINDOW_HEIGHT / WINDOW_HEIGHT;
+ rect.left = rect.left * HIRES_WINDOW_WIDTH / WINDOW_WIDTH;
+ rect.bottom = rect.bottom * HIRES_WINDOW_HEIGHT / WINDOW_HEIGHT;
+ rect.right = rect.right * HIRES_WINDOW_WIDTH / WINDOW_WIDTH;
+}
+
} // End of namespace ZVision
diff --git a/engines/zvision/graphics/render_manager.h b/engines/zvision/graphics/render_manager.h
index c22f9a78c9..e3cbbc34ce 100644
--- a/engines/zvision/graphics/render_manager.h
+++ b/engines/zvision/graphics/render_manager.h
@@ -73,12 +73,8 @@ private:
* are given in this coordinate space. Also, all images are clipped to the
* edges of this Rectangle
*/
- const Common::Rect _workingWindow;
+ Common::Rect _workingWindow;
- // Width of the working window. Saved to prevent extraneous calls to _workingWindow.width()
- const int _workingWidth;
- // Height of the working window. Saved to prevent extraneous calls to _workingWindow.height()
- const int _workingHeight;
// Center of the screen in the x direction
const int _screenCenterX;
// Center of the screen in the y direction
@@ -106,7 +102,6 @@ private:
// A buffer for subtitles
Graphics::Surface _subtitleSurface;
- Common::Rect _subtitleSurfaceDirtyRect;
// Rectangle for subtitles area
Common::Rect _subtitleArea;
@@ -242,6 +237,8 @@ public:
// Subtitles methods
+ void initSubArea(uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow);
+
// Create subtitle area and return ID
uint16 createSubArea(const Common::Rect &area);
uint16 createSubArea();
@@ -335,6 +332,8 @@ public:
void checkBorders();
void rotateTo(int16 to, int16 time);
void updateRotation();
+
+ void upscaleRect(Common::Rect &rect);
};
} // End of namespace ZVision
diff --git a/engines/zvision/scripting/actions.cpp b/engines/zvision/scripting/actions.cpp
index a61fa26223..e989478dd1 100644
--- a/engines/zvision/scripting/actions.cpp
+++ b/engines/zvision/scripting/actions.cpp
@@ -21,14 +21,13 @@
*/
#include "common/scummsys.h"
+#include "video/video_decoder.h"
#include "zvision/scripting/actions.h"
#include "zvision/zvision.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/graphics/render_manager.h"
-#include "zvision/sound/zork_raw.h"
-#include "zvision/video/zork_avi_decoder.h"
#include "zvision/file/save_manager.h"
#include "zvision/scripting/menu.h"
#include "zvision/scripting/effects/timer_effect.h"
@@ -46,10 +45,6 @@
#include "zvision/graphics/effects/wave.h"
#include "zvision/graphics/cursors/cursor_manager.h"
-#include "common/file.h"
-
-#include "audio/decoders/wave.h"
-
namespace ZVision {
//////////////////////////////////////////////////////////////////////////////
@@ -815,7 +810,7 @@ ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotkey, const Commo
}
bool ActionRestoreGame::execute() {
- _engine->getSaveManager()->loadGame(_fileName);
+ _engine->getSaveManager()->loadGame(-1);
return false;
}
@@ -932,35 +927,62 @@ ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotkey, const Commo
}
bool ActionStreamVideo::execute() {
- ZorkAVIDecoder decoder;
- Common::File *_file = _engine->getSearchManager()->openFile(_fileName);
-
- if (_file) {
- if (!decoder.loadStream(_file)) {
+ Video::VideoDecoder *decoder;
+ Common::Rect destRect = Common::Rect(_x1, _y1, _x2 + 1, _y2 + 1);
+ Common::String subname = _fileName;
+ subname.setChar('s', subname.size() - 3);
+ subname.setChar('u', subname.size() - 2);
+ subname.setChar('b', subname.size() - 1);
+ bool subtitleExists = _engine->getSearchManager()->hasFile(subname);
+ bool switchToHires = false;
+
+// NOTE: We only show the hires MPEG2 videos when libmpeg2 is compiled in,
+// otherwise we fall back to the lowres ones
+#ifdef USE_MPEG2
+ Common::String hiresFileName = _fileName;
+ hiresFileName.setChar('d', hiresFileName.size() - 8);
+ hiresFileName.setChar('v', hiresFileName.size() - 3);
+ hiresFileName.setChar('o', hiresFileName.size() - 2);
+ hiresFileName.setChar('b', hiresFileName.size() - 1);
+
+ if (_engine->getScriptManager()->getStateValue(StateKey_MPEGMovies) == 1 &&_engine->getSearchManager()->hasFile(hiresFileName)) {
+ // TODO: Enable once AC3 support is implemented
+ if (!_engine->getSearchManager()->hasFile(_fileName)) // Check for the regular video
return true;
- }
-
- _engine->getCursorManager()->showMouse(false);
+ warning("The hires videos of the DVD version of ZGI aren't supported yet, using lowres");
+ //_fileName = hiresFileName;
+ //switchToHires = true;
+ } else if (!_engine->getSearchManager()->hasFile(_fileName))
+ return true;
+#else
+ if (!_engine->getSearchManager()->hasFile(_fileName))
+ return true;
+#endif
- Common::Rect destRect = Common::Rect(_x1, _y1, _x2 + 1, _y2 + 1);
+ decoder = _engine->loadAnimation(_fileName);
+ Subtitle *sub = (subtitleExists) ? new Subtitle(_engine, subname, switchToHires) : NULL;
- Common::String subname = _fileName;
- subname.setChar('s', subname.size() - 3);
- subname.setChar('u', subname.size() - 2);
- subname.setChar('b', subname.size() - 1);
+ _engine->getCursorManager()->showMouse(false);
- Subtitle *sub = NULL;
+ if (switchToHires) {
+ _engine->initHiresScreen();
+ destRect = Common::Rect(40, -40, 760, 440);
+ Common::Rect workingWindow = _engine->_workingWindow;
+ workingWindow.translate(0, -40);
+ _engine->getRenderManager()->initSubArea(HIRES_WINDOW_WIDTH, HIRES_WINDOW_HEIGHT, workingWindow);
+ }
- if (_engine->getSearchManager()->hasFile(subname))
- sub = new Subtitle(_engine, subname);
+ _engine->playVideo(*decoder, destRect, _skippable, sub);
- _engine->playVideo(decoder, destRect, _skippable, sub);
+ if (switchToHires) {
+ _engine->initScreen();
+ _engine->getRenderManager()->initSubArea(WINDOW_WIDTH, WINDOW_HEIGHT, _engine->_workingWindow);
+ }
- _engine->getCursorManager()->showMouse(true);
+ _engine->getCursorManager()->showMouse(true);
- if (sub)
- delete sub;
- }
+ delete decoder;
+ delete sub;
return true;
}
diff --git a/engines/zvision/scripting/controls/save_control.cpp b/engines/zvision/scripting/controls/save_control.cpp
index 6cedddffeb..2ac77c4776 100644
--- a/engines/zvision/scripting/controls/save_control.cpp
+++ b/engines/zvision/scripting/controls/save_control.cpp
@@ -102,9 +102,7 @@ bool SaveControl::process(uint32 deltaTimeInMillis) {
toSave = false;
if (toSave) {
- // FIXME: At this point, the screen shows the save control, so the save game thumbnails will always
- // show the save control
- _engine->getSaveManager()->saveGameBuffered(iter->saveId, inp->getText());
+ _engine->getSaveManager()->saveGame(iter->saveId, inp->getText(), true);
_engine->getRenderManager()->delayedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVED), 2000);
_engine->getScriptManager()->changeLocation(_engine->getScriptManager()->getLastMenuLocation());
}
diff --git a/engines/zvision/scripting/scr_file_handling.cpp b/engines/zvision/scripting/scr_file_handling.cpp
index 227c43557c..b4da61a119 100644
--- a/engines/zvision/scripting/scr_file_handling.cpp
+++ b/engines/zvision/scripting/scr_file_handling.cpp
@@ -270,6 +270,8 @@ void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::Lis
// Only used by Zork: Nemesis
actionList.push_back(new ActionRegion(_engine, slot, args));
} else if (act.matchString("restore_game", true)) {
+ // Only used by ZGI to load the restart game slot, r.svr.
+ // Used by the credits screen.
actionList.push_back(new ActionRestoreGame(_engine, slot, args));
} else if (act.matchString("rotate_to", true)) {
actionList.push_back(new ActionRotateTo(_engine, slot, args));
diff --git a/engines/zvision/scripting/script_manager.cpp b/engines/zvision/scripting/script_manager.cpp
index 464e8bfe4d..ad049434c3 100644
--- a/engines/zvision/scripting/script_manager.cpp
+++ b/engines/zvision/scripting/script_manager.cpp
@@ -500,7 +500,7 @@ void ScriptManager::changeLocation(char _world, char _room, char _node, char _vi
_nextLocation.node = _node;
_nextLocation.view = _view;
_nextLocation.offset = offset;
- // If next location 0000 - it's indicate to go to previous location.
+ // If next location is 0000, return to the previous location.
if (_nextLocation.world == '0' && _nextLocation.room == '0' && _nextLocation.node == '0' && _nextLocation.view == '0') {
if (getStateValue(StateKey_World) != 'g' || getStateValue(StateKey_Room) != 'j') {
_nextLocation.world = getStateValue(StateKey_LastWorld);
diff --git a/engines/zvision/scripting/script_manager.h b/engines/zvision/scripting/script_manager.h
index 78c1b77dea..f6201c3572 100644
--- a/engines/zvision/scripting/script_manager.h
+++ b/engines/zvision/scripting/script_manager.h
@@ -87,6 +87,7 @@ enum StateKey {
StateKey_JapanFonts = 75,
StateKey_ExecScopeStyle = 76,
StateKey_Brightness = 77,
+ StateKey_MPEGMovies = 78,
StateKey_EF9_R = 91,
StateKey_EF9_G = 92,
StateKey_EF9_B = 93,
diff --git a/engines/zvision/sound/zork_raw.cpp b/engines/zvision/sound/zork_raw.cpp
index 6d1980b1af..0ef5de2f55 100644
--- a/engines/zvision/sound/zork_raw.cpp
+++ b/engines/zvision/sound/zork_raw.cpp
@@ -242,11 +242,27 @@ Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stre
Audio::RewindableAudioStream *makeRawZorkStream(const Common::String &filePath, ZVision *engine) {
Common::File *file = new Common::File();
- if (!engine->getSearchManager()->openFile(*file, filePath))
- error("File not found: %s", filePath.c_str());
+ Common::String actualName = filePath;
+ bool found = engine->getSearchManager()->openFile(*file, actualName);
+ bool isRaw = actualName.hasSuffix(".raw");
+
+ if ((!found && isRaw) || (found && isRaw && file->size() < 10)) {
+ if (found)
+ file->close();
+
+ // Check for an audio patch (.src)
+ actualName.setChar('s', actualName.size() - 3);
+ actualName.setChar('r', actualName.size() - 2);
+ actualName.setChar('c', actualName.size() - 1);
+
+ if (!engine->getSearchManager()->openFile(*file, actualName))
+ error("File not found: %s", actualName.c_str());
+ } else if (!found && !isRaw) {
+ error("File not found: %s", actualName.c_str());
+ }
// Get the file name
- Common::StringTokenizer tokenizer(filePath, "/\\");
+ Common::StringTokenizer tokenizer(actualName, "/\\");
Common::String fileName;
while (!tokenizer.empty()) {
fileName = tokenizer.nextToken();
diff --git a/engines/zvision/text/string_manager.cpp b/engines/zvision/text/string_manager.cpp
index 6b870d0b8d..c62e18f4b0 100644
--- a/engines/zvision/text/string_manager.cpp
+++ b/engines/zvision/text/string_manager.cpp
@@ -51,9 +51,8 @@ void StringManager::initialize(ZVisionGameId gameId) {
void StringManager::loadStrFile(const Common::String &fileName) {
Common::File file;
- if (!_engine->getSearchManager()->openFile(file, fileName)) {
+ if (!_engine->getSearchManager()->openFile(file, fileName))
error("%s does not exist. String parsing failed", fileName.c_str());
- }
uint lineNumber = 0;
while (!file.eos()) {
diff --git a/engines/zvision/text/subtitles.cpp b/engines/zvision/text/subtitles.cpp
index acf4c37c2f..ffc9e2b808 100644
--- a/engines/zvision/text/subtitles.cpp
+++ b/engines/zvision/text/subtitles.cpp
@@ -27,7 +27,7 @@
namespace ZVision {
-Subtitle::Subtitle(ZVision *engine, const Common::String &subname) :
+Subtitle::Subtitle(ZVision *engine, const Common::String &subname, bool upscaleToHires) :
_engine(engine),
_areaId(-1),
_subId(-1) {
@@ -44,6 +44,8 @@ Subtitle::Subtitle(ZVision *engine, const Common::String &subname) :
int32 x1, y1, x2, y2;
sscanf(str.c_str(), "%*[^:]:%d %d %d %d", &x1, &y1, &x2, &y2);
Common::Rect rct = Common::Rect(x1, y1, x2, y2);
+ if (upscaleToHires)
+ _engine->getRenderManager()->upscaleRect(rct);
_areaId = _engine->getRenderManager()->createSubArea(rct);
} else if (str.matchString("*TextFile*", true)) {
char filename[64];
@@ -67,6 +69,11 @@ Subtitle::Subtitle(ZVision *engine, const Common::String &subname) :
int32 sb;
if (sscanf(str.c_str(), "%*[^:]:(%d,%d)=%d", &st, &en, &sb) == 3) {
if (sb <= (int32)_subs.size()) {
+ if (upscaleToHires) {
+ // Convert from 15FPS (AVI) to 29.97FPS (VOB)
+ st = st * 2997 / 1500;
+ en = en * 2997 / 1500;
+ }
_subs[sb].start = st;
_subs[sb].stop = en;
}
diff --git a/engines/zvision/text/subtitles.h b/engines/zvision/text/subtitles.h
index c3da6583a4..329339be55 100644
--- a/engines/zvision/text/subtitles.h
+++ b/engines/zvision/text/subtitles.h
@@ -31,7 +31,7 @@ class ZVision;
class Subtitle {
public:
- Subtitle(ZVision *engine, const Common::String &subname);
+ Subtitle(ZVision *engine, const Common::String &subname, bool upscaleToHires = false);
~Subtitle();
void process(int32 time);
diff --git a/engines/zvision/video/video.cpp b/engines/zvision/video/video.cpp
index 0913b28818..d5ffbeb536 100644
--- a/engines/zvision/video/video.cpp
+++ b/engines/zvision/video/video.cpp
@@ -23,13 +23,16 @@
#include "common/scummsys.h"
#include "common/system.h"
#include "video/video_decoder.h"
+#ifdef USE_MPEG2
+#include "video/mpegps_decoder.h"
+#endif
#include "engines/util.h"
#include "graphics/surface.h"
#include "zvision/zvision.h"
#include "zvision/core/clock.h"
#include "zvision/graphics/render_manager.h"
-#include "zvision/scripting//script_manager.h"
+#include "zvision/scripting/script_manager.h"
#include "zvision/text/subtitles.h"
#include "zvision/video/rlf_decoder.h"
#include "zvision/video/zork_avi_decoder.h"
@@ -45,6 +48,10 @@ Video::VideoDecoder *ZVision::loadAnimation(const Common::String &fileName) {
animation = new RLFDecoder();
else if (tmpFileName.hasSuffix(".avi"))
animation = new ZorkAVIDecoder();
+#ifdef USE_MPEG2
+ else if (tmpFileName.hasSuffix(".vob"))
+ animation = new Video::MPEGPSDecoder();
+#endif
else
error("Unknown suffix for animation %s", fileName.c_str());
diff --git a/engines/zvision/zvision.cpp b/engines/zvision/zvision.cpp
index 54991aced3..b42906fef3 100644
--- a/engines/zvision/zvision.cpp
+++ b/engines/zvision/zvision.cpp
@@ -52,7 +52,7 @@
namespace ZVision {
-#define ZVISION_SETTINGS_KEYS_COUNT 11
+#define ZVISION_SETTINGS_KEYS_COUNT 12
struct zvisionIniSettings {
const char *name;
@@ -73,7 +73,8 @@ struct zvisionIniSettings {
{"panarotatespeed", StateKey_RotateSpeed, 540, false, true}, // checked by universe.scr
{"noanimwhileturning", StateKey_NoTurnAnim, -1, false, true}, // toggle playing animations during pana rotation
{"venusenabled", StateKey_VenusEnable, -1, true, true},
- {"subtitles", StateKey_Subtitles, -1, true, true}
+ {"subtitles", StateKey_Subtitles, -1, true, true},
+ {"mpegmovies", StateKey_MPEGMovies, -1, true, true} // Zork: Grand Inquisitor DVD hi-res MPEG movies (0 = normal, 1 = hires, 2 = disable option)
};
ZVision::ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc)
@@ -105,15 +106,6 @@ ZVision::ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc)
debug(1, "ZVision::ZVision");
- uint16 workingWindowWidth = (gameDesc->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH;
- uint16 workingWindowHeight = (gameDesc->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT;
- _workingWindow = Common::Rect(
- (WINDOW_WIDTH - workingWindowWidth) / 2,
- (WINDOW_HEIGHT - workingWindowHeight) / 2,
- ((WINDOW_WIDTH - workingWindowWidth) / 2) + workingWindowWidth,
- ((WINDOW_HEIGHT - workingWindowHeight) / 2) + workingWindowHeight
- );
-
memset(_cheatBuffer, 0, sizeof(_cheatBuffer));
}
@@ -193,25 +185,17 @@ void ZVision::initialize() {
_searchManager->addDir("addon");
if (_gameDescription->gameId == GID_GRANDINQUISITOR) {
- _searchManager->loadZix("INQUIS.ZIX");
- _searchManager->addPatch("C000H01Q.RAW", "C000H01Q.SRC");
- _searchManager->addPatch("CM00H01Q.RAW", "CM00H01Q.SRC");
- _searchManager->addPatch("DM00H01Q.RAW", "DM00H01Q.SRC");
- _searchManager->addPatch("E000H01Q.RAW", "E000H01Q.SRC");
- _searchManager->addPatch("EM00H50Q.RAW", "EM00H50Q.SRC");
- _searchManager->addPatch("GJNPH65P.RAW", "GJNPH65P.SRC");
- _searchManager->addPatch("GJNPH72P.RAW", "GJNPH72P.SRC");
- _searchManager->addPatch("H000H01Q.RAW", "H000H01Q.SRC");
- _searchManager->addPatch("M000H01Q.RAW", "M000H01Q.SRC");
- _searchManager->addPatch("P000H01Q.RAW", "P000H01Q.SRC");
- _searchManager->addPatch("Q000H01Q.RAW", "Q000H01Q.SRC");
- _searchManager->addPatch("SW00H01Q.RAW", "SW00H01Q.SRC");
- _searchManager->addPatch("T000H01Q.RAW", "T000H01Q.SRC");
- _searchManager->addPatch("U000H01Q.RAW", "U000H01Q.SRC");
- } else if (_gameDescription->gameId == GID_NEMESIS)
- _searchManager->loadZix("NEMESIS.ZIX");
+ if (!_searchManager->loadZix("INQUIS.ZIX"))
+ error("Unable to load the game ZIX file");
+ } else if (_gameDescription->gameId == GID_NEMESIS) {
+ if (!_searchManager->loadZix("NEMESIS.ZIX")) {
+ // The game might not be installed, try MEDIUM.ZIX instead
+ if (!_searchManager->loadZix("ZNEMSCR/MEDIUM.ZIX"))
+ error("Unable to load the game ZIX file");
+ }
+ }
- initGraphics(WINDOW_WIDTH, WINDOW_HEIGHT, true, &_screenPixelFormat);
+ initScreen();
// Register random source
_rnd = new Common::RandomSource("zvision");
@@ -239,6 +223,11 @@ void ZVision::initialize() {
loadSettings();
+#ifndef USE_MPEG2
+ // libmpeg2 not loaded, disable the MPEG2 movies option
+ _scriptManager->setStateValue(StateKey_MPEGMovies, 2);
+#endif
+
// Create debugger console. It requires GFX to be initialized
_console = new Console(this);
_doubleFPS = ConfMan.getBool("doublefps");
@@ -358,4 +347,23 @@ void ZVision::fpsTimer() {
_renderedFrameCount = 0;
}
+void ZVision::initScreen() {
+ uint16 workingWindowWidth = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH;
+ uint16 workingWindowHeight = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT;
+ _workingWindow = Common::Rect(
+ (WINDOW_WIDTH - workingWindowWidth) / 2,
+ (WINDOW_HEIGHT - workingWindowHeight) / 2,
+ ((WINDOW_WIDTH - workingWindowWidth) / 2) + workingWindowWidth,
+ ((WINDOW_HEIGHT - workingWindowHeight) / 2) + workingWindowHeight
+ );
+
+ initGraphics(WINDOW_WIDTH, WINDOW_HEIGHT, true, &_screenPixelFormat);
+}
+
+void ZVision::initHiresScreen() {
+ _renderManager->upscaleRect(_workingWindow);
+
+ initGraphics(HIRES_WINDOW_WIDTH, HIRES_WINDOW_HEIGHT, true, &_screenPixelFormat);
+}
+
} // End of namespace ZVision
diff --git a/engines/zvision/zvision.h b/engines/zvision/zvision.h
index ad22ddaaa2..854cd77bb8 100644
--- a/engines/zvision/zvision.h
+++ b/engines/zvision/zvision.h
@@ -67,6 +67,27 @@ class TextRenderer;
class Subtitle;
class MidiManager;
+enum {
+ WINDOW_WIDTH = 640,
+ WINDOW_HEIGHT = 480,
+
+ HIRES_WINDOW_WIDTH = 800,
+ HIRES_WINDOW_HEIGHT = 600,
+
+ // Zork nemesis working window sizes
+ ZNM_WORKING_WINDOW_WIDTH = 512,
+ ZNM_WORKING_WINDOW_HEIGHT = 320,
+
+ // ZGI working window sizes
+ ZGI_WORKING_WINDOW_WIDTH = 640,
+ ZGI_WORKING_WINDOW_HEIGHT = 344,
+
+ ROTATION_SCREEN_EDGE_OFFSET = 60,
+ MAX_ROTATION_SPEED = 400, // Pixels per second
+
+ KEYBUF_SIZE = 20
+};
+
class ZVision : public Engine {
public:
ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc);
@@ -83,24 +104,6 @@ public:
const Graphics::PixelFormat _screenPixelFormat;
private:
- enum {
- WINDOW_WIDTH = 640,
- WINDOW_HEIGHT = 480,
-
- // Zork nemesis working window sizes
- ZNM_WORKING_WINDOW_WIDTH = 512,
- ZNM_WORKING_WINDOW_HEIGHT = 320,
-
- // ZGI working window sizes
- ZGI_WORKING_WINDOW_WIDTH = 640,
- ZGI_WORKING_WINDOW_HEIGHT = 344,
-
- ROTATION_SCREEN_EDGE_OFFSET = 60,
- MAX_ROTATION_SPEED = 400, // Pixels per second
-
- KEYBUF_SIZE = 20
- };
-
Console *_console;
const ZVisionGameDescription *_gameDescription;
@@ -194,6 +197,9 @@ public:
_clock.stop();
}
+ void initScreen();
+ void initHiresScreen();
+
/**
* Play a video until it is finished. This is a blocking call. It will call
* _clock.stop() when the video starts and _clock.start() when the video finishes.
diff --git a/video/module.mk b/video/module.mk
index 5754350e42..be014598f2 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
coktel_decoder.o \
dxa_decoder.o \
flic_decoder.o \
+ mpegps_decoder.o \
psx_decoder.o \
qt_decoder.o \
smk_decoder.o \
diff --git a/video/mpegps_decoder.cpp b/video/mpegps_decoder.cpp
new file mode 100644
index 0000000000..d8f7f5a68c
--- /dev/null
+++ b/video/mpegps_decoder.cpp
@@ -0,0 +1,732 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "video/mpegps_decoder.h"
+#include "image/codecs/mpeg.h"
+
+// The demuxing code is based on libav's demuxing code
+
+namespace Video {
+
+enum {
+ kStartCodePack = 0x1BA,
+ kStartCodeSystemHeader = 0x1BB,
+ kStartCodeProgramStreamMap = 0x1BC,
+ kStartCodePrivateStream1 = 0x1BD,
+ kStartCodePaddingStream = 0x1BE,
+ kStartCodePrivateStream2 = 0x1BF
+};
+
+MPEGPSDecoder::MPEGPSDecoder() {
+ _stream = 0;
+ memset(_psmESType, 0, 256);
+}
+
+MPEGPSDecoder::~MPEGPSDecoder() {
+ close();
+}
+
+bool MPEGPSDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ _stream = stream;
+
+ if (!addFirstVideoTrack()) {
+ close();
+ return false;
+ }
+
+ _stream->seek(0);
+ return true;
+}
+
+void MPEGPSDecoder::close() {
+ VideoDecoder::close();
+
+ delete _stream;
+ _stream = 0;
+
+ _streamMap.clear();
+
+ memset(_psmESType, 0, 256);
+}
+
+void MPEGPSDecoder::readNextPacket() {
+ if (_stream->eos())
+ return;
+
+ for (;;) {
+ int32 startCode;
+ uint32 pts, dts;
+ int size = readNextPacketHeader(startCode, pts, dts);
+
+ if (size < 0) {
+ // End of stream
+ for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo)
+ ((MPEGVideoTrack *)*it)->setEndOfTrack();
+ return;
+ }
+
+ MPEGStream *stream = 0;
+ Common::SeekableReadStream *packet = _stream->readStream(size);
+
+ if (_streamMap.contains(startCode)) {
+ // We already found the stream
+ stream = _streamMap[startCode];
+ } else {
+ // We haven't seen this before
+
+ if (startCode == kStartCodePrivateStream1) {
+ PrivateStreamType streamType = detectPrivateStreamType(packet);
+ packet->seek(0);
+
+ // TODO: Handling of these types (as needed)
+
+ const char *typeName;
+
+ switch (streamType) {
+ case kPrivateStreamAC3:
+ typeName = "AC-3";
+ break;
+ case kPrivateStreamDTS:
+ typeName = "DTS";
+ break;
+ case kPrivateStreamDVDPCM:
+ typeName = "DVD PCM";
+ break;
+ case kPrivateStreamPS2Audio:
+ typeName = "PS2 Audio";
+ break;
+ default:
+ typeName = "Unknown";
+ break;
+ }
+
+ warning("Unhandled DVD private stream: %s", typeName);
+
+ // Make it 0 so we don't get the warning twice
+ _streamMap[startCode] = 0;
+ } else if (startCode >= 0x1E0 && startCode <= 0x1EF) {
+ // Video stream
+ // TODO: Multiple video streams
+ warning("Found extra video stream 0x%04X", startCode);
+ _streamMap[startCode] = 0;
+ } else if (startCode >= 0x1C0 && startCode <= 0x1DF) {
+#ifdef USE_MAD
+ // MPEG Audio stream
+ MPEGAudioTrack *audioTrack = new MPEGAudioTrack(packet);
+ stream = audioTrack;
+ _streamMap[startCode] = audioTrack;
+ addTrack(audioTrack);
+#else
+ warning("Found audio stream 0x%04X, but no MAD support compiled in", startCode);
+ _streamMap[startCode] = 0;
+#endif
+ } else {
+ // Probably not relevant
+ debug(0, "Found unhandled MPEG-PS stream type 0x%04x", startCode);
+ _streamMap[startCode] = 0;
+ }
+ }
+
+ if (stream) {
+ bool done = stream->sendPacket(packet, pts, dts);
+
+ if (done && stream->getStreamType() == MPEGStream::kStreamTypeVideo)
+ return;
+ } else {
+ delete packet;
+ }
+ }
+}
+
+#define MAX_SYNC_SIZE 100000
+
+int MPEGPSDecoder::findNextStartCode(uint32 &size) {
+ size = MAX_SYNC_SIZE;
+ int32 state = 0xFF;
+
+ while (size > 0) {
+ byte v = _stream->readByte();
+
+ if (_stream->eos())
+ return -1;
+
+ size--;
+
+ if (state == 0x1)
+ return ((state << 8) | v) & 0xFFFFFF;
+
+ state = ((state << 8) | v) & 0xFFFFFF;
+ }
+
+ return -1;
+}
+
+int MPEGPSDecoder::readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts) {
+ for (;;) {
+ uint32 size;
+ startCode = findNextStartCode(size);
+
+ if (_stream->eos())
+ return -1;
+
+ if (startCode < 0)
+ continue;
+
+ uint32 lastSync = _stream->pos();
+
+ if (startCode == kStartCodePack || startCode == kStartCodeSystemHeader)
+ continue;
+
+ int length = _stream->readUint16BE();
+
+ if (startCode == kStartCodePaddingStream || startCode == kStartCodePrivateStream2) {
+ _stream->skip(length);
+ continue;
+ }
+
+ if (startCode == kStartCodeProgramStreamMap) {
+ parseProgramStreamMap(length);
+ continue;
+ }
+
+ // Find matching stream
+ if (!((startCode >= 0x1C0 && startCode <= 0x1DF) ||
+ (startCode >= 0x1E0 && startCode <= 0x1EF) ||
+ startCode == kStartCodePrivateStream1 || startCode == 0x1FD))
+ continue;
+
+ // Stuffing
+ byte c;
+ for (;;) {
+ if (length < 1) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ c = _stream->readByte();
+ length--;
+
+ // XXX: for mpeg1, should test only bit 7
+ if (c != 0xFF)
+ break;
+ }
+
+ if ((c & 0xC0) == 0x40) {
+ // Buffer scale and size
+ _stream->readByte();
+ c = _stream->readByte();
+ length -= 2;
+ }
+
+ pts = 0xFFFFFFFF;
+ dts = 0xFFFFFFFF;
+
+ if ((c & 0xE0) == 0x20) {
+ dts = pts = readPTS(c);
+ length -= 4;
+
+ if (c & 0x10) {
+ dts = readPTS(-1);
+ length -= 5;
+ }
+ } else if ((c & 0xC0) == 0x80) {
+ // MPEG-2 PES
+ byte flags = _stream->readByte();
+ int headerLength = _stream->readByte();
+ length -= 2;
+
+ if (headerLength > length) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ length -= headerLength;
+
+ if (flags & 0x80) {
+ dts = pts = readPTS(-1);
+ headerLength -= 5;
+
+ if (flags & 0x40) {
+ dts = readPTS(-1);
+ headerLength -= 5;
+ }
+ }
+
+ if (flags & 0x3F && headerLength == 0) {
+ flags &= 0xC0;
+ warning("Further flags set but no bytes left");
+ }
+
+ if (flags & 0x01) { // PES extension
+ byte pesExt =_stream->readByte();
+ headerLength--;
+
+ // Skip PES private data, program packet sequence
+ int skip = (pesExt >> 4) & 0xB;
+ skip += skip & 0x9;
+
+ if (pesExt & 0x40 || skip > headerLength) {
+ warning("pesExt %x is invalid", pesExt);
+ pesExt = skip = 0;
+ } else {
+ _stream->skip(skip);
+ headerLength -= skip;
+ }
+
+ if (pesExt & 0x01) { // PES extension 2
+ byte ext2Length = _stream->readByte();
+ headerLength--;
+
+ if ((ext2Length & 0x7F) != 0) {
+ byte idExt = _stream->readByte();
+
+ if ((idExt & 0x80) == 0)
+ startCode = (startCode & 0xFF) << 8;
+
+ headerLength--;
+ }
+ }
+ }
+
+ if (headerLength < 0) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ _stream->skip(headerLength);
+ } else if (c != 0xF) {
+ continue;
+ }
+
+ if (length < 0) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ return length;
+ }
+}
+
+uint32 MPEGPSDecoder::readPTS(int c) {
+ byte buf[5];
+
+ buf[0] = (c < 0) ? _stream->readByte() : c;
+ _stream->read(buf + 1, 4);
+
+ return ((buf[0] & 0x0E) << 29) | ((READ_BE_UINT16(buf + 1) >> 1) << 15) | (READ_BE_UINT16(buf + 3) >> 1);
+}
+
+void MPEGPSDecoder::parseProgramStreamMap(int length) {
+ _stream->readByte();
+ _stream->readByte();
+
+ // skip program stream info
+ _stream->skip(_stream->readUint16BE());
+
+ int esMapLength = _stream->readUint16BE();
+
+ while (esMapLength >= 4) {
+ byte type = _stream->readByte();
+ byte esID = _stream->readByte();
+ uint16 esInfoLength = _stream->readUint16BE();
+
+ // Remember mapping from stream id to stream type
+ _psmESType[esID] = type;
+
+ // Skip program stream info
+ _stream->skip(esInfoLength);
+
+ esMapLength -= 4 + esInfoLength;
+ }
+
+ _stream->readUint32BE(); // CRC32
+}
+
+bool MPEGPSDecoder::addFirstVideoTrack() {
+ for (;;) {
+ int32 startCode;
+ uint32 pts, dts;
+ int size = readNextPacketHeader(startCode, pts, dts);
+
+ // End of stream? We failed
+ if (size < 0)
+ return false;
+
+ if (startCode >= 0x1E0 && startCode <= 0x1EF) {
+ // Video stream
+ // Can be MPEG-1/2 or MPEG-4/h.264. We'll assume the former and
+ // I hope we never need the latter.
+ Common::SeekableReadStream *firstPacket = _stream->readStream(size);
+ MPEGVideoTrack *track = new MPEGVideoTrack(firstPacket, getDefaultHighColorFormat());
+ addTrack(track);
+ _streamMap[startCode] = track;
+ delete firstPacket;
+ break;
+ }
+
+ _stream->skip(size);
+ }
+
+ return true;
+}
+
+MPEGPSDecoder::PrivateStreamType MPEGPSDecoder::detectPrivateStreamType(Common::SeekableReadStream *packet) {
+ uint32 dvdCode = packet->readUint32LE();
+ if (packet->eos())
+ return kPrivateStreamUnknown;
+
+ uint32 ps2Header = packet->readUint32BE();
+ if (!packet->eos() && ps2Header == MKTAG('S', 'S', 'h', 'd'))
+ return kPrivateStreamPS2Audio;
+
+ switch (dvdCode & 0xE0) {
+ case 0x80:
+ if ((dvdCode & 0xF8) == 0x88)
+ return kPrivateStreamDTS;
+
+ return kPrivateStreamAC3;
+ case 0xA0:
+ return kPrivateStreamDVDPCM;
+ }
+
+ return kPrivateStreamUnknown;
+}
+
+MPEGPSDecoder::MPEGVideoTrack::MPEGVideoTrack(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format) {
+ _surface = 0;
+ _endOfTrack = false;
+ _curFrame = -1;
+ _nextFrameStartTime = Audio::Timestamp(0, 27000000); // 27 MHz timer
+
+ findDimensions(firstPacket, format);
+
+#ifdef USE_MPEG2
+ _mpegDecoder = new Image::MPEGDecoder();
+#endif
+}
+
+MPEGPSDecoder::MPEGVideoTrack::~MPEGVideoTrack() {
+#ifdef USE_MPEG2
+ delete _mpegDecoder;
+#endif
+
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ }
+}
+
+uint16 MPEGPSDecoder::MPEGVideoTrack::getWidth() const {
+ return _surface ? _surface->w : 0;
+}
+
+uint16 MPEGPSDecoder::MPEGVideoTrack::getHeight() const {
+ return _surface ? _surface->h : 0;
+}
+
+Graphics::PixelFormat MPEGPSDecoder::MPEGVideoTrack::getPixelFormat() const {
+ if (!_surface)
+ return Graphics::PixelFormat();
+
+ return _surface->format;
+}
+
+const Graphics::Surface *MPEGPSDecoder::MPEGVideoTrack::decodeNextFrame() {
+ return _surface;
+}
+
+bool MPEGPSDecoder::MPEGVideoTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
+#ifdef USE_MPEG2
+ uint32 framePeriod;
+ bool foundFrame = _mpegDecoder->decodePacket(*packet, framePeriod, _surface);
+
+ if (foundFrame) {
+ _curFrame++;
+ _nextFrameStartTime = _nextFrameStartTime.addFrames(framePeriod);
+ }
+#endif
+
+ delete packet;
+
+#ifdef USE_MPEG2
+ return foundFrame;
+#else
+ return true;
+#endif
+}
+
+void MPEGPSDecoder::MPEGVideoTrack::findDimensions(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format) {
+ // First, check for the picture start code
+ if (firstPacket->readUint32BE() != 0x1B3)
+ error("Failed to detect MPEG sequence start");
+
+ // This is part of the bitstream, but there's really no purpose
+ // to use Common::BitStream just for this: 12 bits width, 12 bits
+ // height
+ uint16 width = firstPacket->readByte() << 4;
+ uint16 height = firstPacket->readByte();
+ width |= (height & 0xF0) >> 4;
+ height = ((height & 0x0F) << 8) | firstPacket->readByte();
+
+ debug(0, "MPEG dimensions: %dx%d", width, height);
+
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, format);
+
+ firstPacket->seek(0);
+}
+
+#ifdef USE_MAD
+
+// The audio code here is almost entirely based on what we do in mp3.cpp
+
+MPEGPSDecoder::MPEGAudioTrack::MPEGAudioTrack(Common::SeekableReadStream *firstPacket) {
+ // The MAD_BUFFER_GUARD must always contain zeros (the reason
+ // for this is that the Layer III Huffman decoder of libMAD
+ // may read a few bytes beyond the end of the input buffer).
+ memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD);
+
+ _state = MP3_STATE_INIT;
+ _audStream = 0;
+
+ // Find out our audio parameters
+ initStream(firstPacket);
+
+ while (_state != MP3_STATE_EOS)
+ readHeader(firstPacket);
+
+ _audStream = Audio::makeQueuingAudioStream(_frame.header.samplerate, MAD_NCHANNELS(&_frame.header) == 2);
+
+ deinitStream();
+
+ firstPacket->seek(0);
+ _state = MP3_STATE_INIT;
+}
+
+MPEGPSDecoder::MPEGAudioTrack::~MPEGAudioTrack() {
+ deinitStream();
+ delete _audStream;
+}
+
+static inline int scaleSample(mad_fixed_t sample) {
+ // round
+ sample += (1L << (MAD_F_FRACBITS - 16));
+
+ // clip
+ if (sample > MAD_F_ONE - 1)
+ sample = MAD_F_ONE - 1;
+ else if (sample < -MAD_F_ONE)
+ sample = -MAD_F_ONE;
+
+ // quantize and scale to not saturate when mixing a lot of channels
+ return sample >> (MAD_F_FRACBITS + 1 - 16);
+}
+
+bool MPEGPSDecoder::MPEGAudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
+ while (_state != MP3_STATE_EOS)
+ decodeMP3Data(packet);
+
+ _state = MP3_STATE_READY;
+ delete packet;
+ return true;
+}
+
+Audio::AudioStream *MPEGPSDecoder::MPEGAudioTrack::getAudioStream() const {
+ return _audStream;
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::initStream(Common::SeekableReadStream *packet) {
+ if (_state != MP3_STATE_INIT)
+ deinitStream();
+
+ // Init MAD
+ mad_stream_init(&_stream);
+ mad_frame_init(&_frame);
+ mad_synth_init(&_synth);
+
+ // Reset the stream data
+ packet->seek(0, SEEK_SET);
+
+ // Update state
+ _state = MP3_STATE_READY;
+
+ // Read the first few sample bytes
+ readMP3Data(packet);
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::deinitStream() {
+ if (_state == MP3_STATE_INIT)
+ return;
+
+ // Deinit MAD
+ mad_synth_finish(&_synth);
+ mad_frame_finish(&_frame);
+ mad_stream_finish(&_stream);
+
+ _state = MP3_STATE_EOS;
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::readMP3Data(Common::SeekableReadStream *packet) {
+ uint32 remaining = 0;
+
+ // Give up immediately if we already used up all data in the stream
+ if (packet->eos()) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ if (_stream.next_frame) {
+ // If there is still data in the MAD stream, we need to preserve it.
+ // Note that we use memmove, as we are reusing the same buffer,
+ // and hence the data regions we copy from and to may overlap.
+ remaining = _stream.bufend - _stream.next_frame;
+ assert(remaining < BUFFER_SIZE); // Paranoia check
+ memmove(_buf, _stream.next_frame, remaining);
+ }
+
+ memset(_buf + remaining, 0, BUFFER_SIZE - remaining);
+
+ // Try to read the next block
+ uint32 size = packet->read(_buf + remaining, BUFFER_SIZE - remaining);
+ if (size == 0) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ // Feed the data we just read into the stream decoder
+ _stream.error = MAD_ERROR_NONE;
+ mad_stream_buffer(&_stream, _buf, size + remaining);
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::readHeader(Common::SeekableReadStream *packet) {
+ if (_state != MP3_STATE_READY)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data(packet);
+
+ while (_state != MP3_STATE_EOS) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next header. Note: mad_frame_decode would do this for us, too.
+ // However, for seeking we don't want to decode the full frame (else it would
+ // be far too slow). Hence we perform this explicitly in a separate step.
+ if (mad_header_decode(&_frame.header, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ readMP3Data(packet); // Read more data
+ continue;
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ debug(6, "MPEGAudioTrack::readHeader(): Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MPEGAudioTrack::readHeader(): Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ break;
+ }
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::decodeMP3Data(Common::SeekableReadStream *packet) {
+ if (_state == MP3_STATE_INIT)
+ initStream(packet);
+
+ if (_state == MP3_STATE_EOS)
+ return;
+
+ do {
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data(packet);
+
+ while (_state == MP3_STATE_READY) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next frame
+ if (mad_frame_decode(&_frame, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ break; // Read more data
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here.
+ // These are normal and expected (caused by our frame skipping (i.e. "seeking")
+ // code above).
+ debug(6, "MPEGAudioTrack::decodeMP3Data(): Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MPEGAudioTrack::decodeMP3Data(): Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Synthesize PCM data
+ mad_synth_frame(&_synth, &_frame);
+
+ // Output it to our queue
+ if (_synth.pcm.length != 0) {
+ byte *buffer = (byte *)malloc(_synth.pcm.length * 2 * MAD_NCHANNELS(&_frame.header));
+ int16 *ptr = (int16 *)buffer;
+
+ for (int i = 0; i < _synth.pcm.length; i++) {
+ *ptr++ = (int16)scaleSample(_synth.pcm.samples[0][i]);
+
+ if (MAD_NCHANNELS(&_frame.header) == 2)
+ *ptr++ = (int16)scaleSample(_synth.pcm.samples[1][i]);
+ }
+
+ int flags = Audio::FLAG_16BITS;
+
+ if (_audStream->isStereo())
+ flags |= Audio::FLAG_STEREO;
+
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+
+ _audStream->queueBuffer(buffer, _synth.pcm.length * 2 * MAD_NCHANNELS(&_frame.header), DisposeAfterUse::YES, flags);
+ }
+ break;
+ }
+ } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN);
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+#endif
+
+} // End of namespace Video
diff --git a/video/mpegps_decoder.h b/video/mpegps_decoder.h
new file mode 100644
index 0000000000..0184d6f9ba
--- /dev/null
+++ b/video/mpegps_decoder.h
@@ -0,0 +1,189 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef VIDEO_MPEGPS_DECODER_H
+#define VIDEO_MPEGPS_DECODER_H
+
+#include "common/hashmap.h"
+#include "graphics/surface.h"
+#include "video/video_decoder.h"
+
+#ifdef USE_MAD
+#include <mad.h>
+#endif
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct PixelFormat;
+}
+
+namespace Image {
+class MPEGDecoder;
+}
+
+namespace Video {
+
+/**
+ * Decoder for MPEG Program Stream videos.
+ * Video decoder used in engines:
+ * - zvision
+ */
+class MPEGPSDecoder : public VideoDecoder {
+public:
+ MPEGPSDecoder();
+ virtual ~MPEGPSDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+
+protected:
+ void readNextPacket();
+ bool useAudioSync() const { return false; }
+
+private:
+ // Base class for handling MPEG streams
+ class MPEGStream {
+ public:
+ virtual ~MPEGStream() {}
+
+ enum StreamType {
+ kStreamTypeVideo,
+ kStreamTypeAudio
+ };
+
+ virtual bool sendPacket(Common::SeekableReadStream *firstPacket, uint32 pts, uint32 dts) = 0;
+ virtual StreamType getStreamType() const = 0;
+ };
+
+ // An MPEG 1/2 video track
+ class MPEGVideoTrack : public VideoTrack, public MPEGStream {
+ public:
+ MPEGVideoTrack(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format);
+ ~MPEGVideoTrack();
+
+ bool endOfTrack() const { return _endOfTrack; }
+ uint16 getWidth() const;
+ uint16 getHeight() const;
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ uint32 getNextFrameStartTime() const { return _nextFrameStartTime.msecs(); }
+ const Graphics::Surface *decodeNextFrame();
+
+ bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
+ StreamType getStreamType() const { return kStreamTypeVideo; }
+
+ void setEndOfTrack() { _endOfTrack = true; }
+
+ private:
+ bool _endOfTrack;
+ int _curFrame;
+ Audio::Timestamp _nextFrameStartTime;
+ Graphics::Surface *_surface;
+
+ void findDimensions(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format);
+
+#ifdef USE_MPEG2
+ Image::MPEGDecoder *_mpegDecoder;
+#endif
+ };
+
+#ifdef USE_MAD
+ // An MPEG audio track
+ // TODO: Merge this with the normal MP3Stream somehow
+ class MPEGAudioTrack : public AudioTrack, public MPEGStream {
+ public:
+ MPEGAudioTrack(Common::SeekableReadStream *firstPacket);
+ ~MPEGAudioTrack();
+
+ bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
+ StreamType getStreamType() const { return kStreamTypeAudio; }
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audStream;
+
+ enum State {
+ MP3_STATE_INIT, // Need to init the decoder
+ MP3_STATE_READY, // ready for processing data
+ MP3_STATE_EOS // end of data reached (may need to loop)
+ };
+
+ State _state;
+
+ mad_stream _stream;
+ mad_frame _frame;
+ mad_synth _synth;
+
+ enum {
+ BUFFER_SIZE = 5 * 8192
+ };
+
+ // This buffer contains a slab of input data
+ byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+ void initStream(Common::SeekableReadStream *packet);
+ void deinitStream();
+ void readMP3Data(Common::SeekableReadStream *packet);
+ void readHeader(Common::SeekableReadStream *packet);
+ void decodeMP3Data(Common::SeekableReadStream *packet);
+ };
+#endif
+
+ // The different types of private streams we can detect at the moment
+ enum PrivateStreamType {
+ kPrivateStreamUnknown,
+ kPrivateStreamAC3,
+ kPrivateStreamDTS,
+ kPrivateStreamDVDPCM,
+ kPrivateStreamPS2Audio
+ };
+
+ PrivateStreamType detectPrivateStreamType(Common::SeekableReadStream *packet);
+
+ bool addFirstVideoTrack();
+
+ int readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts);
+ int findNextStartCode(uint32 &size);
+ uint32 readPTS(int c);
+
+ void parseProgramStreamMap(int length);
+ byte _psmESType[256];
+
+ // A map from stream types to stream handlers
+ typedef Common::HashMap<int, MPEGStream *> StreamMap;
+ StreamMap _streamMap;
+
+ Common::SeekableReadStream *_stream;
+};
+
+} // End of namespace Video
+
+#endif