mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
32e89153b3
We were using the Common::Language value to identify language blocks
in the teenagent.dat file. But that meant that modifying the enum
would cause the dat file to become obsolete and the engine to fail
to find some language block. This recently happened with commit
13b659b that added Canadian French to Common::Language.
This commit introduces custom language IDs that match the
Common::Language values at the time teenagent.dat was last
generated, This means we do not need to generate it again with
those changes.
This fixes bug #16568.
446 lines
12 KiB
C++
446 lines
12 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
// Disable symbol overrides so that we can use system headers.
|
|
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#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, 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, 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, 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, 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, Language language) {
|
|
Common::Array<Common::Array<ObjectNameDesc>> *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<uint16> 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, 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)0xff);
|
|
|
|
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;
|
|
}
|