mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
* toggle maintenance mode in a more reasonable manner (i.e. not when displaying help test) * move connectivity tests to class DB * restore generate everything functionality when running --sql and --build with an empty parameter set
358 lines
14 KiB
PHP
358 lines
14 KiB
PHP
<?php
|
|
|
|
if (!defined('AOWOW_REVISION'))
|
|
die('illegal access');
|
|
|
|
if (!CLI)
|
|
die('not in cli mode');
|
|
|
|
|
|
class CLISetup
|
|
{
|
|
public static $locales = [];
|
|
public static $localeIds = [];
|
|
|
|
public static $srcDir = 'setup/mpqdata/';
|
|
|
|
private static $mpqFiles = [];
|
|
public static $expectedPaths = array( // update paths [yes, you can have en empty string as key]
|
|
'' => 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.', '=<subScriptList,>' ],
|
|
'build' => [1, [], 0x12, 'Compile image files and data dumps.', '=<subScriptList,>' ],
|
|
'sync' => [1, [], 0x12, 'Regenerate tables/files that depend on given world DB table.', '=<worldTableList,>'],
|
|
'dbc' => [1, [], 0x11, 'Extract dbc files from mpqDataDir into sql table. Structure must be defined in setup/dbc.class.php.', '=<dbcfileList,>' ],
|
|
'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)', '=<regionCodes,>' ],
|
|
'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 (self::$opts['log'])
|
|
CLI::initLogFile(trim(self::$opts['log']));
|
|
|
|
// alternative data source (no quotes, use forward slash)
|
|
if (self::$opts['mpqDataDir'])
|
|
self::$srcDir = CLI::nicePath($self::$opts['mpqDataDir']);
|
|
|
|
// optional limit handled locales
|
|
if (self::$opts['locales'])
|
|
{
|
|
// engb and enus are identical for all intents and purposes
|
|
$from = ['engb', 'esmx', 'encn'];
|
|
$to = ['enus', 'eses', 'zhcn'];
|
|
$_['locales'] = str_ireplace($from, $to, strtolower($_['locales']));
|
|
|
|
self::$locales = array_intersect(Util::$localeStrings, explode(',', $_['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;
|
|
}
|
|
}
|
|
|
|
?>
|