From dc9c538a62d64b40c3a39a673d2f680fa9422f41 Mon Sep 17 00:00:00 2001 From: Fabio Battaglia Date: Tue, 7 Apr 2009 19:52:46 +0000 Subject: Sword2: PSX version support, and GMM loading/saving svn-id: r39896 --- engines/sword2/animation.cpp | 10 ++ engines/sword2/anims.cpp | 2 +- engines/sword2/console.cpp | 2 +- engines/sword2/controls.cpp | 7 ++ engines/sword2/function.cpp | 15 ++- engines/sword2/header.h | 158 +++++++++++++++++++++--- engines/sword2/icons.cpp | 17 ++- engines/sword2/layers.cpp | 98 ++++++++++++++- engines/sword2/logic.cpp | 8 ++ engines/sword2/logic.h | 3 + engines/sword2/maketext.cpp | 145 +++++++++++++++++++--- engines/sword2/menu.cpp | 60 +++++++-- engines/sword2/mouse.cpp | 95 +++++++++++--- engines/sword2/mouse.h | 5 + engines/sword2/music.cpp | 17 ++- engines/sword2/palette.cpp | 13 +- engines/sword2/protocol.cpp | 288 ++++++++++++++++++++++++++++++++++++++----- engines/sword2/render.cpp | 275 ++++++++++++++++++++++++++++++++++++++++- engines/sword2/resman.cpp | 84 ++++++++++--- engines/sword2/resman.h | 9 +- engines/sword2/screen.cpp | 101 +++++++++++++-- engines/sword2/screen.h | 32 ++++- engines/sword2/sound.cpp | 50 +++++--- engines/sword2/sound.h | 2 +- engines/sword2/sprite.cpp | 239 +++++++++++++++++++++++++++++++---- engines/sword2/sword2.cpp | 92 +++++++++++++- engines/sword2/sword2.h | 17 ++- 27 files changed, 1650 insertions(+), 194 deletions(-) (limited to 'engines/sword2') 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; @@ -536,6 +550,256 @@ int32 Screen::initialiseBackgroundLayer(byte *parallax) { return RD_OK; } +/** + * 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. */ @@ -543,6 +807,9 @@ int32 Screen::initialiseBackgroundLayer(byte *parallax) { 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 @@ -480,6 +505,25 @@ bool ResourceManager::checkValid(uint32 res) { return true; } +/** + * 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++; @@ -244,6 +251,118 @@ int32 Screen::decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colT return rv; } +/** + * 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 @@ -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); -- cgit v1.2.3