aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/kyra/debugger.cpp53
-rw-r--r--engines/kyra/debugger.h1
-rw-r--r--engines/kyra/eobcommon.h1
-rw-r--r--engines/kyra/saveload_eob.cpp362
-rw-r--r--engines/kyra/script_eob.cpp14
-rw-r--r--engines/kyra/script_eob.h2
-rw-r--r--engines/kyra/sequences_darkmoon.cpp1
7 files changed, 421 insertions, 13 deletions
diff --git a/engines/kyra/debugger.cpp b/engines/kyra/debugger.cpp
index c0a91ac098..75981958d6 100644
--- a/engines/kyra/debugger.cpp
+++ b/engines/kyra/debugger.cpp
@@ -483,11 +483,12 @@ Debugger_EoB::Debugger_EoB(EoBCoreEngine *vm) : Debugger(vm), _vm(vm) {
void Debugger_EoB::initialize() {
DCmd_Register("import_savefile", WRAP_METHOD(Debugger_EoB, cmd_importSaveFile));
+ DCmd_Register("save_original", WRAP_METHOD(Debugger_EoB, cmd_saveOriginal));
}
bool Debugger_EoB::cmd_importSaveFile(int argc, const char **argv) {
if (!_vm->_allowImport) {
- DebugPrintf("This command may only be used from the main menu.\n");
+ DebugPrintf("This command only works from the main menu.\n");
return true;
}
@@ -507,6 +508,56 @@ bool Debugger_EoB::cmd_importSaveFile(int argc, const char **argv) {
return true;
}
+bool Debugger_EoB::cmd_saveOriginal(int argc, const char **argv) {
+ if (!_vm->_runFlag) {
+ DebugPrintf("This command doesn't work during intro or outro sequences,\nfrom the main menu or from the character generation.\n");
+ return true;
+ }
+
+ Common::String dir = ConfMan.get("savepath");
+ if (dir == "None")
+ dir.clear();
+
+ Common::FSNode nd(dir);
+ if (!nd.isDirectory())
+ return false;
+
+ if (_vm->game() == GI_EOB1) {
+ if (argc == 1) {
+ if (_vm->saveAsOriginalSaveFile()) {
+ Common::FSNode nf = nd.getChild(Common::String::format("EOBDATA.SAV"));
+ if (nf.isReadable())
+ DebugPrintf("Saved to file: %s\n\n", nf.getPath().c_str());
+ else
+ DebugPrintf("Failure.\n");
+ } else {
+ DebugPrintf("Failure.\n");
+ }
+ } else {
+ DebugPrintf("Syntax: save_original\n (Saves game in original file format to a file which can be used with the orginal game executable.)\n\n");
+ }
+ return true;
+
+ } else if (argc == 2) {
+ int slot = atoi(argv[1]);
+ if (slot < 0 || slot > 5) {
+ DebugPrintf("Slot must be between (including) 0 and 5.\n");
+ } else if (_vm->saveAsOriginalSaveFile(slot)) {
+ Common::FSNode nf = nd.getChild(Common::String::format("EOBDATA%d.SAV", slot));
+ if (nf.isReadable())
+ DebugPrintf("Saved to file: %s\n\n", nf.getPath().c_str());
+ else
+ DebugPrintf("Failure.\n");
+ } else {
+ DebugPrintf("Failure.\n");
+ }
+ return true;
+ }
+
+ DebugPrintf("Syntax: save_original <slot>\n (Saves game in original file format to a file which can be used with the orginal game executable.\n A save slot between 0 and 5 must be specified.)\n\n");
+ return true;
+}
+
#endif // ENABLE_EOB
} // End of namespace Kyra
diff --git a/engines/kyra/debugger.h b/engines/kyra/debugger.h
index 2b1dcbe505..e4ab39102a 100644
--- a/engines/kyra/debugger.h
+++ b/engines/kyra/debugger.h
@@ -120,6 +120,7 @@ protected:
EoBCoreEngine *_vm;
bool cmd_importSaveFile(int argc, const char **argv);
+ bool cmd_saveOriginal(int argc, const char **argv);
};
#endif // ENABLE_EOB
diff --git a/engines/kyra/eobcommon.h b/engines/kyra/eobcommon.h
index 70200d3049..1a74321364 100644
--- a/engines/kyra/eobcommon.h
+++ b/engines/kyra/eobcommon.h
@@ -850,6 +850,7 @@ protected:
// Default parameters will import all present original save files and push them to the top of the save dialog.
bool importOriginalSaveFile(int destSlot, const char *sourceFile = 0);
Common::String readOriginalSaveFile(Common::String &file);
+ bool saveAsOriginalSaveFile(int slot = -1);
void *generateMonsterTempData(LevelTempData *tmp);
void restoreMonsterTempData(LevelTempData *tmp);
diff --git a/engines/kyra/saveload_eob.cpp b/engines/kyra/saveload_eob.cpp
index e4f53a852d..1e5b40af83 100644
--- a/engines/kyra/saveload_eob.cpp
+++ b/engines/kyra/saveload_eob.cpp
@@ -695,6 +695,8 @@ Common::String EoBCoreEngine::readOriginalSaveFile(Common::String &file) {
in.read(c->effectsRemainder, 4);
c->effectFlags = in.readUint32();
if (c->effectFlags && _flags.gameID == GI_EOB1) {
+ // Spell effect flags are completely different in EOB I. We only use EOB II style flags in ScummVM.
+ // Doesn't matter much, since these are the temporary spell effects only anyway.
warning("EoBCoreEngine::readOriginalSaveFile(): Unhandled character effect flags encountered in original EOB1 save file '%s' ('%s')", file.c_str(), desc.c_str());
c->effectFlags = 0;
}
@@ -713,6 +715,8 @@ Common::String EoBCoreEngine::readOriginalSaveFile(Common::String &file) {
_hasTempDataFlags = (_flags.gameID == GI_EOB1) ? in.readUint16() : in.readUint32();
_partyEffectFlags = (_flags.gameID == GI_EOB1) ? in.readUint16() : in.readUint32();
if (_partyEffectFlags && _flags.gameID == GI_EOB1) {
+ // Spell effect flags are completely different in EOB I. We only use EOB II style flags in ScummVM.
+ // Doesn't matter much, since these are the temporary spell effects only anyway.
warning("EoBCoreEngine::readOriginalSaveFile(): Unhandled party effect flags encountered in original EOB1 save file '%s' ('%s')", file.c_str(), desc.c_str());
_partyEffectFlags = 0;
}
@@ -737,7 +741,7 @@ Common::String EoBCoreEngine::readOriginalSaveFile(Common::String &file) {
t->value = in.readSByte();
}
- int numParts = (_flags.gameID == GI_EOB1) ? 13 : 18;
+ int numParts = (_flags.gameID == GI_EOB1) ? 12 : 17;
int partSize = (_flags.gameID == GI_EOB1) ? 2040 : 2130;
uint32 nextPart = in.pos();
uint8 *cmpData = new uint8[1200];
@@ -831,7 +835,7 @@ Common::String EoBCoreEngine::readOriginalSaveFile(Common::String &file) {
for (int ii = 0; ii < 5; ii++) {
WallOfForce *w = &lw[ii];
w->block = in.readUint16();
- w->duration = in.readUint32();
+ w->duration = in.readUint32() * _tickLength;
}
}
@@ -845,10 +849,7 @@ Common::String EoBCoreEngine::readOriginalSaveFile(Common::String &file) {
_itemTypes = new EoBItemType[65];
memset(_itemTypes, 0, sizeof(EoBItemType) * 65);
- if (_flags.gameID == GI_EOB1)
- return desc;
-
- for (int i = 51; i < 65; i++) {
+ for (int i = 51; i < 57; i++) {
EoBItemType *t = &_itemTypes[i];
t->invFlags = in.readUint16();
t->handFlags = in.readUint16();
@@ -868,6 +869,355 @@ Common::String EoBCoreEngine::readOriginalSaveFile(Common::String &file) {
return in.err() ? Common::String() : desc;
}
+static uint32 encodeFrame4(const uint8 *src, uint8 *dst, uint32 insize) {
+ const uint8 *end = src + insize;
+
+ bool updateCounter = true;
+ const uint8 *in = src;
+
+ uint8 *out = dst;
+ uint8 *cntPtr = dst;
+
+ *dst++ = 0x81;
+ *dst++ = *src++;
+
+ while (src < end) {
+ const uint8 *src2 = in;
+ const uint8 *src3 = 0;
+ uint16 len = 1;
+
+ for (bool loop = true; loop; ) {
+ uint16 count = 0;
+ uint16 scansize = end - src - 1;
+ if (scansize > 64) {
+ if (src[0] == src[64]) {
+ for (uint16 i = 0; i < scansize && src[0] == src[i]; ++i)
+ count++;
+ if (count > 64) {
+ updateCounter = false;
+ *dst++ = 0xFE;
+ WRITE_LE_UINT16(dst, count);
+ dst += 2;
+ *dst++ = src[0];
+ src += count;
+ loop = true;
+ continue;
+ }
+ }
+ }
+
+ const uint8 *tmp = src2;
+
+ do {
+ count = src - tmp;
+ loop = false;
+ if (count == 0)
+ break;
+
+ while (count--) {
+ if (*src == *tmp++) {
+ loop = true;
+ break;
+ }
+ }
+ if (!loop)
+ break;
+ } while (*(src + len - 1) != *(tmp + len - 2));
+
+ if (!loop)
+ break;
+
+ src2 = tmp--;
+ const uint8 *tsrc = src;
+ count = end - src;
+ bool nmatch = false;
+
+ while (count--) {
+ if (*tsrc++ != *tmp++) {
+ nmatch = true;
+ break;
+ }
+ }
+ if (!nmatch)
+ tmp++;
+
+ count = tmp - src2;
+ if (count >= len) {
+ len = count;
+ src3 = src2 - 1;
+ }
+ }
+
+ if (len <= 2) {
+ for (bool forceLoop = !updateCounter; forceLoop || *cntPtr == 0xBF; forceLoop = false) {
+ cntPtr = dst;
+ *dst++ = 0x80;
+ }
+ (*cntPtr)++;
+ *dst++ = *src++;
+ updateCounter = true;
+ continue;
+
+ } else if (len > 10 || (src - src3) > 4095) {
+ if (len <= 64) {
+ *dst++ = (len - 3) | 0xC0;
+ } else {
+ *dst++ = 0xFF;
+ WRITE_LE_UINT16(dst, len);
+ dst += 2;
+ }
+ WRITE_LE_UINT16(dst, src3 - in);
+ } else {
+ WRITE_BE_UINT16(dst, (src - src3) + ((len - 3) << 12));
+ }
+
+ dst += 2;
+ src += len;
+ updateCounter = false;
+ }
+
+ *dst++ = 0x80;
+
+ return dst - out;
+}
+
+bool EoBCoreEngine::saveAsOriginalSaveFile(int slot) {
+ if (_flags.gameID == GI_EOB2 && (slot < 0 || slot > 5))
+ return false;
+
+ Common::String dir = ConfMan.get("savepath");
+ if (dir == "None")
+ dir.clear();
+
+ Common::FSNode nd(dir);
+ if (!nd.isDirectory())
+ return false;
+
+ Common::FSNode nf = nd.getChild(_flags.gameID == GI_EOB1 ? "EOBDATA.SAV" : Common::String::format("EOBDATA%d.SAV", slot));
+ Common::WriteStream *out = nf.createWriteStream();
+
+ if (_flags.gameID == GI_EOB2) {
+ static const char tempStr[20] = "SCUMMVM EXPORT ";
+ out->write(tempStr, 20);
+ }
+
+ completeDoorOperations();
+ generateTempData();
+ advanceTimers(_restPartyElapsedTime);
+ _restPartyElapsedTime = 0;
+
+ for (int i = 0; i < 6; i++)
+ timerSpecialCharacterUpdate(0x30 + i);
+
+ for (int i = 0; i < 6; i++) {
+ EoBCharacter *c = &_characters[i];
+ out->writeByte(c->id);
+ out->writeByte(c->flags);
+ out->write(c->name, 11);
+ out->writeSByte(c->strengthCur);
+ out->writeSByte(c->strengthMax);
+ out->writeSByte(c->strengthExtCur);
+ out->writeSByte(c->strengthExtMax);
+ out->writeSByte(c->intelligenceCur);
+ out->writeSByte(c->intelligenceMax);
+ out->writeSByte(c->wisdomCur);
+ out->writeSByte(c->wisdomMax);
+ out->writeSByte(c->dexterityCur);
+ out->writeSByte(c->dexterityMax);
+ out->writeSByte(c->constitutionCur);
+ out->writeSByte(c->constitutionMax);
+ out->writeSByte(c->charismaCur);
+ out->writeSByte(c->charismaMax);
+ if (_flags.gameID == GI_EOB1) {
+ out->writeSByte(c->hitPointsCur);
+ out->writeSByte(c->hitPointsMax);
+ } else {
+ out->writeSint16LE(c->hitPointsCur);
+ out->writeSint16LE(c->hitPointsMax);
+ }
+ out->writeSByte(c->armorClass);
+ out->writeByte(c->disabledSlots);
+ out->writeByte(c->raceSex);
+ out->writeByte(c->cClass);
+ out->writeByte(c->alignment);
+ out->writeSByte(c->portrait);
+ out->writeByte(c->food);
+ out->write(c->level, 3);
+ for (int ii = 0; ii < 3; ii++)
+ out->writeUint32LE(c->experience[ii]);
+ out->writeUint32LE(0);
+ out->write(c->mageSpells, (_flags.gameID == GI_EOB1) ? 30 : 80);
+ out->write(c->clericSpells, (_flags.gameID == GI_EOB1) ? 30 : 80);
+ out->writeUint32LE(c->mageSpellsAvailableFlags);
+ for (int ii = 0; ii < 27; ii++)
+ out->writeSint16LE(c->inventory[ii]);
+ uint32 ct = _system->getMillis();
+ for (int ii = 0; ii < 10; ii++)
+ out->writeUint32LE((c->timers[ii] && c->timers[ii] > ct) ? (c->timers[ii] - ct) / _tickLength : 0);
+ out->write(c->events, 10);
+ out->write(c->effectsRemainder, 4);
+
+ if (c->effectFlags && _flags.gameID == GI_EOB1) {
+ // Spell effect flags are completely different in original EOB I. We only use EOB II style flags in ScummVM.
+ // This doesn't matter much here, since these flags only apply to the temporary spell effects (things like prayer, haste, etc.) anyway.
+ warning("EoBCoreEngine::saveAsOriginalFile(): Character effect flags lost while exporting original EOB1 save file");
+ out->writeUint32LE(0);
+ } else {
+ out->writeUint32LE(c->effectFlags);
+ }
+ out->writeByte(c->damageTaken);
+ out->write(c->slotStatus, 5);
+ for (int ii = 0; ii < 6; ii++)
+ out->writeByte(0);
+ }
+
+ out->writeUint16LE(_currentLevel);
+ if (_flags.gameID == GI_EOB2)
+ out->writeSint16LE(_currentSub);
+ out->writeUint16LE(_currentBlock);
+ out->writeUint16LE(_currentDirection);
+ out->writeSint16LE(_itemInHand);
+ if (_flags.gameID == GI_EOB1) {
+ out->writeUint16LE(_hasTempDataFlags);
+ out->writeUint16LE(0);
+ if (_partyEffectFlags)
+ // Spell effect flags are completely different in original EOB I. We only use EOB II style flags in ScummVM.
+ // This doesn't matter much here, since these flags only apply to the temporary spell effects (things like prayer, haste, etc.) anyway.
+ warning("EoBCoreEngine::saveAsOriginalFile(): Party effect flags lost while exporting original EOB1 save file");
+ } else {
+ out->writeUint32LE(_hasTempDataFlags);
+ out->writeUint32LE(_partyEffectFlags);
+ }
+ if (_flags.gameID == GI_EOB2)
+ out->writeByte(0);
+ _inf->saveState(out, true);
+
+ int numItems = (_flags.gameID == GI_EOB1) ? 500 : 600;
+ for (int i = 0; i < numItems; i++) {
+ EoBItem *t = &_items[i];
+ out->writeByte(t->nameUnid);
+ out->writeByte(t->nameId);
+ out->writeByte(t->flags);
+ out->writeSByte(t->icon);
+ out->writeSByte(t->type);
+ out->writeSByte(t->pos);
+ out->writeSint16LE(t->block);
+ out->writeSint16LE(t->next);
+ out->writeSint16LE(t->prev);
+ out->writeByte(t->level);
+ out->writeSByte(t->value);
+ }
+
+ int numParts = (_flags.gameID == GI_EOB1) ? 12 : 17;
+ int partSize = (_flags.gameID == GI_EOB1) ? 2040 : 2130;
+ uint8 *tempData = new uint8[4096];
+ uint8 *cmpData = new uint8[1200];
+
+ for (int i = 0; i < numParts; i++) {
+ LevelTempData *l = _lvlTempData[i];
+ memset(tempData, 0, 4096);
+ memset(cmpData, 0, 1200);
+
+ if (!l || !(_hasTempDataFlags & (1 << i))) {
+ out->write(tempData, partSize);
+ continue;
+ }
+
+ _curBlockFile = getBlockFileName(i + 1, 0);
+ const uint8 *p = getBlockFileData();
+ uint16 len = READ_LE_UINT16(p + 4);
+ p += 6;
+
+ uint8 *d = tempData;
+ for (int ii = 0; ii < 1024; ii++) {
+ for (int iii = 0; iii < 4; iii++)
+ *d++ = l->wallsXorData[ii * len + iii] ^ p[ii * len + iii];
+ }
+
+ uint32 outsize = encodeFrame4(tempData, cmpData, 4096);
+ if (outsize > 1200)
+ error("Map compression failure: size of map = %d", outsize);
+
+ out->write(cmpData, 1200);
+
+ for (int ii = 0; ii < 30; ii++) {
+ EoBMonsterInPlay *m = &((EoBMonsterInPlay*)l->monsters)[ii];
+ out->writeByte(m->type);
+ out->writeByte(m->unit);
+ out->writeUint16LE(m->block);
+ out->writeByte(m->pos);
+ out->writeSByte(m->dir);
+ out->writeByte(m->animStep);
+ out->writeByte(m->shpIndex);
+ out->writeSByte(m->mode);
+ out->writeSByte(m->f_9);
+ out->writeSByte(m->curAttackFrame);
+ out->writeSByte(m->spellStatusLeft);
+ out->writeSint16LE(m->hitPointsMax);
+ out->writeSint16LE(m->hitPointsCur);
+ out->writeUint16LE(m->dest);
+ out->writeUint16LE(m->randItem);
+ out->writeUint16LE(m->fixedItem);
+ out->writeByte(m->flags);
+ out->writeByte(m->idleAnimState);
+
+ if (_flags.gameID == GI_EOB1)
+ out->writeByte(m->stepsTillRemoteAttack);
+ else
+ out->writeByte(m->curRemoteWeapon);
+
+ out->writeByte(m->numRemoteAttacks);
+ out->writeSByte(m->palette);
+
+ if (_flags.gameID == GI_EOB1) {
+ out->writeByte(0);
+ } else {
+ out->writeByte(m->directionChanged);
+ out->writeByte(m->stepsTillRemoteAttack);
+ out->writeByte(m->sub);
+ }
+ }
+
+ if (_flags.gameID == GI_EOB1)
+ continue;
+
+ for (int ii = 0; ii < 5; ii++) {
+ WallOfForce *w= &((WallOfForce*)l->wallsOfForce)[ii];
+ out->writeUint16LE(w->block);
+ out->writeUint32LE(w->duration / _tickLength);
+ }
+ }
+
+ delete[] cmpData;
+ delete[] tempData;
+
+ out->writeByte(_configMusic ? 1 : 0);
+ out->writeByte(_configMusic ? 1 : 0);
+ out->writeByte(_configHpBarGraphs ? 1 : 0);
+
+ for (int i = 51; i < 57; i++) {
+ EoBItemType *t = &_itemTypes[i];
+ out->writeUint16LE(t->invFlags);
+ out->writeUint16LE(t->handFlags);
+ out->writeSByte(t->armorClass);
+ out->writeSByte(t->allowedClasses);
+ out->writeSByte(t->requiredHands);
+ out->writeSByte(t->dmgNumDiceS);
+ out->writeSByte(t->dmgNumPipsS);
+ out->writeSByte(t->dmgIncS);
+ out->writeSByte(t->dmgNumDiceL);
+ out->writeSByte(t->dmgNumPipsL);
+ out->writeSByte(t->dmgIncL);
+ out->writeByte(t->unk1);
+ out->writeUint16LE(t->extraProperties);
+ }
+
+ out->finalize();
+ delete out;
+
+ return true;
+}
+
void *EoBCoreEngine::generateMonsterTempData(LevelTempData *tmp) {
EoBMonsterInPlay *m = new EoBMonsterInPlay[30];
memcpy(m, _monsters, sizeof(EoBMonsterInPlay) * 30);
diff --git a/engines/kyra/script_eob.cpp b/engines/kyra/script_eob.cpp
index e5ccbf2c2e..de4d01b254 100644
--- a/engines/kyra/script_eob.cpp
+++ b/engines/kyra/script_eob.cpp
@@ -221,10 +221,16 @@ void EoBInfProcessor::loadState(Common::SeekableSubReadStreamEndian &in, bool or
_flagTable[i] = in.readUint32();
}
-void EoBInfProcessor::saveState(Common::OutSaveFile *out) {
- out->writeByte(_preventRest);
- for (int i = 0; i < 18; i++)
- out->writeUint32BE(_flagTable[i]);
+void EoBInfProcessor::saveState(Common::OutSaveFile *out, bool origFile) {
+ if (_vm->game() == GI_EOB2 || !origFile)
+ out->writeByte(_preventRest);
+ int numFlags = (_vm->game() == GI_EOB1 && origFile) ? 13 : 18;
+ for (int i = 0; i < numFlags; i++) {
+ if (origFile)
+ out->writeUint32LE(_flagTable[i]);
+ else
+ out->writeUint32BE(_flagTable[i]);
+ }
}
void EoBInfProcessor::reset() {
diff --git a/engines/kyra/script_eob.h b/engines/kyra/script_eob.h
index ff3a5e0fac..8e2dbd8423 100644
--- a/engines/kyra/script_eob.h
+++ b/engines/kyra/script_eob.h
@@ -48,7 +48,7 @@ public:
bool preventRest() const;
void loadState(Common::SeekableSubReadStreamEndian &in, bool origFile = false);
- void saveState(Common::OutSaveFile *out);
+ void saveState(Common::OutSaveFile *out, bool origFile = false);
void reset();
private:
diff --git a/engines/kyra/sequences_darkmoon.cpp b/engines/kyra/sequences_darkmoon.cpp
index d2e6e85218..03b15d6950 100644
--- a/engines/kyra/sequences_darkmoon.cpp
+++ b/engines/kyra/sequences_darkmoon.cpp
@@ -142,7 +142,6 @@ int DarkMoonEngine::mainMenu() {
case 3:
// transfer party
- //seq_playFinale();
menuChoice = -3;
break;