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 "common/file.h"
24 #include "common/keyboard.h"
25 #include "common/memstream.h"
26 #include "common/punycode.h"
27 #include "common/tokenizer.h"
28 #include "common/zlib.h"
29
30 #include "graphics/macgui/macwindowmanager.h"
31 #include "graphics/macgui/macfontmanager.h"
32
33 #include "director/director.h"
34 #include "director/movie.h"
35 #include "director/util.h"
36
37 namespace Director {
38
39 static struct MacKeyCodeMapping {
40 Common::KeyCode scummvm;
41 int mac;
42 } MackeyCodeMappings[] = {
43 { Common::KEYCODE_ESCAPE, 53 },
44 { Common::KEYCODE_F1, 122 },
45 { Common::KEYCODE_F2, 120 },
46 { Common::KEYCODE_F3, 99 },
47 { Common::KEYCODE_F4, 118 },
48 { Common::KEYCODE_F5, 96 },
49 { Common::KEYCODE_F6, 97 },
50 { Common::KEYCODE_F7, 98 },
51 { Common::KEYCODE_F8, 100 },
52 { Common::KEYCODE_F9, 101 },
53 { Common::KEYCODE_F10, 109 },
54 { Common::KEYCODE_F11, 103 },
55 { Common::KEYCODE_F12, 111 },
56 { Common::KEYCODE_F13, 105 }, // mirrored by print
57 { Common::KEYCODE_F14, 107 }, // mirrored by scroll lock
58 { Common::KEYCODE_F15, 113 }, // mirrored by pause
59
60 { Common::KEYCODE_BACKQUOTE, 10 },
61 { Common::KEYCODE_1, 18 },
62 { Common::KEYCODE_2, 19 },
63 { Common::KEYCODE_3, 20 },
64 { Common::KEYCODE_4, 21 },
65 { Common::KEYCODE_5, 23 },
66 { Common::KEYCODE_6, 22 },
67 { Common::KEYCODE_7, 26 },
68 { Common::KEYCODE_8, 28 },
69 { Common::KEYCODE_9, 25 },
70 { Common::KEYCODE_0, 29 },
71 { Common::KEYCODE_MINUS, 27 },
72 { Common::KEYCODE_EQUALS, 24 },
73 { Common::KEYCODE_BACKSPACE, 51 },
74
75 { Common::KEYCODE_TAB, 48 },
76 { Common::KEYCODE_q, 12 },
77 { Common::KEYCODE_w, 13 },
78 { Common::KEYCODE_e, 14 },
79 { Common::KEYCODE_r, 15 },
80 { Common::KEYCODE_t, 17 },
81 { Common::KEYCODE_y, 16 },
82 { Common::KEYCODE_u, 32 },
83 { Common::KEYCODE_i, 34 },
84 { Common::KEYCODE_o, 31 },
85 { Common::KEYCODE_p, 35 },
86 { Common::KEYCODE_LEFTBRACKET, 33 },
87 { Common::KEYCODE_RIGHTBRACKET, 30 },
88 { Common::KEYCODE_BACKSLASH, 42 },
89
90 { Common::KEYCODE_CAPSLOCK, 57 },
91 { Common::KEYCODE_a, 0 },
92 { Common::KEYCODE_s, 1 },
93 { Common::KEYCODE_d, 2 },
94 { Common::KEYCODE_f, 3 },
95 { Common::KEYCODE_g, 5 },
96 { Common::KEYCODE_h, 4 },
97 { Common::KEYCODE_j, 38 },
98 { Common::KEYCODE_k, 40 },
99 { Common::KEYCODE_l, 37 },
100 { Common::KEYCODE_SEMICOLON, 41 },
101 { Common::KEYCODE_QUOTE, 39 },
102 { Common::KEYCODE_RETURN, 36 },
103
104 { Common::KEYCODE_LSHIFT, 56 },
105 { Common::KEYCODE_z, 6 },
106 { Common::KEYCODE_x, 7 },
107 { Common::KEYCODE_c, 8 },
108 { Common::KEYCODE_v, 9 },
109 { Common::KEYCODE_b, 11 },
110 { Common::KEYCODE_n, 45 },
111 { Common::KEYCODE_m, 46 },
112 { Common::KEYCODE_COMMA, 43 },
113 { Common::KEYCODE_PERIOD, 47 },
114 { Common::KEYCODE_SLASH, 44 },
115 { Common::KEYCODE_RSHIFT, 56 },
116
117 { Common::KEYCODE_LCTRL, 54 }, // control
118 { Common::KEYCODE_LALT, 58 }, // option
119 { Common::KEYCODE_LSUPER, 55 }, // command
120 { Common::KEYCODE_SPACE, 49 },
121 { Common::KEYCODE_RSUPER, 55 }, // command
122 { Common::KEYCODE_RALT, 58 }, // option
123 { Common::KEYCODE_RCTRL, 54 }, // control
124
125 { Common::KEYCODE_LEFT, 123 },
126 { Common::KEYCODE_RIGHT, 124 },
127 { Common::KEYCODE_DOWN, 125 },
128 { Common::KEYCODE_UP, 126 },
129
130 { Common::KEYCODE_NUMLOCK, 71 },
131 { Common::KEYCODE_KP_EQUALS, 81 },
132 { Common::KEYCODE_KP_DIVIDE, 75 },
133 { Common::KEYCODE_KP_MULTIPLY, 67 },
134
135 { Common::KEYCODE_KP7, 89 },
136 { Common::KEYCODE_KP8, 91 },
137 { Common::KEYCODE_KP9, 92 },
138 { Common::KEYCODE_KP_MINUS, 78 },
139
140 { Common::KEYCODE_KP4, 86 },
141 { Common::KEYCODE_KP5, 87 },
142 { Common::KEYCODE_KP6, 88 },
143 { Common::KEYCODE_KP_PLUS, 69 },
144
145 { Common::KEYCODE_KP1, 83 },
146 { Common::KEYCODE_KP2, 84 },
147 { Common::KEYCODE_KP3, 85 },
148
149 { Common::KEYCODE_KP0, 82 },
150 { Common::KEYCODE_KP_PERIOD, 65 },
151 { Common::KEYCODE_KP_ENTER, 76 },
152
153 { Common::KEYCODE_MENU, 50 }, // international
154 { Common::KEYCODE_PRINT, 105 }, // mirrored by F13
155 { Common::KEYCODE_SCROLLOCK, 107 }, // mirrored by F14
156 { Common::KEYCODE_PAUSE, 113 }, // mirrored by F15
157 { Common::KEYCODE_INSERT, 114 },
158 { Common::KEYCODE_HOME, 115 },
159 { Common::KEYCODE_PAGEUP, 116 },
160 { Common::KEYCODE_DELETE, 117 },
161 { Common::KEYCODE_END, 119 },
162 { Common::KEYCODE_PAGEDOWN, 121 },
163
164 { Common::KEYCODE_INVALID, 0 }
165 };
166
167 static struct WinKeyCodeMapping {
168 Common::KeyCode scummvm;
169 int win;
170 } WinkeyCodeMappings[] = {
171 { Common::KEYCODE_BACKSPACE, 0x08 },
172 { Common::KEYCODE_TAB, 0x09 },
173 { Common::KEYCODE_CAPSLOCK, 0x14 },
174
175 { Common::KEYCODE_SPACE, 0x20 },
176 { Common::KEYCODE_MENU, 0x12 },
177 { Common::KEYCODE_CANCEL, 0x03 },
178 { Common::KEYCODE_RETURN, 0x0D },
179 { Common::KEYCODE_PAUSE, 0x13 },
180 { Common::KEYCODE_CAPSLOCK, 0x14 },
181 { Common::KEYCODE_PRINT, 0x2A },
182
183 { Common::KEYCODE_0, 0x30 },
184 { Common::KEYCODE_1, 0x31 },
185 { Common::KEYCODE_2, 0x32 },
186 { Common::KEYCODE_3, 0x33 },
187 { Common::KEYCODE_4, 0x34 },
188 { Common::KEYCODE_5, 0x35 },
189 { Common::KEYCODE_6, 0x36 },
190 { Common::KEYCODE_7, 0x37 },
191 { Common::KEYCODE_8, 0x38 },
192 { Common::KEYCODE_9, 0x39 },
193
194 { Common::KEYCODE_a, 0x41 },
195 { Common::KEYCODE_b, 0x42 },
196 { Common::KEYCODE_c, 0x43 },
197 { Common::KEYCODE_d, 0x44 },
198 { Common::KEYCODE_e, 0x45 },
199 { Common::KEYCODE_f, 0x46 },
200 { Common::KEYCODE_g, 0x47 },
201 { Common::KEYCODE_h, 0x48 },
202 { Common::KEYCODE_i, 0x49 },
203 { Common::KEYCODE_j, 0x4A },
204 { Common::KEYCODE_k, 0x4B },
205 { Common::KEYCODE_l, 0x4C },
206 { Common::KEYCODE_m, 0x4D },
207 { Common::KEYCODE_n, 0x4E },
208 { Common::KEYCODE_o, 0x4F },
209 { Common::KEYCODE_p, 0x50 },
210 { Common::KEYCODE_q, 0x51 },
211 { Common::KEYCODE_r, 0x52 },
212 { Common::KEYCODE_s, 0x53 },
213 { Common::KEYCODE_t, 0x54 },
214 { Common::KEYCODE_u, 0x55 },
215 { Common::KEYCODE_v, 0x56 },
216 { Common::KEYCODE_w, 0x57 },
217 { Common::KEYCODE_x, 0x58 },
218 { Common::KEYCODE_y, 0x59 },
219 { Common::KEYCODE_z, 0x5A },
220
221 { Common::KEYCODE_KP0, 0x60 },
222 { Common::KEYCODE_KP1, 0x61 },
223 { Common::KEYCODE_KP2, 0x62 },
224 { Common::KEYCODE_KP3, 0x63 },
225 { Common::KEYCODE_KP4, 0x64 },
226 { Common::KEYCODE_KP5, 0x65 },
227 { Common::KEYCODE_KP6, 0x66 },
228 { Common::KEYCODE_KP7, 0x67 },
229 { Common::KEYCODE_KP8, 0x68 },
230 { Common::KEYCODE_KP9, 0x69 },
231
232 { Common::KEYCODE_KP_PLUS, 0x6B },
233 { Common::KEYCODE_KP_MULTIPLY, 0x6A },
234 { Common::KEYCODE_KP_DIVIDE, 0x6F },
235 { Common::KEYCODE_KP_MINUS, 0x6D },
236
237 { Common::KEYCODE_F1, 0x70 },
238 { Common::KEYCODE_F2, 0x71 },
239 { Common::KEYCODE_F3, 0x72 },
240 { Common::KEYCODE_F4, 0x73 },
241 { Common::KEYCODE_F5, 0x74 },
242 { Common::KEYCODE_F6, 0x75 },
243 { Common::KEYCODE_F7, 0x76 },
244 { Common::KEYCODE_F8, 0x77 },
245 { Common::KEYCODE_F9, 0x78 },
246 { Common::KEYCODE_F10, 0x79 },
247 { Common::KEYCODE_F11, 0x7A },
248 { Common::KEYCODE_F12, 0x7B },
249 { Common::KEYCODE_F13, 0x7C },
250 { Common::KEYCODE_F14, 0x7D },
251 { Common::KEYCODE_F15, 0x7E },
252 { Common::KEYCODE_F16, 0x7F },
253 { Common::KEYCODE_F17, 0x80 },
254 { Common::KEYCODE_F18, 0x81 },
255
256 { Common::KEYCODE_LEFT, 0x25 },
257 { Common::KEYCODE_RIGHT, 0x27 },
258 { Common::KEYCODE_DOWN, 0x28 },
259 { Common::KEYCODE_UP, 0x26 },
260
261 { Common::KEYCODE_NUMLOCK, 0x90 },
262 { Common::KEYCODE_SCROLLOCK, 0x91 },
263 { Common::KEYCODE_SLEEP, 0x5F },
264 { Common::KEYCODE_INSERT, 0x2D },
265 { Common::KEYCODE_HELP, 0x2F },
266 { Common::KEYCODE_SELECT, 0x29 },
267 { Common::KEYCODE_HOME, 0x24 },
268 { Common::KEYCODE_PRINT, 0x2A },
269 { Common::KEYCODE_ESCAPE, 0x18 },
270 { Common::KEYCODE_PAGEUP, 0x21 },
271 { Common::KEYCODE_DELETE, 0x2E },
272 { Common::KEYCODE_END, 0x23 },
273 { Common::KEYCODE_PAGEDOWN, 0x22 },
274
275 { Common::KEYCODE_INVALID, 0 }
276 };
277
loadKeyCodes()278 void DirectorEngine::loadKeyCodes() {
279 if (g_director->getPlatform() == Common::kPlatformWindows) {
280 for (WinKeyCodeMapping *k = WinkeyCodeMappings; k->scummvm != Common::KEYCODE_INVALID; k++)
281 _KeyCodes[k->scummvm] = k->win;
282 } else {
283 for (MacKeyCodeMapping *k = MackeyCodeMappings; k->scummvm != Common::KEYCODE_INVALID; k++)
284 _KeyCodes[k->scummvm] = k->mac;
285 }
286 }
287
castNumToNum(const char * str)288 int castNumToNum(const char *str) {
289 if (strlen(str) != 3)
290 return -1;
291
292 if (tolower(str[0]) >= 'a' && tolower(str[0]) <= 'h' &&
293 str[1] >= '1' && str[1] <= '8' &&
294 str[2] >= '1' && str[2] <= '8') {
295
296 return (tolower(str[0]) - 'a') * 64 + (str[1] - '1') * 8 + (str[2] - '1') + 1;
297 }
298
299 return -1;
300 }
301
numToCastNum(int num)302 char *numToCastNum(int num) {
303 static char res[4];
304
305 res[0] = res[1] = res[2] = '?';
306 res[3] = '\0';
307 num--;
308
309 if (num >= 0 && num < 512) {
310 int c = num / 64;
311 res[0] = 'A' + c;
312 num -= 64 * c;
313
314 c = num / 8;
315 res[1] = '1' + c;
316 num -= 8 * c;
317
318 res[2] = '1' + num;
319 }
320
321 return res;
322 }
323
asString() const324 Common::String CastMemberID::asString() const {
325 Common::String res = Common::String::format("member %d", member);
326
327 if (g_director->getVersion() < 400 || g_director->getCurrentMovie()->_allowOutdatedLingo)
328 res += "(" + Common::String(numToCastNum(member)) + ")";
329 else if (g_director->getVersion() >= 500)
330 res += Common::String::format(" of castLib %d", castLib);
331
332 return res;
333 }
334
convertPath(Common::String & path)335 Common::String convertPath(Common::String &path) {
336 if (path.empty())
337 return path;
338
339 if (!path.contains(':') && !path.contains('\\') && !path.contains('@')) {
340 return path;
341 }
342
343 Common::String res;
344 uint32 idx = 0;
345
346 if (path.hasPrefix("::")) { // Parent directory
347 idx = 2;
348 } else if (path.hasPrefix("@:")) { // Root of the game
349 idx = 2;
350 } else if (path.size() >= 3
351 && Common::isAlpha(path[0])
352 && path[1] == ':'
353 && path[2] == '\\') { // Windows drive letter
354 idx = 3;
355 } else if (path[0] == ':') {
356 idx = 1;
357 }
358
359 while (idx < path.size()) {
360 if (path[idx] == ':' || path[idx] == '\\')
361 res += g_director->_dirSeparator;
362 else
363 res += path[idx];
364
365 idx++;
366 }
367
368 return res;
369 }
370
unixToMacPath(const Common::String & path)371 Common::String unixToMacPath(const Common::String &path) {
372 Common::String res;
373 for (uint32 idx = 0; idx < path.size(); idx++) {
374 if (path[idx] == ':')
375 res += '/';
376 else if (path[idx] == '/')
377 res += ':';
378 else
379 res += path[idx];
380 }
381 return res;
382 }
383
getPath(Common::String path,Common::String cwd)384 Common::String getPath(Common::String path, Common::String cwd) {
385 const char *s;
386 if ((s = strrchr(path.c_str(), g_director->_dirSeparator))) {
387 return Common::String(path.c_str(), s + 1);
388 }
389
390 return cwd; // The path is not altered
391 }
392
testPath(Common::String & path,bool directory)393 bool testPath(Common::String &path, bool directory) {
394 if (directory) {
395 Common::FSNode d = Common::FSNode(*g_director->getGameDataDir());
396
397 // check for the game data dir
398 if (!path.contains(g_director->_dirSeparator) && path.equalsIgnoreCase(d.getName())) {
399 path = "";
400 return true;
401 }
402
403 Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
404
405 if (d.getChild(directory_list.nextToken()).exists()) {
406 // then this part is for the "relative to current directory"
407 // we find the child directory recursively
408 directory_list.reset();
409 while (!directory_list.empty() && d.exists())
410 d = d.getChild(directory_list.nextToken());
411 } else {
412 return false;
413 }
414
415 return d.exists();
416 }
417
418 Common::File f;
419 if (f.open(Common::Path(path, g_director->_dirSeparator))) {
420 if (f.size())
421 return true;
422 f.close();
423 }
424 return false;
425 }
426
427 // if we are finding the file path, then this func will return exactly the executable file path
428 // if we are finding the directory path, then we will get the path relative to the game data dir.
429 // e.g. if we have game data dir as SSwarlock, then "A:SSwarlock" -> "", "A:SSwarlock:Nav" -> "Nav"
pathMakeRelative(Common::String path,bool recursive,bool addexts,bool directory)430 Common::String pathMakeRelative(Common::String path, bool recursive, bool addexts, bool directory) {
431 Common::String initialPath(path);
432
433 if (recursive) // first level
434 initialPath = convertPath(initialPath);
435
436 debug(9, "pathMakeRelative(): s1 %s -> %s", path.c_str(), initialPath.c_str());
437
438 initialPath = Common::normalizePath(g_director->getCurrentPath() + initialPath, g_director->_dirSeparator);
439 Common::String convPath = initialPath;
440
441 debug(9, "pathMakeRelative(): s2 %s", convPath.c_str());
442
443 // Strip the leading whitespace from the path
444 initialPath.trim();
445
446 if (testPath(initialPath, directory))
447 return initialPath;
448
449 // Now try to search the file
450 bool opened = false;
451
452 while (convPath.contains(g_director->_dirSeparator)) {
453 int pos = convPath.find(g_director->_dirSeparator);
454 convPath = Common::String(&convPath.c_str()[pos + 1]);
455
456 debug(9, "pathMakeRelative(): s3 try %s", convPath.c_str());
457
458 if (!testPath(convPath, directory))
459 continue;
460
461 debug(9, "pathMakeRelative(): s3 converted %s -> %s", path.c_str(), convPath.c_str());
462
463 opened = true;
464
465 break;
466 }
467
468 if (!opened) {
469 // Try stripping all of the characters not allowed in FAT
470 convPath = stripMacPath(initialPath.c_str());
471
472 debug(9, "pathMakeRelative(): s4 %s", convPath.c_str());
473
474 if (testPath(initialPath, directory))
475 return initialPath;
476
477 // Now try to search the file
478 while (convPath.contains(g_director->_dirSeparator)) {
479 int pos = convPath.find(g_director->_dirSeparator);
480 convPath = Common::String(&convPath.c_str()[pos + 1]);
481
482 debug(9, "pathMakeRelative(): s5 try %s", convPath.c_str());
483
484 if (!testPath(convPath, directory))
485 continue;
486
487 debug(9, "pathMakeRelative(): s5 converted %s -> %s", path.c_str(), convPath.c_str());
488
489 opened = true;
490
491 break;
492 }
493 }
494
495 if (!opened && recursive && !directory) {
496 // Hmmm. We couldn't find the path as is.
497 // Let's try to translate file path into 8.3 format
498 Common::String addedexts;
499
500 if (g_director->getPlatform() == Common::kPlatformWindows && g_director->getVersion() < 500) {
501 convPath.clear();
502 const char *ptr = initialPath.c_str();
503 Common::String component;
504
505 while (*ptr) {
506 if (*ptr == g_director->_dirSeparator) {
507 if (component.equals(".")) {
508 convPath += component;
509 } else {
510 convPath += convertMacFilename(component.c_str());
511 }
512
513 component.clear();
514 convPath += g_director->_dirSeparator;
515 } else {
516 component += *ptr;
517 }
518
519 ptr++;
520 }
521
522 if (hasExtension(component)) {
523 Common::String nameWithoutExt = component.substr(0, component.size() - 4);
524 Common::String ext = component.substr(component.size() - 4);
525 Common::String newpath = convPath + convertMacFilename(nameWithoutExt.c_str()) + ext;
526
527 debug(9, "pathMakeRelative(): s6 %s -> try %s", initialPath.c_str(), newpath.c_str());
528 Common::String res = pathMakeRelative(newpath, false, false);
529
530 if (testPath(res))
531 return res;
532 }
533
534 if (addexts)
535 addedexts = testExtensions(component, initialPath, convPath);
536 } else {
537 if (addexts)
538 addedexts = testExtensions(initialPath, initialPath, convPath);
539 }
540
541 if (!addedexts.empty()) {
542 return addedexts;
543 }
544
545 return initialPath; // Anyway nothing good is happening
546 }
547
548 if (opened)
549 return convPath;
550 else
551 return initialPath;
552 }
553
hasExtension(Common::String filename)554 bool hasExtension(Common::String filename) {
555 uint len = filename.size();
556 return len >= 4 && filename[len - 4] == '.'
557 && Common::isAlpha(filename[len - 3])
558 && Common::isAlpha(filename[len - 2])
559 && Common::isAlpha(filename[len - 1]);
560 }
561
testExtensions(Common::String component,Common::String initialPath,Common::String convPath)562 Common::String testExtensions(Common::String component, Common::String initialPath, Common::String convPath) {
563 const char *extsD3[] = { ".MMM", 0 };
564 const char *extsD4[] = { ".DIR", ".DXR", 0 };
565
566 const char **exts = (g_director->getVersion() >= 400) ? extsD4 : extsD3;
567 for (int i = 0; exts[i]; ++i) {
568 Common::String newpath = convPath + convertMacFilename(component.c_str()) + exts[i];
569
570 debug(9, "pathMakeRelative(): s6 %s -> try %s", initialPath.c_str(), newpath.c_str());
571 Common::String res = pathMakeRelative(newpath, false, false);
572
573 if (testPath(res))
574 return res;
575 }
576
577 return Common::String();
578 }
579
getFileName(Common::String path)580 Common::String getFileName(Common::String path) {
581 while (path.contains(g_director->_dirSeparator)) {
582 int pos = path.find(g_director->_dirSeparator);
583 path = Common::String(&path.c_str()[pos + 1]);
584 }
585 return path;
586 }
587
588 //////////////////
589 ////// Mac --> Windows filename conversion
590 //////////////////
myIsVowel(byte c)591 static bool myIsVowel(byte c) {
592 return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
593 }
594
myIsAlpha(byte c)595 static bool myIsAlpha(byte c) {
596 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
597 }
598
myIsDigit(byte c)599 static bool myIsDigit(byte c) {
600 return c >= '0' && c <= '9';
601 }
602
myIsAlnum(byte c)603 static bool myIsAlnum(byte c) {
604 return myIsAlpha(c) || myIsDigit(c);
605 }
606
myIsSpace(byte c)607 static bool myIsSpace(byte c) {
608 return c == ' ';
609 }
610
myIsFATChar(byte c)611 static bool myIsFATChar(byte c) {
612 return (c >= ' ' && c <= '!') || (c >= '#' && c == ')') || (c >= '-' && c <= '.') ||
613 (c >= '?' && c <= '@') || (c >= '^' && c <= '`') || c == '{' || (c >= '}' && c <= '~');
614 }
615
stripMacPath(const char * name)616 Common::String stripMacPath(const char *name) {
617 Common::String res;
618
619 int origlen = strlen(name);
620
621 // Remove trailing spaces
622 const char *end = &name[origlen - 1];
623 while (myIsSpace(*end))
624 end--;
625 const char *ptr = name;
626
627 while (ptr <= end) {
628 if (myIsAlnum(*ptr) || myIsFATChar(*ptr) || *ptr == g_director->_dirSeparator) {
629 res += *ptr;
630 }
631 ptr++;
632 }
633
634 return res;
635 }
636
convertMacFilename(const char * name)637 Common::String convertMacFilename(const char *name) {
638 Common::String res;
639
640 int origlen = strlen(name);
641
642 if (g_director->getVersion() < 400) {
643 // Remove trailing spaces
644 const char *ptr = &name[origlen - 1];
645 while (myIsSpace(*ptr))
646 ptr--;
647
648 int numDigits = 0;
649 char digits[10];
650
651 // Count trailing digits, but leave front letter
652 while (myIsDigit(*ptr) && (numDigits < (8 - 1)))
653 digits[++numDigits] = *ptr--;
654
655 // Count file name without vowels, spaces and digits in-between
656 ptr = name;
657 int cnt = 0;
658 while (cnt < (8 - numDigits) && ptr < &name[origlen]) {
659 char c = toupper(*ptr++);
660
661 if ((myIsVowel(c) && (cnt != 0)) || myIsSpace(c) || myIsDigit(c))
662 continue;
663
664 if ((c != '_') && !myIsAlnum(c))
665 continue;
666
667 cnt++;
668 }
669
670 // Make sure all trailing digits fit
671 int numVowels = 8 - (numDigits + cnt);
672 ptr = name;
673
674 // Put enough characters from beginning
675 for (cnt = 0; cnt < (8 - numDigits) && ptr < &name[origlen];) {
676 char c = toupper(*ptr++);
677
678 if (myIsVowel(c) && (cnt != 0)) {
679 if (numVowels > 0)
680 numVowels--;
681 else
682 continue;
683 }
684
685 if (myIsSpace(c) || myIsDigit(c) || ((c != '_') && !myIsAlnum(c)))
686 continue;
687
688 res += c;
689
690 cnt++;
691 }
692
693 // Now attach all digits
694 while (numDigits)
695 res += digits[numDigits--];
696 } else {
697 const char *ptr = name;
698 for (int cnt = 0; cnt < 8 && ptr < &name[origlen];) {
699 char c = toupper(*ptr++);
700
701 if (myIsSpace(c) || (!myIsAlnum(c) && !myIsFATChar(c)))
702 continue;
703
704 res += c;
705
706 cnt++;
707 }
708 }
709
710 return res;
711 }
712
dumpScriptName(const char * prefix,int type,int id,const char * ext)713 Common::String dumpScriptName(const char *prefix, int type, int id, const char *ext) {
714 Common::String typeName;
715
716 switch (type) {
717 case kNoneScript:
718 default:
719 error("dumpScriptName(): Incorrect call (type %d)", type);
720 case kMovieScript:
721 typeName = "movie";
722 break;
723 case kCastScript:
724 typeName = "cast";
725 break;
726 case kEventScript:
727 typeName = "event";
728 break;
729 case kScoreScript:
730 typeName = "score";
731 break;
732 }
733
734 return Common::String::format("./dumps/%s-%s-%d.%s", prefix, typeName.c_str(), id, ext);
735 }
736
setSeed(int seed)737 void RandomState::setSeed(int seed) {
738 init(32);
739
740 _seed = seed ? seed : 1;
741 }
742
getRandom(int32 range)743 int32 RandomState::getRandom(int32 range) {
744 int32 res;
745
746 if (_seed == 0)
747 init(32);
748
749 res = perlin(genNextRandom() * 71);
750
751 if (range > 0)
752 res = ((res & 0x7fffffff) % range);
753
754 return res;
755 }
756
757 static const uint32 masks[31] = {
758 0x00000003, 0x00000006, 0x0000000c, 0x00000014, 0x00000030, 0x00000060, 0x000000b8, 0x00000110,
759 0x00000240, 0x00000500, 0x00000ca0, 0x00001b00, 0x00003500, 0x00006000, 0x0000b400, 0x00012000,
760 0x00020400, 0x00072000, 0x00090000, 0x00140000, 0x00300000, 0x00400000, 0x00d80000, 0x01200000,
761 0x03880000, 0x07200000, 0x09000000, 0x14000000, 0x32800000, 0x48000000, 0xa3000000
762 };
763
init(int len)764 void RandomState::init(int len) {
765 if (len < 2 || len > 32) {
766 len = 32;
767 _len = (uint32)-1; // Since we cannot shift 32 bits
768 } else {
769 _len = (1 << len) - 1;
770 }
771
772 _seed = 1;
773 _mask = masks[len - 2];
774 }
775
genNextRandom()776 int32 RandomState::genNextRandom() {
777 if (_seed & 1)
778 _seed = (_seed >> 1) ^ _mask;
779 else
780 _seed >>= 1;
781
782 return _seed;
783 }
784
perlin(int32 val)785 int32 RandomState::perlin(int32 val) {
786 int32 res;
787
788 val = ((val << 13) ^ val) - (val >> 21);
789
790 res = (val * (val * val * 15731 + 789221) + 1376312589) & 0x7fffffff;
791 res += val;
792 res = ((res << 13) ^ res) - (res >> 21);
793
794 return res;
795 }
796
readVarInt(Common::SeekableReadStream & stream)797 uint32 readVarInt(Common::SeekableReadStream &stream) {
798 // Shockwave variable-length integer
799 uint32 val = 0;
800 byte b;
801 do {
802 b = stream.readByte();
803 val = (val << 7) | (b & 0x7f); // The 7 least significant bits are appended to the result
804 } while (b >> 7); // If the most significant bit is 1, there's another byte after
805 return val;
806 }
807
readZlibData(Common::SeekableReadStream & stream,unsigned long len,unsigned long * outLen,bool bigEndian)808 Common::SeekableReadStreamEndian *readZlibData(Common::SeekableReadStream &stream, unsigned long len, unsigned long *outLen, bool bigEndian) {
809 #ifdef USE_ZLIB
810 byte *in = (byte *)malloc(len);
811 byte *out = (byte *)malloc(*outLen);
812 stream.read(in, len);
813
814 if (!Common::uncompress(out, outLen, in, len)) {
815 free(in);
816 free(out);
817 return nullptr;
818 }
819
820 free(in);
821 return new Common::MemoryReadStreamEndian(out, *outLen, bigEndian, DisposeAfterUse::YES);
822 # else
823 return nullptr;
824 # endif
825 }
826
humanVersion(uint16 ver)827 uint16 humanVersion(uint16 ver) {
828 if (ver >= kFileVer1201)
829 return 1201;
830 if (ver >= kFileVer1200)
831 return 1200;
832 if (ver >= kFileVer1150)
833 return 1150;
834 if (ver >= kFileVer1100)
835 return 1100;
836 if (ver >= kFileVer1000)
837 return 1000;
838 if (ver >= kFileVer850)
839 return 850;
840 if (ver >= kFileVer800)
841 return 800;
842 if (ver >= kFileVer700)
843 return 700;
844 if (ver >= kFileVer600)
845 return 600;
846 if (ver >= kFileVer500)
847 return 500;
848 if (ver >= kFileVer404)
849 return 404;
850 if (ver >= kFileVer400)
851 return 400;
852 if (ver >= kFileVer310)
853 return 310;
854 if (ver >= kFileVer300)
855 return 300;
856 return 200;
857 }
858
platformFromID(uint16 id)859 Common::Platform platformFromID(uint16 id) {
860 switch (id) {
861 case 1:
862 return Common::kPlatformMacintosh;
863 case 2:
864 return Common::kPlatformWindows;
865 default:
866 warning("platformFromID: Unknown platform ID %d", id);
867 break;
868 }
869 return Common::kPlatformUnknown;
870 }
871
isButtonSprite(SpriteType spriteType)872 bool isButtonSprite(SpriteType spriteType) {
873 return spriteType == kButtonSprite || spriteType == kCheckboxSprite || spriteType == kRadioButtonSprite;
874 }
875
getEncoding(Common::Platform platform,Common::Language language)876 Common::CodePage getEncoding(Common::Platform platform, Common::Language language) {
877 switch (language) {
878 case Common::JA_JPN:
879 return Common::kWindows932; // Shift JIS
880 default:
881 break;
882 }
883 return (platform == Common::kPlatformWindows)
884 ? Common::kWindows1252
885 : Common::kMacRoman;
886 }
887
detectFontEncoding(Common::Platform platform,uint16 fontId)888 Common::CodePage detectFontEncoding(Common::Platform platform, uint16 fontId) {
889 return getEncoding(platform, g_director->_wm->_fontMan->getFontLanguage(fontId));
890 }
891
charToNum(Common::u32char_type_t ch)892 int charToNum(Common::u32char_type_t ch) {
893 Common::String encodedCh = Common::U32String(ch).encode(g_director->getPlatformEncoding());
894 int res = 0;
895 while (encodedCh.size()) {
896 res = (res << 8) | (byte)encodedCh.firstChar();
897 encodedCh.deleteChar(0);
898 }
899 return res;
900 }
901
numToChar(int num)902 Common::u32char_type_t numToChar(int num) {
903 Common::String encodedCh;
904 while (num) {
905 encodedCh.insertChar((char)(num & 0xFF), 0);
906 num >>= 8;
907 }
908 Common::U32String str = encodedCh.decode(g_director->getPlatformEncoding());
909 return str.lastChar();
910 }
911
compareStrings(const Common::String & s1,const Common::String & s2)912 int compareStrings(const Common::String &s1, const Common::String &s2) {
913 Common::U32String u32S1 = s1.decode(Common::kUtf8);
914 Common::U32String u32S2 = s2.decode(Common::kUtf8);
915 const Common::u32char_type_t *p1 = u32S1.c_str();
916 const Common::u32char_type_t *p2 = u32S2.c_str();
917 uint32 c1, c2;
918 while ((c1 = charToNum(*p1)) && (c2 = charToNum(*p2)) && c1 == c2) {
919 p1++;
920 p2++;
921 }
922 return c1 - c2;
923 }
924
encodePathForDump(const Common::String & path)925 Common::String encodePathForDump(const Common::String &path) {
926 return punycode_encodepath(Common::Path(path, g_director->_dirSeparator)).toString();
927 }
928
utf8ToPrintable(const Common::String & str)929 Common::String utf8ToPrintable(const Common::String &str) {
930 return Common::toPrintable(Common::U32String(str));
931 }
932
castTypeToString(const CastType & type)933 Common::String castTypeToString(const CastType &type) {
934 Common::String res;
935 switch(type) {
936 case kCastBitmap:
937 res = "bitmap";
938 break;
939 case kCastPalette:
940 res = "palette";
941 break;
942 case kCastButton:
943 res = "button";
944 break;
945 case kCastPicture:
946 res = "picture";
947 break;
948 case kCastDigitalVideo:
949 res = "digitalVideo";
950 break;
951 case kCastLingoScript:
952 res = "script";
953 break;
954 case kCastShape:
955 res = "shape";
956 break;
957 case kCastFilmLoop:
958 res = "filmLoop";
959 break;
960 case kCastSound:
961 res = "sound";
962 break;
963 case kCastMovie:
964 res = "movie";
965 break;
966 case kCastText:
967 res = "text";
968 break;
969 default:
970 res = "empty";
971 break;
972 }
973 return res;
974 }
975
976 } // End of namespace Director
977