1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "ags/shared/game/tra_file.h"
24 #include "ags/shared/ac/words_dictionary.h"
25 #include "ags/shared/debugging/out.h"
26 #include "ags/shared/util/data_ext.h"
27 #include "ags/shared/util/string_compat.h"
28 #include "ags/shared/util/string_utils.h"
29
30 namespace AGS3 {
31 namespace AGS {
32 namespace Shared {
33
34 const char *TRASignature = "AGSTranslation";
35
36
GetTraFileErrorText(TraFileErrorType err)37 String GetTraFileErrorText(TraFileErrorType err) {
38 switch (err) {
39 case kTraFileErr_NoError:
40 return "No error.";
41 case kTraFileErr_SignatureFailed:
42 return "Not an AGS translation file or an unsupported format.";
43 case kTraFileErr_FormatNotSupported:
44 return "Format version not supported.";
45 case kTraFileErr_GameIDMismatch:
46 return "Game ID does not match, translation is meant for a different game.";
47 case kTraFileErr_UnexpectedEOF:
48 return "Unexpected end of file.";
49 case kTraFileErr_UnknownBlockType:
50 return "Unknown block type.";
51 case kTraFileErr_BlockDataOverlapping:
52 return "Block data overlapping.";
53 }
54 return "Unknown error.";
55 }
56
GetTraBlockName(TraFileBlock id)57 String GetTraBlockName(TraFileBlock id) {
58 switch (id) {
59 case kTraFblk_Dict: return "Dictionary";
60 case kTraFblk_GameID: return "GameID";
61 case kTraFblk_TextOpts: return "TextOpts";
62 default: break;
63 }
64 return "unknown";
65 }
66
OpenTraFile(Stream * in)67 HError OpenTraFile(Stream *in) {
68 // Test the file signature
69 char sigbuf[16] = { 0 };
70 in->Read(sigbuf, 15);
71 if (ags_stricmp(TRASignature, sigbuf) != 0)
72 return new TraFileError(kTraFileErr_SignatureFailed);
73 return HError::None();
74 }
75
ReadTraBlock(Translation & tra,Stream * in,TraFileBlock block,const String & ext_id,soff_t block_len)76 HError ReadTraBlock(Translation &tra, Stream *in, TraFileBlock block, const String &ext_id, soff_t block_len) {
77 switch (block) {
78 case kTraFblk_Dict:
79 {
80 char original[1024];
81 char translation[1024];
82 // Read lines until we find zero-length key & value
83 while (true) {
84 read_string_decrypt(in, original, sizeof(original));
85 read_string_decrypt(in, translation, sizeof(translation));
86 if (!original[0] && !translation[0])
87 break;
88 tra.Dict.insert(std::make_pair(String(original), String(translation)));
89 }
90 return HError::None();
91 }
92 case kTraFblk_GameID:
93 {
94 char gamename[256];
95 tra.GameUid = in->ReadInt32();
96 read_string_decrypt(in, gamename, sizeof(gamename));
97 tra.GameName = gamename;
98 return HError::None();
99 }
100 case kTraFblk_TextOpts:
101 tra.NormalFont = in->ReadInt32();
102 tra.SpeechFont = in->ReadInt32();
103 tra.RightToLeft = in->ReadInt32();
104 return HError::None();
105 case kTraFblk_ExtStrID:
106 // continue reading extensions with string ID
107 break;
108 default:
109 return new TraFileError(kTraFileErr_UnknownBlockType,
110 String::FromFormat("Type: %d, known range: %d - %d.", block, kTraFblk_Dict, kTraFblk_TextOpts));
111 }
112
113 if (ext_id.CompareNoCase("ext_sopts") == 0) {
114 StrUtil::ReadStringMap(tra.StrOptions, in);
115 return HError::None();
116 }
117
118 return new TraFileError(kTraFileErr_UnknownBlockType,
119 String::FromFormat("Type: %s", ext_id.GetCStr()));
120 }
121
122 // TRABlockReader reads whole TRA data, block by block
123 class TRABlockReader : public DataExtReader {
124 public:
TRABlockReader(Translation & tra,Stream * in)125 TRABlockReader(Translation &tra, Stream *in)
126 : DataExtReader(in, kDataExt_NumID32 | kDataExt_File32)
127 , _tra(tra) {
128 }
129
130 // Reads only the Game ID block and stops
ReadGameID()131 HError ReadGameID() {
132 HError err = FindOne(kTraFblk_GameID);
133 if (!err)
134 return err;
135 return ReadTraBlock(_tra, _in, kTraFblk_GameID, "", _blockLen);
136 }
137
138 private:
GetOldBlockName(int block_id) const139 String GetOldBlockName(int block_id) const override {
140 return GetTraBlockName((TraFileBlock)block_id);
141 }
142
GetOverLeeway(int block_id) const143 soff_t GetOverLeeway(int block_id) const override {
144 // TRA files made by pre-3.0 editors have a block length miscount by 1 byte
145 if (block_id == kTraFblk_GameID) return 1;
146 return 0;
147 }
148
ReadBlock(int block_id,const String & ext_id,soff_t block_len,bool & read_next)149 HError ReadBlock(int block_id, const String &ext_id,
150 soff_t block_len, bool &read_next) override {
151 return ReadTraBlock(_tra, _in, (TraFileBlock)block_id, ext_id, block_len);
152 }
153
154 Translation &_tra;
155 };
156
157
TestTraGameID(int game_uid,const String & game_name,Stream * in)158 HError TestTraGameID(int game_uid, const String &game_name, Stream *in) {
159 HError err = OpenTraFile(in);
160 if (!err)
161 return err;
162
163 Translation tra;
164 TRABlockReader reader(tra, in);
165 err = reader.ReadGameID();
166
167 if (!err)
168 return err;
169 // Test the identifiers, if they are not present then skip the test
170 if ((tra.GameUid != 0 && (game_uid != tra.GameUid)) ||
171 (!tra.GameName.IsEmpty() && (game_name != tra.GameName)))
172 return new TraFileError(kTraFileErr_GameIDMismatch,
173 String::FromFormat("The translation is designed for '%s'", tra.GameName.GetCStr()));
174 return HError::None();
175 }
176
ReadTraData(Translation & tra,Stream * in)177 HError ReadTraData(Translation &tra, Stream *in) {
178 HError err = OpenTraFile(in);
179 if (!err)
180 return err;
181
182 TRABlockReader reader(tra, in);
183 return reader.Read();
184 }
185
186 // TODO: perhaps merge with encrypt/decrypt utilities
EncryptText(std::vector<char> & en_buf,const String & s)187 static const char *EncryptText(std::vector<char> &en_buf, const String &s) {
188 if (en_buf.size() < s.GetLength() + 1)
189 en_buf.resize(s.GetLength() + 1);
190 strncpy(&en_buf.front(), s.GetCStr(), s.GetLength() + 1);
191 encrypt_text(&en_buf.front());
192 return &en_buf.front();
193 }
194
195 // TODO: perhaps merge with encrypt/decrypt utilities
EncryptEmptyString(std::vector<char> & en_buf)196 static const char *EncryptEmptyString(std::vector<char> &en_buf) {
197 en_buf[0] = 0;
198 encrypt_text(&en_buf.front());
199 return &en_buf.front();
200 }
201
WriteGameID(const Translation & tra,Stream * out)202 void WriteGameID(const Translation &tra, Stream *out) {
203 std::vector<char> en_buf;
204 out->WriteInt32(tra.GameUid);
205 StrUtil::WriteString(EncryptText(en_buf, tra.GameName), tra.GameName.GetLength() + 1, out);
206 }
207
WriteDict(const Translation & tra,Stream * out)208 void WriteDict(const Translation &tra, Stream *out) {
209 std::vector<char> en_buf;
210 for (const auto &kv : tra.Dict) {
211 const String &src = kv._key;
212 const String &dst = kv._value;
213 if (!dst.IsNullOrSpace()) {
214 String unsrc = StrUtil::Unescape(src);
215 String undst = StrUtil::Unescape(dst);
216 StrUtil::WriteString(EncryptText(en_buf, unsrc), unsrc.GetLength() + 1, out);
217 StrUtil::WriteString(EncryptText(en_buf, undst), undst.GetLength() + 1, out);
218 }
219 }
220 // Write a pair of empty key/values
221 StrUtil::WriteString(EncryptEmptyString(en_buf), 1, out);
222 StrUtil::WriteString(EncryptEmptyString(en_buf), 1, out);
223 }
224
WriteTextOpts(const Translation & tra,Stream * out)225 void WriteTextOpts(const Translation &tra, Stream *out) {
226 out->WriteInt32(tra.NormalFont);
227 out->WriteInt32(tra.SpeechFont);
228 out->WriteInt32(tra.RightToLeft);
229 }
230
231 static const Translation *writer_tra;
232 static void(*writer_writer)(const Translation &tra, Stream *out);
233
WriteStrOptions(const Translation & tra,Stream * out)234 void WriteStrOptions(const Translation &tra, Stream *out) {
235 StrUtil::WriteStringMap(tra.StrOptions, out);
236 }
237
WriteTraBlockWriter(Stream * out)238 static void WriteTraBlockWriter(Stream *out) {
239 writer_writer(*writer_tra, out);
240 }
241
WriteTraBlock(const Translation & tra,TraFileBlock block,void (* writer)(const Translation & tra,Stream * out),Stream * out)242 inline void WriteTraBlock(const Translation &tra, TraFileBlock block,
243 void(*writer)(const Translation &tra, Stream *out), Stream *out) {
244 writer_tra = &tra;
245 writer_writer = writer;
246
247 WriteExtBlock(block, WriteTraBlockWriter,
248 kDataExt_NumID32 | kDataExt_File32, out);
249 }
250
WriteTraBlock(const Translation & tra,const String & ext_id,void (* writer)(const Translation & tra,Stream * out),Stream * out)251 inline void WriteTraBlock(const Translation &tra, const String &ext_id,
252 void(*writer)(const Translation &tra, Stream *out), Stream *out) {
253 writer_tra = &tra;
254 writer_writer = writer;
255
256 WriteExtBlock(ext_id, WriteTraBlockWriter,
257 kDataExt_NumID32 | kDataExt_File32, out);
258 }
259
WriteTraData(const Translation & tra,Stream * out)260 void WriteTraData(const Translation &tra, Stream *out) {
261 // Write header
262 out->Write(TRASignature, strlen(TRASignature) + 1);
263
264 // Write all blocks
265 WriteTraBlock(tra, kTraFblk_GameID, WriteGameID, out);
266 WriteTraBlock(tra, kTraFblk_Dict, WriteDict, out);
267 WriteTraBlock(tra, kTraFblk_TextOpts, WriteTextOpts, out);
268 WriteTraBlock(tra, "ext_sopts", WriteStrOptions, out);
269
270 // Write ending
271 out->WriteInt32(kTraFile_EOF);
272 }
273
274 } // namespace Shared
275 } // namespace AGS
276 } // namespace AGS3
277