Files
Thierry Crozat 32e89153b3 TEENAGENT: Use custom language IDs for teenagent.dat
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.
2026-02-21 22:03:55 +00:00

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;
}