LOCALE_EN, 'enGB' => LOCALE_EN, 'enUS' => LOCALE_EN, 'frFR' => LOCALE_FR, 'deDE' => LOCALE_DE, 'zhCN' => LOCALE_CN, 'enCN' => LOCALE_CN, 'esES' => LOCALE_ES, 'esMX' => LOCALE_ES, 'ruRU' => LOCALE_RU ); public const LOCK_OFF = 0; public const LOCK_ON = 1; public const LOCK_RESTORE = 2; private static $lock = 1; private const ARGV_REQUIRED = 0x01; private const ARGV_OPTIONAL = 0x02; private const ARGV_ARRAY = 0x10; private static $opts = []; private static $optGroups = ['AoWoW Setup', 'Utility Functions', 'Additional Options', 'Additional arguments specific to --build=simpleImg', 'Additional arguments specific to --build=complexImg']; private static $optDefs = array( // cmd => [groupId, aliasses[], flags, description, appendix] 'setup' => [0, ['s', 'firstrun'], 0x00, 'Step by step initial setup. Resumes if interrupted.', '' ], 'update' => [0, ['u'], 0x00, 'Apply new sql updates fetched from Github and run --sync as needed.', '' ], 'dbconfig' => [1, [], 0x00, 'Set up DB connection.', '' ], 'siteconfig' => [1, [], 0x00, 'Set up site variables.', '' ], 'account' => [1, [], 0x00, 'Create an account with admin privileges.', '' ], 'sql' => [1, [], 0x12, 'Generate DB content from your world tables.', '=' ], 'build' => [1, [], 0x12, 'Compile image files and data dumps.', '=' ], 'sync' => [1, [], 0x12, 'Regenerate tables/files that depend on given world DB table.', '='], 'dbc' => [1, [], 0x11, 'Extract dbc files from mpqDataDir into sql table. Structure must be defined in setup/dbc.class.php.', '=' ], 'delete' => [2, ['d'], 0x00, 'Delete dbc_* tables generated by this prompt when done.', '' ], 'log' => [2, [], 0x01, 'Write CLI ouput to file.', '=logfile' ], 'help' => [2, ['h'], 0x00, 'Display contextual help, if available.', '' ], 'force' => [2, ['f'], 0x00, 'Force existing files to be overwritten.', '' ], 'locales' => [2, [], 0x12, 'Limit setup to enUS, frFR, deDE, zhCN, esES and/or ruRU. (does not override config settings)', '=' ], 'mpqDataDir' => [2, [], 0x02, 'Manually point to directory with extracted mpq files. This is limited to setup/ (default: setup/mpqData/)', '=path/' ], 'icons' => [3, ['1'], 0x00, 'Generate icons for spells, items, classes, races, ect.', '' ], 'glyphs' => [3, ['2'], 0x00, 'Generate decorative glyph symbols displayed on related item and spell pages.', '' ], 'pagetexts' => [3, ['3'], 0x00, 'Generate images contained in text on readable items and gameobjects.', '' ], 'loadingscreens' => [3, ['4'], 0x00, 'Generate loading screen images (not used on page; skipped by default)', '' ], 'talentbgs' => [4, ['1'], 0x00, 'Generate backgrounds for the talent calculator.', '' ], 'maps' => [4, ['2'], 0x00, 'Generate zone and continental maps.', '' ], 'spawn-maps' => [4, ['3'], 0x00, 'Fallback to generate alpha masks for each zone to match creature and gameobject spawn points.', '' ], 'artwork' => [4, ['4'], 0x00, 'Generate images from /glues/credits (not used on page; skipped by default))', '' ], 'area-maps' => [4, ['5'], 0x00, 'Generate additional area maps with highlighting for subzones (optional; skipped by default)', '' ] ); /**************************/ /* command line arguments */ /**************************/ public static function init() : void { $short = ''; $long = []; $alias = []; foreach (self::$optDefs as $opt => [$idx, $aliasses, $flags, , ]) { if ($flags & self::ARGV_REQUIRED) $opt .= ':'; else if ($flags & self::ARGV_OPTIONAL) $opt .= '::'; $long[] = $opt; foreach ($aliasses as $a) { if ($flags & self::ARGV_REQUIRED) // neither should be set with shortOpts $_a = $a.':'; else if ($flags & self::ARGV_OPTIONAL) $_a = $a.'::'; else $_a = $a; $alias[$a] = $opt; if (strlen($a) == 1) $short .= $_a; else $long[] = $_a; } } if ($opts = getopt($short, $long)) foreach ($opts as $o => $v) self::$opts[$alias[$o] ?? $o] = (self::$optDefs[$alias[$o] ?? $o][2] & self::ARGV_ARRAY) ? ($v ? explode(',', $v) : []) : ($v ?: true); // optional logging if (isset(self::$opts['log'])) CLI::initLogFile(trim(self::$opts['log'])); // alternative data source (no quotes, use forward slash) if (isset(self::$opts['mpqDataDir'])) self::$srcDir = CLI::nicePath(self::$opts['mpqDataDir']); // optional limit handled locales if (isset(self::$opts['locales'])) { // engb and enus are identical for all intents and purposes $from = ['engb', 'esmx', 'encn']; $to = ['enus', 'eses', 'zhcn']; $opts['locales'] = str_ireplace($from, $to, strtolower($opts['locales'])); self::$locales = array_intersect(Util::$localeStrings, explode(',', $opts['locales'])); } if (!self::$locales) self::$locales = array_filter(Util::$localeStrings); // restrict actual locales foreach (self::$locales as $idx => $str) if (!defined('CFG_LOCALES') || CFG_LOCALES & (1 << $idx)) self::$localeIds[] = $idx; // get site status if (DB::isConnectable(DB_AOWOW)) self::$lock = (int)DB::Aowow()->selectCell('SELECT `value` FROM ?_config WHERE `key` = "maintenance"'); else self::$lock = self::LOCK_ON; } public static function getOpt(...$args) { if (!$args) return false; $result = []; // groupMask case if (is_int($args[0])) { foreach (self::$optDefs as $o => [$group, , , , ]) if (((1 << $group) & $args[0]) && isset(self::$opts[$o])) $result[] = $o; return $result; } // single key case if (count($args) == 1) return self::$opts[$args[0]] ?? false; // multiple keys case foreach ($args as $a) if (isset(self::$optDefs[$a])) $result[$a] = self::$opts[$a] ?? false; return $result; } public static function optHelp(int $groupMask = 0x0) : void { $lines = []; foreach (self::$optGroups as $idx => $og) { if ($groupMask && !($groupMask & (1 << $idx))) continue; $lines[] = [$og, '']; foreach (self::$optDefs as $opt => [$group, $alias, , $desc, $app]) { if ($group != $idx) continue; $cmd = ' --'.$opt; foreach ($alias as $a) $cmd .= ' | '.(strlen($a) == 1 ? '-'.$a : '--'.$a); $lines[] = [$cmd.$app, $desc]; } } CLI::writeTable($lines); } /*******************/ /* web page access */ /*******************/ public static function siteLock(int $mode = self::LOCK_RESTORE) : void { if (DB::isConnectable(DB_AOWOW)) DB::Aowow()->query('UPDATE ?_config SET `value` = ?d WHERE `key` = "maintenance"', (int)!!($mode == self::LOCK_RESTORE ? self::$lock : $mode)); } /*******************/ /* MPQ-file access */ /*******************/ /* the problem 1) paths provided in dbc files are case-insensitive and random 2) paths to the actual textures contained in the mpq archives are case-insensitive and random unix systems will throw a fit if you try to get from one to the other, so lets save the paths from 2) and cast it to lowercase lookups will be done in lowercase. A successfull match will return the real path. */ private static function buildFileList() : bool { CLI::write(); CLI::write('indexing game data from '.self::$srcDir.' for first time use...'); $setupDirs = glob('setup/*'); foreach ($setupDirs as $sd) { if (mb_substr(self::$srcDir, -1) == '/') self::$srcDir = mb_substr(self::$srcDir, 0, -1); if (mb_substr($sd, -1) == '/') $sd = mb_substr($sd, 0, -1); if (Util::lower($sd) == Util::lower(self::$srcDir)) { self::$srcDir = $sd.'/'; break; } } try { $iterator = new RecursiveDirectoryIterator(self::$srcDir); $iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS); foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST) as $path) { $_ = str_replace('\\', '/', $path->getPathname()); self::$mpqFiles[strtolower($_)] = $_; } CLI::write('done'); CLI::write(); } catch (UnexpectedValueException $e) { CLI::write('- mpqData dir '.self::$srcDir.' does not exist', CLI::LOG_ERROR); return false; } return true; } public static function fileExists(&$file) { // read mpq source file structure to tree if (!self::$mpqFiles) if (!self::buildFileList()) return false; // backslash to forward slash $_ = strtolower(str_replace('\\', '/', $file)); // remove trailing slash if (mb_substr($_, -1, 1) == '/') $_ = mb_substr($_, 0, -1); if (isset(self::$mpqFiles[$_])) { $file = self::$mpqFiles[$_]; return true; } return false; } public static function filesInPath($path, $useRegEx = false) { $result = []; // read mpq source file structure to tree if (!self::$mpqFiles) if (!self::buildFileList()) return []; // backslash to forward slash $_ = strtolower(str_replace('\\', '/', $path)); foreach (self::$mpqFiles as $lowerFile => $realFile) { if (!$useRegEx && strstr($lowerFile, $_)) $result[] = $realFile; else if ($useRegEx && preg_match($path, $lowerFile)) $result[] = $realFile; } return $result; } /*****************/ /* file handling */ /*****************/ public static function writeFile($file, $content) { if (Util::writeFile($file, $content)) { CLI::write(sprintf(ERR_NONE, CLI::bold($file)), CLI::LOG_OK); return true; } $e = error_get_last(); CLI::write($e['message'].' '.CLI::bold($file), CLI::LOG_ERROR); return false; } public static function writeDir($dir) { if (Util::writeDir($dir)) return true; CLI::write(error_get_last()['message'].' '.CLI::bold($dir), CLI::LOG_ERROR); return false; } public static function loadDBC($name) { if (DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'dbc_'.$name) && DB::Aowow()->selectCell('SELECT count(1) FROM ?#', 'dbc_'.$name)) return true; $dbc = new DBC($name, ['temporary' => self::getOpt('delete')]); if ($dbc->error) { CLI::write('CLISetup::loadDBC() - required DBC '.$name.'.dbc not found!', CLI::LOG_ERROR); return false; } if (!$dbc->readFile()) { CLI::write('CLISetup::loadDBC() - DBC '.$name.'.dbc could not be written to DB!', CLI::LOG_ERROR); return false; } return true; } } ?>