scummvm/engines/director/util.cpp
Vladimir Serbinenko af7d9cb850 COMMON: Move punycode_*codepath to path.cpp
This allows to reduce access to DIR_SEPARATOR from outside of Common::Path
2022-12-14 05:08:46 +01:00

1215 lines
36 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/fs.h"
#include "common/keyboard.h"
#include "common/macresman.h"
#include "common/memstream.h"
#include "common/punycode.h"
#include "common/tokenizer.h"
#include "common/compression/zlib.h"
#include "director/types.h"
#include "graphics/macgui/macwindowmanager.h"
#include "graphics/macgui/macfontmanager.h"
#include "director/director.h"
#include "director/movie.h"
#include "director/lingo/lingo.h"
#include "director/util.h"
namespace Director {
static 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 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) {
for (WinKeyCodeMapping *k = WinkeyCodeMappings; k->scummvm != Common::KEYCODE_INVALID; k++)
_KeyCodes[k->scummvm] = k->win;
} else {
for (MacKeyCodeMapping *k = MackeyCodeMappings; k->scummvm != Common::KEYCODE_INVALID; k++)
_KeyCodes[k->scummvm] = k->mac;
}
}
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;
}
Common::String convertPath(Common::String &path) {
if (path.empty())
return path;
if (!path.contains(':') && !path.contains('\\') && !path.contains('@')) {
return path;
}
Common::String res;
uint32 idx = 0;
if (path.hasPrefix("::")) { // Parent directory
idx = 2;
} else if (path.hasPrefix("@:")) { // 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] == '\\')
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(Common::String path, 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
}
bool testPath(Common::String &path, bool directory) {
Common::FSNode d = Common::FSNode(*g_director->getGameDataDir());
Common::FSNode node;
// Test if we have it right in the SearchMan. Also accept MacBinary
// for Mac and Pippin
if (SearchMan.hasFile(Common::Path(path, g_director->_dirSeparator)) ||
((g_director->getPlatform() == Common::kPlatformMacintoshII
|| g_director->getPlatform() == Common::kPlatformMacintosh
|| g_director->getPlatform() == Common::kPlatformPippin) &&
Common::MacResManager::exists(Common::Path(path, g_director->_dirSeparator))))
return true;
debug(9, "testPath: %s dir: %d", path.c_str(), directory);
// check for the game data dir
if (!path.contains(g_director->_dirSeparator) && path.equalsIgnoreCase(d.getName())) {
if (!directory)
return false;
path = "";
return true;
}
Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
Common::String newPath;
Common::FSList fslist;
while (!directory_list.empty()) {
Common::String token = directory_list.nextToken();
fslist.clear();
Common::FSNode::ListMode mode = Common::FSNode::kListDirectoriesOnly;
if (directory_list.empty() && !directory) {
mode = Common::FSNode::kListAll;
}
bool hasChildren = d.getChildren(fslist, mode);
if (!hasChildren)
continue;
bool exists = false;
for (Common::FSList::iterator i = fslist.begin(); i != fslist.end(); ++i) {
// for each element in the path, choose the first FSNode
// with a case-insensitive matching name
if (i->getName().equalsIgnoreCase(token)) {
// If this the final path component, check if we're allowed to match with a directory
node = Common::FSNode(*i);
if (directory_list.empty() && !directory && node.isDirectory()) {
continue;
}
exists = true;
newPath += i->getName();
if (!directory_list.empty())
newPath += (g_director->_dirSeparator);
d = node;
break;
}
}
if (!exists) {
debug(9, "testPath: Not exists");
return false;
}
}
debug(9, "testPath: ***** HAVE MATCH");
// write back path with correct case
path = newPath;
return true;
}
Common::String pathMakeRelative(Common::String path, bool recursive, bool addexts, bool directory) {
//Wrap pathMakeRelative to search in extra paths defined by the game
Common::String foundPath;
Datum searchPath = g_director->getLingo()->_searchPath;
if (searchPath.type == ARRAY && searchPath.u.farr->arr.size() > 0) {
for (uint i = 0; i < searchPath.u.farr->arr.size(); i++) {
Common::String searchIn = searchPath.u.farr->arr[i].asString();
debug(9, "pathMakeRelative(): searchPath: %s", searchIn.c_str());
foundPath = wrappedPathMakeRelative(searchIn + path, recursive, addexts, directory);
if (testPath(foundPath))
return foundPath;
debug(9, "pathMakeRelative(): -- searchPath not found: %s", foundPath.c_str());
}
}
for (auto i = g_director->_extraSearchPath.begin(); i != g_director->_extraSearchPath.end(); ++i) {
debug(9, "pathMakeRelative(): extraSearchPath: %s", i->c_str());
foundPath = wrappedPathMakeRelative(*i + path, recursive, addexts, directory);
if (testPath(foundPath))
return foundPath;
debug(9, "pathMakeRelative(): -- extraSearchPath not found: %s", foundPath.c_str());
}
return wrappedPathMakeRelative(path, recursive, addexts, directory);
}
// if we are finding the file path, then this func will return exactly the executable file path
// if we are finding the directory path, then we will get the path relative to the game data dir.
// e.g. if we have game data dir as SSwarlock, then "A:SSwarlock" -> "", "A:SSwarlock:Nav" -> "Nav"
Common::String wrappedPathMakeRelative(Common::String path, bool recursive, bool addexts, bool directory) {
Common::String initialPath(path);
debug(9, "wrappedPathMakeRelative(): s0 %s -> %s", path.c_str(), initialPath.c_str());
if (recursive) // first level
initialPath = convertPath(initialPath);
debug(9, "wrappedPathMakeRelative(): s1 %s -> %s", path.c_str(), initialPath.c_str());
initialPath = Common::normalizePath(g_director->getCurrentPath() + initialPath, g_director->_dirSeparator);
Common::String convPath = initialPath;
debug(9, "wrappedPathMakeRelative(): s2 %s", convPath.c_str());
if (testPath(initialPath, directory))
return initialPath;
debug(9, "wrappedPathMakeRelative(): s2.1 -- not found %s", initialPath.c_str());
// Now try to search the file
bool opened = false;
while (convPath.contains(g_director->_dirSeparator)) {
int pos = convPath.find(g_director->_dirSeparator);
convPath = Common::String(&convPath.c_str()[pos + 1]);
debug(9, "wrappedPathMakeRelative(): s3 try %s", convPath.c_str());
if (!testPath(convPath, directory)) {
// If we were supplied a path with subdirectories,
// attempt to combine it with the current movie path at every iteration
Common::String locPath = Common::normalizePath(g_director->getCurrentPath() + convPath, g_director->_dirSeparator);
debug(9, "wrappedPathMakeRelative(): s3.1 try %s", locPath.c_str());
if (!testPath(locPath, directory)) {
debug(9, "wrappedPathMakeRelative(): s3.1 -- not found %s", locPath.c_str());
continue;
}
}
debug(9, "wrappedPathMakeRelative(): s3 converted %s -> %s", path.c_str(), convPath.c_str());
opened = true;
break;
}
if (!opened) {
// Try stripping all of the characters not allowed in FAT
convPath = stripMacPath(initialPath.c_str());
debug(9, "wrappedPathMakeRelative(): s4 %s", convPath.c_str());
if (testPath(initialPath, directory))
return initialPath;
debug(9, "wrappedPathMakeRelative(): s4.1 -- not found %s", initialPath.c_str());
// Now try to search the file
while (convPath.contains(g_director->_dirSeparator)) {
int pos = convPath.find(g_director->_dirSeparator);
convPath = Common::String(&convPath.c_str()[pos + 1]);
debug(9, "wrappedPathMakeRelative(): s5 try %s", convPath.c_str());
if (!testPath(convPath, directory)) {
debug(9, "wrappedPathMakeRelative(): s5 -- not found %s", convPath.c_str());
continue;
}
debug(9, "wrappedPathMakeRelative(): s5 converted %s -> %s", path.c_str(), convPath.c_str());
opened = true;
break;
}
}
if (!opened && recursive && !directory) {
// Hmmm. We couldn't find the path as is.
// Let's try to translate file path into 8.3 format
Common::String addedexts;
convPath.clear();
const char *ptr = initialPath.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 (g_director->getPlatform() == Common::kPlatformWindows) {
if (hasExtension(component)) {
Common::String nameWithoutExt = component.substr(0, component.size() - 4);
Common::String ext = component.substr(component.size() - 4);
Common::String newpath = convPath + convertMacFilename(nameWithoutExt.c_str()) + ext;
debug(9, "wrappedPathMakeRelative(): s6 %s -> try %s", initialPath.c_str(), newpath.c_str());
Common::String res = wrappedPathMakeRelative(newpath, false, false);
if (testPath(res))
return res;
debug(9, "wrappedPathMakeRelative(): s6 -- not found %s", res.c_str());
}
}
if (addexts)
addedexts = testExtensions(component, initialPath, convPath);
if (!addedexts.empty()) {
return addedexts;
}
return initialPath; // Anyway nothing good is happening
}
if (opened)
return convPath;
else
return initialPath;
}
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 testExtensions(Common::String component, Common::String initialPath, Common::String convPath) {
const char *extsD3[] = { ".MMM", nullptr };
const char *extsD4[] = { ".DIR", ".DXR", nullptr };
const char **exts = (g_director->getVersion() >= 400) ? extsD4 : extsD3;
for (int i = 0; exts[i]; ++i) {
Common::String newpath = convPath + component.c_str() + exts[i];
debug(9, "testExtensions(): sT %s -> try %s, comp: %s", initialPath.c_str(), newpath.c_str(), component.c_str());
Common::String res = wrappedPathMakeRelative(newpath, false, false);
if (testPath(res))
return res;
}
for (int i = 0; exts[i]; ++i) {
Common::String newpath = convPath + convertMacFilename(component.c_str()) + exts[i];
debug(9, "testExtensions(): sT %s -> try %s, comp: %s", initialPath.c_str(), newpath.c_str(), component.c_str());
Common::String res = wrappedPathMakeRelative(newpath, false, false);
if (testPath(res))
return res;
}
return Common::String();
}
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::String dumpScriptName(const char *prefix, int type, int id, const char *ext) {
Common::String typeName;
switch (type) {
case kNoneScript:
typeName = "unknown";
break;
case kMovieScript:
typeName = "movie";
break;
case kCastScript:
typeName = "cast";
break;
case kEventScript:
typeName = "event";
break;
case kScoreScript:
typeName = "score";
break;
default:
error("dumpScriptName(): Incorrect call (type %d)", type);
break;
}
return Common::String::format("./dumps/%s-%s-%d.%s", prefix, typeName.c_str(), id, ext);
}
Common::String dumpFactoryName(const char *prefix, const char *name, const char *ext) {
return 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, unsigned long len, unsigned long *outLen, bool bigEndian) {
#ifdef USE_ZLIB
byte *in = (byte *)malloc(len);
byte *out = (byte *)malloc(*outLen);
stream.read(in, len);
if (!Common::uncompress(out, outLen, in, len)) {
free(in);
free(out);
return nullptr;
}
free(in);
return new Common::MemoryReadStreamEndian(out, *outLen, bigEndian, DisposeAfterUse::YES);
# else
return nullptr;
# endif
}
uint16 humanVersion(uint16 ver) {
if (ver >= kFileVer1201)
return 1201;
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("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;
}
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 castTypeToString(const CastType &type) {
Common::String res;
switch (type) {
case kCastBitmap:
res = "bitmap";
break;
case kCastPalette:
res = "palette";
break;
case kCastButton:
res = "button";
break;
case kCastPicture:
res = "picture";
break;
case kCastDigitalVideo:
res = "digitalVideo";
break;
case kCastLingoScript:
res = "script";
break;
case kCastShape:
res = "shape";
break;
case kCastFilmLoop:
res = "filmLoop";
break;
case kCastSound:
res = "sound";
break;
case kCastMovie:
res = "movie";
break;
case kCastText:
res = "text";
break;
default:
res = "empty";
break;
}
return res;
}
Common::String decodePlatformEncoding(Common::String input) {
return input.decode(g_director->getPlatformEncoding());
}
/////////////////////////////////////////////////////////////
// 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[\]^_`
// {|}~.†°¢£§•¶®©™´¨≠∞±≤≥¥µ∂∑∏π∫ªºΩ
// ¿¡¬√ƒ≈Δ…—÷◊¤‡·„‰<E280B0><EE809E>¯˘˙˚¸˝˛.
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-5.0 Win, cp1250 encoding
//
// ................................
//  !"«»#$%&'()*+,-./0123456789:
// ;<=>?@AÁŔÂÄĂĹĆaáŕâäăĺćBbCÇcçDĐdđ
// EÉČĘËeéčęëFfGgHhIÍĚÎĎiíěîďJjKkLl
// MmNŃnńOÓŇÔÖŐŘŚoóňôöőřśPpQqRrSŠsš
// ßTtUÚŮŰÜuúůűüVvWwXxYÝźyý.Zz[\]^_
// `{|}~.€<><EFBFBD>„…†‡<E280A0>ŤŽŹ<C5BD>“”•<E28093>ťžˇ
// ˘Ł¤Ą¦§¨©Ş¬®Ż°±˛ł´µ¶·¸ąşĽ˝ľż×Ţţ÷
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, cp1250 encoding
//
// ............................<2E>ŤŹ<C5A4>
// ť'-–—  .....!"#$%&()*,./:;?@[\]
// ^<5E>_`{|}~ˇ¦¨Ż´¸ż<C2B8>“”„+<=>±«»×
// ÷˘Ł¤Ą§©¬®°µ¶·†‡•…‰€0Ľ˝ľ1ą2˛3ł456
// 789aAŞáÁŕŔâÂäÄăĂĺĹćĆbBcCçÇdDđĐeE
// éÉčČęĘëËfF<66>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,
};
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::kPlatformWindows && lang != Common::JA_JPN && version < 600)
return orderTableD4win[num];
if (pl == Common::kPlatformWindows && lang != Common::JA_JPN && version < 700)
return orderTableD6win[num];
return num;
}
int compareStrings(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();
uint32 c1, c2;
do {
c1 = getCharOrder(*p1);
c2 = getCharOrder(*p2);
p1++;
p2++;
} while (c1 == c2 && c1);
return c1 - c2;
}
} // End of namespace Director