/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
// Disable symbol overrides so that we can use system headers.
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include
#include
#include
#include "util.h"
#include "create_teenagent.h"
#include "static_tables.h"
void writeStringsBlock(FILE *fd, const char **stringArr, uint size) {
for (uint i = 0; i < size; i++) {
for (uint j = 0; j < strlen(stringArr[i]); j++) {
if (stringArr[i][j] == '\n')
writeByte(fd, '\0');
else
writeByte(fd, stringArr[i][j]);
}
writeByte(fd, '\0');
writeByte(fd, '\0');
}
}
void writeCombinations(FILE *fd, Common::Language language) {
const char **combineMessages = englishCombineMessages;
if (language == CS_CZE)
combineMessages = czechCombineMessages;
else if (language == PL_POL)
combineMessages = polishCombineMessages;
else if (language == RU_RUS)
combineMessages = russianCombineMessages;
for (uint i = 0; i < kNumCombinations; i++) {
combiningTable[i].write(fd);
for (uint j = 0; j < strlen(combineMessages[i]); j++) {
if (combineMessages[i][j] == '\n')
writeByte(fd, '\0');
else
writeByte(fd, combineMessages[i][j]);
}
writeByte(fd, '\0');
writeByte(fd, '\0');
}
}
void writeDialogStacks(FILE *fd, Common::Language language) {
const char ***dialogs = englishDialogs;
if (language == CS_CZE)
dialogs = czechDialogs;
else if (language == PL_POL)
dialogs = polishDialogs;
else if (language == RU_RUS)
dialogs = russianDialogs;
uint16 offset = 0;
uint16 dialogOffsets[kNumDialogs];
for (uint i = 0; i < kNumDialogs; i++) {
dialogOffsets[i] = offset;
bool dialogEnd = false;
uint j = 0;
while (!dialogEnd) {
offset += strlen(dialogs[i][j]);
if (strcmp(dialogs[i][j], END_DIALOG) == 0)
dialogEnd = true;
j++;
}
}
for (uint i = 0; i < sizeof(dialogStacks) / sizeof(uint16); i++) {
if (dialogStacks[i] != 0xffff) {
if (i == 0) {
// skip ANIM_WAIT (0xff) byte
writeUint16LE(fd, dialogOffsets[dialogStacks[i]] + 1);
} else if (i == 190) {
// There are two extra null bytes
// in at the beginning of this dialog.
// Skip them.
writeUint16LE(fd, dialogOffsets[dialogStacks[i]] + 2);
} else {
writeUint16LE(fd, dialogOffsets[dialogStacks[i]]);
}
} else {
writeUint16LE(fd, 0xffff);
}
}
}
void writeDialogs(FILE *fd, Common::Language language) {
const char ***dialogs = englishDialogs;
if (language == CS_CZE)
dialogs = czechDialogs;
else if (language == PL_POL)
dialogs = polishDialogs;
else if (language == RU_RUS)
dialogs = russianDialogs;
// Write out dialog string block
static const char nulls[6] = "\0\0\0\0\0";
for (uint i = 0; i < kNumDialogs; i++) {
//printf("Writing Dialog #%d\n", i);
bool dialogEnd = false;
uint j = 0;
while (!dialogEnd) {
uint nullCount = 0;
if (strcmp(dialogs[i][j], NEW_LINE) == 0) {
nullCount = 1;
} else if (strcmp(dialogs[i][j], DISPLAY_MESSAGE) == 0) {
nullCount = 2;
} else if (strcmp(dialogs[i][j], CHANGE_CHARACTER) == 0) {
nullCount = 3;
} else if (strcmp(dialogs[i][j], END_DIALOG) == 0) {
nullCount = 4;
dialogEnd = true;
} else { // Deals with normal dialogue and ANIM_WAIT cases
if (fwrite(dialogs[i][j], 1, strlen(dialogs[i][j]), fd) != strlen(dialogs[i][j])) {
perror("Writing dialog string");
exit(1);
}
}
if (nullCount != 0 && nullCount < 5) {
if (fwrite(nulls, 1, nullCount, fd) != nullCount) {
perror("Writing dialog string nulls");
exit(1);
}
}
j++;
}
}
}
void writeItems(FILE *fd, Common::Language language) {
const char ***items = englishItems;
if (language == CS_CZE)
items = czechItems;
else if (language == PL_POL)
items = polishItems;
else if (language == RU_RUS)
items = russianItems;
const uint kNumInventoryItems = 92;
for (uint i = 0; i < kNumInventoryItems; i++) {
// Write item id
writeByte(fd, i + 1);
// Write animated flag
if (i == 6 || i == 13 || i == 47 || i == 49 || i == 67 || i == 91)
writeByte(fd, 0x01);
else
writeByte(fd, 0x00);
// Write name and description (if exists) of an item
uint j = 0;
bool endItem = false;
while (!endItem) {
if (strcmp(items[i][j], "\n") == 0) { // Separator between name and description
writeByte(fd, '\0');
} else if (strcmp(items[i][j], "\n\n") == 0) {
writeByte(fd, '\0');
writeByte(fd, '\0');
endItem = true;
} else {
if (fwrite(items[i][j], 1, strlen(items[i][j]), fd) != strlen(items[i][j])) {
perror("Writing item string");
exit(1);
}
}
j++;
}
}
}
void writeSceneObjects(FILE *fd, Common::Language language) {
Common::Array> *objNamesDescs = &englishSceneObjectNamesDescs;
SettableObjectName *settableSceneObjects = englishSettableObjectNames;
if (language == CS_CZE) {
objNamesDescs = &czechSceneObjectNamesDescs;
settableSceneObjects = czechSettableObjectNames;
} else if (language == PL_POL) {
objNamesDescs = &polishSceneObjectNamesDescs;
settableSceneObjects = polishSettableObjectNames;
} else if (language == RU_RUS) {
objNamesDescs = &russianSceneObjectNamesDescs;
settableSceneObjects = russianSettableObjectNames;
}
uint sceneObjTableAddrsPos = ftell(fd);
uint16 sceneObjTableAddrs[42]{};
uint16 curOffset = 0;
for (uint i = 0; i < sceneObjects.size(); i++)
writeUint16LE(fd, 0);
curOffset += 84; // 2 bytes * 42 scenes
for (uint i = 0; i < sceneObjects.size(); i++) {
sceneObjTableAddrs[i] = curOffset;
uint firstObjsAddrFilePos = ftell(fd);
Common::Array sceneObjAddrs(sceneObjects[i].size(), 0);
// Add blank object to the end
sceneObjAddrs.push_back(0);
for (uint16 addr : sceneObjAddrs)
writeUint16LE(fd, addr);
curOffset += sizeof(uint16) * sceneObjAddrs.size();
for (uint j = 0; j < sceneObjects[i].size(); j++) {
sceneObjAddrs[j] = curOffset;
// Write the object data
sceneObjects[i][j].write(fd);
curOffset += 19;
// Name
const char *name = (*objNamesDescs)[i][j]._name;
for (uint k = 0; k < strlen(name); k++) {
if (name[k] == '\n')
writeByte(fd, '\0');
else
writeByte(fd, name[k]);
}
bool nameIsSettable = false;
const char *setName = nullptr;
for (byte k = 0; k < 4; k++) {
if (strcmp(name, settableSceneObjects[k]._initialName) == 0) {
nameIsSettable = true;
setName = settableSceneObjects[k]._setName;
if (strlen(setName) > strlen(settableSceneObjects[k]._initialName)) {
uint nameLengthDiff = strlen(setName) - strlen(settableSceneObjects[k]._initialName);
for (uint c = 0; c < nameLengthDiff; c++) {
writeByte(fd, '\0');
curOffset++;
}
}
break;
}
}
writeByte(fd, '\0');
curOffset += strlen(name) + 1;
// Description (if exists)
const char *description = (*objNamesDescs)[i][j]._description;
if (strlen(description) == 0) {
writeByte(fd, '\0');
writeByte(fd, '\0');
curOffset += 2;
} else if (strcmp(description, "\001") == 0) {
writeByte(fd, '\001');
curOffset++;
} else {
for (uint k = 0; k < strlen(description); k++) {
if (description[k] == '\n')
writeByte(fd, '\0');
else
writeByte(fd, description[k]);
}
writeByte(fd, '\0');
writeByte(fd, '\0');
curOffset += strlen(description);
curOffset += 2;
}
if (nameIsSettable) {
fwrite(setName, 1, strlen(setName), fd);
writeByte(fd, '\0');
writeByte(fd, 0xFF);
curOffset += strlen(setName) + 2;
}
}
uint pos = ftell(fd);
fseek(fd, firstObjsAddrFilePos, SEEK_SET);
fwrite(sceneObjAddrs.data(), sizeof(uint16), sceneObjAddrs.size(), fd);
fseek(fd, pos, SEEK_SET);
}
uint pos = ftell(fd);
fseek(fd, sceneObjTableAddrsPos, SEEK_SET);
for (uint i = 0; i < sceneObjects.size(); i++) {
writeUint16LE(fd, sceneObjTableAddrs[i]);
}
fseek(fd, pos, SEEK_SET);
}
uint32 writeResource(FILE *fd, ResourceType resType, Common::Language language) {
uint prevFilePos = ftell(fd);
switch (resType) {
case kResCredits: {
const char **credits = englishCredits;
if (language == CS_CZE)
credits = czechCredits;
else if (language == PL_POL)
credits = polishCredits;
else if (language == RU_RUS)
credits = russianCredits;
writeStringsBlock(fd, credits, kNumCredits);
break;
}
case kResDialogStacks:
writeDialogStacks(fd, language);
break;
case kResDialogs:
writeDialogs(fd, language);
break;
case kResItems:
writeItems(fd, language);
break;
case kResSceneObjects:
writeSceneObjects(fd, language);
break;
case kResMessages: {
const char **messages = englishMessages;
if (language == CS_CZE)
messages = czechMessages;
else if (language == PL_POL)
messages = polishMessages;
else if (language == RU_RUS)
messages = russianMessages;
writeStringsBlock(fd, messages, kNumMessages);
break;
}
case kResCombinations:
writeCombinations(fd, language);
break;
};
uint currentFilePos = ftell(fd);
uint32 resourceSize = currentFilePos - prevFilePos;
return resourceSize;
}
int main(int argc, char *argv[]) {
const char *dat_name = "teenagent.dat";
FILE *fout = fopen(dat_name, "wb");
if (fout == nullptr) {
perror("opening output file");
exit(1);
}
// Write header
fwrite("TEENAGENT", 9, 1, fout);
writeByte(fout, TEENAGENT_DAT_VERSION);
if (fwrite(cseg, CSEG_SIZE, 1, fout) != 1) {
perror("Writing code segment");
exit(1);
}
if (fwrite(dsegStartBlock, DSEG_STARTBLK_SIZE, 1, fout) != 1) {
perror("Writing data segment start block");
exit(1);
}
// Skip messages block
// It is written as a resource after the data segment
uint msgBlockSize = 11415; // The size of messages block in the English exe
fseek(fout, msgBlockSize, SEEK_CUR);
if (fwrite(dsegEndBlock, DSEG_ENDBLK_SIZE, 1, fout) != 1) {
perror("Writing data segment end block");
exit(1);
}
uint32 languageOffset = ftell(fout);
for (uint lang = 0; lang < NUM_LANGS; lang++) {
// Write language ID
writeByte(fout, supportedLanguages[lang]);
writeUint32LE(fout, 0);
}
writeByte(fout, (byte)Common::Language::UNK_LANG);
for (uint lang = 0; lang < NUM_LANGS; lang++) {
// Write offset to data
uint32 dataOffset = ftell(fout);
fseek(fout, languageOffset + (lang * 5 + 1), SEEK_SET);
writeUint32LE(fout, dataOffset);
fseek(fout, dataOffset, SEEK_SET);
ResourceInfo resourceInfos[NUM_RESOURCES];
uint32 resInfoPos = ftell(fout);
fseek(fout, (2 * sizeof(uint32) + sizeof(byte)) * NUM_RESOURCES, SEEK_CUR);
for (uint i = 0; i < NUM_RESOURCES; i++) {
resourceInfos[i]._id = i;
resourceInfos[i]._offset = ftell(fout);
uint32 size = writeResource(fout, ResourceType(i), supportedLanguages[lang]);
resourceInfos[i]._size = size;
}
fseek(fout, resInfoPos, SEEK_SET);
for (uint i = 0; i < NUM_RESOURCES; i++) {
writeByte(fout, resourceInfos[i]._id);
if (resourceInfos[i]._id != 0) {
// Offsets are stored relative to first resource's offset
// NOTE: First resource is kResDialogs(1), not kResDialogStacks(0)
// because kResDialogStacks is not stored with the rest of resources.
writeUint32LE(fout, resourceInfos[i]._offset - resourceInfos[1]._offset);
} else
writeUint32LE(fout, resourceInfos[i]._offset);
writeUint32LE(fout, resourceInfos[i]._size);
}
// Go back to current file pos
fseek(fout, resourceInfos[NUM_RESOURCES - 1]._offset + resourceInfos[NUM_RESOURCES - 1]._size, SEEK_SET);
}
fclose(fout);
return 0;
}