aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sword2/animation.cpp10
-rw-r--r--engines/sword2/anims.cpp2
-rw-r--r--engines/sword2/console.cpp2
-rw-r--r--engines/sword2/controls.cpp7
-rw-r--r--engines/sword2/function.cpp15
-rw-r--r--engines/sword2/header.h158
-rw-r--r--engines/sword2/icons.cpp17
-rw-r--r--engines/sword2/layers.cpp98
-rw-r--r--engines/sword2/logic.cpp8
-rw-r--r--engines/sword2/logic.h3
-rw-r--r--engines/sword2/maketext.cpp145
-rw-r--r--engines/sword2/menu.cpp60
-rw-r--r--engines/sword2/mouse.cpp95
-rw-r--r--engines/sword2/mouse.h5
-rw-r--r--engines/sword2/music.cpp17
-rw-r--r--engines/sword2/palette.cpp13
-rw-r--r--engines/sword2/protocol.cpp288
-rw-r--r--engines/sword2/render.cpp275
-rw-r--r--engines/sword2/resman.cpp84
-rw-r--r--engines/sword2/resman.h9
-rw-r--r--engines/sword2/screen.cpp101
-rw-r--r--engines/sword2/screen.h32
-rw-r--r--engines/sword2/sound.cpp50
-rw-r--r--engines/sword2/sound.h2
-rw-r--r--engines/sword2/sprite.cpp239
-rw-r--r--engines/sword2/sword2.cpp92
-rw-r--r--engines/sword2/sword2.h17
27 files changed, 1650 insertions, 194 deletions
diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp
index 57b6235b0a..73877326e4 100644
--- a/engines/sword2/animation.cpp
+++ b/engines/sword2/animation.cpp
@@ -35,6 +35,7 @@
#include "sword2/maketext.h"
#include "sword2/resman.h"
#include "sword2/sound.h"
+#include "sword2/screen.h"
#include "sword2/animation.h"
#include "gui/message.h"
@@ -207,6 +208,7 @@ void MoviePlayer::openTextObject(uint32 index) {
text->_textSprite.h = frame.height;
text->_textSprite.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION;
text->_textSprite.data = text->_textMem + FrameHeader::size();
+ text->_textSprite.isText = true;
_vm->_screen->createSurface(&text->_textSprite, &_textSurface);
_textX = 320 - text->_textSprite.w / 2;
@@ -239,6 +241,14 @@ void MoviePlayer::drawTextObject(uint32 index, byte *screen) {
uint16 width = text->_textSprite.w;
uint16 height = text->_textSprite.h;
+ // Resize text sprites for PSX version
+ if (Sword2Engine::isPsx()) {
+ height *= 2;
+ byte *buffer = (byte *)malloc(width * height);
+ Screen::resizePsxSprite(buffer, src, width, height);
+ src = buffer;
+ }
+
byte *dst = screen + _textY * _decoder->getWidth() + _textX;
for (int y = 0; y < height; y++) {
diff --git a/engines/sword2/anims.cpp b/engines/sword2/anims.cpp
index 1bf3967047..dae300d0bd 100644
--- a/engines/sword2/anims.cpp
+++ b/engines/sword2/anims.cpp
@@ -103,7 +103,7 @@ int Router::doAnimate(byte *ob_logic, byte *ob_graph, int32 animRes, bool revers
// point to anim header
anim_head.read(_vm->fetchAnimHeader(anim_file));
-
+
// now running an anim, looping back to this call again
obLogic.setLooping(1);
obGraph.setAnimResource(animRes);
diff --git a/engines/sword2/console.cpp b/engines/sword2/console.cpp
index 3d2b89df55..eb3b885cf7 100644
--- a/engines/sword2/console.cpp
+++ b/engines/sword2/console.cpp
@@ -274,7 +274,7 @@ bool Debugger::Cmd_Res(int argc, const char **argv) {
bool Debugger::Cmd_ResList(int argc, const char **argv) {
// By default, list only resources that are being held open.
- uint minCount = 1;
+ uint32 minCount = 1;
if (argc > 1)
minCount = atoi(argv[1]);
diff --git a/engines/sword2/controls.cpp b/engines/sword2/controls.cpp
index 2596fe594e..2de831aebf 100644
--- a/engines/sword2/controls.cpp
+++ b/engines/sword2/controls.cpp
@@ -268,6 +268,13 @@ Dialog::Dialog(Sword2Engine *vm)
// Usually the mouse pointer will already be "normal", but not always.
_vm->_mouse->setMouse(NORMAL_MOUSE_ID);
+
+ // Force mouse mode as system menu: normally not needed,
+ // but value is not correct in case of game start dialog
+ // (when asking to restart or load a game).
+ // This is forced to avoid GMM loading/saving being enabled
+ // during initial dialog.
+ _vm->_mouse->setMouseMode(MOUSE_system_menu);
}
Dialog::~Dialog() {
diff --git a/engines/sword2/function.cpp b/engines/sword2/function.cpp
index 6c6c4939cb..259fdb2f5d 100644
--- a/engines/sword2/function.cpp
+++ b/engines/sword2/function.cpp
@@ -71,8 +71,11 @@ int32 Logic::fnInitBackground(int32 *params) {
// params: 0 res id of normal background layer - cannot be 0
// 1 1 yes 0 no for a new palette
-
- _vm->_screen->initBackground(params[0], params[1]);
+
+ if (Sword2Engine::isPsx())
+ _vm->_screen->initPsxBackground(params[0], params[1]);
+ else
+ _vm->_screen->initBackground(params[0], params[1]);
return IR_CONT;
}
@@ -392,13 +395,13 @@ int32 Logic::fnSetFrame(int32 *params) {
assert(_vm->_resman->fetchType(res) == ANIMATION_FILE);
// set up pointer to the animation header
- AnimHeader anim_head;
-
+ AnimHeader anim_head;
+
anim_head.read(_vm->fetchAnimHeader(anim_file));
-
+
// set up anim resource in graphic object
ObjectGraphic obGraph(decodePtr(params[0]));
-
+
obGraph.setAnimResource(res);
obGraph.setAnimPc(params[2] ? anim_head.noAnimFrames - 1 : 0);
diff --git a/engines/sword2/header.h b/engines/sword2/header.h
index 8cd8c900f8..875ccbfc4d 100644
--- a/engines/sword2/header.h
+++ b/engines/sword2/header.h
@@ -159,15 +159,27 @@ struct AnimHeader {
void read(byte *addr) {
Common::MemoryReadStream readS(addr, size());
- runTimeComp = readS.readByte();
- noAnimFrames = readS.readUint16LE();
- feetStartX = readS.readUint16LE();
- feetStartY = readS.readUint16LE();
- feetStartDir = readS.readByte();
- feetEndX = readS.readUint16LE();
- feetEndY = readS.readUint16LE();
- feetEndDir = readS.readByte();
- blend = readS.readUint16LE();
+ if (Sword2Engine::isPsx()) {
+ noAnimFrames = readS.readUint16LE();
+ feetStartX = readS.readUint16LE();
+ feetStartY = readS.readUint16LE();
+ feetEndX = readS.readUint16LE();
+ feetEndY = readS.readUint16LE();
+ blend = readS.readUint16LE();
+ runTimeComp = readS.readByte();
+ feetStartDir = readS.readByte();
+ feetEndDir = readS.readByte();
+ } else {
+ runTimeComp = readS.readByte();
+ noAnimFrames = readS.readUint16LE();
+ feetStartX = readS.readUint16LE();
+ feetStartY = readS.readUint16LE();
+ feetStartDir = readS.readByte();
+ feetEndX = readS.readUint16LE();
+ feetEndY = readS.readUint16LE();
+ feetEndDir = readS.readByte();
+ blend = readS.readUint16LE();
+ }
}
void write(byte *addr) {
@@ -210,16 +222,27 @@ struct CdtEntry {
// corner at (x,y), otherwise see below...
static int size() {
- return 9;
+ if (Sword2Engine::isPsx())
+ return 12;
+ else
+ return 9;
}
void read(byte *addr) {
Common::MemoryReadStream readS(addr, size());
- x = readS.readUint16LE();
- y = readS.readUint16LE();
- frameOffset = readS.readUint32LE();
- frameType = readS.readByte();
+ if (Sword2Engine::isPsx()) {
+ readS.readByte(); // Skip a byte in psx version
+ x = readS.readUint16LE();
+ y = readS.readUint16LE();
+ frameOffset = readS.readUint32LE();
+ frameType = readS.readByte();
+ } else {
+ x = readS.readUint16LE();
+ y = readS.readUint16LE();
+ frameOffset = readS.readUint32LE();
+ frameType = readS.readByte();
+ }
}
void write(byte *addr) {
@@ -260,6 +283,11 @@ struct FrameHeader {
compSize = readS.readUint32LE();
width = readS.readUint16LE();
height = readS.readUint16LE();
+
+ if (Sword2Engine::isPsx()) { // In PSX version, frames are half height
+ height *= 2;
+ width = (width % 2) ? width + 1 : width;
+ }
}
void write(byte *addr) {
@@ -504,6 +532,108 @@ struct TextHeader {
// line of text,0
// line of text,0
+//----------------------------------------------------------
+// SCREENS.CLU file
+//----------------------------------------------------------
+// This file is present in PSX version of the game only.
+// It keeps parallax and background images, aligned at 1024 bytes
+// for faster access by the psx cd drive.
+//
+// SCREENS.CLU structure:
+// In first 2048 Bytes there's an offset table. Each entry is an
+// 32bit offset for a background/parallax group. If entry is 0, screen
+// does not exist.
+// To find matching screen for the location, you must count LOCATION_NO
+// words and then go to the corresponding offset indicated by last 32bit
+// word.
+// Each screen then begins with a PSXScreensEntry entry:
+
+struct PSXScreensEntry {
+ uint16 fgPlxXres; // If these values are 0, subsequent fgPlx* values must be
+ uint16 fgPlxYres; // ignored, as this means foreground parallax is not present.
+ uint32 fgPlxOffset; // This offset is relative, counting from the beginning of Resource Header
+ uint32 fgPlxSize; // Size of parallax, the size is aligned at 1024 bytes.
+ // fgPlxSize/1024 gives number of sector the parallax is divided into.
+ uint16 bgXres;
+ uint16 bgYres;
+ uint32 bgOffset; // relative
+ uint32 bgSize;
+ uint16 bgPlxXres; // same considerations for fg parallaxes apply
+ uint16 bgPlxYres;
+ uint32 bgPlxOffset; // relative
+ uint32 bgPlxSize;
+
+ static int size() {
+ return 36;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ bgPlxXres = readS.readUint16LE();
+ bgPlxYres = readS.readUint16LE();
+ bgPlxOffset = readS.readUint32LE();
+ bgPlxSize = readS.readUint32LE();
+ bgXres = readS.readUint16LE();
+ bgYres = readS.readUint16LE();
+ bgOffset = readS.readUint32LE();
+ bgSize = readS.readUint32LE();
+ fgPlxXres = readS.readUint16LE();
+ fgPlxYres = readS.readUint16LE();
+ fgPlxOffset = readS.readUint32LE();
+ fgPlxSize = readS.readUint32LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint16LE(bgPlxXres);
+ writeS.writeUint16LE(bgPlxYres);
+ writeS.writeUint32LE(bgPlxOffset);
+ writeS.writeUint32LE(bgPlxSize);
+ writeS.writeUint16LE(bgXres);
+ writeS.writeUint16LE(bgYres);
+ writeS.writeUint32LE(bgOffset);
+ writeS.writeUint32LE(bgSize);
+ writeS.writeUint16LE(fgPlxXres);
+ writeS.writeUint16LE(fgPlxYres);
+ writeS.writeUint32LE(fgPlxOffset);
+ writeS.writeUint32LE(fgPlxSize);
+ }
+};
+
+// PSXFontEntry is present in font resource file, it is used
+// to address a single char in the character atlas image.
+
+struct PSXFontEntry {
+ uint16 offset;
+ uint16 skipLines;
+ uint16 charWidth;
+ uint16 charHeight;
+
+ static int size() {
+ return 8;
+ }
+
+ void read(byte *addr) {
+ Common::MemoryReadStream readS(addr, size());
+
+ offset = readS.readUint16LE() / 2;
+ skipLines = readS.readUint16LE();
+ charWidth = readS.readUint16LE() / 2;
+ charHeight = readS.readUint16LE();
+ }
+
+ void write(byte *addr) {
+ Common::MemoryWriteStream writeS(addr, size());
+
+ writeS.writeUint16LE(offset);
+ writeS.writeUint16LE(skipLines);
+ writeS.writeUint16LE(charWidth);
+ writeS.writeUint16LE(charHeight);
+ }
+};
+
} // End of namespace Sword2
#endif
diff --git a/engines/sword2/icons.cpp b/engines/sword2/icons.cpp
index 5ecad3b8f7..4652fc0bc1 100644
--- a/engines/sword2/icons.cpp
+++ b/engines/sword2/icons.cpp
@@ -77,6 +77,12 @@ void Mouse::addSubject(int32 id, int32 ref) {
void Mouse::buildMenu() {
uint32 i, j;
+ byte menuIconWidth;
+
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
// Clear the temporary inventory list, since we are going to build a
// new one from scratch.
@@ -178,7 +184,7 @@ void Mouse::buildMenu() {
// greyed out one.
if (icon_coloured)
- icon += (RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ icon += (menuIconWidth * RDMENU_ICONDEEP);
}
setMenuIcon(RDMENU_BOTTOM, i, icon);
@@ -203,6 +209,13 @@ void Mouse::buildSystemMenu() {
RESTART_ICON
};
+ byte menuIconWidth;
+
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
+
// Build them all high in full colour - when one is clicked on all the
// rest will grey out.
@@ -213,7 +226,7 @@ void Mouse::buildSystemMenu() {
// is dead. Then SAVE is not available.
if (!_vm->_logic->readVar(DEAD) || icon_list[i] != SAVE_ICON)
- icon += (RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ icon += (menuIconWidth * RDMENU_ICONDEEP);
setMenuIcon(RDMENU_TOP, i, icon);
_vm->_resman->closeResource(icon_list[i]);
diff --git a/engines/sword2/layers.cpp b/engines/sword2/layers.cpp
index ba59889acb..c54003920f 100644
--- a/engines/sword2/layers.cpp
+++ b/engines/sword2/layers.cpp
@@ -179,7 +179,7 @@ void Screen::initBackground(int32 res, int32 new_palette) {
}
// Background parallax layers
-
+
for (i = 0; i < 2; i++) {
if (screenLayerTable.bg_parallax[i])
initialiseBackgroundLayer(_vm->fetchBackgroundParallaxLayer(file, i));
@@ -188,11 +188,11 @@ void Screen::initBackground(int32 res, int32 new_palette) {
}
// Normal backround layer
-
+
initialiseBackgroundLayer(_vm->fetchBackgroundLayer(file));
// Foreground parallax layers
-
+
for (i = 0; i < 2; i++) {
if (screenLayerTable.fg_parallax[i])
initialiseBackgroundLayer(_vm->fetchForegroundParallaxLayer(file, i));
@@ -203,4 +203,96 @@ void Screen::initBackground(int32 res, int32 new_palette) {
_vm->_resman->closeResource(_thisScreen.background_layer_id);
}
+/**
+ * This function is called when entering a new room, PSX edition
+ * @param res resource id of the normal background layer
+ * @param new_palette 1 for new palette, otherwise 0
+ */
+
+void Screen::initPsxBackground(int32 res, int32 new_palette) {
+ int i;
+
+ assert(res);
+
+ _vm->_sound->clearFxQueue(false);
+ waitForFade();
+
+ debug(1, "CHANGED TO LOCATION \"%s\"", _vm->_resman->fetchName(res));
+
+ _vm->_logic->writeVar(EXIT_CLICK_ID, 0);
+
+ // Close the previous screen, if one is open
+ if (_thisScreen.background_layer_id)
+ closeBackgroundLayer();
+
+ _thisScreen.background_layer_id = res;
+ _thisScreen.new_palette = new_palette;
+
+ // ok, now read the resource and pull out all the normal sort layer
+ // info/and set them up at the beginning of the sort list - why do it
+ // each cycle
+
+ byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);
+ ScreenHeader screen_head;
+
+ screen_head.read(_vm->fetchScreenHeader(file));
+ screen_head.height *= 2;
+
+ // set number of special sort layers
+ _thisScreen.number_of_layers = screen_head.noLayers;
+ _thisScreen.screen_wide = screen_head.width;
+ _thisScreen.screen_deep = screen_head.height;
+
+ debug(2, "layers=%d width=%d depth=%d", screen_head.noLayers, screen_head.width, screen_head.height);
+
+ // initialise the driver back buffer
+ setLocationMetrics(screen_head.width, screen_head.height);
+
+ for (i = 0; i < screen_head.noLayers; i++) {
+ debug(3, "init layer %d", i);
+
+ LayerHeader layer;
+
+ layer.read(_vm->fetchLayerHeader(file, i));
+ _sortList[i].layer_number = i + 1;
+ _sortList[i].sort_y = layer.y + layer.height;
+ }
+
+ // reset scroll offsets
+ _thisScreen.scroll_offset_x = 0;
+ _thisScreen.scroll_offset_y = 0;
+
+ if (screen_head.width > _screenWide || screen_head.height > _screenDeep) {
+ _thisScreen.scroll_flag = 2;
+
+ _thisScreen.max_scroll_offset_x = screen_head.width - _screenWide;
+ _thisScreen.max_scroll_offset_y = screen_head.height - (_screenDeep - (MENUDEEP * 2));
+ } else {
+ // The later fits on the phyiscal screen. Switch off scrolling.
+ _thisScreen.scroll_flag = 0;
+ }
+
+ resetRenderEngine();
+
+ // These are the physical screen coords where the system will try to
+ // maintain George's actual feet coords.
+
+ _thisScreen.feet_x = 320;
+ _thisScreen.feet_y = 340;
+
+ // Background parallax layers
+ initialisePsxParallaxLayer(_vm->fetchBackgroundParallaxLayer(file, 0));
+ initialisePsxParallaxLayer(NULL);
+
+ // Normal backround layer
+ initialisePsxBackgroundLayer(_vm->fetchBackgroundLayer(file));
+
+ // Foreground parallax layers
+ initialisePsxParallaxLayer(_vm->fetchForegroundParallaxLayer(file, 1));
+ initialisePsxParallaxLayer(NULL);
+
+ _vm->_resman->closeResource(_thisScreen.background_layer_id);
+
+}
+
} // End of namespace Sword2
diff --git a/engines/sword2/logic.cpp b/engines/sword2/logic.cpp
index 79bf544ec3..c790c69acf 100644
--- a/engines/sword2/logic.cpp
+++ b/engines/sword2/logic.cpp
@@ -284,4 +284,12 @@ void Logic::pauseMovie(bool pause) {
_moviePlayer->pauseMovie(pause);
}
+/**
+ * Read current location number from script vars
+ */
+
+uint32 Logic::getLocationNum() {
+ return readVar(LOCATION);
+}
+
} // End of namespace Sword2
diff --git a/engines/sword2/logic.h b/engines/sword2/logic.h
index bd89e50e46..d9e733c3dc 100644
--- a/engines/sword2/logic.h
+++ b/engines/sword2/logic.h
@@ -314,6 +314,9 @@ public:
void resetKillList();
void pauseMovie(bool pause);
+
+ // Read location number from script vars
+ uint32 getLocationNum();
};
} // End of namespace Sword2
diff --git a/engines/sword2/maketext.cpp b/engines/sword2/maketext.cpp
index 65a98b8f32..ccfeda24a2 100644
--- a/engines/sword2/maketext.cpp
+++ b/engines/sword2/maketext.cpp
@@ -59,8 +59,11 @@ namespace Sword2 {
#define MAX_LINES 30 // max character lines in output sprite
#define BORDER_COL 200 // source colour for character border (only
- // needed for remapping colours)
+ // needed for remapping colours)
+
#define LETTER_COL 193 // source colour for bulk of character ( " )
+#define LETTER_COL_PSX1 33
+#define LETTER_COL_PSX2 34
#define SPACE ' '
#define FIRST_CHAR SPACE // first character in character set
#define LAST_CHAR 255 // last character in character set
@@ -91,7 +94,10 @@ byte *FontRenderer::makeTextSprite(byte *sentence, uint16 maxWidth, uint8 pen, u
// of the resource.
if (fontRes == _vm->_speechFontId) {
- _lineSpacing = -6;
+ if (Sword2Engine::isPsx())
+ _lineSpacing = -4; // Text would be unreadable with psx font if linespacing is higher
+ else
+ _lineSpacing = -6;
_charSpacing = -3;
} else if (fontRes == CONSOLE_FONT_ID) {
_lineSpacing = 0;
@@ -214,6 +220,13 @@ byte *FontRenderer::buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, L
if (line[i].width > spriteWidth)
spriteWidth = line[i].width;
+
+ // Check that text sprite has even horizontal resolution in PSX version
+ // (needed to work around a problem in some sprites, which reports an odd
+ // number as horiz resolution, but then have the next even number as true width)
+ if (Sword2Engine::isPsx())
+ spriteWidth = (spriteWidth % 2) ? spriteWidth + 1 : spriteWidth;
+
// Find the total height of the text sprite: the total height of the
// text lines, plus the total height of the spacing between them.
@@ -234,6 +247,14 @@ byte *FontRenderer::buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, L
frame_head.width = spriteWidth;
frame_head.height = spriteHeight;
+ // Normally for PSX frame header we double the height
+ // of the sprite artificially to regain correct aspect
+ // ratio, but this is an "artificially generated" text
+ // sprite, which gets created with correct aspect, so
+ // fix the height.
+ if (Sword2Engine::isPsx())
+ frame_head.height /= 2;
+
frame_head.write(textSprite);
debug(4, "Text sprite size: %ux%u", spriteWidth, spriteHeight);
@@ -264,13 +285,23 @@ byte *FontRenderer::buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, L
assert(frame_head.height == char_height);
copyChar(charPtr, spritePtr, spriteWidth, pen);
+
+ // We must remember to free memory for generated character in psx,
+ // as it is extracted differently than pc version (copyed from a
+ // char atlas).
+ if (Sword2Engine::isPsx())
+ free(charPtr);
+
spritePtr += frame_head.width + _charSpacing;
}
// Skip space at end of last word in this line
pos++;
-
- linePtr += (char_height + _lineSpacing) * spriteWidth;
+
+ if (Sword2Engine::isPsx())
+ linePtr += (char_height / 2 + _lineSpacing) * spriteWidth;
+ else
+ linePtr += (char_height + _lineSpacing) * spriteWidth;
}
_vm->_resman->closeResource(fontRes);
@@ -286,10 +317,17 @@ byte *FontRenderer::buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, L
uint16 FontRenderer::charWidth(byte ch, uint32 fontRes) {
byte *charSet = _vm->_resman->openResource(fontRes);
+ byte *charBuf;
FrameHeader frame_head;
- frame_head.read(findChar(ch, charSet));
+ charBuf = findChar(ch, charSet);
+
+ frame_head.read(charBuf);
+
+ if(Sword2Engine::isPsx())
+ free(charBuf);
+
_vm->_resman->closeResource(fontRes);
return frame_head.width;
@@ -307,10 +345,17 @@ uint16 FontRenderer::charWidth(byte ch, uint32 fontRes) {
uint16 FontRenderer::charHeight(uint32 fontRes) {
byte *charSet = _vm->_resman->openResource(fontRes);
+ byte *charbuf;
FrameHeader frame_head;
- frame_head.read(findChar(FIRST_CHAR, charSet));
+ charbuf = findChar(FIRST_CHAR, charSet);
+
+ frame_head.read(charbuf);
+
+ if(Sword2Engine::isPsx())
+ free(charbuf);
+
_vm->_resman->closeResource(fontRes);
return frame_head.height;
@@ -324,9 +369,77 @@ uint16 FontRenderer::charHeight(uint32 fontRes) {
*/
byte *FontRenderer::findChar(byte ch, byte *charSet) {
- if (ch < FIRST_CHAR)
- ch = DUD;
- return _vm->fetchFrameHeader(charSet, ch - FIRST_CHAR);
+
+ // PSX version doesn't use an animation table to keep all letters,
+ // instead a big sprite (char atlas) is used, and the single char
+ // must be extracted from that.
+
+ if (Sword2Engine::isPsx()) {
+ byte *buffer;
+ PSXFontEntry header;
+ FrameHeader bogusHeader;
+
+ charSet += ResHeader::size() + 2;
+
+ if (ch < FIRST_CHAR)
+ ch = DUD;
+
+ // Read font entry of the corresponding char.
+ header.read(charSet + PSXFontEntry::size() * (ch - 32));
+
+ // We have no such character, generate an empty one
+ // on the fly, size 6x12.
+ if (header.charWidth == 0) {
+
+ // Prepare a "bogus" FrameHeader to be returned with
+ // "empty" character data.
+ bogusHeader.compSize = 0;
+ bogusHeader.width = 6;
+ bogusHeader.height = 12;
+
+ buffer = (byte *)malloc(24 * 3 + FrameHeader::size());
+ memset(buffer, 0, 24 * 3 + FrameHeader::size());
+ bogusHeader.write(buffer);
+
+ return buffer;
+ }
+
+ buffer = (byte *)malloc(FrameHeader::size() + header.charWidth * header.charHeight * 4);
+ byte *tempchar = (byte *)malloc(header.charWidth * header.charHeight);
+
+ // Prepare the "bogus" header to be returned with character
+ bogusHeader.compSize = 0;
+ bogusHeader.width = header.charWidth * 2;
+ bogusHeader.height = header.charHeight;
+
+ // Go to the beginning of char atlas
+ charSet += 2062;
+
+ memset(buffer, 0, FrameHeader::size() + header.charWidth * header.charHeight * 4);
+
+ bogusHeader.write(buffer);
+
+ // Copy and stretch the char into destination buffer
+ for (int idx = 0; idx < header.charHeight; idx++) {
+ memcpy(tempchar + header.charWidth * idx, charSet + header.offset + 128 * (header.skipLines + idx), header.charWidth);
+ }
+
+ for (int line = 0; line < header.charHeight; line++) {
+ for (int col = 0; col < header.charWidth; col++) {
+ *(buffer + FrameHeader::size() + line * bogusHeader.width + col * 2) = *(tempchar + line * header.charWidth + col);
+ *(buffer + FrameHeader::size() + line * bogusHeader.width + col * 2 + 1) = *(tempchar + line * header.charWidth + col);
+ }
+ }
+
+ free(tempchar);
+
+ return buffer;
+
+ } else {
+ if (ch < FIRST_CHAR)
+ ch = DUD;
+ return _vm->fetchFrameHeader(charSet, ch - FIRST_CHAR);
+ }
}
/**
@@ -354,20 +467,23 @@ void FontRenderer::copyChar(byte *charPtr, byte *spritePtr, uint16 spriteWidth,
// Use the specified colours
for (uint j = 0; j < frame.width; j++) {
switch (*source++) {
+ case 0:
+ // Do nothing if source pixel is zero,
+ // ie. transparent
+ break;
+ case LETTER_COL_PSX1: // Values for colored zone
+ case LETTER_COL_PSX2:
case LETTER_COL:
*dest = pen;
break;
case BORDER_COL:
+ default:
// Don't do a border pixel if there's
// already a bit of another character
// underneath (for overlapping!)
if (!*dest)
*dest = _borderPen;
break;
- default:
- // Do nothing if source pixel is zero,
- // ie. transparent
- break;
}
dest++;
}
@@ -400,7 +516,7 @@ uint32 FontRenderer::buildNewBloc(byte *ascii, int16 x, int16 y, uint16 width, u
assert(i < MAX_text_blocs);
// Create and position the sprite
-
+
_blocList[i].text_mem = makeTextSprite(ascii, width, pen, fontRes);
// 'NO_JUSTIFICATION' means print sprite with top-left at (x,y)
@@ -498,6 +614,7 @@ void FontRenderer::printTextBlocs() {
spriteInfo.blend = 0;
spriteInfo.data = _blocList[i].text_mem + FrameHeader::size();
spriteInfo.colourTable = 0;
+ spriteInfo.isText = true;
uint32 rv = _vm->_screen->drawSprite(&spriteInfo);
if (rv)
diff --git a/engines/sword2/menu.cpp b/engines/sword2/menu.cpp
index 41a2e34c8b..c90a1a9df3 100644
--- a/engines/sword2/menu.cpp
+++ b/engines/sword2/menu.cpp
@@ -42,16 +42,25 @@ namespace Sword2 {
void Mouse::clearIconArea(int menu, int pocket, Common::Rect *r) {
byte *buf = _vm->_screen->getScreen();
int16 screenWide = _vm->_screen->getScreenWide();
+ byte menuIconWidth;
+
+ // Initialize menu icon width at correct size
+ // depending if we are using pc or psx version.
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
+
r->top = menu * (RENDERDEEP + MENUDEEP) + (MENUDEEP - RDMENU_ICONDEEP) / 2;
r->bottom = r->top + RDMENU_ICONDEEP;
- r->left = RDMENU_ICONSTART + pocket * (RDMENU_ICONWIDE + RDMENU_ICONSPACING);
- r->right = r->left + RDMENU_ICONWIDE;
+ r->left = RDMENU_ICONSTART + pocket * (menuIconWidth + RDMENU_ICONSPACING);
+ r->right = r->left + menuIconWidth;
byte *dst = buf + r->top * screenWide + r->left;
for (int i = 0; i < RDMENU_ICONDEEP; i++) {
- memset(dst, 0, RDMENU_ICONWIDE);
+ memset(dst, 0, menuIconWidth);
dst += screenWide;
}
}
@@ -71,6 +80,13 @@ void Mouse::processMenu() {
byte *buf = _vm->_screen->getScreen();
int16 screenWide = _vm->_screen->getScreenWide();
+ byte menuIconWidth;
+
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
+
if (lastTime == 0) {
lastTime = _vm->getMillis();
@@ -141,7 +157,7 @@ void Mouse::processMenu() {
_menuStatus[menu] = RDMENU_HIDDEN;
// Draw the menu here.
- int32 curx = RDMENU_ICONSTART + RDMENU_ICONWIDE / 2;
+ int32 curx = RDMENU_ICONSTART + menuIconWidth / 2;
int32 cury = (MENUDEEP / 2) + (RENDERDEEP + MENUDEEP) * menu;
for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
@@ -154,14 +170,14 @@ void Mouse::processMenu() {
clearIconArea(menu, i, &r1);
if (_pocketStatus[menu][i] == MAXMENUANIMS) {
- xoff = (RDMENU_ICONWIDE / 2);
+ xoff = (menuIconWidth / 2);
r2.left = curx - xoff;
- r2.right = r2.left + RDMENU_ICONWIDE;
+ r2.right = r2.left + menuIconWidth;
yoff = (RDMENU_ICONDEEP / 2);
r2.top = cury - yoff;
r2.bottom = r2.top + RDMENU_ICONDEEP;
} else {
- xoff = (RDMENU_ICONWIDE / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
+ xoff = (menuIconWidth / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
r2.left = curx - xoff;
r2.right = curx + xoff;
yoff = (RDMENU_ICONDEEP / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
@@ -176,18 +192,18 @@ void Mouse::processMenu() {
if (_pocketStatus[menu][i] != MAXMENUANIMS) {
_vm->_screen->scaleImageFast(
dst, screenWide, r2.right - r2.left, r2.bottom - r2.top,
- src, RDMENU_ICONWIDE, RDMENU_ICONWIDE, RDMENU_ICONDEEP);
+ src, menuIconWidth, menuIconWidth, RDMENU_ICONDEEP);
} else {
for (j = 0; j < RDMENU_ICONDEEP; j++) {
- memcpy(dst, src, RDMENU_ICONWIDE);
- src += RDMENU_ICONWIDE;
+ memcpy(dst, src, menuIconWidth);
+ src += menuIconWidth;
dst += screenWide;
}
}
}
_vm->_screen->updateRect(&r1);
}
- curx += (RDMENU_ICONSPACING + RDMENU_ICONWIDE);
+ curx += (RDMENU_ICONSPACING + menuIconWidth);
}
}
}
@@ -199,6 +215,13 @@ void Mouse::processMenu() {
*/
int32 Mouse::showMenu(uint8 menu) {
+
+ // Do not show menu in PSX version, as there was really
+ // nothing similar in the original game (menu was started
+ // using SELECT button in psx pad)
+ if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
+ return RD_OK;
+
// Check for invalid menu parameter
if (menu > RDMENU_BOTTOM)
return RDERR_INVALIDMENU;
@@ -219,6 +242,11 @@ int32 Mouse::showMenu(uint8 menu) {
*/
int32 Mouse::hideMenu(uint8 menu) {
+
+ // In PSX version, do nothing. There is no such menu.
+ if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
+ return RD_OK;
+
// Check for invalid menu parameter
if (menu > RDMENU_BOTTOM)
return RDERR_INVALIDMENU;
@@ -267,6 +295,12 @@ void Mouse::closeMenuImmediately() {
int32 Mouse::setMenuIcon(uint8 menu, uint8 pocket, byte *icon) {
Common::Rect r;
+ byte menuIconWidth;
+
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
// Check for invalid menu parameter.
if (menu > RDMENU_BOTTOM)
@@ -288,10 +322,10 @@ int32 Mouse::setMenuIcon(uint8 menu, uint8 pocket, byte *icon) {
// Only put the icon in the pocket if it is not NULL
if (icon != NULL) {
_iconCount++;
- _icons[menu][pocket] = (byte *)malloc(RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ _icons[menu][pocket] = (byte *)malloc(menuIconWidth * RDMENU_ICONDEEP);
if (_icons[menu][pocket] == NULL)
return RDERR_OUTOFMEMORY;
- memcpy(_icons[menu][pocket], icon, RDMENU_ICONWIDE * RDMENU_ICONDEEP);
+ memcpy(_icons[menu][pocket], icon, menuIconWidth * RDMENU_ICONDEEP);
}
return RD_OK;
diff --git a/engines/sword2/mouse.cpp b/engines/sword2/mouse.cpp
index af4f121e96..d940ec8a2d 100644
--- a/engines/sword2/mouse.cpp
+++ b/engines/sword2/mouse.cpp
@@ -280,14 +280,21 @@ bool Mouse::heldIsInInventory() {
int Mouse::menuClick(int menu_items) {
int x = getX();
+ byte menuIconWidth;
+
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
+
if (x < RDMENU_ICONSTART)
return -1;
- if (x > RDMENU_ICONSTART + menu_items * (RDMENU_ICONWIDE + RDMENU_ICONSPACING) - RDMENU_ICONSPACING)
+ if (x > RDMENU_ICONSTART + menu_items * (menuIconWidth + RDMENU_ICONSPACING) - RDMENU_ICONSPACING)
return -1;
- return (x - RDMENU_ICONSTART) / (RDMENU_ICONWIDE + RDMENU_ICONSPACING);
+ return (x - RDMENU_ICONSTART) / (menuIconWidth + RDMENU_ICONSPACING);
}
void Mouse::systemMenuMouse() {
@@ -330,6 +337,13 @@ void Mouse::systemMenuMouse() {
if (hit < 0)
return;
+ // Do nothing if using PSX version and are on TOP menu.
+
+ if ((icon_list[hit] == OPTIONS_ICON || icon_list[hit] == QUIT_ICON
+ || icon_list[hit] == SAVE_ICON || icon_list[hit] == RESTORE_ICON
+ || icon_list[hit] == RESTART_ICON ) && Sword2Engine::isPsx() )
+ return;
+
// No save when dead
if (icon_list[hit] == SAVE_ICON && _vm->_logic->readVar(DEAD))
@@ -857,6 +871,14 @@ uint32 Mouse::chooseMouse() {
// Unlike the other mouse "engines", this one is called directly by the
// fnChoose() opcode.
+ byte menuIconWidth;
+
+ if (Sword2Engine::isPsx())
+ menuIconWidth = RDMENU_PSXICONWIDE;
+ else
+ menuIconWidth = RDMENU_ICONWIDE;
+
+
uint i;
_vm->_logic->writeVar(AUTO_SELECTED, 0);
@@ -912,7 +934,7 @@ uint32 Mouse::chooseMouse() {
error("fnChoose with no subjects");
for (i = 0; i < in_subject; i++) {
- icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size() + RDMENU_ICONWIDE * RDMENU_ICONDEEP;
+ icon = _vm->_resman->openResource(_subjectList[i].res) + ResHeader::size() + menuIconWidth * RDMENU_ICONDEEP;
setMenuIcon(RDMENU_BOTTOM, i, icon);
_vm->_resman->closeResource(_subjectList[i].res);
}
@@ -1485,23 +1507,41 @@ void Mouse::decompressMouse(byte *decomp, byte *comp, uint8 frame, int width, in
int x = 0;
int y = 0;
- comp = comp + READ_LE_UINT32(comp + frame * 4) - MOUSE_ANIM_HEADER_SIZE;
+ if (Sword2Engine::isPsx()) {
+ comp = comp + READ_LE_UINT32(comp + 2 + frame * 4) - MOUSE_ANIM_HEADER_SIZE;
- while (i < size) {
- if (*comp > 183) {
- decomp[(y + yOff) * pitch + x + xOff] = *comp++;
- if (++x >= width) {
- x = 0;
- y++;
- }
- i++;
- } else {
- x += *comp;
- while (x >= width) {
- y++;
- x -= width;
+ yOff /= 2; // Without this, distance of object from cursor is too big.
+
+ byte *buffer;
+
+ buffer = (byte *)malloc(size);
+ Screen::decompressHIF(comp, buffer);
+
+ for (int line = 0; line < height; line++) {
+ memcpy(decomp + (line + yOff) * pitch + xOff, buffer + line * width, width);
+ }
+
+ free(buffer);
+
+ } else {
+ comp = comp + READ_LE_UINT32(comp + frame * 4) - MOUSE_ANIM_HEADER_SIZE;
+
+ while (i < size) {
+ if (*comp > 183) {
+ decomp[(y + yOff) * pitch + x + xOff] = *comp++;
+ if (++x >= width) {
+ x = 0;
+ y++;
+ }
+ i++;
+ } else {
+ x += *comp;
+ while (x >= width) {
+ y++;
+ x -= width;
+ }
+ i += *comp++;
}
- i += *comp++;
}
}
}
@@ -1563,6 +1603,17 @@ void Mouse::drawMouse() {
decompressMouse(mouseData, _mouseAnim.data, _mouseFrame,
_mouseAnim.mousew, _mouseAnim.mouseh, mouse_width);
+ // Fix height for mouse sprite in PSX version
+ if (Sword2Engine::isPsx()) {
+ mouse_height *= 2;
+
+ byte *buffer = (byte *)malloc(mouse_width * mouse_height);
+ Screen::resizePsxSprite(buffer, mouseData, mouse_width, mouse_height);
+
+ free(mouseData);
+ mouseData = buffer;
+ }
+
CursorMan.replaceCursor(mouseData, mouse_width, mouse_height, hotspot_x, hotspot_y, 0);
free(mouseData);
@@ -1675,4 +1726,12 @@ int32 Mouse::setLuggageAnim(byte *ma, int32 size) {
return RD_OK;
}
+int Mouse::getMouseMode() {
+ return _mouseMode;
+}
+
+void Mouse::setMouseMode(int mouseMode) {
+ _mouseMode = mouseMode;
+}
+
} // End of namespace Sword2
diff --git a/engines/sword2/mouse.h b/engines/sword2/mouse.h
index b87129ac7f..a45d786088 100644
--- a/engines/sword2/mouse.h
+++ b/engines/sword2/mouse.h
@@ -70,6 +70,7 @@ enum {
};
#define RDMENU_ICONWIDE 35
+#define RDMENU_PSXICONWIDE 36
#define RDMENU_ICONDEEP 30
#define RDMENU_ICONSTART 24
#define RDMENU_ICONSPACING 5
@@ -269,6 +270,10 @@ public:
uint32 chooseMouse();
int menuClick(int menu_items);
+
+ int getMouseMode();
+
+ void setMouseMode(int mouseMode); // Used to force mouse mode
};
} // End of namespace Sword2
diff --git a/engines/sword2/music.cpp b/engines/sword2/music.cpp
index 92af3d9276..4e20eebcf1 100644
--- a/engines/sword2/music.cpp
+++ b/engines/sword2/music.cpp
@@ -40,6 +40,7 @@
#include "sound/flac.h"
#include "sound/rate.h"
#include "sound/wave.h"
+#include "sound/vag.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
@@ -83,6 +84,7 @@ bool SafeSubReadStream::seek(int32 offset, int whence) {
}
static Audio::AudioStream *makeCLUStream(Common::File *fp, int size);
+static Audio::AudioStream *makePSXCLUStream(Common::File *fp, int size);
static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) {
bool alreadyOpen;
@@ -189,7 +191,10 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
switch (fh->fileType) {
case kCLUMode:
- return makeCLUStream(&fh->file, enc_len);
+ if (Sword2Engine::isPsx())
+ return makePSXCLUStream(&fh->file, enc_len);
+ else
+ return makeCLUStream(&fh->file, enc_len);
#ifdef USE_MAD
case kMP3Mode:
tmp = new SafeSubReadStream(&fh->file, pos, pos + enc_len, false);
@@ -289,6 +294,16 @@ Audio::AudioStream *makeCLUStream(Common::File *file, int size) {
return new CLUInputStream(file, size);
}
+Audio::AudioStream *makePSXCLUStream(Common::File *file, int size) {
+
+ // Buffer audio file data, and ask MemoryReadStream to dispose of it
+ // when not needed anymore.
+
+ byte *buffer = (byte *)malloc(size);
+ file->read(buffer, size);
+ return new Audio::VagStream(new Common::MemoryReadStream(buffer, size, true));
+}
+
// ----------------------------------------------------------------------------
// Another custom AudioStream class, to wrap around the various AudioStream
// classes used for music decompression, and to add looping, fading, etc.
diff --git a/engines/sword2/palette.cpp b/engines/sword2/palette.cpp
index eb9e73701a..6015f4bcf6 100644
--- a/engines/sword2/palette.cpp
+++ b/engines/sword2/palette.cpp
@@ -48,7 +48,11 @@ void Screen::startNewPalette() {
byte *screenFile = _vm->_resman->openResource(_thisScreen.background_layer_id);
- memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(screenFile), PALTABLESIZE);
+ // Don't fetch palette match table while using PSX version,
+ // because it is not present.
+ if(!Sword2Engine::isPsx())
+ memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(screenFile), PALTABLESIZE);
+
setPalette(0, 256, _vm->fetchPalette(screenFile), RDPAL_FADE);
// Indicating that it's a screen palette
@@ -116,7 +120,12 @@ void Screen::setFullPalette(int32 palRes) {
} else {
if (_thisScreen.background_layer_id) {
byte *data = _vm->_resman->openResource(_thisScreen.background_layer_id);
- memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(data), PALTABLESIZE);
+
+ // Do not fetch palette match table when using PSX version,
+ // because it is not present.
+ if (!Sword2Engine::isPsx())
+ memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(data), PALTABLESIZE);
+
setPalette(0, 256, _vm->fetchPalette(data), RDPAL_INSTANT);
_vm->_resman->closeResource(_thisScreen.background_layer_id);
} else
diff --git a/engines/sword2/protocol.cpp b/engines/sword2/protocol.cpp
index 65b236e2db..e5fbf53ee9 100644
--- a/engines/sword2/protocol.cpp
+++ b/engines/sword2/protocol.cpp
@@ -25,11 +25,13 @@
* $Id$
*/
-
+#include "common/file.h"
+#include "common/endian.h"
#include "sword2/sword2.h"
#include "sword2/header.h"
#include "sword2/resman.h"
+#include "sword2/logic.h"
namespace Sword2 {
@@ -39,11 +41,16 @@ namespace Sword2 {
*/
byte *Sword2Engine::fetchPalette(byte *screenFile) {
- MultiScreenHeader mscreenHeader;
+ byte *palette;
- mscreenHeader.read(screenFile + ResHeader::size());
+ if(isPsx()) { // PSX version doesn't have a "MultiScreenHeader", instead there's a ScreenHeader and a tag
+ palette = screenFile + ResHeader::size() + ScreenHeader::size() + 2;
+ } else {
+ MultiScreenHeader mscreenHeader;
- byte *palette = screenFile + ResHeader::size() + mscreenHeader.palette;
+ mscreenHeader.read(screenFile + ResHeader::size());
+ palette = screenFile + ResHeader::size() + mscreenHeader.palette;
+ }
// Always set colour 0 to black, because while most background screen
// palettes have a bright colour 0 it should come out as black in the
@@ -60,9 +67,14 @@ byte *Sword2Engine::fetchPalette(byte *screenFile) {
/**
* Returns a pointer to the start of the palette match table, given the pointer
* to the start of the screen file.
+ * It returns NULL when used with PSX version, as there are no palette match tables in
+ * the resource files.
*/
byte *Sword2Engine::fetchPaletteMatchTable(byte *screenFile) {
+
+ if (isPsx()) return NULL;
+
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
@@ -76,11 +88,14 @@ byte *Sword2Engine::fetchPaletteMatchTable(byte *screenFile) {
*/
byte *Sword2Engine::fetchScreenHeader(byte *screenFile) {
- MultiScreenHeader mscreenHeader;
-
- mscreenHeader.read(screenFile + ResHeader::size());
+ if (isPsx()) { // In PSX version there's no MultiScreenHeader, so just skip resource header
+ return screenFile + ResHeader::size();
+ } else {
+ MultiScreenHeader mscreenHeader;
- return screenFile + ResHeader::size() + mscreenHeader.screen;
+ mscreenHeader.read(screenFile + ResHeader::size());
+ return screenFile + ResHeader::size() + mscreenHeader.screen;
+ }
}
/**
@@ -97,19 +112,25 @@ byte *Sword2Engine::fetchLayerHeader(byte *screenFile, uint16 layerNo) {
assert(layerNo < screenHead.noLayers);
#endif
- MultiScreenHeader mscreenHeader;
-
- mscreenHeader.read(screenFile + ResHeader::size());
-
- return screenFile + ResHeader::size() + mscreenHeader.layers + layerNo * LayerHeader::size();
+ if (isPsx()) {
+ return screenFile + ResHeader::size() + ScreenHeader::size() + 2 + 0x400 + layerNo * LayerHeader::size();
+ } else {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ return screenFile + ResHeader::size() + mscreenHeader.layers + layerNo * LayerHeader::size();
+ }
}
/**
* Returns a pointer to the start of the shading mask, given the pointer to the
* start of the screen file.
+ * If we are non PSX, this will return NULL, as we don't have shading masks.
*/
byte *Sword2Engine::fetchShadingMask(byte *screenFile) {
+ if (isPsx()) return NULL;
+
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
@@ -139,7 +160,7 @@ byte *Sword2Engine::fetchCdtEntry(byte *animFile, uint16 frameNo) {
animHead.read(fetchAnimHeader(animFile));
if (frameNo > animHead->noAnimFrames - 1)
- error("fetchCdtEntry(animFile,%d) - anim only %d frames", frameNo, animHead.noAnimFrames);
+ error("fetchCdtEntry(animFile,%d) - anim only %d frames", frameNo, animHead->noAnimFrames);
#endif
return fetchAnimHeader(animFile) + AnimHeader::size() + frameNo * CdtEntry::size();
@@ -153,6 +174,7 @@ byte *Sword2Engine::fetchCdtEntry(byte *animFile, uint16 frameNo) {
byte *Sword2Engine::fetchFrameHeader(byte *animFile, uint16 frameNo) {
// required address = (address of the start of the anim header) + frameOffset
+
CdtEntry cdt;
cdt.read(fetchCdtEntry(animFile, frameNo));
@@ -165,30 +187,86 @@ byte *Sword2Engine::fetchFrameHeader(byte *animFile, uint16 frameNo) {
*/
byte *Sword2Engine::fetchBackgroundParallaxLayer(byte *screenFile, int layer) {
- MultiScreenHeader mscreenHeader;
-
- mscreenHeader.read(screenFile + ResHeader::size());
- assert(mscreenHeader.bg_parallax[layer]);
-
- return screenFile + ResHeader::size() + mscreenHeader.bg_parallax[layer];
+ if (isPsx()) {
+ byte *psxParallax = _screen->getPsxScrCache(0);
+
+ // Manage cache for background psx parallaxes
+ if (!_screen->getPsxScrCacheStatus(0)) { // This parallax layer is not present
+ return NULL;
+ } else if (psxParallax != NULL) { // Parallax layer present, and already in cache
+ return psxParallax;
+ } else { // Present, but not cached
+ uint32 locNo = _logic->getLocationNum();
+
+ // At game startup, we have a wrong location number stored
+ // in game vars (0, instead of 3), work around this.
+ locNo = (locNo == 0) ? 3 : locNo;
+
+ psxParallax = fetchPsxParallax(locNo, 0);
+ _screen->setPsxScrCache(psxParallax, 0);
+ return psxParallax;
+ }
+ } else {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ assert(mscreenHeader.bg_parallax[layer]);
+ return screenFile + ResHeader::size() + mscreenHeader.bg_parallax[layer];
+ }
}
byte *Sword2Engine::fetchBackgroundLayer(byte *screenFile) {
- MultiScreenHeader mscreenHeader;
-
- mscreenHeader.read(screenFile + ResHeader::size());
- assert(mscreenHeader.screen);
-
- return screenFile + ResHeader::size() + mscreenHeader.screen + ScreenHeader::size();
+ if (isPsx()) {
+ byte *psxBackground = _screen->getPsxScrCache(1);
+
+ // Manage cache for psx backgrounds
+ if (psxBackground) { // Background is cached
+ return psxBackground;
+ } else { // Background not cached
+ uint32 locNo = _logic->getLocationNum();
+
+ // We have a wrong location number at start, fix that
+ locNo = (locNo == 0) ? 3 : locNo;
+
+ psxBackground = fetchPsxBackground(locNo);
+ _screen->setPsxScrCache(psxBackground, 1);
+ return psxBackground;
+ }
+ } else {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ assert(mscreenHeader.screen);
+ return screenFile + ResHeader::size() + mscreenHeader.screen + ScreenHeader::size();
+ }
}
byte *Sword2Engine::fetchForegroundParallaxLayer(byte *screenFile, int layer) {
- MultiScreenHeader mscreenHeader;
-
- mscreenHeader.read(screenFile + ResHeader::size());
- assert(mscreenHeader.fg_parallax[layer]);
-
- return screenFile + ResHeader::size() + mscreenHeader.fg_parallax[layer];
+ if (isPsx()) {
+ byte *psxParallax = _screen->getPsxScrCache(2);
+
+ // Manage cache for psx parallaxes
+ if (!_screen->getPsxScrCacheStatus(2)) { // This parallax layer is not present
+ return NULL;
+ } else if (psxParallax) { // Parallax layer present and cached
+ return psxParallax;
+ } else { // Present, but still not cached
+ uint32 locNo = _logic->getLocationNum();
+
+ // We have a wrong location number at start, fix that
+ locNo = (locNo == 0) ? 3 : locNo;
+
+ psxParallax = fetchPsxParallax(locNo, 1);
+ _screen->setPsxScrCache(psxParallax, 2);
+ return psxParallax;
+ }
+ } else {
+ MultiScreenHeader mscreenHeader;
+
+ mscreenHeader.read(screenFile + ResHeader::size());
+ assert(mscreenHeader.fg_parallax[layer]);
+ return screenFile + ResHeader::size() + mscreenHeader.fg_parallax[layer];
+ }
}
byte *Sword2Engine::fetchTextLine(byte *file, uint32 text_line) {
@@ -211,6 +289,152 @@ byte *Sword2Engine::fetchTextLine(byte *file, uint32 text_line) {
return file + READ_LE_UINT32(file + ResHeader::size() + 4 + 4 * text_line);
}
+/**
+ * Returns a pointer to psx background data for passed location number
+ * At the beginning of the passed data there's an artificial header composed by
+ * uint16: background X resolution
+ * uint16: background Y resolution
+ * uint32: offset to subtract from offset table entries
+ */
+
+byte *Sword2Engine::fetchPsxBackground(uint32 location) {
+ Common::File file;
+ PSXScreensEntry header;
+ uint32 screenOffset, dataOffset;
+ uint32 totSize; // Total size of background, counting data, offset table and additional header
+ byte *buffer;
+
+ if (!file.open("screens.clu")) {
+ GUIErrorMessage("Broken Sword 2: Cannot open screens.clu");
+ return NULL;
+ }
+
+ file.seek(location * 4, SEEK_SET);
+ screenOffset = file.readUint32LE();
+
+ if (screenOffset == 0) { // We don't have screen data for this location number.
+ file.close();
+ return NULL;
+ }
+
+ // Get to the beginning of PSXScreensEntry
+ file.seek(screenOffset + ResHeader::size(), SEEK_SET);
+
+ buffer = (byte *)malloc(PSXScreensEntry::size());
+ file.read(buffer, PSXScreensEntry::size());
+
+ // Prepare the header
+ header.read(buffer);
+ free(buffer);
+
+ file.seek(screenOffset + header.bgOffset + 4, SEEK_SET);
+ dataOffset = file.readUint32LE();
+
+ file.seek(screenOffset + header.bgOffset, SEEK_SET);
+
+ totSize = header.bgSize + (dataOffset - header.bgOffset) + 8;
+ buffer = (byte *)malloc(totSize);
+
+ // Write some informations before background data
+ WRITE_LE_UINT16(buffer, header.bgXres);
+ WRITE_LE_UINT16(buffer + 2, header.bgYres);
+ WRITE_LE_UINT32(buffer + 4, header.bgOffset);
+
+ file.read(buffer + 8, totSize - 8); // Do not write on the header
+ file.close();
+
+ return buffer;
+}
+
+/**
+ * Returns a pointer to selected psx parallax data for passed location number
+ * At the beginning of the passed data there's an artificial header composed by
+ * uint16: parallax X resolution
+ * uint16: parallax Y resolution
+ * uint16: width in 64x16 tiles of parallax
+ * uint16: height in 64x16 tiles of parallax
+ */
+
+byte *Sword2Engine::fetchPsxParallax(uint32 location, uint8 level) {
+ Common::File file;
+ PSXScreensEntry header;
+ uint32 screenOffset;
+ uint16 horTiles; // Number of horizontal tiles in the parallax grid
+ uint16 verTiles; // Number of vertical tiles in parallax grid
+ uint32 totSize; // Total size of parallax, counting data, grid, and additional header
+ byte *buffer;
+
+ uint16 plxXres;
+ uint16 plxYres;
+ uint32 plxOffset;
+ uint32 plxSize;
+
+ if (level > 1)
+ return NULL;
+
+ if (!file.open("screens.clu")) {
+ GUIErrorMessage("Broken Sword 2: Cannot open screens.clu");
+ return NULL;
+ }
+
+ file.seek(location * 4, SEEK_SET);
+ screenOffset = file.readUint32LE();
+
+ if (screenOffset == 0) // There is no screen here
+ return NULL;
+
+ // Get to the beginning of PSXScreensEntry
+ file.seek(screenOffset + ResHeader::size(), SEEK_SET);
+
+ buffer = (byte *)malloc(PSXScreensEntry::size());
+ file.read(buffer, PSXScreensEntry::size());
+
+ // Initialize the header
+ header.read(buffer);
+ free(buffer);
+
+ // We are fetching...
+ if (level == 0) { // a background parallax
+ plxXres = header.bgPlxXres;
+ plxYres = header.bgPlxYres;
+ plxOffset = header.bgPlxOffset;
+ plxSize = header.bgPlxSize;
+ } else { // a foreground parallax
+ plxXres = header.fgPlxXres;
+ plxYres = header.fgPlxYres;
+ plxOffset = header.fgPlxOffset;
+ plxSize = header.fgPlxSize;
+ }
+
+ if (plxXres == 0 || plxYres == 0 || plxSize == 0) // This screen has no parallax data.
+ return NULL;
+
+ debug(2, "fetchPsxParallax() -> %s parallax, xRes: %u, yRes: %u", (level == 0) ? "Background" : "Foreground", plxXres, plxYres);
+
+ // Calculate the number of tiles which compose the parallax grid.
+ horTiles = plxXres % 64 ? (plxXres / 64) + 1 : plxXres / 64;
+ verTiles = plxYres % 16 ? (plxYres / 16) + 1 : plxYres / 16;
+
+ totSize = plxSize + horTiles * verTiles * 4 + 8;
+
+ file.seek(screenOffset + plxOffset, SEEK_SET);
+ buffer = (byte *)malloc(totSize);
+
+ // Insert parallax resolution information in the buffer,
+ // preceding parallax data.
+ WRITE_LE_UINT16(buffer, plxXres);
+ WRITE_LE_UINT16(buffer + 2, plxYres);
+ WRITE_LE_UINT16(buffer + 4, horTiles);
+ WRITE_LE_UINT16(buffer + 6, verTiles);
+
+ // Read parallax data from file and store it inside the buffer,
+ // skipping the generated header.
+ file.read(buffer + 8, totSize - 8);
+ file.close();
+
+ return buffer;
+}
+
// Used for testing text & speech (see fnISpeak in speech.cpp)
bool Sword2Engine::checkTextLine(byte *file, uint32 text_line) {
diff --git a/engines/sword2/render.cpp b/engines/sword2/render.cpp
index f5464efd4b..f9032187b9 100644
--- a/engines/sword2/render.cpp
+++ b/engines/sword2/render.cpp
@@ -252,21 +252,35 @@ void Screen::setLocationMetrics(uint16 w, uint16 h) {
*/
void Screen::renderParallax(byte *ptr, int16 l) {
- Parallax p;
int16 x, y;
+ uint16 xRes, yRes;
Common::Rect r;
- p.read(ptr);
+ if (!ptr)
+ return;
+
+ // Fetch resolution data from parallax
+
+ if (Sword2Engine::isPsx()) {
+ xRes = READ_LE_UINT16(ptr);
+ yRes = READ_LE_UINT16(ptr + 2) * 2;
+ } else {
+ Parallax p;
+
+ p.read(ptr);
+ xRes = p.w;
+ yRes = p.h;
+ }
if (_locationWide == _screenWide)
x = 0;
else
- x = ((int32)((p.w - _screenWide) * _scrollX) / (int32)(_locationWide - _screenWide));
+ x = ((int32)((xRes - _screenWide) * _scrollX) / (int32)(_locationWide - _screenWide));
if (_locationDeep == _screenDeep - MENUDEEP * 2)
y = 0;
else
- y = ((int32)((p.h - (_screenDeep - MENUDEEP * 2)) * _scrollY) / (int32)(_locationDeep - (_screenDeep - MENUDEEP * 2)));
+ y = ((int32)((yRes - (_screenDeep - MENUDEEP * 2)) * _scrollY) / (int32)(_locationDeep - (_screenDeep - MENUDEEP * 2)));
Common::Rect clipRect;
@@ -537,12 +551,265 @@ int32 Screen::initialiseBackgroundLayer(byte *parallax) {
}
/**
+ * This converts PSX format background data into a format that
+ * can be understood by renderParallax functions.
+ * PSX Backgrounds are divided into tiles of 64x32 (with aspect
+ * ratio correction), while PC backgrounds are in tiles of 64x64.
+ */
+
+int32 Screen::initialisePsxBackgroundLayer(byte *parallax) {
+ uint16 bgXres, bgYres;
+ uint16 trueXres, stripeNumber, totStripes;
+ uint32 baseAddress, stripePos;
+ uint16 i, j;
+ byte *dst;
+
+ debug(2, "initialisePsxBackgroundLayer");
+
+ assert(_layer < MAXLAYERS);
+
+ if (!parallax) {
+ _layer++;
+ return RD_OK;
+ }
+
+ // Fetch data from buffer
+
+ bgXres = READ_LE_UINT16(parallax);
+ bgYres = READ_LE_UINT16(parallax + 2) * 2;
+ baseAddress = READ_LE_UINT32(parallax + 4);
+ parallax += 8;
+
+ // Calculate TRUE resolution of background, must be
+ // a multiple of 64
+
+ trueXres = (bgXres % 64) ? ((bgXres/64) + 1) * 64 : bgXres;
+ totStripes = trueXres / 64;
+
+ _xBlocks[_layer] = (bgXres + BLOCKWIDTH - 1) / BLOCKWIDTH;
+ _yBlocks[_layer] = (bgYres + BLOCKHEIGHT - 1) / BLOCKHEIGHT;
+
+ uint16 remLines = bgYres % 64;
+
+ byte *tileChunk = (byte *)malloc(BLOCKHEIGHT * BLOCKWIDTH);
+ if (!tileChunk)
+ return RDERR_OUTOFMEMORY;
+
+ _blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *));
+ if (!_blockSurfaces[_layer])
+ return RDERR_OUTOFMEMORY;
+
+ // Group PSX background (64x32, when stretched vertically) tiles together,
+ // to make them compatible with pc version (composed by 64x64 tiles)
+
+ stripeNumber = 0;
+ stripePos = 0;
+ for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) {
+ bool block_has_data = false;
+ bool block_is_transparent = false;
+
+ int posX = i / _yBlocks[_layer];
+ int posY = i % _yBlocks[_layer];
+
+ uint32 stripeOffset = READ_LE_UINT32(parallax + stripeNumber * 8 + 4) + stripePos - baseAddress;
+
+ memset(tileChunk, 1, BLOCKHEIGHT * BLOCKWIDTH);
+
+ if (!(remLines && posY == _yBlocks[_layer] - 1))
+ remLines = 32;
+
+ for(j = 0; j < remLines; j++) {
+ memcpy(tileChunk + j * BLOCKWIDTH * 2, parallax + stripeOffset + j * BLOCKWIDTH, BLOCKWIDTH);
+ memcpy(tileChunk + j * BLOCKWIDTH * 2 + BLOCKWIDTH, parallax + stripeOffset + j * BLOCKWIDTH, BLOCKWIDTH);
+ }
+
+ for (j = 0; j < BLOCKHEIGHT * BLOCKWIDTH; j++) {
+ if (tileChunk[j])
+ block_has_data = true;
+ else
+ block_is_transparent = true;
+ }
+
+ int tileIndex = totStripes * posY + posX;
+
+ // Only assign a surface to the block if it contains data.
+
+ if (block_has_data) {
+ _blockSurfaces[_layer][tileIndex] = (BlockSurface *)malloc(sizeof(BlockSurface));
+
+ // Copy the data into the surfaces.
+ dst = _blockSurfaces[_layer][tileIndex]->data;
+ memcpy(dst, tileChunk, BLOCKWIDTH * BLOCKHEIGHT);
+
+ _blockSurfaces[_layer][tileIndex]->transparent = block_is_transparent;
+
+ } else
+ _blockSurfaces[_layer][tileIndex] = NULL;
+
+ if (posY == _yBlocks[_layer] - 1) {
+ stripeNumber++;
+ stripePos = 0;
+ } else {
+ stripePos += 0x800;
+ }
+ }
+
+ free(tileChunk);
+ _layer++;
+
+ return RD_OK;
+}
+
+/**
+ * This converts PSX format parallax data into a format that
+ * can be understood by renderParallax functions.
+ */
+
+int32 Screen::initialisePsxParallaxLayer(byte *parallax) {
+ uint16 plxXres, plxYres;
+ uint16 xTiles, yTiles;
+ uint16 i, j, k;
+ byte *data;
+ byte *dst;
+
+ debug(2, "initialisePsxParallaxLayer");
+
+ assert(_layer < MAXLAYERS);
+
+ if (!parallax) {
+ _layer++;
+ return RD_OK;
+ }
+
+ plxXres = READ_LE_UINT16(parallax);
+ plxYres = READ_LE_UINT16(parallax + 2);
+ xTiles = READ_LE_UINT16(parallax + 4);
+ yTiles = READ_LE_UINT16(parallax + 6);
+
+ // Beginning of parallax table composed by uint32,
+ // if word is 0, corresponding tile contains no data and must be skipped,
+ // if word is 0x400 tile contains data.
+ parallax += 8;
+
+ // Beginning if tiles data.
+ data = parallax + xTiles * yTiles * 4;
+
+ _xBlocks[_layer] = xTiles;
+ _yBlocks[_layer] = (yTiles / 2) + (yTiles % 2 ? 1 : 0);
+ bool oddTiles = (yTiles % 2 ? true : false);
+
+ _blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *));
+ if (!_blockSurfaces[_layer])
+ return RDERR_OUTOFMEMORY;
+
+ // We have to check two tiles for every block in PSX version, if one of those
+ // has data in it, the whole block has data. Also, tiles must be doublelined to
+ // get correct aspect ratio.
+ for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) {
+ bool block_has_data = false;
+ bool block_is_transparent = false;
+ bool firstTilePresent, secondTilePresent;
+
+ int posX = i / _yBlocks[_layer];
+ int posY = i % _yBlocks[_layer];
+
+ if (oddTiles && posY == _yBlocks[_layer] - 1) {
+ firstTilePresent = READ_LE_UINT32(parallax) == 0x400;
+ secondTilePresent = false;
+ parallax += 4;
+ } else {
+ firstTilePresent = READ_LE_UINT32(parallax) == 0x400;
+ secondTilePresent = READ_LE_UINT32(parallax + 4) == 0x400;
+ parallax += 8;
+ }
+
+ // If one of the two grouped tiles has data, then the whole block has data
+ if (firstTilePresent || secondTilePresent) {
+ block_has_data = true;
+
+ // If one of the two grouped blocks is without data, then we also have transparency
+ if(!firstTilePresent || !secondTilePresent)
+ block_is_transparent = true;
+ }
+
+ // Now do a second check to see if we have a partially transparent block
+ if (block_has_data && !block_is_transparent) {
+ byte *block = data;
+ if (firstTilePresent) {
+ for (k = 0; k < 0x400; k++) {
+ if ( *(block + k) == 0) {
+ block_is_transparent = true;
+ break;
+ }
+ }
+ block += 0x400; // On to next block...
+ }
+
+ // If we didn't find transparency in first block and we have
+ // a second tile, check it
+ if (secondTilePresent && !block_is_transparent) {
+ for (k = 0; k < 0x400; k++) {
+ if ( *(block + k) == 0) {
+ block_is_transparent = true;
+ break;
+ }
+ }
+ }
+ }
+
+ int tileIndex = xTiles * posY + posX;
+
+ // Only assign a surface to the block if it contains data.
+
+ if (block_has_data) {
+ _blockSurfaces[_layer][tileIndex] = (BlockSurface *)malloc(sizeof(BlockSurface));
+ memset(_blockSurfaces[_layer][tileIndex], 0, BLOCKHEIGHT * BLOCKWIDTH);
+
+ // Copy the data into the surfaces.
+ dst = _blockSurfaces[_layer][tileIndex]->data;
+
+ if (firstTilePresent) { //There is data in the first tile
+ for (j = 0; j < 16; j++) {
+ memcpy(dst, data, BLOCKWIDTH);
+ dst += BLOCKWIDTH;
+ memcpy(dst, data, BLOCKWIDTH);
+ dst += BLOCKWIDTH;
+ data += BLOCKWIDTH;
+ }
+ } else {
+ dst += 0x800;
+ }
+
+ if (secondTilePresent) {
+ for (j = 0; j < 16; j++) {
+ memcpy(dst, data, BLOCKWIDTH);
+ dst += BLOCKWIDTH;
+ memcpy(dst, data, BLOCKWIDTH);
+ dst += BLOCKWIDTH;
+ data += BLOCKWIDTH;
+ }
+ }
+
+ _blockSurfaces[_layer][tileIndex]->transparent = block_is_transparent;
+ } else
+ _blockSurfaces[_layer][tileIndex] = NULL;
+ }
+
+ _layer++;
+
+ return RD_OK;
+}
+
+/**
* Should be called once after leaving the room to free up memory.
*/
void Screen::closeBackgroundLayer() {
debug(2, "CloseBackgroundLayer");
+ if (Sword2Engine::isPsx())
+ flushPsxScrCache();
+
for (int i = 0; i < MAXLAYERS; i++) {
if (_blockSurfaces[i]) {
for (int j = 0; j < _xBlocks[i] * _yBlocks[i]; j++)
diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp
index e7ef62d672..a6a8a9e54a 100644
--- a/engines/sword2/resman.cpp
+++ b/engines/sword2/resman.cpp
@@ -159,7 +159,10 @@ bool ResourceManager::init() {
file.close();
- if (!file.open("cd.inf")) {
+ // Check that we have cd.inf file, unless we are running PSX
+ // version, which has all files on one disc.
+
+ if (!file.open("cd.inf") && !Sword2Engine::isPsx()) {
GUIErrorMessage("Broken Sword 2: Cannot open cd.inf");
return false;
}
@@ -167,15 +170,21 @@ bool ResourceManager::init() {
CdInf *cdInf = new CdInf[_totalClusters];
for (i = 0; i < _totalClusters; i++) {
- file.read(cdInf[i].clusterName, sizeof(cdInf[i].clusterName));
- cdInf[i].cd = file.readByte();
+ if (Sword2Engine::isPsx()) { // We are running PSX version, artificially fill CdInf structure
+ cdInf[i].cd = CD1;
+ } else { // We are running PC version, read cd.inf file
+ file.read(cdInf[i].clusterName, sizeof(cdInf[i].clusterName));
+
+ cdInf[i].cd = file.readByte();
+
+ if (file.ioFailed()) {
+ delete cdInf;
+ file.close();
+ GUIErrorMessage("Broken Sword 2: Cannot read cd.inf");
+ return false;
+ }
- if (file.ioFailed()) {
- delete cdInf;
- file.close();
- GUIErrorMessage("Broken Sword 2: Cannot read cd.inf");
- return false;
}
// It has been reported that there are two different versions
@@ -208,19 +217,24 @@ bool ResourceManager::init() {
file.close();
- for (i = 0; i < _totalClusters; i++) {
- for (j = 0; j < _totalClusters; j++) {
- if (scumm_stricmp((char *)cdInf[j].clusterName, _resFiles[i].fileName) == 0)
- break;
- }
+ // We check the presence of resource files in cd.inf
+ // This is ok in PC version, but in PSX version we don't
+ // have cd.inf so we'll have to skip this.
+ if (!Sword2Engine::isPsx()) {
+ for (i = 0; i < _totalClusters; i++) {
+ for (j = 0; j < _totalClusters; j++) {
+ if (scumm_stricmp((char *)cdInf[j].clusterName, _resFiles[i].fileName) == 0)
+ break;
+ }
- if (j == _totalClusters) {
- delete[] cdInf;
- GUIErrorMessage(Common::String(_resFiles[i].fileName) + " is not in cd.inf");
- return false;
- }
+ if (j == _totalClusters) {
+ delete[] cdInf;
+ GUIErrorMessage(Common::String(_resFiles[i].fileName) + " is not in cd.inf");
+ return false;
+ }
- _resFiles[i].cd = cdInf[j].cd;
+ _resFiles[i].cd = cdInf[j].cd;
+ }
}
delete[] cdInf;
@@ -247,11 +261,19 @@ bool ResourceManager::init() {
byte *ResourceManager::openResource(uint32 res, bool dump) {
assert(res < _totalResFiles);
+
+ // FIXME: In PSX edition, not all top menu icons are present (TOP menu is not used).
+ // Though, at present state, the engine still ask for the resources.
+ if (Sword2Engine::isPsx()) { // We need to "rewire" missing icons
+ if (res == 342) res = 364; // Rewire RESTORE ICON to SAVE ICON
+ }
+
// Is the resource in memory already? If not, load it.
if (!_resList[res].ptr) {
// Fetch the correct file and read in the correct portion.
uint16 cluFileNum = _resConvTable[res * 2]; // points to the number of the ascii filename
+
assert(cluFileNum != 0xffff);
// Relative resource within the file
@@ -264,7 +286,10 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {
// of the CDs, remember which one so that we can play the
// correct speech and music.
- setCD(_resFiles[cluFileNum].cd);
+ if (Sword2Engine::isPsx()) // We have only one disk in PSX version
+ setCD(CD1);
+ else
+ setCD(_resFiles[cluFileNum].cd);
// Actually, as long as the file can be found we don't really
// care which CD it's on. But if we can't find it, keep asking
@@ -481,6 +506,25 @@ bool ResourceManager::checkValid(uint32 res) {
}
/**
+ * Fetch resource type
+ */
+
+uint8 ResourceManager::fetchType(byte *ptr) {
+ if (!Sword2Engine::isPsx()) {
+ return ptr[0];
+ } else { // in PSX version, some files got a "garbled" resource header, with type stored in ninth byte
+ if (ptr[0]) {
+ return ptr[0];
+ } else if (ptr[8]) {
+ return ptr[8];
+ } else { // In PSX version there is no resource header for audio files,
+ return WAV_FILE; // but hopefully all audio files got first 16 bytes zeroed,
+ } // Allowing us to check for this condition.
+
+ }
+}
+
+/**
* Returns the total file length of a resource - i.e. all headers are included
* too.
*/
diff --git a/engines/sword2/resman.h b/engines/sword2/resman.h
index 4546448167..fb253a010f 100644
--- a/engines/sword2/resman.h
+++ b/engines/sword2/resman.h
@@ -64,7 +64,7 @@ private:
void removeFromCacheList(Resource *res);
void addToCacheList(Resource *res);
void checkMemUsage();
-
+
Sword2Engine *_vm;
int _curCD;
@@ -96,18 +96,15 @@ public:
bool checkValid(uint32 res);
uint32 fetchLen(uint32 res);
+ uint8 fetchType(byte *ptr);
uint8 fetchType(uint32 res) {
byte *ptr = openResource(res);
- uint8 type = ptr[0];
+ uint8 type = fetchType(ptr);
closeResource(res);
return type;
}
- uint8 fetchType(byte *ptr) {
- return ptr[0];
- }
-
byte *fetchName(uint32 res, byte *buf = NULL) {
static byte tempbuf[NAME_LEN];
diff --git a/engines/sword2/screen.cpp b/engines/sword2/screen.cpp
index b552f385a9..da7122386e 100644
--- a/engines/sword2/screen.cpp
+++ b/engines/sword2/screen.cpp
@@ -76,7 +76,7 @@ Screen::Screen(Sword2Engine *vm, int16 width, int16 height) {
_needFullRedraw = false;
memset(&_thisScreen, 0, sizeof(_thisScreen));
-
+
_fps = 0;
_frameCount = 0;
_cycleTime = 0;
@@ -100,9 +100,18 @@ Screen::Screen(Sword2Engine *vm, int16 width, int16 height) {
_pauseTicks = 0;
_pauseStartTick = 0;
+
+ // Clean the cache for PSX version SCREENS.CLU
+ _psxScrCache[0] = NULL;
+ _psxScrCache[1] = NULL;
+ _psxScrCache[2] = NULL;
+ _psxCacheEnabled[0] = true;
+ _psxCacheEnabled[1] = true;
+ _psxCacheEnabled[2] = true;
}
Screen::~Screen() {
+ flushPsxScrCache();
free(_buffer);
free(_dirtyGrid);
closeBackgroundLayer();
@@ -277,7 +286,8 @@ void Screen::buildDisplay() {
MultiScreenHeader screenLayerTable;
- screenLayerTable.read(file + ResHeader::size());
+ if (!Sword2Engine::isPsx()) // On PSX version, there would be nothing to read here
+ screenLayerTable.read(file + ResHeader::size());
// Render at least one frame, but if the screen is scrolling, and if
// there is time left, we will render extra frames to smooth out the
@@ -285,13 +295,13 @@ void Screen::buildDisplay() {
do {
// first background parallax + related anims
- if (screenLayerTable.bg_parallax[0]) {
+ if (Sword2Engine::isPsx() || screenLayerTable.bg_parallax[0]) { // No need to check on PSX version
renderParallax(_vm->fetchBackgroundParallaxLayer(file, 0), 0);
drawBackPar0Frames();
}
// second background parallax + related anims
- if (screenLayerTable.bg_parallax[1]) {
+ if (!Sword2Engine::isPsx() && screenLayerTable.bg_parallax[1]) { // Nothing here in PSX version
renderParallax(_vm->fetchBackgroundParallaxLayer(file, 1), 1);
drawBackPar1Frames();
}
@@ -306,14 +316,14 @@ void Screen::buildDisplay() {
// first foreground parallax + related anims
- if (screenLayerTable.fg_parallax[0]) {
+ if (Sword2Engine::isPsx() || screenLayerTable.fg_parallax[0]) {
renderParallax(_vm->fetchForegroundParallaxLayer(file, 0), 3);
drawForePar0Frames();
}
// second foreground parallax + related anims
- if (screenLayerTable.fg_parallax[1]) {
+ if (!Sword2Engine::isPsx() && screenLayerTable.fg_parallax[1]) {
renderParallax(_vm->fetchForegroundParallaxLayer(file, 1), 4);
drawForePar1Frames();
}
@@ -333,6 +343,7 @@ void Screen::buildDisplay() {
} while (!endRenderCycle());
_vm->_resman->closeResource(_thisScreen.background_layer_id);
+
}
/**
@@ -381,6 +392,7 @@ void Screen::displayMsg(byte *text, int time) {
spriteInfo.blend = 0;
spriteInfo.data = text_spr + FrameHeader::size();
spriteInfo.colourTable = 0;
+ spriteInfo.isText = true;
uint32 rv = drawSprite(&spriteInfo);
if (rv)
@@ -490,6 +502,7 @@ void Screen::drawForePar1Frames() {
}
void Screen::processLayer(byte *file, uint32 layer_number) {
+
LayerHeader layer_head;
layer_head.read(_vm->fetchLayerHeader(file, layer_number));
@@ -503,9 +516,19 @@ void Screen::processLayer(byte *file, uint32 layer_number) {
spriteInfo.scaledWidth = 0;
spriteInfo.scaledHeight = 0;
spriteInfo.h = layer_head.height;
- spriteInfo.type = RDSPR_TRANS | RDSPR_RLE256FAST;
+ spriteInfo.isText = false;
+
+ // Layers are uncompressed in PSX version, RLE256 compressed
+ // in PC version.
+ if (Sword2Engine::isPsx()) {
+ spriteInfo.type = RDSPR_TRANS | RDSPR_NOCOMPRESSION;
+ spriteInfo.data = file + layer_head.offset;
+ } else {
+ spriteInfo.type = RDSPR_TRANS | RDSPR_RLE256FAST;
+ spriteInfo.data = file + ResHeader::size() + layer_head.offset;
+ }
+
spriteInfo.blend = 0;
- spriteInfo.data = file + ResHeader::size() + layer_head.offset;
spriteInfo.colourTable = 0;
// check for largest layer for debug info
@@ -575,6 +598,8 @@ void Screen::processImage(BuildUnit *build_unit) {
// points to just after last cdt_entry, ie.
// start of colour table
colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size() + anim_head.noAnimFrames * CdtEntry::size();
+ if (Sword2Engine::isPsx())
+ colTablePtr++; // There is one additional byte to skip before the table in psx version
break;
}
}
@@ -598,6 +623,7 @@ void Screen::processImage(BuildUnit *build_unit) {
// points to just after frame header, ie. start of sprite data
spriteInfo.data = frame + FrameHeader::size();
spriteInfo.colourTable = colTablePtr;
+ spriteInfo.isText = false;
// check for largest layer for debug info
uint32 current_sprite_area = frame_head.width * frame_head.height;
@@ -864,7 +890,7 @@ void Screen::rollCredits() {
// that this is a coincidence, but let's use the image palette
// directly anyway, just to be safe.
//
- // credits.clu - The credits text
+ // credits.clu - The credits text (credits.txt in PSX version)
//
// This is simply a text file with CRLF line endings.
// '^' is not shown, but used to mark the center of the line.
@@ -883,6 +909,8 @@ void Screen::rollCredits() {
Common::File f;
int i;
+ spriteInfo.isText = false;
+
// Read the "Smacker" logo
uint16 logoWidth = 0;
@@ -925,9 +953,16 @@ void Screen::rollCredits() {
int paragraphStart = 0;
bool hasCenterMark = false;
- if (!f.open("credits.clu")) {
- warning("Can't find credits.clu");
- return;
+ if (Sword2Engine::isPsx()) {
+ if (!f.open("credits.txt")) {
+ warning("Can't find credits.txt");
+ return;
+ }
+ } else {
+ if (!f.open("credits.clu")) {
+ warning("Can't find credits.clu");
+ return;
+ }
}
while (1) {
@@ -1088,6 +1123,7 @@ void Screen::rollCredits() {
spriteInfo.w = frame.width;
spriteInfo.h = frame.height;
spriteInfo.data = creditsLines[i]->sprite + FrameHeader::size();
+ spriteInfo.isText = true;
switch (creditsLines[i]->type) {
case LINE_LEFT:
@@ -1214,6 +1250,7 @@ void Screen::splashScreen() {
barSprite.blend = 0;
barSprite.colourTable = 0;
barSprite.data = frame + FrameHeader::size();
+ barSprite.isText = false;
drawSprite(&barSprite);
@@ -1234,4 +1271,44 @@ void Screen::splashScreen() {
waitForFade();
}
+// Following functions are used to manage screen cache for psx version.
+
+void Screen::setPsxScrCache(byte *psxScrCache, uint8 level) {
+ if (level < 3) {
+ if (psxScrCache)
+ _psxCacheEnabled[level] = true;
+ else
+ _psxCacheEnabled[level] = false;
+
+ _psxScrCache[level] = psxScrCache;
+ }
+}
+
+byte *Screen::getPsxScrCache(uint8 level) {
+ if (level > 3) {
+ level = 0;
+ }
+
+ if (_psxCacheEnabled[level])
+ return _psxScrCache[level];
+ else
+ return NULL;
+}
+
+bool Screen::getPsxScrCacheStatus(uint8 level) {
+ if (level > 3) {
+ level = 0;
+ }
+
+ return _psxCacheEnabled[level];
+}
+
+void Screen::flushPsxScrCache() {
+ for (uint8 i = 0; i < 3; i++) {
+ free(_psxScrCache[i]);
+ _psxScrCache[i] = NULL;
+ _psxCacheEnabled[i] = true;
+ }
+}
+
} // End of namespace Sword2
diff --git a/engines/sword2/screen.h b/engines/sword2/screen.h
index 608fb0c686..c098b155ed 100644
--- a/engines/sword2/screen.h
+++ b/engines/sword2/screen.h
@@ -173,6 +173,7 @@ struct SpriteInfo {
uint16 blend; // holds the blending values.
byte *data; // pointer to the sprite data
byte *colourTable; // pointer to 16-byte colour table, only applicable to 16-col compression type
+ bool isText; // It is a engine-generated sprite containing text
};
struct BlockSurface {
@@ -214,7 +215,7 @@ private:
// positions, etc.
ScreenInfo _thisScreen;
-
+
int32 _renderCaps;
int8 _renderLevel;
@@ -330,15 +331,23 @@ private:
void mirrorSprite(byte *dst, byte *src, int16 w, int16 h);
int32 decompressRLE256(byte *dst, byte *src, int32 decompSize);
- void unwindRaw16(byte *dst, byte *src, uint8 blockSize, byte *colTable);
+ void unwindRaw16(byte *dst, byte *src, uint16 blockSize, byte *colTable);
int32 decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colTable);
void renderParallax(byte *ptr, int16 layer);
+
void markAsDirty(int16 x0, int16 y0, int16 x1, int16 y1);
uint8 _xBlocks[MAXLAYERS];
uint8 _yBlocks[MAXLAYERS];
+ // This is used to cache PSX backgrounds and parallaxes
+ // data, as they are kept in a file unmanageable from
+ // resource manager. These gets freed everytime an user
+ // exits from a room.
+ byte *_psxScrCache[3];
+ bool _psxCacheEnabled[3];
+
// An array of sub-blocks, one for each of the parallax layers.
BlockSurface **_blockSurfaces[MAXLAYERS];
@@ -396,11 +405,14 @@ public:
void setLocationMetrics(uint16 w, uint16 h);
int32 initialiseBackgroundLayer(byte *parallax);
+ int32 initialisePsxParallaxLayer(byte *parallax); // These are used to initialize psx backgrounds and
+ int32 initialisePsxBackgroundLayer(byte *parallax); // parallaxes, which are different from pc counterparts.
void closeBackgroundLayer();
void initialiseRenderCycle();
void initBackground(int32 res, int32 new_palette);
+ void initPsxBackground(int32 res, int32 new_palette);
void registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega);
void setScrollFraction(uint8 f) { _scrollFraction = f; }
@@ -446,6 +458,22 @@ public:
void rollCredits();
void splashScreen();
+
+ // Some sprites are compressed in HIF format
+ static uint32 decompressHIF(byte *src, byte *dst, uint32 *skipData = NULL);
+ // This is used to resize psx sprites back to original resolution
+ static void resizePsxSprite(byte *dst, byte *src, uint16 destW, uint16 destH);
+ // Some sprites are divided into 254 pixel wide stripes, this recomposes them
+ // and generates a "normal" sprite.
+ static void recomposePsxSprite(SpriteInfo *s);
+ static void recomposeCompPsxSprite(SpriteInfo *s);
+
+ // These functions manage the PSX screen cache
+ void setPsxScrCache(byte *psxScrCache, uint8 level);
+ byte *getPsxScrCache(uint8 level);
+ bool getPsxScrCacheStatus(uint8 level);
+ void flushPsxScrCache();
+
};
} // End of namespace Sword2
diff --git a/engines/sword2/sound.cpp b/engines/sword2/sound.cpp
index ee9fa1debf..eb0b6d82eb 100644
--- a/engines/sword2/sound.cpp
+++ b/engines/sword2/sound.cpp
@@ -47,6 +47,7 @@
#include "sword2/sound.h"
#include "sound/wave.h"
+#include "sound/vag.h"
namespace Sword2 {
@@ -209,10 +210,15 @@ void Sound::playMovieSound(int32 res, int type) {
}
byte *data = _vm->_resman->openResource(res);
- uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
-
+ uint32 len = _vm->_resman->fetchLen(res);
+
assert(_vm->_resman->fetchType(data) == WAV_FILE);
- data += ResHeader::size();
+
+ // In PSX version we have nothing to skip here, as data starts right away
+ if (!Sword2Engine::isPsx()) {
+ data += ResHeader::size();
+ len -= ResHeader::size();
+ }
_vm->_sound->playFx(handle, data, len, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType);
}
@@ -263,7 +269,11 @@ void Sound::queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan)
assert(_vm->_resman->fetchType(data) == WAV_FILE);
- uint32 len = _vm->_resman->fetchLen(res) - ResHeader::size();
+ uint32 len = _vm->_resman->fetchLen(res);
+
+ // Skip the header if using PC version
+ if (!Sword2Engine::isPsx())
+ len -= ResHeader::size();
if (type == FX_RANDOM) {
// For spot effects and loops the delay is the
@@ -281,7 +291,12 @@ void Sound::queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan)
pan = -pan;
_fxQueue[i].resource = res;
- _fxQueue[i].data = data + ResHeader::size();
+
+ if (Sword2Engine::isPsx())
+ _fxQueue[i].data = data;
+ else
+ _fxQueue[i].data = data + ResHeader::size();
+
_fxQueue[i].len = len;
_fxQueue[i].delay = delay;
_fxQueue[i].volume = volume;
@@ -311,22 +326,27 @@ int32 Sound::playFx(Audio::SoundHandle *handle, byte *data, uint32 len, uint8 vo
if (_vm->_mixer->isSoundHandleActive(*handle))
return RDERR_FXALREADYOPEN;
- Common::MemoryReadStream stream(data, len);
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, len);
int rate, size;
byte flags;
- if (!Audio::loadWAVFromStream(stream, size, rate, flags)) {
- warning("playFX: Not a valid WAV file");
- return RDERR_INVALIDWAV;
- }
+ if (Sword2Engine::isPsx()) {
+ _vm->_mixer->playInputStream(soundType, handle, new Audio::VagStream(stream, loop), -1, vol, pan, true, false, isReverseStereo());
+ } else {
+ if (!Audio::loadWAVFromStream(*stream, size, rate, flags)) {
+ warning("playFX: Not a valid WAV file");
+ return RDERR_INVALIDWAV;
+ }
- if (isReverseStereo())
- flags |= Audio::Mixer::FLAG_REVERSE_STEREO;
+ if (isReverseStereo())
+ flags |= Audio::Mixer::FLAG_REVERSE_STEREO;
- if (loop)
- flags |= Audio::Mixer::FLAG_LOOP;
+ if (loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ _vm->_mixer->playRaw(soundType, handle, data + stream->pos(), size, rate, flags, -1, vol, pan, 0, 0);
+ }
- _vm->_mixer->playRaw(soundType, handle, data + stream.pos(), size, rate, flags, -1, vol, pan, 0, 0);
return RD_OK;
}
diff --git a/engines/sword2/sound.h b/engines/sword2/sound.h
index 9ba7976ba4..f58794c8f1 100644
--- a/engines/sword2/sound.h
+++ b/engines/sword2/sound.h
@@ -231,7 +231,7 @@ public:
int readBuffer(int16 *buffer, const int numSamples);
bool isStereo() const { return false; }
bool endOfData() const;
- int getRate() const { return 22050; }
+ int getRate() const { return Sword2Engine::isPsx() ? 11025 : 22050; }
// End of AudioStream API
diff --git a/engines/sword2/sprite.cpp b/engines/sword2/sprite.cpp
index bd452049aa..52ed0efbd0 100644
--- a/engines/sword2/sprite.cpp
+++ b/engines/sword2/sprite.cpp
@@ -25,7 +25,7 @@
* $Id$
*/
-
+#include "common/endian.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
@@ -138,16 +138,23 @@ int32 Screen::decompressRLE256(byte *dst, byte *src, int32 decompSize) {
* Unwinds a run of 16-colour data into 256-colour palette data.
*/
-void Screen::unwindRaw16(byte *dst, byte *src, uint8 blockSize, byte *colTable) {
+void Screen::unwindRaw16(byte *dst, byte *src, uint16 blockSize, byte *colTable) {
// for each pair of pixels
while (blockSize > 1) {
- // 1st colour = number in table at position given by upper
- // nibble of source byte
- *dst++ = colTable[(*src) >> 4];
+
+ if (Sword2Engine::isPsx()) {
+ // 1st colour = number in table at position given by upper
+ // nibble of source byte
+ *dst++ = colTable[(*src) & 0x0f];
+
+ // 2nd colour = number in table at position given by lower
+ // nibble of source byte
+ *dst++ = colTable[(*src) >> 4];
+ } else {
+ *dst++ = colTable[(*src) >> 4];
+ *dst++ = colTable[(*src) & 0x0f];
+ }
- // 2nd colour = number in table at position given by lower
- // nibble of source byte
- *dst++ = colTable[(*src) & 0x0f];
// point to next source byte
src++;
@@ -245,6 +252,118 @@ int32 Screen::decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colT
}
/**
+ * This function takes a compressed HIF image and decompresses it.
+ * Used for PSX version sprites.
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param skipData if pointer != NULL, value of pointed var
+ * is set to number of bytes composing the compressed data.
+ */
+
+uint32 Screen::decompressHIF(byte *src, byte *dst, uint32 *skipData) {
+ uint32 decompSize = 0;
+ uint32 readByte = 0;
+
+ for (;;) { // Main loop
+ byte control_byte = *src++;
+ readByte++;
+ uint32 byte_count = 0;
+ while (byte_count < 8) {
+ if (control_byte & 0x80) {
+ uint16 info_word = READ_BE_UINT16(src); // Read the info word
+ src += 2;
+ readByte += 2;
+ if (info_word == 0xFFFF) { // Got 0xFFFF code, finished.
+ if (skipData != NULL) *(skipData) = readByte;
+ return decompSize;
+ }
+
+ int32 repeat_count = (info_word >> 12) + 2; // How many time data needs to be refetched
+ while(repeat_count >= 0) {
+ uint16 refetchData = (info_word & 0xFFF) + 1;
+ if (refetchData > decompSize) return 0; // We have a problem here...
+ uint8 *old_data_src = dst - refetchData;
+ *dst++ = *old_data_src;
+ decompSize++;
+ repeat_count--;
+ }
+ } else {
+ *dst++ = *src++;
+ readByte++;
+ decompSize++;
+ }
+ byte_count++;
+ control_byte <<= 1; // Shifting left the control code one bit
+ }
+ }
+}
+
+// Double line image to keep aspect ratio.
+// Used in PSX version.
+void Screen::resizePsxSprite(byte *dst, byte *src, uint16 destW, uint16 destH) {
+ for (int i = 0; i < destH / 2; i++) {
+ memcpy(dst + i * destW * 2, src + i * destW, destW);
+ memcpy(dst + i * destW * 2 + destW, src + i * destW, destW);
+ }
+}
+
+// Sprites wider than 254px in PSX version are divided
+// into slices, this recomposes the image.
+void Screen::recomposePsxSprite(SpriteInfo *s) {
+ if (!s)
+ return;
+
+ uint16 noStripes = (s->w / 254) + ((s->w % 254) ? 1 : 0);
+ uint16 lastStripeSize = (s->w % 254) ? s->w % 254 : 254;
+ byte *buffer = (byte *)malloc(s->w * s->h / 2);
+
+ memset(buffer, 0, s->w * s->h / 2);
+
+ for (int idx = 0; idx < noStripes; idx++) {
+ uint16 stripeSize = (idx == noStripes - 1) ? lastStripeSize : 254;
+ for (int line = 0; line < s->h / 2; line++) {
+ memcpy(buffer + idx * 254 + line * s->w, s->data, stripeSize);
+ s->data += stripeSize;
+ }
+ }
+
+ s->data = buffer;
+
+}
+
+// Recomposes sprites wider than 254 pixels but also
+// compressed with HIF.
+// Used in PSX version.
+void Screen::recomposeCompPsxSprite(SpriteInfo *s) {
+ if (!s)
+ return;
+
+ uint16 noStripes = (s->w / 254) + ((s->w % 254) ? 1 : 0);
+ uint16 lastStripeSize = (s->w % 254) ? s->w % 254 : 254;
+ byte *buffer = (byte *)malloc(s->w * s->h / 2);
+ byte *stripeBuffer = (byte *)malloc(254 * s->h);;
+
+ memset(buffer, 0, s->w * s->h / 2);
+ uint32 skipData = 0;
+ uint32 compBytes = 0;
+
+ for (int idx = 0; idx < noStripes; idx++) {
+ uint16 stripeSize = (idx == noStripes - 1) ? lastStripeSize : 254;
+
+ decompressHIF((s->data) + skipData, stripeBuffer, &compBytes);
+ skipData += compBytes;
+
+ for (int line = 0; line < s->h / 2; line++) {
+ memcpy(buffer + idx * 254 + line * s->w, stripeBuffer + line * stripeSize, stripeSize);
+ }
+ }
+
+ free(stripeBuffer);
+ s->data = buffer;
+
+}
+
+/**
* Creates a sprite surface. Sprite surfaces are used by the in-game dialogs
* and for displaying cutscene subtitles, which makes them much easier to draw
* than standard sprites.
@@ -378,23 +497,90 @@ int32 Screen::drawSprite(SpriteInfo *s) {
// -----------------------------------------------------------------
// Decompression and mirroring
// -----------------------------------------------------------------
+ if (s->type & RDSPR_NOCOMPRESSION) {
+ if(Sword2Engine::isPsx()) { // PSX Uncompressed sprites
+ if (s->w > 254 && !s->isText) { // We need to recompose these frames
+ recomposePsxSprite(s);
+ }
+
+ freeSprite = true;
+ byte *tempBuf = (byte *)malloc(s->w * s->h * 2);
+ memset(tempBuf, 0, s->w * s->h * 2);
+ resizePsxSprite(tempBuf, s->data, s->w, s->h);
+
+ if (s->w > 254 && !s->isText) {
+ free(s->data);
+ }
- if (s->type & RDSPR_NOCOMPRESSION)
- sprite = s->data;
- else {
- sprite = (byte *)malloc(s->w * s->h);
+ sprite = tempBuf;
+ } else { // PC Uncompressed sprites
+ sprite = s->data;
+ }
+ } else {
freeSprite = true;
- if (!sprite)
- return RDERR_OUTOFMEMORY;
+
if ((s->type & 0xff00) == RDSPR_RLE16) {
- if (decompressRLE16(sprite, s->data, s->w * s->h, s->colourTable)) {
- free(sprite);
- return RDERR_DECOMPRESSION;
+ if (Sword2Engine::isPsx()) { // PSX HIF16 sprites
+ uint32 decompData;
+ byte *tempBuf = (byte *)malloc(s->w * s->h);
+ memset(tempBuf, 0, s->w * s->h);
+
+ decompData = decompressHIF(s->data, tempBuf);
+
+ s->w = (decompData / (s->h / 2)) * 2;
+ byte *tempBuf2 = (byte *)malloc(s->w * s->h * 10);
+ memset(tempBuf2, 0, s->w * s->h * 2);
+
+ unwindRaw16(tempBuf2, tempBuf, (s->w * (s->h / 2)), s->colourTable);
+ sprite = (byte *)malloc(s->w * s->h);
+
+ if (!sprite)
+ return RDERR_OUTOFMEMORY;
+
+ resizePsxSprite(sprite, tempBuf2, s->w, s->h);
+
+ free(tempBuf2);
+ free(tempBuf);
+ } else { // PC RLE16 sprites
+ sprite = (byte *)malloc(s->w * s->h);
+
+ if (!sprite)
+ return RDERR_OUTOFMEMORY;
+
+ if (decompressRLE16(sprite, s->data, s->w * s->h, s->colourTable)) {
+ free(sprite);
+ return RDERR_DECOMPRESSION;
+ }
}
} else {
- if (decompressRLE256(sprite, s->data, s->w * s->h)) {
- free(sprite);
- return RDERR_DECOMPRESSION;
+ if (Sword2Engine::isPsx()) { // PSX HIF256 sprites
+ if (s->w > 255) {
+ sprite = (byte *)malloc(s->w * s->h);
+ recomposeCompPsxSprite(s);
+ resizePsxSprite(sprite, s->data, s->w, s->h);
+ free(s->data);
+ } else {
+ byte *tempBuf = (byte *)malloc(s->w * s->h);
+ uint32 decompData = decompressHIF(s->data, tempBuf);
+ s->w = (decompData / (s->h / 2));
+ sprite = (byte *)malloc(s->w * s->h);
+
+ if (!sprite)
+ return RDERR_OUTOFMEMORY;
+
+ resizePsxSprite(sprite, tempBuf, s->w, s->h);
+ free(tempBuf);
+ }
+ } else { // PC RLE256 sprites
+ sprite = (byte *)malloc(s->w * s->h);
+
+ if (!sprite)
+ return RDERR_OUTOFMEMORY;
+
+ if (decompressRLE256(sprite, s->data, s->w * s->h)) {
+ free(sprite);
+ return RDERR_DECOMPRESSION;
+ }
}
}
}
@@ -500,7 +686,9 @@ int32 Screen::drawSprite(SpriteInfo *s) {
return RDERR_OUTOFMEMORY;
}
- if (_renderCaps & RDBLTFX_EDGEBLEND)
+ // We cannot use good scaling for PSX version, as we are missing
+ // some required data.
+ if (_renderCaps & RDBLTFX_EDGEBLEND && !Sword2Engine::isPsx())
scaleImageGood(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h, _buffer + _screenWide * rd.top + rd.left);
else
scaleImageFast(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h);
@@ -518,8 +706,10 @@ int32 Screen::drawSprite(SpriteInfo *s) {
// The light mask is an optional layer that covers the entire room
// and which is used to simulate light and shadows. Scaled sprites
// (actors, presumably) are always affected.
+ // Light masking makes use of palette match table, so it's unavailable
+ // in PSX version.
- if ((_renderCaps & RDBLTFX_SHADOWBLEND) && _lightMask && (scale != 256 || (s->type & RDSPR_SHADOW))) {
+ if ((_renderCaps & RDBLTFX_SHADOWBLEND) && _lightMask && (scale != 256 || ((s->type & RDSPR_SHADOW) && !Sword2Engine::isPsx()) )) {
byte *lightMap;
// Make sure that we never apply the shadow to the original
@@ -557,7 +747,7 @@ int32 Screen::drawSprite(SpriteInfo *s) {
src = sprite + rs.top * srcPitch + rs.left;
dst = _buffer + _screenWide * rd.top + rd.left;
- if (s->type & RDSPR_BLEND) {
+ if (s->type & RDSPR_BLEND && !Sword2Engine::isPsx()) { // Blending is unavailable in PSX version
// The original code had two different blending cases. One for
// s->blend & 0x01 and one for s->blend & 0x02. However, the
// only values that actually appear in the cluster files are
@@ -639,6 +829,9 @@ int32 Screen::openLightMask(SpriteInfo *s) {
if (!_lightMask)
return RDERR_OUTOFMEMORY;
+ if (s->data == NULL) // Check, as there's no mask in psx version
+ return RDERR_NOTOPEN;
+
if (decompressRLE256(_lightMask, s->data, s->w * s->h))
return RDERR_DECOMPRESSION;
diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp
index af40e7d635..78fb890deb 100644
--- a/engines/sword2/sword2.cpp
+++ b/engines/sword2/sword2.cpp
@@ -51,6 +51,7 @@
#include "sword2/router.h"
#include "sword2/screen.h"
#include "sword2/sound.h"
+#include "sword2/saveload.h"
namespace Sword2 {
@@ -104,7 +105,9 @@ bool Sword2MetaEngine::hasFeature(MetaEngineFeature f) const {
bool Sword2::Sword2Engine::hasFeature(EngineFeature f) const {
return
(f == kSupportsRTL) ||
- (f == kSupportsSubtitleOptions);
+ (f == kSupportsSubtitleOptions) ||
+ (f == kSupportsSavingDuringRuntime) ||
+ (f == kSupportsLoadingDuringRuntime);
}
GameList Sword2MetaEngine::getSupportedGames() const {
@@ -299,6 +302,8 @@ Sword2Engine::Sword2Engine(OSystem *syst) : Engine(syst) {
_gameCycle = 0;
_gameSpeed = 1;
+ _gmmLoadSlot = -1; // Used to manage GMM Loading
+
syst->getEventManager()->registerRandomSource(_rnd, "sword2");
}
@@ -429,8 +434,8 @@ Common::Error Sword2Engine::run() {
if (!dialog.runModal())
startGame();
}
- } else if (!_bootParam && saveExists()) {
- int32 pars[2] = { 221, FX_LOOP };
+ } else if (!_bootParam && saveExists() && !isPsx()) { // Initial load/restart panel disabled in PSX
+ int32 pars[2] = { 221, FX_LOOP }; // version because of missing panel resources
bool result;
_mouse->setMouse(NORMAL_MOUSE_ID);
@@ -465,6 +470,26 @@ Common::Error Sword2Engine::run() {
}
#endif
+ // Handle GMM Loading
+ if (_gmmLoadSlot != -1) {
+
+ // Hide mouse cursor and fade screen
+ _mouse->hideMouse();
+ _screen->fadeDown();
+
+ // Clean up and load game
+ _logic->_router->freeAllRouteMem();
+
+ // TODO: manage error handling
+ restoreGame(_gmmLoadSlot);
+
+ // Reset load slot
+ _gmmLoadSlot = -1;
+
+ // Show mouse
+ _mouse->addHuman();
+ }
+
KeyboardEvent *ke = keyboardEvent();
if (ke) {
@@ -805,4 +830,65 @@ uint32 Sword2Engine::getMillis() {
return _system->getMillis();
}
+Common::Error Sword2Engine::saveGameState(int slot, const char *desc) {
+ uint32 saveVal = saveGame(slot, (byte *)desc);
+
+ if (saveVal == SR_OK)
+ return Common::kNoError;
+ else if (saveVal == SR_ERR_WRITEFAIL || saveVal == SR_ERR_FILEOPEN)
+ return Common::kWritingFailed;
+ else
+ return Common::kUnknownError;
+}
+
+bool Sword2Engine::canSaveGameStateCurrently() {
+ bool canSave = true;
+
+ // No save if dead
+ if (_logic->readVar(DEAD))
+ canSave = false;
+
+ // No save if mouse not shown
+ else if (_mouse->getMouseStatus())
+ canSave = false;
+ // No save if inside a menu
+ else if (_mouse->getMouseMode() == MOUSE_system_menu)
+ canSave = false;
+
+ // No save if fading
+ else if (_screen->getFadeStatus())
+ canSave = false;
+
+ return canSave;
+}
+
+Common::Error Sword2Engine::loadGameState(int slot) {
+
+ // Prepare the game to load through GMM
+ _gmmLoadSlot = slot;
+
+ // TODO: error handling.
+ return Common::kNoError;
+}
+
+bool Sword2Engine::canLoadGameStateCurrently() {
+ bool canLoad = true;
+
+ // No load if mouse is disabled
+ if (_mouse->getMouseStatus())
+ canLoad = false;
+ // No load if mouse is in system menu
+ else if (_mouse->getMouseMode() == MOUSE_system_menu)
+ canLoad = false;
+ // No load if we are fading
+ else if (_screen->getFadeStatus())
+ canLoad = false;
+
+ // But if we are dead, ignore previous conditions
+ if (_logic->readVar(DEAD))
+ canLoad = true;
+
+ return canLoad;
+}
+
} // End of namespace Sword2
diff --git a/engines/sword2/sword2.h b/engines/sword2/sword2.h
index e262a35f09..7168e7ae47 100644
--- a/engines/sword2/sword2.h
+++ b/engines/sword2/sword2.h
@@ -120,8 +120,17 @@ private:
bool _useSubtitles;
int _gameSpeed;
+ // Used to trigger GMM Loading
+ int _gmmLoadSlot;
+
StartUp _startList[MAX_starts];
+ // We need these to fetch data from SCREENS.CLU, which is
+ // a resource file with custom format keeping background and
+ // parallax data (which is removed from multiscreen files).
+ byte *fetchPsxBackground(uint32 location);
+ byte *fetchPsxParallax(uint32 location, uint8 level); // level: 0 -> bg, 1 -> fg
+
// Original game platform (PC/PSX)
static Common::Platform _platform;
@@ -150,6 +159,12 @@ public:
bool getSubtitles() { return _useSubtitles; }
void setSubtitles(bool b) { _useSubtitles = b; }
+ // GMM Loading/Saving
+ Common::Error saveGameState(int slot, const char *desc);
+ bool canSaveGameStateCurrently();
+ Common::Error loadGameState(int slot);
+ bool canLoadGameStateCurrently();
+
uint32 _features;
MemoryManager *_memory;
@@ -203,7 +218,7 @@ public:
byte *fetchTextLine(byte *file, uint32 text_line);
bool checkTextLine(byte *file, uint32 text_line);
byte *fetchPaletteMatchTable(byte *screenFile);
-
+
uint32 saveGame(uint16 slotNo, byte *description);
uint32 restoreGame(uint16 slotNo);
uint32 getSaveDescription(uint16 slotNo, byte *description);