KYRA: (EOB) - add debug function to export save files in original format

This commit is contained in:
athrxx 2013-04-07 22:38:25 +02:00
parent a33045f4fa
commit 66a3b2b2ac
7 changed files with 421 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -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:

View File

@ -142,7 +142,6 @@ int DarkMoonEngine::mainMenu() {
case 3:
// transfer party
//seq_playFinale();
menuChoice = -3;
break;