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