/* 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 .
*
*/
#include "common/file.h"
#include "common/keyboard.h"
#include "common/macresman.h"
#include "common/memstream.h"
#include "common/punycode.h"
#include "common/str-array.h"
#include "common/tokenizer.h"
#include "common/xpfloat.h"
#include "common/compression/deflate.h"
#include "director/types.h"
#include "graphics/macgui/macwindowmanager.h"
#include "gui/filebrowser-dialog.h"
#include "director/director.h"
#include "director/movie.h"
#include "director/lingo/lingo.h"
namespace Director {
static const struct MacKeyCodeMapping {
Common::KeyCode scummvm;
int mac;
} MackeyCodeMappings[] = {
{ Common::KEYCODE_ESCAPE, 53 },
{ Common::KEYCODE_F1, 122 },
{ Common::KEYCODE_F2, 120 },
{ Common::KEYCODE_F3, 99 },
{ Common::KEYCODE_F4, 118 },
{ Common::KEYCODE_F5, 96 },
{ Common::KEYCODE_F6, 97 },
{ Common::KEYCODE_F7, 98 },
{ Common::KEYCODE_F8, 100 },
{ Common::KEYCODE_F9, 101 },
{ Common::KEYCODE_F10, 109 },
{ Common::KEYCODE_F11, 103 },
{ Common::KEYCODE_F12, 111 },
{ Common::KEYCODE_F13, 105 }, // mirrored by print
{ Common::KEYCODE_F14, 107 }, // mirrored by scroll lock
{ Common::KEYCODE_F15, 113 }, // mirrored by pause
{ Common::KEYCODE_BACKQUOTE, 10 },
{ Common::KEYCODE_1, 18 },
{ Common::KEYCODE_2, 19 },
{ Common::KEYCODE_3, 20 },
{ Common::KEYCODE_4, 21 },
{ Common::KEYCODE_5, 23 },
{ Common::KEYCODE_6, 22 },
{ Common::KEYCODE_7, 26 },
{ Common::KEYCODE_8, 28 },
{ Common::KEYCODE_9, 25 },
{ Common::KEYCODE_0, 29 },
{ Common::KEYCODE_MINUS, 27 },
{ Common::KEYCODE_EQUALS, 24 },
{ Common::KEYCODE_BACKSPACE, 51 },
{ Common::KEYCODE_TAB, 48 },
{ Common::KEYCODE_q, 12 },
{ Common::KEYCODE_w, 13 },
{ Common::KEYCODE_e, 14 },
{ Common::KEYCODE_r, 15 },
{ Common::KEYCODE_t, 17 },
{ Common::KEYCODE_y, 16 },
{ Common::KEYCODE_u, 32 },
{ Common::KEYCODE_i, 34 },
{ Common::KEYCODE_o, 31 },
{ Common::KEYCODE_p, 35 },
{ Common::KEYCODE_LEFTBRACKET, 33 },
{ Common::KEYCODE_RIGHTBRACKET, 30 },
{ Common::KEYCODE_BACKSLASH, 42 },
{ Common::KEYCODE_CAPSLOCK, 57 },
{ Common::KEYCODE_a, 0 },
{ Common::KEYCODE_s, 1 },
{ Common::KEYCODE_d, 2 },
{ Common::KEYCODE_f, 3 },
{ Common::KEYCODE_g, 5 },
{ Common::KEYCODE_h, 4 },
{ Common::KEYCODE_j, 38 },
{ Common::KEYCODE_k, 40 },
{ Common::KEYCODE_l, 37 },
{ Common::KEYCODE_SEMICOLON, 41 },
{ Common::KEYCODE_QUOTE, 39 },
{ Common::KEYCODE_RETURN, 36 },
{ Common::KEYCODE_LSHIFT, 56 },
{ Common::KEYCODE_z, 6 },
{ Common::KEYCODE_x, 7 },
{ Common::KEYCODE_c, 8 },
{ Common::KEYCODE_v, 9 },
{ Common::KEYCODE_b, 11 },
{ Common::KEYCODE_n, 45 },
{ Common::KEYCODE_m, 46 },
{ Common::KEYCODE_COMMA, 43 },
{ Common::KEYCODE_PERIOD, 47 },
{ Common::KEYCODE_SLASH, 44 },
{ Common::KEYCODE_RSHIFT, 56 },
{ Common::KEYCODE_LCTRL, 54 }, // control
{ Common::KEYCODE_LALT, 58 }, // option
{ Common::KEYCODE_LSUPER, 55 }, // command
{ Common::KEYCODE_SPACE, 49 },
{ Common::KEYCODE_RSUPER, 55 }, // command
{ Common::KEYCODE_RALT, 58 }, // option
{ Common::KEYCODE_RCTRL, 54 }, // control
{ Common::KEYCODE_LEFT, 123 },
{ Common::KEYCODE_RIGHT, 124 },
{ Common::KEYCODE_DOWN, 125 },
{ Common::KEYCODE_UP, 126 },
{ Common::KEYCODE_NUMLOCK, 71 },
{ Common::KEYCODE_KP_EQUALS, 81 },
{ Common::KEYCODE_KP_DIVIDE, 75 },
{ Common::KEYCODE_KP_MULTIPLY, 67 },
{ Common::KEYCODE_KP7, 89 },
{ Common::KEYCODE_KP8, 91 },
{ Common::KEYCODE_KP9, 92 },
{ Common::KEYCODE_KP_MINUS, 78 },
{ Common::KEYCODE_KP4, 86 },
{ Common::KEYCODE_KP5, 87 },
{ Common::KEYCODE_KP6, 88 },
{ Common::KEYCODE_KP_PLUS, 69 },
{ Common::KEYCODE_KP1, 83 },
{ Common::KEYCODE_KP2, 84 },
{ Common::KEYCODE_KP3, 85 },
{ Common::KEYCODE_KP0, 82 },
{ Common::KEYCODE_KP_PERIOD, 65 },
{ Common::KEYCODE_KP_ENTER, 76 },
{ Common::KEYCODE_MENU, 50 }, // international
{ Common::KEYCODE_PRINT, 105 }, // mirrored by F13
{ Common::KEYCODE_SCROLLOCK, 107 }, // mirrored by F14
{ Common::KEYCODE_PAUSE, 113 }, // mirrored by F15
{ Common::KEYCODE_INSERT, 114 },
{ Common::KEYCODE_HOME, 115 },
{ Common::KEYCODE_PAGEUP, 116 },
{ Common::KEYCODE_DELETE, 117 },
{ Common::KEYCODE_END, 119 },
{ Common::KEYCODE_PAGEDOWN, 121 },
{ Common::KEYCODE_INVALID, 0 }
};
static const struct WinKeyCodeMapping {
Common::KeyCode scummvm;
int win;
} WinkeyCodeMappings[] = {
{ Common::KEYCODE_BACKSPACE, 0x08 },
{ Common::KEYCODE_TAB, 0x09 },
{ Common::KEYCODE_CAPSLOCK, 0x14 },
{ Common::KEYCODE_SPACE, 0x20 },
{ Common::KEYCODE_MENU, 0x12 },
{ Common::KEYCODE_CANCEL, 0x03 },
{ Common::KEYCODE_RETURN, 0x0D },
{ Common::KEYCODE_PAUSE, 0x13 },
{ Common::KEYCODE_CAPSLOCK, 0x14 },
{ Common::KEYCODE_PRINT, 0x2A },
{ Common::KEYCODE_0, 0x30 },
{ Common::KEYCODE_1, 0x31 },
{ Common::KEYCODE_2, 0x32 },
{ Common::KEYCODE_3, 0x33 },
{ Common::KEYCODE_4, 0x34 },
{ Common::KEYCODE_5, 0x35 },
{ Common::KEYCODE_6, 0x36 },
{ Common::KEYCODE_7, 0x37 },
{ Common::KEYCODE_8, 0x38 },
{ Common::KEYCODE_9, 0x39 },
{ Common::KEYCODE_a, 0x41 },
{ Common::KEYCODE_b, 0x42 },
{ Common::KEYCODE_c, 0x43 },
{ Common::KEYCODE_d, 0x44 },
{ Common::KEYCODE_e, 0x45 },
{ Common::KEYCODE_f, 0x46 },
{ Common::KEYCODE_g, 0x47 },
{ Common::KEYCODE_h, 0x48 },
{ Common::KEYCODE_i, 0x49 },
{ Common::KEYCODE_j, 0x4A },
{ Common::KEYCODE_k, 0x4B },
{ Common::KEYCODE_l, 0x4C },
{ Common::KEYCODE_m, 0x4D },
{ Common::KEYCODE_n, 0x4E },
{ Common::KEYCODE_o, 0x4F },
{ Common::KEYCODE_p, 0x50 },
{ Common::KEYCODE_q, 0x51 },
{ Common::KEYCODE_r, 0x52 },
{ Common::KEYCODE_s, 0x53 },
{ Common::KEYCODE_t, 0x54 },
{ Common::KEYCODE_u, 0x55 },
{ Common::KEYCODE_v, 0x56 },
{ Common::KEYCODE_w, 0x57 },
{ Common::KEYCODE_x, 0x58 },
{ Common::KEYCODE_y, 0x59 },
{ Common::KEYCODE_z, 0x5A },
{ Common::KEYCODE_KP0, 0x60 },
{ Common::KEYCODE_KP1, 0x61 },
{ Common::KEYCODE_KP2, 0x62 },
{ Common::KEYCODE_KP3, 0x63 },
{ Common::KEYCODE_KP4, 0x64 },
{ Common::KEYCODE_KP5, 0x65 },
{ Common::KEYCODE_KP6, 0x66 },
{ Common::KEYCODE_KP7, 0x67 },
{ Common::KEYCODE_KP8, 0x68 },
{ Common::KEYCODE_KP9, 0x69 },
{ Common::KEYCODE_KP_PLUS, 0x6B },
{ Common::KEYCODE_KP_MULTIPLY, 0x6A },
{ Common::KEYCODE_KP_DIVIDE, 0x6F },
{ Common::KEYCODE_KP_MINUS, 0x6D },
{ Common::KEYCODE_F1, 0x70 },
{ Common::KEYCODE_F2, 0x71 },
{ Common::KEYCODE_F3, 0x72 },
{ Common::KEYCODE_F4, 0x73 },
{ Common::KEYCODE_F5, 0x74 },
{ Common::KEYCODE_F6, 0x75 },
{ Common::KEYCODE_F7, 0x76 },
{ Common::KEYCODE_F8, 0x77 },
{ Common::KEYCODE_F9, 0x78 },
{ Common::KEYCODE_F10, 0x79 },
{ Common::KEYCODE_F11, 0x7A },
{ Common::KEYCODE_F12, 0x7B },
{ Common::KEYCODE_F13, 0x7C },
{ Common::KEYCODE_F14, 0x7D },
{ Common::KEYCODE_F15, 0x7E },
{ Common::KEYCODE_F16, 0x7F },
{ Common::KEYCODE_F17, 0x80 },
{ Common::KEYCODE_F18, 0x81 },
{ Common::KEYCODE_LEFT, 0x25 },
{ Common::KEYCODE_RIGHT, 0x27 },
{ Common::KEYCODE_DOWN, 0x28 },
{ Common::KEYCODE_UP, 0x26 },
{ Common::KEYCODE_NUMLOCK, 0x90 },
{ Common::KEYCODE_SCROLLOCK, 0x91 },
{ Common::KEYCODE_SLEEP, 0x5F },
{ Common::KEYCODE_INSERT, 0x2D },
{ Common::KEYCODE_HELP, 0x2F },
{ Common::KEYCODE_SELECT, 0x29 },
{ Common::KEYCODE_HOME, 0x24 },
{ Common::KEYCODE_PRINT, 0x2A },
{ Common::KEYCODE_ESCAPE, 0x18 },
{ Common::KEYCODE_PAGEUP, 0x21 },
{ Common::KEYCODE_DELETE, 0x2E },
{ Common::KEYCODE_END, 0x23 },
{ Common::KEYCODE_PAGEDOWN, 0x22 },
{ Common::KEYCODE_INVALID, 0 }
};
void DirectorEngine::loadKeyCodes() {
if ((g_director->getPlatform() == Common::kPlatformWindows) && (g_director->getVersion() < 400)) {
// Allegedly this keykode list applies for the Windows version of D3.
// D4 and D5 for Windows are both confirmed to use the Mac keycode table.
for (const WinKeyCodeMapping *k = WinkeyCodeMappings; k->scummvm != Common::KEYCODE_INVALID; k++)
_KeyCodes[k->scummvm] = k->win;
} else {
for (const MacKeyCodeMapping *k = MackeyCodeMappings; k->scummvm != Common::KEYCODE_INVALID; k++)
_KeyCodes[k->scummvm] = k->mac;
}
}
void DirectorEngine::setMachineType(int machineType) {
_machineType = machineType;
switch (machineType) {
case 1: // Macintosh 512Ke
case 2: // Macintosh Plus
case 3: // Macintosh SE
case 4: // Macintosh II
case 5: // Macintosh IIx
case 6: // Macintosh IIcx
case 7: // Macintosh SE/30
case 8: // Macintosh Portable
case 9: // Macintosh IIci
_wmWidth = 512;
_wmHeight = 384;
_colorDepth = 8;
break;
case 11: // Macintosh IIfx
_wmWidth = 1152;
_wmHeight = 870;
_colorDepth = 1;
break;
case 15: // Macintosh Classic
case 16: // Macintosh IIsi
case 17: // Macintosh LC
case 20: // Macintosh Quadra 700
case 21: // Classic II
case 22: // PowerBook 100
case 23: // PowerBook 140
case 27: // Macintosh LCIII
case 28: // Macintosh Centris 650
case 30: // PowerBook Duo 230
case 31: // PowerBook 180
case 32: // PowerBook 160
case 33: // Macintosh Quadra 800
case 35: // Macintosh LC II
case 42: // Macintosh IIvi
case 46: // Macintosh IIvx
case 47: // Macintosh Color Classic
case 48: // PowerBook 165c
case 50: // Macintosh Centris 610
case 52: // PowerBook 145
_wmWidth = 640;
_wmHeight = 480;
_colorDepth = 8;
break;
case 45: // Power Macintosh 7100/70
case 53: // PowerComputing 8100/100
_wmWidth = 832;
_wmHeight = 624;
_colorDepth = 8;
break;
case 70: // PowerBook 540C
_wmWidth = 640;
_wmHeight = 480;
_colorDepth = 16;
break;
case 73: // Power Macintosh 6100/60
_wmWidth = 832;
_wmHeight = 624;
_colorDepth = 16;
break;
case 18: // Macintosh Quadra 900
case 24: // Macintosh Quadra 950
case 76: // Macintosh Quadra 840av
_wmWidth = 832;
_wmHeight = 624;
_colorDepth = 32;
break;
case 19: // PowerBook 170
case 25: // PowerBook Duo 210
_wmWidth = 640;
_wmHeight = 400;
_colorDepth = 4;
break;
case 256: // IBM PC-type machine
default:
_wmWidth = 640;
_wmHeight = 480;
_colorDepth = 8;
break;
}
}
int castNumToNum(const char *str) {
if (strlen(str) != 3)
return -1;
if (tolower(str[0]) >= 'a' && tolower(str[0]) <= 'h' &&
str[1] >= '1' && str[1] <= '8' &&
str[2] >= '1' && str[2] <= '8') {
return (tolower(str[0]) - 'a') * 64 + (str[1] - '1') * 8 + (str[2] - '1') + 1;
}
return -1;
}
char *numToCastNum(int num) {
static char res[4];
res[0] = res[1] = res[2] = '?';
res[3] = '\0';
num--;
if (num >= 0 && num < 512) {
int c = num / 64;
res[0] = 'A' + c;
num -= 64 * c;
c = num / 8;
res[1] = '1' + c;
num -= 8 * c;
res[2] = '1' + num;
}
return res;
}
Common::String CastMemberID::asString() const {
Common::String res = Common::String::format("member %d", member);
if (g_director->getVersion() < 400 || g_director->getCurrentMovie()->_allowOutdatedLingo)
res += "(" + Common::String(numToCastNum(member)) + ")";
else if (g_director->getVersion() >= 500)
res += Common::String::format(" of castLib %d", castLib);
return res;
}
const int recLevel = 0;
const char *const tabs[] = { "",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
};
const char *recIndent() {
if (recLevel >= ARRAYSIZE(tabs)) {
warning("recIndent() too deep: %d", recLevel);
return tabs[0];
}
return tabs[recLevel];
}
bool isAbsolutePath(const Common::String &path) {
// Starts with Mac directory notation for the game root
if (path.hasPrefix(Common::String("@:")) ||
path.hasPrefix(Common::String("@\\")) ||
path.hasPrefix(Common::String("@/"))) {
return true;
}
// Starts with a Windows drive letter
if (path.size() >= 3
&& Common::isAlpha(path[0])
&& path[1] == ':'
&& path[2] == '\\')
return true;
return false;
}
bool isPathWithRelativeMarkers(const Common::String &path) {
if (path.contains("::"))
return true;
if (path.hasPrefix(".\\") || path.hasSuffix("\\.") || path.contains("\\.\\"))
return true;
if (path.hasPrefix("..\\") || path.hasSuffix("\\..") || path.contains("\\..\\"))
return true;
return false;
}
Common::String rectifyRelativePath(const Common::String &path, const Common::Path &base) {
Common::StringArray components = base.splitComponents();
uint32 idx = 0;
// If a path is provided that begins with @, it will be relative to the top level, not the base.
if ((path.size() > 0) && (path[0] == '@')) {
idx++;
components.clear();
}
while (idx < path.size()) {
uint32 start = idx;
while (idx < path.size() && path[idx] != ':' && path[idx] != '\\')
idx++;
Common::String comp = path.substr(start, idx - start);
if (comp.equals("..") && !components.empty()) {
components.pop_back();
} else if (!comp.empty() && !comp.equals(".")) {
components.push_back(comp);
}
if (idx >= path.size())
break;
if (path[idx] == ':') {
idx += 1;
while (idx < path.size() && path[idx] == ':') {
if (!components.empty())
components.pop_back();
idx += 1;
}
continue;
}
if (path[idx] == '\\') {
idx += 1;
continue;
}
}
Common::String result = "@:" + Common::Path::joinComponents(components).toString(g_director->_dirSeparator);
debugC(1, kDebugPaths, "rectifyRelativePath(): '%s' + '%s' => '%s'", base.toString(g_director->_dirSeparator).c_str(), path.c_str(), result.c_str());
warning("rectifyRelativePath(): '%s' + '%s' => '%s'", base.toString(g_director->_dirSeparator).c_str(), path.c_str(), result.c_str());
return result;
}
Common::Path toSafePath(const Common::String &path) {
// Encode a Director raw path as a platform-independent path.
// This needs special care, as Mac filenames allow using '/' in them!
// - Scrub the pathname to be relative with the correct dir separator
// - Split it into tokens
// - Encode each token with punycode_encodefilename
// - Join the tokens back together with the default dir separator
Common::StringTokenizer pathList(convertPath(path), Common::String(g_director->_dirSeparator));
Common::Path result;
while (!pathList.empty()) {
Common::String token = pathList.nextToken();
token = Common::punycode_encodefilename(token);
if (!result.empty())
result.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
result.appendInPlace(token);
}
return result;
}
Common::String convertPath(const Common::String &path) {
if (path.empty())
return path;
if (!path.contains(':') && !path.contains('\\') && !path.contains('@')
&& (g_director->getVersion() >= 500 && !path.contains('/'))) {
return path;
}
Common::String res;
uint32 idx = 0;
if (path.hasPrefix("::")) { // Parent directory
idx = 2;
} else if (path.hasPrefix(Common::String("@:")) ||
path.hasPrefix(Common::String("@\\")) ||
path.hasPrefix(Common::String("@/"))) { // Root of the game
idx = 2;
} else if (path.size() >= 3
&& Common::isAlpha(path[0])
&& path[1] == ':'
&& path[2] == '\\') { // Windows drive letter
idx = 3;
} else if (path[0] == ':') {
idx = 1;
}
while (idx < path.size()) {
if (path[idx] == ':' || path[idx] == '\\' || (g_director->getVersion() >= 500 && path[idx] == '/'))
res += g_director->_dirSeparator;
else
res += path[idx];
idx++;
}
return res;
}
Common::String unixToMacPath(const Common::String &path) {
Common::String res;
for (uint32 idx = 0; idx < path.size(); idx++) {
if (path[idx] == ':')
res += '/';
else if (path[idx] == '/')
res += ':';
else
res += path[idx];
}
return res;
}
Common::String getPath(const Common::String &path, const Common::String &cwd) {
const char *s;
if ((s = strrchr(path.c_str(), g_director->_dirSeparator))) {
return Common::String(path.c_str(), s + 1);
}
return cwd; // The path is not altered
}
Common::String convert83Path(const Common::String &path) {
Common::String addedexts;
Common::String convPath;
const char *ptr = path.c_str();
Common::String component;
while (*ptr) {
if (*ptr == g_director->_dirSeparator) {
if (component.equals(".")) {
convPath += component;
} else {
convPath += convertMacFilename(component.c_str());
}
component.clear();
convPath += g_director->_dirSeparator;
} else {
component += *ptr;
}
ptr++;
}
if (hasExtension(component)) {
Common::String nameWithoutExt = component.substr(0, component.size() - 4);
Common::String ext = component.substr(component.size() - 4);
convPath += convertMacFilename(nameWithoutExt.c_str()) + ext;
} else {
convPath += convertMacFilename(component.c_str());
}
return convPath;
}
Common::Path resolveFSPath(const Common::String &path, const Common::Path &base, bool directory) {
// Path is the raw input from Director. Scrub it to be a clean relative path.
Common::String converted = convertPath(path);
debugC(2, kDebugPaths, " convertPath(): '%s' => '%s'", path.c_str(), converted.c_str());
// Absolute path to the game directory
Common::Path gamePath = Common::Path(g_director->getGameDataDir()->getPath());
// Absolute path to the game directory + the base search path
Common::Path testPath = gamePath;
if (!base.empty()) {
testPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
testPath.appendInPlace(base);
}
// FSNode for the current walk location in the filesystem
Common::FSNode filesystem(testPath);
// Split this into a component list for iteration.
Common::StringTokenizer directory_list(converted, Common::String(g_director->_dirSeparator));
// newPath is our final result; construct this based on successful filesystem tests
Common::Path newPath = Common::Path(base);
if (!base.empty())
newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
Common::FSList fslist;
bool exists = false;
while (!directory_list.empty()) {
Common::String token = punycode_decodefilename(directory_list.nextToken());
fslist.clear();
Common::FSNode::ListMode mode = Common::FSNode::kListDirectoriesOnly;
if (directory_list.empty() && !directory) {
mode = Common::FSNode::kListAll;
}
bool hasChildren = filesystem.getChildren(fslist, mode);
if (!hasChildren)
continue;
exists = false;
for (auto &i : fslist) {
// for each element in the path, choose the first FSNode
// with a case-insensitive matching name
Common::String decodedName = i.getName();
if (decodedName.equalsIgnoreCase(token)) {
// If this the final path component, check if we're allowed to match with a directory
if (directory_list.empty() && (directory != i.isDirectory())) {
continue;
}
exists = true;
newPath.appendInPlace(i.getRealName());
if (!directory_list.empty() && !newPath.empty())
newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
filesystem = i;
break;
}
}
if (!exists) {
break;
}
}
if (exists) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "resolveFSPath(): Found filesystem match for %s -> %s", path.c_str(), newPath.toString().c_str());
return newPath;
}
return Common::Path();
}
Common::Path resolvePathInner(const Common::String &path, const Common::Path &base, bool directory) {
Common::Path result = resolveFSPath(path, base, directory);
if (!result.empty()) {
return result;
}
// No filesystem match, check caches
Common::Path newPath = base;
if (!newPath.empty())
newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
newPath.appendInPlace(toSafePath(path));
if (!directory) {
// Check SearchMan
if (SearchMan.hasFile(newPath)) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "resolvePathInner(): Found SearchMan match for %s -> %s", path.c_str(), newPath.toString().c_str());
return newPath;
}
// Check MacResArchive
if (Common::MacResManager::exists(newPath)) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "resolvePathInner(): Found MacResManager match for %s -> %s", path.c_str(), newPath.toString().c_str());
return newPath;
}
} else {
// Iterate through every SearchMan file to check for directory matches
Common::StringArray srcComponents = newPath.splitComponents();
Common::ArchiveMemberList list;
SearchMan.listMembers(list);
for (auto &it : list) {
Common::Path test(it->getName());
Common::Path testParent = test.getParent();
Common::StringArray destComponents = testParent.splitComponents();
if (destComponents[destComponents.size() - 1].empty()) {
destComponents.pop_back();
testParent = Common::Path::joinComponents(destComponents);
}
if (srcComponents.size() != destComponents.size()) {
continue;
}
bool match = true;
for (size_t i = 0; i < srcComponents.size(); i++) {
Common::String component = Common::punycode_decodefilename(destComponents[i]);
if (!component.equalsIgnoreCase(srcComponents[i])) {
match = false;
break;
}
}
if (match) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "resolvePathInner(): Found SearchMan match for %s -> %s", path.c_str(), testParent.toString().c_str());
return testParent;
}
}
}
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "resolvePathInner(): No match found for %s", path.c_str());
return Common::Path();
}
Common::Path resolvePath(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::Path result = resolvePathInner(path, base, directory);
if (result.empty() && !directory && exts) {
Common::String fileBase = path;
if (hasExtension(fileBase))
fileBase = fileBase.substr(0, fileBase.size() - 4);
for (int i = 0; exts[i]; i++) {
Common::String fileExt = fileBase + exts[i];
result = resolvePathInner(fileExt, base, directory);
if (!result.empty())
break;
}
}
return result;
}
Common::Path resolvePartialPath(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::String converted = convertPath(path);
debugC(2, kDebugPaths, " convertPath(): '%s' => '%s'", path.c_str(), converted.c_str());
Common::Path result;
Common::StringArray baseTokens = base.splitComponents();
bool basesLeft = true;
while (basesLeft) {
Common::Path testBase = Common::Path::joinComponents(baseTokens);
// Try removing leading components of the target path
Common::StringArray tokens = Common::StringTokenizer(converted, Common::String(g_director->_dirSeparator)).split();
while (tokens.size()) {
Common::String subpath;
for (uint i = 0; i < tokens.size(); i++) {
subpath += tokens[i];
if (i < tokens.size() - 1) {
subpath += g_director->_dirSeparator;
}
}
result = resolvePath(subpath, testBase, directory, exts);
if (!result.empty()) {
break;
}
tokens.remove_at(0);
}
if (!result.empty())
break;
if (!baseTokens.size()) {
basesLeft = false;
} else {
baseTokens.pop_back();
}
}
return result;
}
Common::Path resolvePathWithFuzz(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::Path result = resolvePath(path, base, directory, exts);
if (result.empty()) {
// Try again with all non-FAT compatible characters stripped
Common::String newPath = stripMacPath(path.c_str());
if (newPath != path)
result = resolvePath(newPath, base, directory, exts);
}
if (result.empty()) {
// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
Common::String newPath = convert83Path(path);
if (newPath != path)
result = resolvePath(newPath, base, directory, exts);
}
return result;
}
Common::Path resolvePartialPathWithFuzz(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::Path result = resolvePartialPath(path, base, directory, exts);
if (result.empty()) {
// Try again with all non-FAT compatible characters stripped
Common::String newPath = stripMacPath(path.c_str());
if (newPath != path)
result = resolvePartialPath(newPath, base, directory, exts);
}
if (result.empty()) {
// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
Common::String newPath = convert83Path(path);
if (newPath != path)
result = resolvePartialPath(newPath, base, directory, exts);
}
return result;
}
Common::Path findAbsolutePath(const Common::String &path, bool directory, const char **exts) {
Common::Path result, base;
if (isAbsolutePath(path)) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findAbsolutePath(): searching absolute path");
result = resolvePathWithFuzz(path, base, directory, exts);
if (!result.empty()) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findAbsolutePath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
}
}
return result;
}
Common::Path findPath(const Common::Path &path, bool currentFolder, bool searchPaths, bool directory, const char **exts) {
return findPath(path.toString(g_director->_dirSeparator), currentFolder, searchPaths, directory, exts);
}
Common::Path findPath(const Common::String &path, bool currentFolder, bool searchPaths, bool directory, const char **exts) {
Common::Path result, base;
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): beginning search for \"%s\"", path.c_str());
Common::String currentPath = g_director->getCurrentPath();
Common::Path current = resolvePath(currentPath, base, true, exts);
Common::String testPath = path;
// If the path contains relative elements, rectify it with respect to the current folder
if (isPathWithRelativeMarkers(testPath)) {
testPath = rectifyRelativePath(testPath, current);
}
// For an absolute path, first check it relative to the filesystem
result = findAbsolutePath(testPath, directory, exts);
if (!result.empty()) {
return result;
}
if (currentFolder) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): searching current folder %s", current.toString().c_str());
base = current;
result = resolvePartialPathWithFuzz(testPath, base, directory, exts);
if (!result.empty()) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): resolved \"%s\" -> \"%s\"", testPath.c_str(), result.toString().c_str());
return result;
}
}
// Fall back to checking the game root path
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): searching game root path");
base = Common::Path();
result = resolvePartialPathWithFuzz(testPath, base, directory, exts);
if (!result.empty()) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): resolved \"%s\" -> \"%s\"", testPath.c_str(), result.toString().c_str());
return result;
}
// Check each of the search paths in sequence
if (searchPaths) {
Common::Array searchPathList;
Datum searchPath = g_director->getLingo()->_searchPath;
if (searchPath.type == ARRAY) {
for (auto &it : searchPath.u.farr->arr) {
searchPathList.push_back(it.asString());
}
}
for (auto &it : g_director->_extraSearchPath) {
searchPathList.push_back(it);
}
for (auto &searchIn : searchPathList) {
base = Common::Path();
base = resolvePathWithFuzz(searchIn, base, true, exts);
if (base.empty()) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): couldn't resolve search path folder %s, skipping", searchIn.c_str());
continue;
}
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): searching search path folder %s", searchIn.c_str());
result = resolvePartialPathWithFuzz(testPath, base, directory, exts);
if (!result.empty()) {
debugCN(1, kDebugPaths, "%s", recIndent());
debugC(1, kDebugPaths, "findPath(): resolved \"%s\" -> \"%s\"", testPath.c_str(), result.toString().c_str());
return result;
}
}
}
// Return empty path
debugC(1, kDebugPaths, "findPath(): failed to resolve \"%s\"", path.c_str());
return Common::Path();
}
Common::Path findMoviePath(const Common::String &path, bool currentFolder, bool searchPaths) {
const char *extsD3[] = { ".MMM", nullptr };
const char *extsD4[] = { ".DIR", ".DXR", ".EXE", nullptr };
const char *extsD5[] = { ".DIR", ".DXR", ".CST", ".CXT", ".EXE", nullptr };
const char *extsD6[] = { ".DIR", ".DXR", ".CST", ".CXT", ".EXE", ".DCR", ".DCT", nullptr };
const char **exts = nullptr;
if (g_director->getVersion() < 400) {
exts = extsD3;
} else if (g_director->getVersion() >= 400 && g_director->getVersion() < 500) {
exts = extsD4;
} else if (g_director->getVersion() >= 500 && g_director->getVersion() < 600) {
exts = extsD5;
} else {
exts = extsD6;
}
Common::Path result = findPath(path, currentFolder, searchPaths, false, exts);
return result;
}
Common::Path findXLibPath(const Common::String &path, bool currentFolder, bool searchPaths) {
const char *extsD3[] = { ".DLL", nullptr };
const char *extsD5[] = { ".DLL", ".X16", ".X32", nullptr };
const char **exts = nullptr;
if (g_director->getVersion() < 500) {
exts = extsD3;
} else {
exts = extsD5;
}
Common::Path result = findPath(path, currentFolder, searchPaths, false, exts);
return result;
}
Common::Path findAudioPath(const Common::String &path, bool currentFolder, bool searchPaths) {
const char *exts[] = { ".AIF", ".WAV", nullptr };
Common::Path result = findPath(path, currentFolder, searchPaths, false, exts);
return result;
}
Common::String getFileNameFromModal(bool save, const Common::String &suggested, const Common::String &title, const char *ext) {
Common::String prefix = savePrefix();
Common::String mask = prefix + "*";
if (ext) {
mask += ".";
mask += ext;
/*
The file browser dialog and the save system forces a .txt file extension.
To support other file extensions we need to add .txt to the end if the file extension is different.
*/
if (strncmp(ext, "txt", 3) != 0) {
mask += ".txt";
}
}
GUI::FileBrowserDialog browser(title.c_str(), "txt", save ? GUI::kFBModeSave : GUI::kFBModeLoad, mask.c_str(), suggested.c_str());
if (browser.runModal() <= 0) {
return Common::String();
}
Common::String result = browser.getResult();
if (!result.empty() && !result.hasPrefixIgnoreCase(prefix))
result = prefix + result;
return result;
}
Common::String savePrefix() {
return g_director->getTargetName() + '-';
}
bool hasExtension(Common::String filename) {
uint len = filename.size();
return len >= 4 && filename[len - 4] == '.'
&& Common::isAlpha(filename[len - 3])
&& Common::isAlpha(filename[len - 2])
&& Common::isAlpha(filename[len - 1]);
}
Common::String getFileName(Common::String path) {
while (path.contains(g_director->_dirSeparator)) {
int pos = path.find(g_director->_dirSeparator);
path = Common::String(&path.c_str()[pos + 1]);
}
return path;
}
//////////////////
////// Mac --> Windows filename conversion
//////////////////
static bool myIsVowel(byte c) {
return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}
static bool myIsAlpha(byte c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static bool myIsDigit(byte c) {
return c >= '0' && c <= '9';
}
static bool myIsAlnum(byte c) {
return myIsAlpha(c) || myIsDigit(c);
}
static bool myIsSpace(byte c) {
return c == ' ';
}
static bool myIsFATChar(byte c) {
return (c >= ' ' && c <= '!') || (c >= '#' && c == ')') || (c >= '-' && c <= '.') ||
(c >= '?' && c <= '@') || (c >= '^' && c <= '`') || c == '{' || (c >= '}' && c <= '~');
}
Common::String stripMacPath(const char *name) {
Common::String res;
int origlen = strlen(name);
// Remove trailing spaces
const char *end = &name[origlen - 1];
while (myIsSpace(*end))
end--;
const char *ptr = name;
while (ptr <= end) {
if (myIsAlnum(*ptr) || myIsFATChar(*ptr) || *ptr == g_director->_dirSeparator) {
res += *ptr;
}
ptr++;
}
return res;
}
Common::String convertMacFilename(const char *name) {
Common::String res;
int origlen = strlen(name);
if (g_director->getVersion() < 400) {
// Remove trailing spaces
const char *ptr = &name[origlen - 1];
while (myIsSpace(*ptr))
ptr--;
int numDigits = 0;
char digits[10];
// Count trailing digits, but leave front letter
while (myIsDigit(*ptr) && (numDigits < (8 - 1)))
digits[++numDigits] = *ptr--;
// Count file name without vowels, spaces and digits in-between
ptr = name;
int cnt = 0;
while (cnt < (8 - numDigits) && ptr < &name[origlen]) {
char c = toupper(*ptr++);
if ((myIsVowel(c) && (cnt != 0)) || myIsSpace(c) || myIsDigit(c))
continue;
if ((c != '_') && !myIsAlnum(c))
continue;
cnt++;
}
// Make sure all trailing digits fit
int numVowels = 8 - (numDigits + cnt);
ptr = name;
// Put enough characters from beginning
for (cnt = 0; cnt < (8 - numDigits) && ptr < &name[origlen];) {
char c = toupper(*ptr++);
if (myIsVowel(c) && (cnt != 0)) {
if (numVowels > 0)
numVowels--;
else
continue;
}
if (myIsSpace(c) || myIsDigit(c) || ((c != '_') && !myIsAlnum(c)))
continue;
res += c;
cnt++;
}
// Now attach all digits
while (numDigits)
res += digits[numDigits--];
} else {
const char *ptr = name;
for (int cnt = 0; cnt < 8 && ptr < &name[origlen];) {
char c = toupper(*ptr++);
if (myIsSpace(c) || (!myIsAlnum(c) && !myIsFATChar(c)))
continue;
res += c;
cnt++;
}
// If the result filename ends with '.', remove it
if (res.hasSuffix("."))
res = res.substr(0, res.size() - 1);
}
return res;
}
Common::Path dumpScriptName(const char *prefix, int type, int id, const char *ext) {
Common::String typeName;
switch (type) {
case kNoneScript:
typeName = "UnknownScript";
break;
case kMovieScript:
typeName = "MovieScript";
break;
case kCastScript:
typeName = "CastScript";
break;
case kEventScript:
typeName = "EventScript";
break;
case kScoreScript:
if (g_director->getVersion() >= 600)
typeName = "BehaviorScript";
else
typeName = "ScoreScript";
break;
case kParentScript:
typeName = "ParentScript";
break;
default:
error("dumpScriptName(): Incorrect call (type %d)", type);
break;
}
return Common::Path(Common::String::format("./dumps/%s-%s-%d.%s", prefix, typeName.c_str(), id, ext), '/');
}
Common::Path dumpFactoryName(const char *prefix, const char *name, const char *ext) {
return Common::Path(Common::String::format("./dumps/%s-factory-%s.%s", prefix, name, ext), '/');
}
void RandomState::setSeed(int seed) {
init(32);
_seed = seed ? seed : 1;
}
int32 RandomState::getRandom(int32 range) {
int32 res;
if (_seed == 0)
init(32);
res = perlin(genNextRandom() * 71);
if (range > 0)
res = ((res & 0x7fffffff) % range);
return res;
}
static const uint32 masks[31] = {
0x00000003, 0x00000006, 0x0000000c, 0x00000014, 0x00000030, 0x00000060, 0x000000b8, 0x00000110,
0x00000240, 0x00000500, 0x00000ca0, 0x00001b00, 0x00003500, 0x00006000, 0x0000b400, 0x00012000,
0x00020400, 0x00072000, 0x00090000, 0x00140000, 0x00300000, 0x00400000, 0x00d80000, 0x01200000,
0x03880000, 0x07200000, 0x09000000, 0x14000000, 0x32800000, 0x48000000, 0xa3000000
};
void RandomState::init(int len) {
if (len < 2 || len > 32) {
len = 32;
_len = (uint32)-1; // Since we cannot shift 32 bits
} else {
_len = (1 << len) - 1;
}
_seed = 1;
_mask = masks[len - 2];
}
int32 RandomState::genNextRandom() {
if (_seed & 1)
_seed = (_seed >> 1) ^ _mask;
else
_seed >>= 1;
return _seed;
}
int32 RandomState::perlin(int32 val) {
int32 res;
val = ((val << 13) ^ val) - (val >> 21);
res = (val * (val * val * 15731 + 789221) + 1376312589) & 0x7fffffff;
res += val;
res = ((res << 13) ^ res) - (res >> 21);
return res;
}
uint32 readVarInt(Common::SeekableReadStream &stream) {
// Shockwave variable-length integer
uint32 val = 0;
byte b;
do {
b = stream.readByte();
val = (val << 7) | (b & 0x7f); // The 7 least significant bits are appended to the result
} while (b >> 7); // If the most significant bit is 1, there's another byte after
return val;
}
Common::SeekableReadStreamEndian *readZlibData(Common::SeekableReadStream &stream, uint32 len, uint32 *outLen, bool bigEndian) {
unsigned long outLenUL = static_cast(*outLen);
byte *in = (byte *)malloc(len);
byte *out = (byte *)malloc(*outLen);
stream.read(in, len);
if (!Common::inflateZlib(out, &outLenUL, in, len)) {
free(in);
free(out);
return nullptr;
}
*outLen = static_cast(outLenUL);
free(in);
return new Common::MemoryReadStreamEndian(out, *outLen, bigEndian, DisposeAfterUse::YES);
}
uint16 humanVersion(uint16 ver) {
if (ver >= kFileVer1200)
return 1200;
if (ver >= kFileVer1150)
return 1150;
if (ver >= kFileVer1100)
return 1100;
if (ver >= kFileVer1000)
return 1000;
if (ver >= kFileVer850)
return 850;
if (ver >= kFileVer800)
return 800;
if (ver >= kFileVer700)
return 700;
if (ver >= kFileVer600)
return 600;
if (ver >= kFileVer500)
return 500;
if (ver >= kFileVer404)
return 404;
if (ver >= kFileVer400)
return 400;
if (ver >= kFileVer310)
return 310;
if (ver >= kFileVer300)
return 300;
return 200;
}
Common::Platform platformFromID(uint16 id) {
switch (id) {
case 1:
return Common::kPlatformMacintosh;
case 2:
return Common::kPlatformWindows;
default:
warning("BUILDBOT: platformFromID: Unknown platform ID %d", id);
break;
}
return Common::kPlatformUnknown;
}
bool isButtonSprite(SpriteType spriteType) {
return spriteType == kButtonSprite || spriteType == kCheckboxSprite || spriteType == kRadioButtonSprite;
}
Common::CodePage getEncoding(Common::Platform platform, Common::Language language) {
switch (language) {
case Common::JA_JPN:
return Common::kWindows932; // Shift JIS
default:
break;
}
// If there's no language override, but there is a Lingo
// request for a double-byte interpreter, assume this means
// the text cast members contain Shift-JIS.
if (!g_lingo->_romanLingo)
return Common::kWindows932; // Shift JIS
return (platform == Common::kPlatformWindows)
? Common::kWindows1252
: Common::kMacRoman;
}
Common::CodePage detectFontEncoding(Common::Platform platform, uint16 fontId) {
return getEncoding(platform, g_director->_wm->_fontMan->getFontLanguage(fontId));
}
int charToNum(Common::u32char_type_t ch) {
Common::String encodedCh = Common::U32String(ch).encode(g_director->getPlatformEncoding());
int res = 0;
while (encodedCh.size()) {
res = (res << 8) | (byte)encodedCh.firstChar();
encodedCh.deleteChar(0);
}
return res;
}
Common::u32char_type_t numToChar(int num) {
Common::String encodedCh;
while (num) {
encodedCh.insertChar((char)(num & 0xFF), 0);
num >>= 8;
}
Common::U32String str = encodedCh.decode(g_director->getPlatformEncoding());
return str.lastChar();
}
Common::String encodePathForDump(const Common::String &path) {
return Common::Path(path, g_director->_dirSeparator).punycodeEncode().toString();
}
Common::String utf8ToPrintable(const Common::String &str) {
return Common::toPrintable(Common::U32String(str));
}
Common::String decodePlatformEncoding(Common::String input) {
return input.decode(g_director->getPlatformEncoding());
}
Common::String formatStringForDump(const Common::String &str) {
Common::String format = str;
for (int i = 0; i < (int)format.size(); i++) {
if (format[i] == '\r')
format.replace(i, 1, "\n");
}
return format;
}
/////////////////////////////////////////////////////////////
// String comparison order tables
//
// Director is using weird order of comparison for strings
// It is declared as case-insensitive, ignoring diacritics,
// but the reality is more complex.
//
// The tables below contain the actual weight of the letter in
// the given position
//
// Director 2.0-4.0 Mac, MacRoman encoding
//
// ................................
// !"«»“”#$%&'‘’()*+,-./012345678
// 9:;<=>?@AÁÀÂÄÃÅaáàâäã寿BbCÇcçDd
// EÉÈÊËeéèêëFffiflGgHhIÍÌÎÏı.íìîïJjK
// kLlMmNÑnñOÓÒÔÖΩØoóòôöõøŒœPpQqRrS
// sßTtUÚÙÛÜuúùûüVvWwXxYyÿŸZz[\]^_`
// {|}~.†°¢£§•¶®©™´¨≠∞±≤≥¥µ∂∑∏π∫ªºΩ
// ¿¡¬√ƒ≈Δ…–—÷◊⁄¤‹›‡·‚„‰��¯˘˙˚¸˝˛.
const byte orderTableD2mac[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x22, 0x23, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x6a, 0x6e, 0x70, 0x72, 0x7d, 0x7f, 0x81, 0x83, 0x85, 0x89,
0x99, 0x9b, 0x9d, 0x9f, 0xa2, 0xa4, 0xae, 0xb0, 0xb2, 0xb4, 0xb8, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
0xbf, 0x4f, 0x59, 0x5c, 0x5f, 0x65, 0x6b, 0x6f, 0x71, 0x77, 0x7e, 0x80, 0x82, 0x84, 0x87, 0x90,
0x9a, 0x9c, 0x9e, 0xa0, 0xa3, 0xa9, 0xaf, 0xb1, 0xb3, 0xb5, 0xb9, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4,
0x4c, 0x4e, 0x5b, 0x61, 0x86, 0x8d, 0xa8, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x5d, 0x66, 0x67,
0x68, 0x69, 0x79, 0x7a, 0x7b, 0x7c, 0x88, 0x91, 0x92, 0x93, 0x94, 0x95, 0xaa, 0xab, 0xac, 0xad,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xa1, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0x56, 0x8f,
0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0x57, 0x96,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0x24, 0x25, 0xe7, 0x21, 0x4a, 0x4d, 0x8e, 0x97, 0x98,
0xe8, 0xe9, 0x26, 0x27, 0x2d, 0x2e, 0xea, 0xeb, 0xb6, 0xb7, 0xec, 0xed, 0xee, 0xef, 0x6c, 0x6d,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0x4b, 0x63, 0x49, 0x64, 0x62, 0x73, 0x75, 0x76, 0x74, 0x8a, 0x8c,
0xf5, 0x8b, 0xa5, 0xa7, 0xa6, 0x77, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
};
//
// Director 4.0 Mac, MacJapanese encoding
//
// �...............................
// ................................
// ............................ !"#
// $%&'()*+,-./0123456789:;<=>?@AaB
// bCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqR
// rSsTtUuVvWwXxYyZz[\]^_`{|}~.����
// ��������������������������������
// ������Ӭԭծ������ܦݠ���������.
const byte orderTableD4Jmac[256] = {
0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
0x7c, 0x7d, 0x7f, 0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d, 0x8f, 0x91, 0x93, 0x95, 0x97, 0x99,
0x9b, 0x9d, 0x9f, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xab, 0xad, 0xaf, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a,
0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb7, 0xb8, 0xb9, 0xba, 0xbb,
0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xf3, 0xbe, 0xc0, 0xc2, 0xc4, 0xc6, 0xe7, 0xe9, 0xeb, 0xd4,
0xbd, 0xbf, 0xc1, 0xc3, 0xc5, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1,
0xd2, 0xd3, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe8, 0xea, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf4, 0xfb, 0xfc,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfe, 0xff,
};
//
// Director 4.0-5.0 Win, cp1252 encoding
//
// ................................
// !"«»#$%&'‘’()*+,-./0123456789:
// ;<=>?@AÁÀÂÄÃÅÆaáàâäãåæBbCÇcçDÐdð
// EÉÈÊËeéèêëFfGgHhIÍÌÎÏiíìîïJjKkLl
// MmNÑnñOÓÒÔÖÕØŒoóòôöõøœPpQqRrSŠsš
// ßTtUÚÙÛÜuúùûüVvWwXxYÝŸyý.Zz[\]^_
// `{|}~.€�‚ƒ„…†‡ˆ‰‹�Ž��“”•–—˜™›�ž¡
// ¢£¤¥¦§¨©ª¬®¯°±²³´µ¶·¸¹º¼½¾¿×Þþ÷
const byte orderTableD4win[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x22, 0x23, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
0x45, 0x46, 0x56, 0x58, 0x5c, 0x60, 0x6a, 0x6c, 0x6e, 0x70, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x86,
0x96, 0x98, 0x9a, 0x9c, 0xa1, 0xa3, 0xad, 0xaf, 0xb1, 0xb3, 0xb9, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0x4e, 0x57, 0x5a, 0x5e, 0x65, 0x6b, 0x6d, 0x6f, 0x75, 0x7b, 0x7d, 0x7f, 0x81, 0x84, 0x8e,
0x97, 0x99, 0x9b, 0x9e, 0xa2, 0xa8, 0xae, 0xb0, 0xb2, 0xb6, 0xba, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5,
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0x9d, 0xd0, 0x8d, 0xd1, 0xd2, 0xd3,
0xd4, 0x2b, 0x2c, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0x9f, 0xdc, 0x95, 0xdd, 0xde, 0xb5,
0x21, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0x24, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0x25, 0xf8, 0xf9, 0xfa, 0xfb,
0x48, 0x47, 0x49, 0x4b, 0x4a, 0x4c, 0x4d, 0x59, 0x62, 0x61, 0x63, 0x64, 0x72, 0x71, 0x73, 0x74,
0x5d, 0x83, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0xfc, 0x8c, 0xa5, 0xa4, 0xa6, 0xa7, 0xb4, 0xfd, 0xa0,
0x50, 0x4f, 0x51, 0x53, 0x52, 0x54, 0x55, 0x5b, 0x67, 0x66, 0x68, 0x69, 0x77, 0x76, 0x78, 0x79,
0x5f, 0x85, 0x90, 0x8f, 0x91, 0x93, 0x92, 0xff, 0x94, 0xaa, 0xa9, 0xab, 0xac, 0xb7, 0xfe, 0xb8,
};
//
// Director 6.0 Win, cp1252 encoding
//
// ............................����
// �'-–— .....!"#$%&()*,./:;?@[\]
// ^ˆ_`{|}~¡¦¨¯´¸¿˜‘’‚“”„‹›+<=>±«»×
// ÷¢£¤¥§©¬®°µ¶·†‡•…‰€0¼½¾1¹2²3³456
// 789aAªáÁàÀâÂäÄãÃåÅæÆbBcCçÇdDðÐeE
// éÉèÈêÊëËfFƒgGhHiIíÍìÌîÎïÏjJkKlLm
// MnNñÑoOºóÓòÒôÔöÖõÕøØœŒpPqQrRsSšŠ
// ßtTþÞ™uUúÚùÙûÛüÜvVwWxXyYýÝ.ŸzZžŽ
const byte orderTableD6win[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a,
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
0x26, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x21, 0x33, 0x34, 0x35, 0x58, 0x36, 0x22, 0x37, 0x38,
0x73, 0x77, 0x79, 0x7b, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x39, 0x3a, 0x59, 0x5a, 0x5b, 0x3b,
0x3c, 0x84, 0x95, 0x97, 0x9b, 0x9f, 0xa9, 0xac, 0xae, 0xb0, 0xba, 0xbc, 0xbe, 0xc0, 0xc2, 0xc6,
0xd7, 0xd9, 0xdb, 0xdd, 0xe2, 0xe7, 0xf1, 0xf3, 0xf5, 0xf7, 0xfd, 0x3d, 0x3e, 0x3f, 0x40, 0x42,
0x43, 0x83, 0x94, 0x96, 0x9a, 0x9e, 0xa8, 0xab, 0xad, 0xaf, 0xb9, 0xbb, 0xbd, 0xbf, 0xc1, 0xc5,
0xd6, 0xd8, 0xda, 0xdc, 0xe1, 0xe6, 0xf0, 0xf2, 0xf4, 0xf6, 0xfc, 0x44, 0x45, 0x46, 0x47, 0x1b,
0x72, 0x1c, 0x52, 0xaa, 0x55, 0x70, 0x6d, 0x6e, 0x41, 0x71, 0xdf, 0x56, 0xd5, 0x1d, 0xff, 0x1e,
0x1f, 0x50, 0x51, 0x53, 0x54, 0x6f, 0x24, 0x25, 0x4f, 0xe5, 0xde, 0x57, 0xd4, 0x20, 0xfe, 0xfb,
0x27, 0x48, 0x61, 0x62, 0x63, 0x64, 0x49, 0x65, 0x4a, 0x66, 0x85, 0x5d, 0x67, 0x23, 0x68, 0x4b,
0x69, 0x5c, 0x7a, 0x7c, 0x4c, 0x6a, 0x6b, 0x6c, 0x4d, 0x78, 0xc7, 0x5e, 0x74, 0x75, 0x76, 0x4e,
0x89, 0x87, 0x8b, 0x8f, 0x8d, 0x91, 0x93, 0x99, 0xa3, 0xa1, 0xa5, 0xa7, 0xb4, 0xb2, 0xb6, 0xb8,
0x9d, 0xc4, 0xcb, 0xc9, 0xcd, 0xd1, 0xcf, 0x5f, 0xd3, 0xeb, 0xe9, 0xed, 0xef, 0xf9, 0xe4, 0xe0,
0x88, 0x86, 0x8a, 0x8e, 0x8c, 0x90, 0x92, 0x98, 0xa2, 0xa0, 0xa4, 0xa6, 0xb3, 0xb1, 0xb5, 0xb7,
0x9c, 0xc3, 0xca, 0xc8, 0xcc, 0xd0, 0xce, 0x60, 0xd2, 0xea, 0xe8, 0xec, 0xee, 0xf8, 0xe3, 0xfa,
};
//
// Director 8.5 Mac, MacRoman encoding
//
// ................................
// !"«»“”#$%&'‘’()*+,-./012345678
// 9:;<=>?@AÁÀÂÄÃÅaáàâäã寿BbCÇcçDd
// EÉÈÊËeéèêëFffiflGgHhIÍÌÎÏı.íìîïJjK
// kLlMmNÑnñOÓÒÔÖΩØoóòôöõøŒœPpQqRrS
// sßTtUÚÙÛÜuúùûüVvWwXxYyÿŸZz[\]^_`
// {|}~.†°¢£§•¶®©™´¨≠∞±≤≥¥µ∂∑∏π∫ªºΩ
// ¿¡¬√ƒ≈Δ…–—÷◊⁄¤‹›‡·‚„‰��¯˘˙˚¸˝˛.
const byte orderTableD8mac[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x22, 0x23, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x6a, 0x6e, 0x70, 0x72, 0x7d, 0x7f, 0x81, 0x83, 0x85, 0x89,
0x99, 0x9b, 0x9d, 0x9f, 0xa2, 0xa4, 0xae, 0xb0, 0xb2, 0xb4, 0xb8, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
0xbf, 0x4f, 0x59, 0x5c, 0x5f, 0x65, 0x6b, 0x6f, 0x71, 0x77, 0x7e, 0x80, 0x82, 0x84, 0x87, 0x90,
0x9a, 0x9c, 0x9e, 0xa0, 0xa3, 0xa9, 0xaf, 0xb1, 0xb3, 0xb5, 0xb9, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4,
0x4c, 0x4e, 0x5b, 0x61, 0x86, 0x8d, 0xa8, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x5d, 0x66, 0x67,
0x68, 0x69, 0x79, 0x7a, 0x7b, 0x7c, 0x88, 0x91, 0x92, 0x93, 0x94, 0x95, 0xaa, 0xab, 0xac, 0xad,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xa1, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0x56, 0x8f,
0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0x57, 0x96,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0x24, 0x25, 0xe7, 0x21, 0x4a, 0x4d, 0x8e, 0x97, 0x98,
0xe8, 0xe9, 0x26, 0x27, 0x2d, 0x2e, 0xea, 0xeb, 0xb6, 0xb7, 0xec, 0xed, 0xee, 0xef, 0x6c, 0x6d,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0x4b, 0x63, 0x49, 0x64, 0x62, 0x73, 0x75, 0x76, 0x74, 0x8a, 0x8c,
0xf5, 0x8b, 0xa5, 0xa7, 0xa6, 0x77, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
};
//
// Director 8.5-10 Win, cp1252 encoding
//
// ............................'-–
// — .....!"#$%&()*,./:;?@[\]ˆ._`{
// |}~¡¦¨¯´¸¿˜‘’‚“”„‹›¢£¤¥€+<=>±«»×
// ÷§©¬®°µ¶·…†‡•‰�����0¼½¾1¹2²3³456
// 789aAªáÁàÀâÂäÄãÃåÅæÆbBcCçÇdDðÐeE
// éÉèÈêÊëËfFƒgGhHiIíÍìÌîÎïÏjJkKlLm
// MnNñÑoOºóÓòÒôÔöÖõÕøØœŒpPqQrRsSšŠ
// ßtTþÞ™uUúÚùÙûÛüÜvVwWxXyYýÝ.ŸzZžŽ
const byte orderTableD8win[256] = {
0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x23, 0x24, 0x25, 0x26, 0x27, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
0x21, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x1d, 0x2e, 0x2f, 0x30, 0x58, 0x31, 0x1e, 0x32, 0x33,
0x73, 0x77, 0x79, 0x7b, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x34, 0x35, 0x59, 0x5a, 0x5b, 0x36,
0x37, 0x84, 0x95, 0x97, 0x9b, 0x9f, 0xa9, 0xac, 0xae, 0xb0, 0xba, 0xbc, 0xbe, 0xc0, 0xc2, 0xc6,
0xd7, 0xd9, 0xdb, 0xdd, 0xe2, 0xe7, 0xf1, 0xf3, 0xf5, 0xf7, 0xfd, 0x38, 0x39, 0x3a, 0x3b, 0x3d,
0x3e, 0x83, 0x94, 0x96, 0x9a, 0x9e, 0xa8, 0xab, 0xad, 0xaf, 0xb9, 0xbb, 0xbd, 0xbf, 0xc1, 0xc5,
0xd6, 0xd8, 0xda, 0xdc, 0xe1, 0xe6, 0xf0, 0xf2, 0xf4, 0xf6, 0xfc, 0x3f, 0x40, 0x41, 0x42, 0x1c,
0x57, 0x6e, 0x4d, 0xaa, 0x50, 0x69, 0x6a, 0x6b, 0x3b, 0x6d, 0xdf, 0x51, 0xd5, 0x6f, 0xff, 0x70,
0x71, 0x4b, 0x4c, 0x4e, 0x4f, 0x6c, 0x1f, 0x20, 0x4a, 0xe5, 0xde, 0x52, 0xd4, 0x72, 0xfe, 0xfb,
0x22, 0x43, 0x53, 0x54, 0x55, 0x56, 0x44, 0x61, 0x45, 0x62, 0x85, 0x5d, 0x63, 0x00, 0x64, 0x46,
0x65, 0x5c, 0x7a, 0x7c, 0x47, 0x66, 0x67, 0x68, 0x48, 0x78, 0xc7, 0x5e, 0x74, 0x75, 0x76, 0x49,
0x89, 0x87, 0x8b, 0x8f, 0x8d, 0x91, 0x93, 0x99, 0xa3, 0xa1, 0xa5, 0xa7, 0xb4, 0xb2, 0xb6, 0xb8,
0x9d, 0xc4, 0xcb, 0xc9, 0xcd, 0xd1, 0xcf, 0x5f, 0xd3, 0xeb, 0xe9, 0xed, 0xef, 0xf9, 0xe4, 0xe0,
0x88, 0x86, 0x8a, 0x8e, 0x8c, 0x90, 0x92, 0x98, 0xa2, 0xa0, 0xa4, 0xa6, 0xb3, 0xb1, 0xb5, 0xb7,
0x9c, 0xc3, 0xca, 0xc8, 0xcc, 0xd0, 0xce, 0x60, 0xd2, 0xea, 0xe8, 0xec, 0xee, 0xf8, 0xe3, 0xfa,
};
/////////////////////////////////////////////////////////////
// String comparison equality tables
//
// Director is using its way to compare if two characters are equal
// It is declared as case-insensitive but the reality is more complex.
//
// The tables below contain the first instance of the letter that is
// comparable in the given position.
//
// e.g. for D5 win character 159 and 255 are equal. Item 159 is equal to 159 and item
// 255 is equal to 159 as well.
//
// The tools to recreate these tables is available in the director tests repository
// https://github.com/scummvm/director-tests/tree/master/stringequality
//
// Director 4 Win, cp1252 encoding
// D4 win: does lowercase comparison and discards diacritics.
//
const byte equalityTableD4win[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x53, 0x8b, 0x4f, 0x8d, 0x8e, 0x8f,
0x90, 0x27, 0x27, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x53, 0x9b, 0x4f, 0x9d, 0x9e, 0x59,
0x20, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0x22, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0x22, 0xbc, 0xbd, 0xbe, 0xbf,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43, 0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0xd7, 0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0xde, 0x53,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43, 0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0xf7, 0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0xde, 0x59
};
//
// Director 5 Win, cp1252 encoding
//
const byte equalityTableD5win[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x8a, 0x9b, 0x8c, 0x9d, 0x9e, 0x9f,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0x98, 0x9c, 0x9e, 0x9d, 0xd5, 0xd6, 0xf7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0x9f
};
//
// Director 6 Win, cp1252 encoding
//
const byte equalityTableD6win[256] = {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x8a, 0x9b, 0x8c, 0x9d, 0x9e, 0x9f,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0x98, 0x8c, 0x9e, 0x9d, 0xd5, 0xd6, 0xf7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0x9f
};
//
// Director 3, 4, 5 Mac MacRoman
//
const byte equalityTableD3mac[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x41, 0x41, 0x43, 0x45, 0x4e, 0x4f, 0x55, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43, 0x45, 0x45,
0x45, 0x45, 0x49, 0x49, 0x49, 0x49, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x55, 0x55, 0x55, 0x55,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0x4f,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xae, 0x4f,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0x22, 0x22, 0xc9, 0x20, 0x41, 0x41, 0x4f, 0xce, 0xce,
0xd0, 0xd1, 0x22, 0x22, 0x27, 0x27, 0xd6, 0xd7, 0x59, 0x59, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0x41, 0x45, 0x41, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49, 0x4f, 0x4f,
0xf0, 0x4f, 0x55, 0x55, 0x55, 0x49, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
static int getCharOrder(Common::u32char_type_t ch) {
int num = charToNum(ch);
if (num > 255)
return num;
Common::Platform pl = g_director->getPlatform();
Common::Language lang = g_director->getLanguage();
int version = g_director->getVersion();
if (pl == Common::kPlatformMacintosh && lang != Common::JA_JPN && version < 500)
return orderTableD2mac[num];
if (pl == Common::kPlatformMacintosh && lang == Common::JA_JPN && version < 500)
return orderTableD4Jmac[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 600)
return orderTableD4win[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 700)
return orderTableD6win[num];
if (pl == Common::kPlatformMacintosh && lang != Common::JA_JPN && version < 900)
return orderTableD8mac[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 1100)
return orderTableD8win[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version >= 1100)
return num;
return num;
}
int compareStringOrder(const Common::String &s1, const Common::String &s2) {
Common::U32String u32S1 = s1.decode(Common::kUtf8);
Common::U32String u32S2 = s2.decode(Common::kUtf8);
const Common::u32char_type_t *p1 = u32S1.c_str();
const Common::u32char_type_t *p2 = u32S2.c_str();
int c1, c2;
do {
c1 = getCharOrder(*p1);
c2 = getCharOrder(*p2);
p1++;
p2++;
} while (c1 == c2 && (c1 || c2));
return c1 - c2;
}
static int getCharEquality(Common::u32char_type_t ch) {
int num = charToNum(ch);
if (num > 255)
return num;
Common::Platform pl = g_director->getPlatform();
Common::Language lang = g_director->getLanguage();
int version = g_director->getVersion();
if (pl == Common::kPlatformMacintosh && lang != Common::JA_JPN && version < 600)
return equalityTableD3mac[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 500)
return equalityTableD4win[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 600)
return equalityTableD5win[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 700)
return equalityTableD6win[num];
warning("BUILDBOT: No equality table for Director version: %d-%s", version, Common::getLanguageCode(lang));
return num;
}
bool compareStringEquality(const Common::String &s1, const Common::String &s2) {
Common::U32String u32S1 = s1.decode(Common::kUtf8);
Common::U32String u32S2 = s2.decode(Common::kUtf8);
const Common::u32char_type_t *p1 = u32S1.c_str();
const Common::u32char_type_t *p2 = u32S2.c_str();
int c1, c2;
do {
c1 = getCharEquality(*p1);
c2 = getCharEquality(*p2);
p1++;
p2++;
} while (c1 == c2 && (c1 || c2));
return (c1 == 0) && (c2 == 0);
}
const char *d_strstr(const char *str, const char *substr) {
while (*str && *substr) {
uint32 c1 = getCharEquality(*str);
uint32 c2 = getCharEquality(*substr);
if (c1 == c2) {
// inner loop, keep track of the starting character
const char *baseptr = str;
const char *ref = substr;
while (*baseptr && *ref) {
c1 = getCharEquality(*baseptr);
c2 = getCharEquality(*ref);
// characters equal, increment substring
if (c1 == c2) {
ref++;
} else {
break;
}
// reached the end of the substring, success
if (!*ref)
return str;
baseptr++;
}
}
str++;
}
return nullptr;
}
void DirectorEngine::delayMillis(uint32 delay) {
if (debugChannelSet(-1, kDebugFast))
return;
_system->delayMillis(delay);
}
} // End of namespace Director
double readAppleFloat80(void *ptr_) {
// Floats in an "80 bit IEEE Standard 754 floating
// point number (Standard Apple Numeric Environment [SANE] data type
// Extended).
byte *ptr = (byte *)ptr_;
uint16 signAndExponent = READ_BE_UINT16(&ptr[0]);
uint64 mantissa = READ_BE_UINT64(&ptr[2]);
return Common::XPFloat(signAndExponent, mantissa).toDouble(Common::XPFloat::kSemanticsSANE);
}
void hexdumpIfNotZero(byte *data, int len, const char *prefix) {
bool nonZero = false;
for (int i = 0; i < len; i++) {
if (data[i] != 0) {
nonZero = true;
break;
}
}
if (nonZero) {
if (prefix)
debugN("%s ", prefix);
Common::hexdump(data, len);
}
}