Initial commit
This commit is contained in:
274
engines/ags/shared/game/tra_file.cpp
Normal file
274
engines/ags/shared/game/tra_file.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/shared/game/tra_file.h"
|
||||
#include "ags/shared/ac/words_dictionary.h"
|
||||
#include "ags/shared/debugging/out.h"
|
||||
#include "ags/shared/util/data_ext.h"
|
||||
#include "ags/shared/util/string_compat.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Shared {
|
||||
|
||||
const char *TRASignature = "AGSTranslation";
|
||||
|
||||
|
||||
String GetTraFileErrorText(TraFileErrorType err) {
|
||||
switch (err) {
|
||||
case kTraFileErr_NoError:
|
||||
return "No error.";
|
||||
case kTraFileErr_SignatureFailed:
|
||||
return "Not an AGS translation file or an unsupported format.";
|
||||
case kTraFileErr_FormatNotSupported:
|
||||
return "Format version not supported.";
|
||||
case kTraFileErr_GameIDMismatch:
|
||||
return "Game ID does not match, translation is meant for a different game.";
|
||||
case kTraFileErr_UnexpectedEOF:
|
||||
return "Unexpected end of file.";
|
||||
case kTraFileErr_UnknownBlockType:
|
||||
return "Unknown block type.";
|
||||
case kTraFileErr_BlockDataOverlapping:
|
||||
return "Block data overlapping.";
|
||||
default:
|
||||
return "Unknown error.";
|
||||
}
|
||||
}
|
||||
|
||||
String GetTraBlockName(TraFileBlock id) {
|
||||
switch (id) {
|
||||
case kTraFblk_Dict: return "Dictionary";
|
||||
case kTraFblk_GameID: return "GameID";
|
||||
case kTraFblk_TextOpts: return "TextOpts";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
HError OpenTraFile(Stream *in) {
|
||||
// Test the file signature
|
||||
char sigbuf[16] = { 0 };
|
||||
in->Read(sigbuf, 15);
|
||||
if (ags_stricmp(TRASignature, sigbuf) != 0)
|
||||
return new TraFileError(kTraFileErr_SignatureFailed);
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
HError ReadTraBlock(Translation &tra, Stream *in, TraFileBlock block, const String &ext_id, soff_t /*block_len*/) {
|
||||
switch (block) {
|
||||
case kTraFblk_Dict: {
|
||||
std::vector<char> buf;
|
||||
// Read lines until we find zero-length key & value
|
||||
while (true) {
|
||||
String src_line = read_string_decrypt(in, buf);
|
||||
String dst_line = read_string_decrypt(in, buf);
|
||||
if (src_line.IsEmpty() || dst_line.IsEmpty())
|
||||
break;
|
||||
tra.Dict.insert(std::make_pair(src_line, dst_line));
|
||||
}
|
||||
return HError::None();
|
||||
}
|
||||
case kTraFblk_GameID:
|
||||
{
|
||||
char gamename[256];
|
||||
tra.GameUid = in->ReadInt32();
|
||||
read_string_decrypt(in, gamename, sizeof(gamename));
|
||||
tra.GameName = gamename;
|
||||
return HError::None();
|
||||
}
|
||||
case kTraFblk_TextOpts:
|
||||
tra.NormalFont = in->ReadInt32();
|
||||
tra.SpeechFont = in->ReadInt32();
|
||||
tra.RightToLeft = in->ReadInt32();
|
||||
return HError::None();
|
||||
case kTraFblk_None:
|
||||
// continue reading extensions with string ID
|
||||
break;
|
||||
default:
|
||||
return new TraFileError(kTraFileErr_UnknownBlockType,
|
||||
String::FromFormat("Type: %d, known range: %d - %d.", block, kTraFblk_Dict, kTraFblk_TextOpts));
|
||||
}
|
||||
|
||||
if (ext_id.CompareNoCase("ext_sopts") == 0) {
|
||||
StrUtil::ReadStringMap(tra.StrOptions, in);
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
return new TraFileError(kTraFileErr_UnknownBlockType,
|
||||
String::FromFormat("Type: %s", ext_id.GetCStr()));
|
||||
}
|
||||
|
||||
// TRABlockReader reads whole TRA data, block by block
|
||||
class TRABlockReader : public DataExtReader {
|
||||
public:
|
||||
TRABlockReader(Translation &tra, Stream *in)
|
||||
: DataExtReader(in, kDataExt_NumID32 | kDataExt_File32)
|
||||
, _tra(tra) {
|
||||
}
|
||||
|
||||
// Reads only the Game ID block and stops
|
||||
HError ReadGameID() {
|
||||
HError err = FindOne(kTraFblk_GameID);
|
||||
if (!err)
|
||||
return err;
|
||||
return ReadTraBlock(_tra, _in, kTraFblk_GameID, "", _blockLen);
|
||||
}
|
||||
|
||||
private:
|
||||
String GetOldBlockName(int block_id) const override {
|
||||
return GetTraBlockName((TraFileBlock)block_id);
|
||||
}
|
||||
|
||||
soff_t GetOverLeeway(int block_id) const override {
|
||||
// TRA files made by pre-3.0 editors have a block length miscount by 1 byte
|
||||
if (block_id == kTraFblk_GameID) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
HError ReadBlock(int block_id, const String &ext_id,
|
||||
soff_t block_len, bool &read_next) override {
|
||||
read_next = true;
|
||||
return ReadTraBlock(_tra, _in, (TraFileBlock)block_id, ext_id, block_len);
|
||||
}
|
||||
|
||||
Translation &_tra;
|
||||
};
|
||||
|
||||
|
||||
HError TestTraGameID(int game_uid, const String &game_name, Stream *in) {
|
||||
HError err = OpenTraFile(in);
|
||||
if (!err)
|
||||
return err;
|
||||
|
||||
Translation tra;
|
||||
TRABlockReader reader(tra, in);
|
||||
err = reader.ReadGameID();
|
||||
|
||||
if (!err)
|
||||
return err;
|
||||
// Test the identifiers, if they are not present then skip the test
|
||||
if ((tra.GameUid != 0 && (game_uid != tra.GameUid)) ||
|
||||
(!tra.GameName.IsEmpty() && (game_name != tra.GameName)))
|
||||
return new TraFileError(kTraFileErr_GameIDMismatch,
|
||||
String::FromFormat("The translation is designed for '%s'", tra.GameName.GetCStr()));
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
HError ReadTraData(Translation &tra, Stream *in) {
|
||||
HError err = OpenTraFile(in);
|
||||
if (!err)
|
||||
return err;
|
||||
|
||||
TRABlockReader reader(tra, in);
|
||||
return reader.Read();
|
||||
}
|
||||
|
||||
// TODO: perhaps merge with encrypt/decrypt utilities
|
||||
static const char *EncryptText(std::vector<char> &en_buf, const String &s) {
|
||||
if (en_buf.size() < s.GetLength() + 1)
|
||||
en_buf.resize(s.GetLength() + 1);
|
||||
strncpy(&en_buf.front(), s.GetCStr(), s.GetLength() + 1);
|
||||
encrypt_text(&en_buf.front());
|
||||
return &en_buf.front();
|
||||
}
|
||||
|
||||
// TODO: perhaps merge with encrypt/decrypt utilities
|
||||
static const char *EncryptEmptyString(std::vector<char> &en_buf) {
|
||||
en_buf[0] = 0;
|
||||
encrypt_text(&en_buf.front());
|
||||
return &en_buf.front();
|
||||
}
|
||||
|
||||
void WriteGameID(const Translation &tra, Stream *out) {
|
||||
std::vector<char> en_buf;
|
||||
out->WriteInt32(tra.GameUid);
|
||||
StrUtil::WriteString(EncryptText(en_buf, tra.GameName), tra.GameName.GetLength() + 1, out);
|
||||
}
|
||||
|
||||
void WriteDict(const Translation &tra, Stream *out) {
|
||||
std::vector<char> en_buf;
|
||||
for (const auto &kv : tra.Dict) {
|
||||
const String &src = kv._key;
|
||||
const String &dst = kv._value;
|
||||
if (!dst.IsNullOrSpace()) {
|
||||
String unsrc = StrUtil::Unescape(src);
|
||||
String undst = StrUtil::Unescape(dst);
|
||||
StrUtil::WriteString(EncryptText(en_buf, unsrc), unsrc.GetLength() + 1, out);
|
||||
StrUtil::WriteString(EncryptText(en_buf, undst), undst.GetLength() + 1, out);
|
||||
}
|
||||
}
|
||||
// Write a pair of empty key/values
|
||||
StrUtil::WriteString(EncryptEmptyString(en_buf), 1, out);
|
||||
StrUtil::WriteString(EncryptEmptyString(en_buf), 1, out);
|
||||
}
|
||||
|
||||
void WriteTextOpts(const Translation &tra, Stream *out) {
|
||||
out->WriteInt32(tra.NormalFont);
|
||||
out->WriteInt32(tra.SpeechFont);
|
||||
out->WriteInt32(tra.RightToLeft);
|
||||
}
|
||||
|
||||
static const Translation *writer_tra;
|
||||
static void(*writer_writer)(const Translation &tra, Stream *out);
|
||||
|
||||
void WriteStrOptions(const Translation &tra, Stream *out) {
|
||||
StrUtil::WriteStringMap(tra.StrOptions, out);
|
||||
}
|
||||
|
||||
static void WriteTraBlockWriter(Stream *out) {
|
||||
writer_writer(*writer_tra, out);
|
||||
}
|
||||
|
||||
inline void WriteTraBlock(const Translation &tra, TraFileBlock block,
|
||||
void(*writer)(const Translation &tra, Stream *out), Stream *out) {
|
||||
writer_tra = &tra;
|
||||
writer_writer = writer;
|
||||
|
||||
WriteExtBlock(block, WriteTraBlockWriter,
|
||||
kDataExt_NumID32 | kDataExt_File32, out);
|
||||
}
|
||||
|
||||
inline void WriteTraBlock(const Translation &tra, const String &ext_id,
|
||||
void(*writer)(const Translation &tra, Stream *out), Stream *out) {
|
||||
writer_tra = &tra;
|
||||
writer_writer = writer;
|
||||
|
||||
WriteExtBlock(ext_id, WriteTraBlockWriter,
|
||||
kDataExt_NumID32 | kDataExt_File32, out);
|
||||
}
|
||||
|
||||
void WriteTraData(const Translation &tra, Stream *out) {
|
||||
// Write header
|
||||
out->Write(TRASignature, strlen(TRASignature) + 1);
|
||||
|
||||
// Write all blocks
|
||||
WriteTraBlock(tra, kTraFblk_GameID, WriteGameID, out);
|
||||
WriteTraBlock(tra, kTraFblk_Dict, WriteDict, out);
|
||||
WriteTraBlock(tra, kTraFblk_TextOpts, WriteTextOpts, out);
|
||||
WriteTraBlock(tra, "ext_sopts", WriteStrOptions, out);
|
||||
|
||||
// Write ending
|
||||
out->WriteInt32(kTraFile_EOF);
|
||||
}
|
||||
|
||||
} // namespace Shared
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
Reference in New Issue
Block a user