diff --git a/includes/database.class.php b/includes/database.class.php index fb39bf2e..9124dcbe 100644 --- a/includes/database.class.php +++ b/includes/database.class.php @@ -28,7 +28,7 @@ class DB $options = &self::$optionsCache[$idx]; $interface = DbSimple_Generic::connect(self::createConnectSyntax($options)); - $interface->setErrorHandler(array('DB', 'errorHandler')); + $interface->setErrorHandler(['DB', 'errorHandler']); if ($interface->error) die('Failed to connect to database.'); @@ -46,9 +46,9 @@ class DB if (!error_reporting()) return; - echo "DB ERROR:

\n\n
";
-        print_r($data);
-        echo "
"; + $error = "DB ERROR:

\n\n
".print_r($data, true)."
"; + + echo CLI ? strip_tags($error) : $error; exit; } diff --git a/includes/kernel.php b/includes/kernel.php index 1cbbe965..6f0c33d4 100644 --- a/includes/kernel.php +++ b/includes/kernel.php @@ -4,18 +4,22 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); -require 'includes/defines.php'; -require 'config/config.php'; -require 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master) -require 'includes/utilities.php'; // miscâ„¢ data 'n func -require 'includes/ajaxHandler.class.php'; // handles ajax and jsonp requests -require 'includes/user.class.php'; -require 'includes/markup.class.php'; // manipulate markup text -require 'includes/database.class.php'; // wrap DBSimple -require 'includes/community.class.php'; // handle comments, screenshots and videos -require 'includes/loot.class.php'; // build lv-tabs containing loot-information -require 'localization/lang.class.php'; -require 'pages/genericPage.class.php'; +if (file_exists('config/config.php')) + require_once 'config/config.php'; +else + $AoWoWconf = []; + +require_once 'includes/defines.php'; +require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master) +require_once 'includes/utilities.php'; // miscâ„¢ data 'n func +require_once 'includes/ajaxHandler.class.php'; // handles ajax and jsonp requests +require_once 'includes/user.class.php'; +require_once 'includes/markup.class.php'; // manipulate markup text +require_once 'includes/database.class.php'; // wrap DBSimple +require_once 'includes/community.class.php'; // handle comments, screenshots and videos +require_once 'includes/loot.class.php'; // build lv-tabs containing loot-information +require_once 'localization/lang.class.php'; +require_once 'pages/genericPage.class.php'; // autoload List-classes, associated filters and pages @@ -31,24 +35,22 @@ spl_autoload_register(function ($class) { if (strpos($class, 'list')) { if (!class_exists('BaseType')) - require 'includes/types/basetype.class.php'; + require_once 'includes/types/basetype.class.php'; if (file_exists('includes/types/'.strtr($class, ['list' => '']).'.class.php')) - require 'includes/types/'.strtr($class, ['list' => '']).'.class.php'; + require_once 'includes/types/'.strtr($class, ['list' => '']).'.class.php'; return; } if (file_exists('pages/'.strtr($class, ['page' => '']).'.php')) - require 'pages/'.strtr($class, ['page' => '']).'.php'; + require_once 'pages/'.strtr($class, ['page' => '']).'.php'; }); // Setup DB-Wrapper if (!empty($AoWoWconf['aowow']['db'])) DB::load(DB_AOWOW, $AoWoWconf['aowow']); -else - die('no database credentials given for: aowow'); if (!empty($AoWoWconf['world']['db'])) DB::load(DB_WORLD, $AoWoWconf['world']); @@ -56,15 +58,14 @@ if (!empty($AoWoWconf['world']['db'])) if (!empty($AoWoWconf['auth']['db'])) DB::load(DB_AUTH, $AoWoWconf['auth']); -foreach ($AoWoWconf['characters'] as $realm => $charDBInfo) - if (!empty($charDBInfo)) - DB::load(DB_CHARACTERS . $realm, $charDBInfo); - -unset($AoWoWconf); // link set up: delete passwords +if (!empty($AoWoWconf['characters'])) + foreach ($AoWoWconf['characters'] as $realm => $charDBInfo) + if (!empty($charDBInfo)) + DB::load(DB_CHARACTERS . $realm, $charDBInfo); // load config to constants -$sets = DB::Aowow()->select('SELECT `key` AS ARRAY_KEY, `value`, `flags` FROM ?_config'); +$sets = DB::isConnectable(DB_AOWOW) ? DB::Aowow()->select('SELECT `key` AS ARRAY_KEY, `value`, `flags` FROM ?_config') : []; foreach ($sets as $k => $v) { // this should not have been possible @@ -80,7 +81,7 @@ foreach ($sets as $k => $v) else if ($v['flags'] & CON_FLAG_TYPE_BOOL) $val = (bool)$v['value']; else if ($v['flags'] & CON_FLAG_TYPE_STRING) - $val = preg_replace('/[^\p{L}0-9\s_\-\'\.,]/ui', '', $v['value']); + $val = preg_replace('/[^\p{L}0-9~\s_\-\'\/\.,]/ui', '', $v['value']); else { Util::addNote(U_GROUP_ADMIN | U_GROUP_DEV, 'Kernel: '.($php ? 'PHP' : 'Aowow').' config value '.($php ? strtolower($k) : 'CFG_'.strtoupper($k)).' has no type set. Value forced to 0!'); @@ -94,38 +95,60 @@ foreach ($sets as $k => $v) } -$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || CFG_FORCE_SSL; -$protocoll = $secure ? 'https://' : 'http://'; +error_reporting($AoWoWconf && CFG_DEBUG ? (E_ALL & ~(E_DEPRECATED | E_USER_DEPRECATED | E_STRICT)) : 0); -define('STATIC_URL', substr($protocoll.$_SERVER['SERVER_NAME'].strtr($_SERVER['SCRIPT_NAME'], ['index.php' => '']), 0, -1).'/static'); // points js to images & scripts (change here if you want to use a separate subdomain) -define('HOST_URL', substr($protocoll.$_SERVER['SERVER_NAME'].strtr($_SERVER['SCRIPT_NAME'], ['index.php' => '']), 0, -1)); // points js to executable files +$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || ($AoWoWconf && CFG_FORCE_SSL); +if (defined('CFG_STATIC_HOST')) // points js to images & scripts + define('STATIC_URL', ($secure ? 'https://' : 'http://').CFG_STATIC_HOST); -$e = CFG_DEBUG ? (E_ALL & ~(E_DEPRECATED | E_USER_DEPRECATED | E_STRICT)) : 0; -error_reporting($e); +if (defined('CFG_SITE_HOST')) // points js to executable files + define('HOST_URL', ($secure ? 'https://' : 'http://').CFG_SITE_HOST); -// debug: measure execution times -Util::execTime(CFG_DEBUG); +if (!CLI) +{ + // Setup Session + session_set_cookie_params(15 * YEAR, '/', '', $secure, true); + session_cache_limiter('private'); + session_start(); + if ($AoWoWconf && User::init()) + User::save(); // save user-variables in session + + // todo: (low) - move to setup web-interface (when it begins its existance) + if (!defined('CFG_SITE_HOST') || !defined('CFG_STATIC_HOST')) + { + $host = substr($_SERVER['SERVER_NAME'].strtr($_SERVER['SCRIPT_NAME'], ['index.php' => '']), 0, -1); + + define('HOST_URL', ($secure ? 'https://' : 'http://').$host); + define('STATIC_URL', ($secure ? 'https://' : 'http://').$host.'/static'); + if (User::isInGroup(U_GROUP_ADMIN) && $AoWoWconf) // initial set + { + DB::Aowow()->query('INSERT IGNORE INTO ?_config VALUES (?, ?, ?d, ?), (?, ?, ?d, ?)', + 'site_host', $host, CON_FLAG_TYPE_STRING | CON_FLAG_PERSISTENT, 'default: '.$host.' - points js to executable files (automaticly set on first run)', + 'static_host', $host.'/static', CON_FLAG_TYPE_STRING | CON_FLAG_PERSISTENT, 'default: '.$host.'/static - points js to images & scripts (automaticly set on first run)' + ); + } + } + + // hard-override locale for this call (should this be here..?) + // all strings attached.. + if ($AoWoWconf) + { + if (isset($_GET['locale']) && (CFG_LOCALES & (1 << (int)$_GET['locale']))) + User::useLocale($_GET['locale']); + + Lang::load(User::$localeString); + } + + // parse page-parameters .. sanitize before use! + @list($str, $trash) = explode('&', $_SERVER['QUERY_STRING'], 2); + @list($pageCall, $pageParam) = explode('=', $str, 2); + Util::$wowheadLink = 'http://'.Util::$subDomains[User::$localeId].'.wowhead.com/'.$str; +} +else if ($AoWoWconf) + Lang::load('enus'); -// Setup Session -session_set_cookie_params(15 * YEAR, '/', '', $secure, true); -session_cache_limiter('private'); -session_start(); -if (User::init()) - User::save(); // save user-variables in session - -// hard-override locale for this call (should this be here..?) -// all strings attached.. -if (isset($_GET['locale']) && (CFG_LOCALES & (1 << (int)$_GET['locale']))) - User::useLocale($_GET['locale']); - -Lang::load(User::$localeString); - - -// parse page-parameters .. sanitize before use! -@list($str, $trash) = explode('&', $_SERVER['QUERY_STRING'], 2); -@list($pageCall, $pageParam) = explode('=', $str, 2); -Util::$wowheadLink = 'http://'.Util::$subDomains[User::$localeId].'.wowhead.com/'.$str; +$AoWoWconf = null; // empty auths ?> diff --git a/index.php b/index.php index b53b8506..d733e820 100644 --- a/index.php +++ b/index.php @@ -1,28 +1,39 @@ '.$r." was not found. Please see if it exists, using php -m\n\n"; + $error .= 'Required Extension '.$r." was not found. Please check if it should exist, using \"php -m\"\n\n"; if (version_compare(PHP_VERSION, '5.5.0') < 0) - $error .= 'PHP Version 5.5.0 or higher required! Your version is '.PHP_VERSION.".\nCore functions are unavailable!"; + $error .= 'PHP Version 5.5.0 or higher required! Your version is '.PHP_VERSION.".\nCore functions are unavailable!\n"; + +// not in root dir +if (CLI && getcwd().DIRECTORY_SEPARATOR.'index.php' != __FILE__) + $error .= "Aowow must be used from root directory\n"; if ($error) - die('
'.$error.'
'); +{ + echo CLI ? strip_tags($error) : $error; + die(); +} + // include all necessities, set up basics -require 'includes/kernel.php'; +require_once 'includes/kernel.php'; + + +if (CLI || !file_exists('config/config.php')) +{ + $cwDir = /*$_SERVER['DOCUMENT_ROOT']; //*/getcwd(); + require 'setup/setup.php'; + die(); +} $altClass = ''; @@ -81,7 +92,8 @@ switch ($pageCall) case 'talent': // tool: talent calculator case 'title': case 'titles': - // case 'user': // tool: user profiles [nyi] + // case 'user': // tool: user profiles [nyi] + case 'video': case 'zone': case 'zones': if (in_array($pageCall, ['admin', 'account', 'profile'])) @@ -134,7 +146,8 @@ switch ($pageCall) case 'build': if (User::isInGroup(U_GROUP_EMPLOYEE)) { - require 'setup/tools/filegen/scriptGen.php'; + define('TMP_BUILD', 1); // todo (med): needs better solution + require 'setup/setup.php'; break; } case 'sql': @@ -143,12 +156,6 @@ switch ($pageCall) require 'setup/tools/database/_'.$pageParam.'.php'; break; } - case 'setup': - if (User::isInGroup(U_GROUP_EMPLOYEE)) - { - require 'setup/syncronize.php'; - break; - } default: // unk parameter given -> ErrorPage if (isset($_GET['power'])) die('$WowheadPower.register(0, '.User::$localeId.', {})'); diff --git a/pages/spell.php b/pages/spell.php index 8d0a4eb9..9d2ae70c 100644 --- a/pages/spell.php +++ b/pages/spell.php @@ -221,6 +221,19 @@ class SpellPage extends GenericPage if ($id == $this->typeId) // "Mode" seems to be multilingual acceptable $infobox[] = '[li]Mode'.Lang::$main['colon'].Lang::$game['modes'][$n].'[/li]'; + $effects = $this->createEffects($infobox, $redButtons); + $infobox = $infobox ? '[ul]'.implode('', $infobox).'[/ul]' : ''; + + // append glyph symbol if available + $glyphId = 0; + for ($i = 1; $i < 4; $i++) + if ($this->subject->getField('effect'.$i.'Id') == 74) + $glyphId = $this->subject->getField('effect'.$i.'MiscValue'); + + if ($_ = DB::Aowow()->selectCell('SELECT si.iconString FROM ?_glyphproperties gp JOIN ?_spellicon si ON gp.iconId = si.id WHERE gp.spellId = ?d { OR gp.id = ?d }', $this->typeId, $glyphId ?: DBSIMPLE_SKIP)) + if (file_exists('static/images/wow/interface/Spellbook/'.$_.'.png')) + $infobox .= '[img src='.STATIC_URL.'/images/wow/interface/Spellbook/'.$_.'.png border=0 float=center margin=15]'; + /****************/ /* Main Content */ @@ -236,8 +249,8 @@ class SpellPage extends GenericPage $this->scaling = $this->createScalingData(); $this->items = $this->createRequiredItems(); $this->tools = $this->createTools(); - $this->effects = $this->createEffects($infobox, $redButtons); - $this->infobox = $infobox ? '[ul]'.implode('', $infobox).'[/ul]' : null; + $this->effects = $effects; + $this->infobox = $infobox; $this->powerCost = $this->subject->createPowerCostForCurrent(); $this->castTime = $this->subject->createCastTimeForCurrent(false, false); $this->name = $this->subject->getField('name', true); @@ -934,8 +947,8 @@ class SpellPage extends GenericPage 'params' => array( 'id' => 'teaches-spell', 'name' => '$LANG.tab_teaches', - 'visibleCols' => '$'.json_encode($vis), - 'hiddenCols' => $hid ? '$'.json_encode($hid) : null + 'visibleCols' => '$'.Util::toJSON($vis), + 'hiddenCols' => $hid ? '$'.Util::toJSON($hid) : null ) ); } @@ -1087,7 +1100,7 @@ class SpellPage extends GenericPage { $this->extendGlobalData($sc[1]); $tab = ""; @@ -1116,12 +1129,12 @@ class SpellPage extends GenericPage if ($tt = $this->subject->renderTooltip()) { $pt[] = "\ttooltip_".User::$localeString.": '".Util::jsEscape($tt[0])."'"; - $pt[] = "\tspells_".User::$localeString.": ".json_encode($tt[1], JSON_UNESCAPED_UNICODE); + $pt[] = "\tspells_".User::$localeString.": ".Util::toJSON($tt[1]); } if ($btt = $this->subject->renderBuff()) { $pt[] = "\tbuff_".User::$localeString.": '".Util::jsEscape($btt[0])."'"; - $pt[] = "\tbuffspells_".User::$localeString.": ".json_encode($btt[1], JSON_UNESCAPED_UNICODE);; + $pt[] = "\tbuffspells_".User::$localeString.": ".Util::toJSON($btt[1]);; } $x .= implode(",\n", $pt)."\n});"; diff --git a/setup/db_setup_3.zip b/setup/db_setup_3.zip index 917a9d6e..94d0a701 100644 Binary files a/setup/db_setup_3.zip and b/setup/db_setup_3.zip differ diff --git a/setup/setup.php b/setup/setup.php new file mode 100644 index 00000000..9015024d --- /dev/null +++ b/setup/setup.php @@ -0,0 +1,376 @@ + 3) + return 'null'; + + $iterator = new RecursiveDirectoryIterator($dir); + $iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS); + + $basePath = ''; + foreach ($iterator as $path) + { + $path = $path->getPathname(); + if (!is_dir($path) || $path[0] == '.') // also skip hidden dirs + continue; + + $idx++; + $newParent = $parent; + $newParent[] = basename($path); + $struct[] = "[".$idx.", \"".basename($path)."\", setPath.bind(this, el, '/".implode($newParent, '/')."'), ".buildDirStruct($path, $idx, $newParent, $depths + 1)."]"; + } + + return empty($struct) ? 'null' : '['.implode($struct, ",").']'; +} + +function checkDbcDir($dir, $reqFiles) { + $handle = opendir($dir); + $content = array(); + + while (false !== ($entry = readdir($handle))) { + if (is_dir($dir.'\\'.$entry)) + continue; + + $file = explode('.', $entry); + if ($file[1] == 'dbc') + $content[] = strToLower($file[0]); + } + + + if (empty($content)) + return array(-4, null); // arbitrary directory .. silent death + + foreach ($reqFiles as $k => $v) { + if (in_array(strToLower($v), $content)) + unset($reqFiles[$k]); + } + + if (empty($reqFiles)) { + $f = fopen($dir.'\\Resistances.dbc', 'rb'); + + + if (fread($f, 4) != "WDBC" || filesize($dir.'\\Resistances.dbc') < 20) + return array(-1, 'File looks like DBC but is not in proper format!'); + + $parse = dbc2array($dir.'\\Resistances.dbc', "xxxsssssssssxxxxxxxx"); + + for ($i = 0; $i <= 8; $i++) { + if (empty($parse[0][$i])) + continue; + + if (in_array($i, array(0, 2, 3, 6, 8))) // en, X, fr, de, X, X, es, ru + return array($i, count($content)); + else + return array(-2, 'locale ":$i." not supported!'); + } + } + $path = array_pop(explode('\\', $dir)); + return array(-3, 'Requird files are missing!', '
- '.str_replace($cwDir, '', $path).'\\'.implode($reqFiles, '.dbc
- '.str_replace($cwDir, '', $path).'\\').'.dbc
'); +} + +if (CLI || defined('TMP_BUILD')) +{ + require_once 'tools/filegen/fileGen.class.php'; + require_once 'tools/dbc.class.php'; + require_once 'tools/imagecreatefromblp.php'; + + FileGen::init(@$pageParam ?: ''); + + if (FileGen::$subScripts) + { + // start file generation + FileGen::status('begin generation of '. implode(', ', FileGen::$subScripts)); + FileGen::status(); + + // files with template + foreach (FileGen::$tplFiles as $name => list($file, $destPath)) + { + if (!in_array($name, FileGen::$subScripts)) + continue; + + if (!file_exists(FileGen::$tplPath.$file.'.in')) + { + FileGen::status(sprintf(ERR_MISSING_FILE, FileGen::$tplPath.$file.'.in'), MSG_LVL_ERROR); + continue; + } + + if (!FileGen::writeDir($destPath)) + continue; + + if ($content = file_get_contents(FileGen::$tplPath.$file.'.in')) + { + if ($dest = @fOpen($destPath.$file, "w")) + { + // replace constants + $content = strtr($content, FileGen::$txtConstants); + + // must generate content + // PH format: /*setup:*/ + if (preg_match('/\/\*setup:([\w\d_-]+)\*\//i', $content, $m)) + { + if (file_exists('setup/tools/filegen/'.$m[1].'.func.php')) + require_once 'setup/tools/filegen/'.$m[1].'.func.php'; + else + { + FileGen::status(sprintf(ERR_MISSING_INCL, $m[1], 'setup/tools/filegen/'.$m[1].'.func.php'), MSG_LVL_ERROR); + continue; + } + + if (function_exists($m[1])) + $content = str_replace('/*setup:'.$m[1].'*/', $m[1](), $content); + else + { + $content = ''; + FileGen::status('Placeholder in template file does not match any known function name.', MSG_LVL_ERROR); + } + } + + if (fWrite($dest, $content)) + FileGen::status(sprintf(ERR_NONE, $destPath.$file), MSG_LVL_OK); + else + FileGen::status(sprintf(ERR_WRITE_FILE, $destPath.$file, MSG_LVL_ERROR)); + + fClose($dest); + } + else + FileGen::status(sprintf(ERR_CREATE_FILE, $destPath.$file), MSG_LVL_ERROR); + } + else + FileGen::status(sprintf(ERR_READ_FILE, FileGen::$tplPath.$file.'.in'), MSG_LVL_ERROR); + } + + // files without template + foreach (FileGen::$datasets as $file) + { + if (!in_array($file, FileGen::$subScripts)) + continue; + + if (file_exists('setup/tools/filegen/'.$file.'.func.php')) + { + require_once 'setup/tools/filegen/'.$file.'.func.php'; + + if (function_exists($file)) + FileGen::status(' - subscript \''.$file.'\' returned '.($file() ? 'sucessfully' : 'with errors')); + else + FileGen::status(' - subscript \''.$file.'\' not defined in included file', MSG_LVL_ERROR); + + set_time_limit(FileGen::$defaultExecTime); // reset to default for the next script + } + else + FileGen::status(sprintf(ERR_MISSING_INCL, $file, 'setup/tools/filegen/'.$file.'.func.php', MSG_LVL_ERROR)); + } + + + // end + FileGen::status(); + FileGen::status('finished file generation'); + } + else + FileGen::status('no valid script names supplied'); +} +/* +else +{ + if (isset($_GET['pathMenu'])) { + // set_time_limit(240); // parsing directory-structures seems to be costy... + die(buildDirStruct($cwDir.'/setup', $c)); + } + + $step = @intVal($_GET['step']); + $fields = @explode(';', $_GET['fields']); + + if ($step == 1) { + // unset saved credentials + $_SESSION['step1']['progress'] &= ~(1 << $fields[0]); + $_SESSION['step1'][$fields[0]] = array($fields[1], $fields[3], $fields[4], $fields[2]); + + // try to connect to db with data provided + $link = @mysql_connect($fields[1], $fields[3], $fields[4], true); + if ($link) { + switch ($fields[0]) { + case 0: + if (mysql_select_db($fields[2], $link)) { + if (mysql_fetch_row(mysql_query("SHOW TABLES FROM ".$fields[2]." LIKE '".$fields[5]."glyphpropperties'"))) + die('{"errno":-1, "errstr":"Tables already present in this database will be overwritten!"}'); + else { + $_SESSION['step1']['progress'] |= 0x1; + die('{"errno":0, "errstr":""}'); + } + } + else if (mysql_errno() == 1044) // why doesn't this occur earlier? + die('{"errno":'.mysql_errno().', "errstr":"'.mysql_error().'"}'); + else { + $_SESSION['step1']['progress'] |= 0x1; + die('{"errno":-1, "errstr":"Database will be created during installation!"}'); + } + case 1: + if (mysql_select_db($fields[2], $link)) { + if (mysql_fetch_row(mysql_query("SHOW TABLES FROM ".$fields[2]." LIKE '".$fields[5]."item_template'"))) { + $_SESSION['step1']['progress'] |= 0x2; + die('{"errno":0, "errstr":""}'); + } + } + break; + case 2: + if (mysql_select_db($fields[2], $link)) { + if (mysql_fetch_row(mysql_query("SHOW TABLES FROM ".$fields[2]." LIKE '".$fields[5]."account'"))) { + $_SESSION['step1']['progress'] |= 0x4; + die('{"errno":0, "errstr":""}'); + } + } + break; + case 3: + if (mysql_select_db($fields[2], $link)) { + if (mysql_fetch_row(mysql_query("SHOW TABLES FROM ".$fields[2]." LIKE '".$fields[5]."characters'"))) { + $_SESSION['step1']['progress'] |= 0x8; + die('{"errno":0, "errstr":""}'); + } + } + break; + } + if(!mysql_errno()) + die('{"errno":-1, "errstr":"Required table not found in selected database!"}'); + else + die('{"errno":'.mysql_errno().', "errstr":"'.mysql_error().'"}'); + } + else + die('{"errno":'.mysql_errno().', "errstr":"'.mysql_error().'"}'); + + } + else if ($step == 2) { + $final = array(); + + // sanitize .. clamp dir-choice to DOCUMENT_ROOT + $dir = $cwDir.'/setup'.str_replace(['/..', '/.', '../', './'], '', str_replace(',', '/', $fields[1])); + + // unset saved credentials + $_SESSION['step2']['progress'] &= ~(1 << $fields[0]); + $_SESSION['step2'][$fields[0]] = $dir; + + if (!is_dir($dir)) + die(json_encode(array(str_replace($cwDir, '', $dir) => array("errno" => 5, "errstr" => "Not a directory!")))); + + $handle = opendir($dir); + + switch ($fields[0]) { + case 0: { + $reqDBCs = array_keys($dbcStructure); + // check this directory + $result = checkDbcDir($dir, $reqDBCs); + if ($result[0] < 0 && isset($result[1])) + $final[str_replace($cwDir, '', $dir)] = array('errno' => -$result[0], 'errstr' => $result[1], 'tooltip' => $result[2]); + else if ($result[0] >= 0) { + if ($result[1] == 246) + $final[str_replace($cwDir, '', $dir)] = array('locale' => $result[0], 'errno' => 0, 'errstr' => ''); + else + $final[str_replace($cwDir, '', $dir)] = array('locale' => $result[0], 'errno' => -1, 'errstr' => (246 - $result[1]).' optional files missing.'); + } + + // check first-level child direcotries + while (false !== ($entry = readdir($handle))) { + if ($entry == "." || $entry == "..") + continue; + + if (is_dir($dir.'\\'.$entry) === true) { + $result = checkDbcDir($dir.'\\'.$entry, $reqDBCs); + + if ($result[0] < 0 && isset($result[1])) + $final[$entry] = array('errno' => -$result[0], 'errstr' => $result[1], 'tooltip' => $result[2]); + else if ($result[0] >= 0) { + if ($result[1] == 246) + $final[$entry] = array('locale' => $result[0], 'errno' => 0, 'errstr' => ''); + else + $final[$entry] = array('locale' => $result[0], 'errno' => -1, 'errstr' => (246 - $result[1]).' optional files missing.'); + } + } + } + foreach ($final as $v) + if ($v['errno'] <= 0) + $_SESSION['step2']['progress'] |= 0x1; + + die(json_encode($final)); + } + case 1: { + $loc = array('enUS' => 0, 'enGB' => 0, 'frFR' => 2, 'deDE' => 3, 'esES' => 6, 'esMX' => 6, 'ruRU' => 8); + $expectDir = array('Icons' => array(6308, "/[\w\d\_]+[\.tga|\s|\.]?.blp/i"), 'Spellbook' => array(20, "/UI\-Glyph\-Rune\-[0-9]+\.blp/i"), 'Worldmap' => array(117)); + + foreach ($loc as $k => $v) { + if (!is_dir($dir.'\\'.$k)) + continue; + + if (isset($final[$v])) + continue; + + $final[$v] = array(); + $j = 0; + foreach ($expectDir as $sk => $sv) { + if (!is_dir($dir.'\\'.$k.'\\'.$sk)) + break; + + $handle = opendir($dir.'\\'.$k.'\\'.$sk); + $i = 0; + while (false !== ($entry = readdir($handle))) { + if (isset($sv[1])) { + if (is_dir($dir.'\\'.$k.'\\'.$sk.'\\'.$entry) === true) + continue; + + if (preg_match($sv[1], $entry, $result)) + $i++; + } + else { + if (is_dir($dir.'\\'.$k.'\\'.$sk.'\\'.$entry) === false || $entry == '.' || $entry == '..') + continue; + + $i++; + } + } + if ($i == $sv[0]) { + $final[$v][$sk] = array('errno' => 0, 'errstr' => ''); + $final['total'] |= (1 << $j); + } + else + $final[$v][$sk] = array('errno' => 1, 'errstr' => ($sv[0] - $i).' files missing in '.$k.'\\'.$sk.'!'); + + $j++; + } + if (empty($final[$v])) + $final[$v] = array('errno' => 3, 'errstr' => 'locale directory '.$k.' is empty!'); + } + if ($final['total'] == 0x7) + $_SESSION['step2']['progress'] |= 0x2; + + die(json_encode($final)); + } + case 2: { + while (false !== ($entry = readdir($handle))) { + if (is_dir($entry) === true) + continue; + + $file = explode('.', $entry); + if ($file[1] != 'sql') + continue; + + $fRes = fopen($dir."\\".$entry, 'rb'); + if (preg_match('/\/\* AoWoW 3\.3\.5 (en|fr|de|es|ru)[a-z]{2} locales \*\//i', fread($fRes, 30), $result)) + $final[$result[1]] = str_replace($cwDir, '', $dir).'\\'.$entry; + } + if (!empty($final)) + $_SESSION['step2']['progress'] |= 0x4; + + die(json_encode($final)); + } + } + } + + include 'setup.tpl.php'; +} +*/ + + +?> diff --git a/setup/tools/dbc.class.php b/setup/tools/dbc.class.php new file mode 100644 index 00000000..72a72573 --- /dev/null +++ b/setup/tools/dbc.class.php @@ -0,0 +1,390 @@ + + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +if (!defined('AOWOW_REVISION')) + die('illegal access'); + + +class DBC +{ + private $_formats = array( + 'talent' => 'niiiiiiiixxxxixxixxixii', + 'talenttab' => 'nsxssxxsxsxxxxxxxxiiiiis', + 'gtchancetomeleecrit' => 'f', + 'gtchancetomeleecritbase' => 'f', + 'gtchancetospellcrit' => 'f', + 'gtchancetospellcritbase' => 'f', + 'gtoctregenhp' => 'f', + 'gtregenmpperspt' => 'f', + 'gtregenhpperspt' => 'f', + 'spellicon' => 'ns', + 'itemdisplayinfo' => 'nssxxsxxxxxxxxxxxxxxxxxxx', + 'holidays' => 'nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixxxxxxxxxxiisxix', + 'chrclasses' => 'nxixsxssxxsxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxsxixi', + 'worldmaparea' => 'niisffffxix', // 4.x - niisffffxixxxx + 'worldmapoverlay' => 'niixxxxxsiiiixxxx', // 4.x - niixxxsiiiixxxx + ); + private $_fields = array( + 'talent' => 'Id,tabId,row,column,rank1,rank2,rank3,rank4,rank5,reqTalent,reqRank,talentSpell,petCategory1,petCategory2', + 'talenttab' => 'Id,nameEN,nameFR,nameDE,nameES,nameRU,iconId,raceMask,classMask,creatureFamilyMask,tabNumber,textureFile', + 'gtchancetomeleecrit' => 'chance', + 'gtchancetomeleecritbase' => 'chance', + 'gtchancetospellcrit' => 'chance', + 'gtchancetospellcritbase' => 'chance', + 'gtoctregenhp' => 'ratio', + 'gtregenmpperspt' => 'ratio', + 'gtregenhpperspt' => 'ratio', + 'spellicon' => 'Id,iconPath', + 'itemdisplayinfo' => 'Id,leftModelName,rightModelName,inventoryIcon1', + 'holidays' => 'Id,looping,nameId,descriptionId,textureString,scheduleType', + 'chrclasses' => 'Id,powerType,nameMaleEN,nameMaleFR,nameMaleDE,nameMaleES,nameMaleRU,nameINT,flags,addon', + 'worldmaparea' => 'Id,mapId,areaId,nameINT,left,right,top,bottom,defaultDungeonMapId', + 'worldmapoverlay' => 'Id,worldMapAreaId,areaTableId,textureString,w,h,x,y', + ); + + private $isGameTable = false; + + public $result = []; + public $fields = []; + public $format = ''; + public $file = ''; + + public function __construct($file) + { + $file = strtolower($file); + if (empty($this->_fields[$file]) || empty($this->_formats[$file])) + { + FileGen::status('no structure known for '.$file.'.dbc, aborting.', MSG_LVL_ERROR); + return; + } + + $this->fields = explode(',', $this->_fields[$file]); + $this->format = $this->_formats[$file]; + $this->file = $file; + + // gameTable-DBCs don't have an index and are accessed through value order + // allas, you cannot do this with mysql, so we add a 'virtual' index + $this->isGameTable = $this->format == 'f' && substr($file, 0, 2) == 'gt'; + } + + public function writeToDB() + { + if (!$this->result) + return false; + + $n = 0; + $pKey = $this->fields[0]; + $query = 'CREATE TABLE `dbc_'.$this->file.'` ('; + + if ($this->isGameTable) + { + $query .= '`idx` BIGINT(20) NOT NULL, '; + $pKey = 'idx'; + } + + foreach (str_split($this->format) as $idx => $f) + { + if ($f == 'f') + $query .= '`'.$this->fields[$n].'` FLOAT NOT NULL, '; + else if ($f == 's' || $f == 'b') + $query .= '`'.$this->fields[$n].'` TEXT NOT NULL, '; + else if ($f == 'i' || $f == 'n') + $query .= '`'.$this->fields[$n].'` BIGINT(20) NOT NULL, '; + + if ($f == 'n') + $pKey = $this->fields[$n]; + + if ($f != 'x') + $n++; + } + $query .= 'PRIMARY KEY (`'.$pKey.'`)) COLLATE=\'utf8_general_ci\' ENGINE=MyISAM'; + + DB::Aowow()->query('DROP TABLE IF EXISTS ?#', 'dbc_'.$this->file); + DB::Aowow()->query($query); + + // make inserts more manageable + $offset = 0; + $limit = 1000; + $fields = $this->fields; + + if ($this->isGameTable) + array_unshift($fields, 'idx'); + + while (($offset * $limit) < count($this->result)) + DB::Aowow()->query('INSERT INTO ?# (?#) VALUES (?a)', 'dbc_'.$this->file, $fields, array_slice($this->result, $offset++ * $limit, $limit)); + + return true; + } + + public function readFiltered(Closure $filterFunc = null, $localized = false, $safeIf = true) + { + $result = $this->readArbitrary($localized, $safeIf); + + if (is_object($filterFunc)) + foreach ($result as $key => &$val) + if (!$filterFunc($val, $key)) + unset($result[$key]); + + return $result; + } + + public function readArbitrary($localized = false, $safeIf = true) + { + // try DB first + if (!$this->result) + $this->readFromDB(); + + // try file second + if (!$this->result) + if ($this->readFromFile($localized) && $safeIf) + $this->writeToDB(); + + return $this->getIndexed(); + } + + public function readFromDB() + { + if (!DB::Aowow()->selectCell('SHOW TABLES LIKE ?', 'dbc_'.$this->file)) + return []; + + $key = strstr($this->format, 'n') ? $this->fields[strpos($this->format, 'n')] : ''; + + $this->result = DB::Aowow()->select('SELECT '.($key ? 'tbl.`'.$key.'` AS ARRAY_KEY, ' : '').'tbl.* FROM ?# tbl', 'dbc_'.$this->file); + + return $this->result; + } + + public function readFromFile($localized = false) + { + if (!$this->file) + return []; + + $foundMask = 0x0; + foreach (FileGen::$expectedPaths as $locStr => $locId) + { + if ($foundMask & (1 << $locId)) + continue; + + $fullpath = FileGen::$srcDir.($locStr ? $locStr.'/' : '').'DBFilesClient/'.$this->file.'.dbc'; + if (!FileGen::fileExists($fullpath)) + continue; + + FileGen::status(' - reading '.($localized ? 'and merging ' : '').'data from '.$fullpath); + + if (!$this->read($fullpath, $localized)) + FileGen::status(' - DBC::read() returned with error', MSG_LVL_ERROR); + else + $foundMask |= (1 << $locId); + + if (!$localized) // one match is enough + break; + } + + return $this->getIndexed(); + } + + /* + Convert DBC file content into a 2-dimentional array + $filename - name of the file + $format - format string, that contains 1 character for each field + Supported format characters: + x - not used/unknown, 4 bytes + X - not used/unknown, 1 byte + s - char* + f - float, 4 bytes (rounded to 4 digits after comma) + i - unsigned int, 4 bytes + b - unsigned char, 1 byte + d - sorted by this field, not included in array + n - same, but field included in array + */ + private function read($filename, $mergeStrings = false) + { + $file = fopen($filename, 'rb'); + + if (!$file) + { + FileGen::status('cannot open file '.$filename, MSG_LVL_ERROR); + return false; + } + + $filesize = filesize($filename); + if ($filesize < 20) + { + FileGen::status('file '.$filename.' is too small for a DBC file', MSG_LVL_ERROR); + return false; + } + + if (fread($file, 4) != 'WDBC') + { + FileGen::status('file '.$filename.' has incorrect magic bytes', MSG_LVL_ERROR); + return false; + } + + $header = unpack('VrecordCount/VfieldCount/VrecordSize/VstringSize', fread($file, 16)); + + // Different debug checks to be sure, that file was opened correctly + $debugStr = '(recordCount='.$header['recordCount']. + ' fieldCount=' .$header['fieldCount'] . + ' recordSize=' .$header['recordSize'] . + ' stringSize=' .$header['stringSize'] .')'; + + if ($header['recordCount'] * $header['recordSize'] + $header['stringSize'] + 20 != $filesize) + { + FileGen::status('file '.$filename.' has incorrect size '.$filesize.': '.$debugstr, MSG_LVL_ERROR); + return false; + } + + if ($header['fieldCount'] != strlen($this->format)) + { + FileGen::status('incorrect format string ('.$this->format.') specified for file '.$filename.' fieldCount='.$header['fieldCount'], MSG_LVL_ERROR); + return false; + } + + $unpackStr = ''; + $unpackFmt = array( + 'x' => 'x/x/x/x', + 'X' => 'x', + 's' => 'V', + 'f' => 'f', + 'i' => 'V', + 'b' => 'C', + 'd' => 'x4', + 'n' => 'V' + ); + + // Check that record size also matches + $recSize = 0; + for ($i = 0; $i < strlen($this->format); $i++) + { + $ch = $this->format[$i]; + if ($ch == 'X' || $ch == 'b') + $recSize += 1; + else + $recSize += 4; + + if (!isset($unpackFmt[$ch])) + { + FileGen::status('unknown format parameter \''.$ch.'\' in format string', MSG_LVL_ERROR); + return false; + } + + $unpackStr .= '/'.$unpackFmt[$ch]; + + if ($ch != 'X' && $ch != 'x') + $unpackStr .= 'f'.$i; + } + + $unpackStr = substr($unpackStr, 1); + + // Optimizing unpack string: 'x/x/x/x/x/x' => 'x6' + while (preg_match('/(x\/)+x/', $unpackStr, $r)) + $unpackStr = substr_replace($unpackStr, 'x'.((strlen($r[0]) + 1) / 2), strpos($unpackStr, $r[0]), strlen($r[0])); + + // The last debug check (most of the code in this function is for debug checks) + if ($recSize != $header['recordSize']) + { + FileGen::status('format string size ('.$recSize.') for file '.$filename.' does not match actual size ('.$header['recordSize'].') '.$debugstr, MSG_LVL_ERROR); + return false; + } + + // Cache the data to make it faster + $data = fread($file, $header['recordCount'] * $header['recordSize']); + $strings = fread($file, $header['stringSize']); + fclose($file); + + // And, finally, extract the records + $cache = []; + $rSize = $header['recordSize']; + $rCount = $header['recordCount']; + $fCount = strlen($this->format); + + for ($i = 0; $i < $rCount; $i++) + { + $row = []; + $idx = $i; + $record = unpack($unpackStr, substr($data, $i * $rSize, $rSize)); + + // add 'virtual' enumerator for gt*-dbcs + if ($this->isGameTable) + $row[] = $i; + + for ($j = 0; $j < $fCount; $j++) + { + if (!isset($record['f'.$j])) + continue; + + $value = $record['f'.$j]; + if ($this->format[$j] == 's') + { + if (isset($cache[$value])) + $value = $cache[$value]; + else + { + $s = substr($strings, $value); + $s = substr($s, 0, strpos($s, "\000")); + $cache[$value] = $s; + $value = $s; + } + } + else if ($this->format[$j] == 'f') + $value = round($value, 8); + + $row[] = $value; + + if ($this->format[$j] == 'n') + $idx = $value; + } + + if (!$mergeStrings || empty($this->result[$idx])) + $this->result[$idx] = $row; + else + { + $n = 0; + for ($j = 0; $j < $fCount; $j++) + { + if ($this->format[$j] == 's') + if (!$this->result[$idx][$n] && $row[$n]) + $this->result[$idx][$n] = $row[$n]; + + if ($this->format[$j] != 'x') + $n++; + } + } + } + + return !empty($this->result); + } + + private function getIndexed() + { + $result = $this->result; + $fields = $this->fields; + if ($this->isGameTable) + array_unshift($fields, 'idx'); + + foreach ($result as &$row) + $row = array_combine($fields, $row); + + return $result; + } +} + +?> diff --git a/setup/tools/filegen/complexImg.func.php b/setup/tools/filegen/complexImg.func.php new file mode 100644 index 00000000..4674c3d3 --- /dev/null +++ b/setup/tools/filegen/complexImg.func.php @@ -0,0 +1,729 @@ + + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +if (!defined('AOWOW_REVISION')) + die('illegal access'); + + // note: for the sake of simplicity, this function handles all images, that must be stitched together (which are mostly maps) + function complexImg() + { + if (isset(FileGen::$cliOpts['help'])) + { + echo "\n"; + echo "available options for subScript 'complexImg':\n"; // modeMask + echo "--talentbgs (backgrounds for talent calculator)\n"; // 0x01 + echo "--maps (generates worldmaps)\n"; // 0x02 + echo "--spawn-maps (creates alphaMasks of each zone to check spawns against)\n"; // 0x04 + echo "--artwork (optional: imagery from /glues/credits (not used, skipped by default))\n"; // 0x08 + echo "--area-maps (optional: renders maps with highlighted subZones for each area)\n"; // 0x10 + + return true; + } + + if (!class_exists('DBC')) + { + FileGen::status(' - simpleImg: required class DBC was not included', MSG_LVL_ERROR); + return false; + } + + if (!function_exists('imagecreatefromblp')) + { + FileGen::status(' - complexImg: required include imagecreatefromblp() was not included', MSG_LVL_ERROR); + return false; + } + + $mapWidth = 1002; + $mapHeight = 668; + $runTime = ini_get('max_execution_time'); + $locStr = null; + $dbcPath = FileGen::$srcDir.'%sDBFilesClient/'; + $imgPath = FileGen::$srcDir.'%sInterface/'; + $destDir = 'static/images/wow/'; + $success = true; + $paths = ['WorldMap/', 'TalentFrame/', 'Glues/Credits/']; + $modeMask = 0x7; // talentBGs, regular maps, spawn-related alphaMaps + + $createAlphaImage = function($w, $h) + { + $img = imagecreatetruecolor($w, $h); + + imagesavealpha($img, true); + imagealphablending($img, false); + + $bgColor = imagecolorallocatealpha($img, 0, 0, 0, 127); + imagefilledrectangle($img, 0, 0, imagesx($img) - 1, imagesy($img) - 1, $bgColor); + imagecolordeallocate($img, $bgColor); + + imagealphablending($img, true); + + return $img; + }; + + // prefer manually converted PNG files (as the imagecreatefromblp-script has issues with some formats) + // alpha channel issues observed with locale deDE Hilsbrad and Elwynn - maps + // see: https://github.com/Kanma/BLPConverter + $loadImageFile = function($path) + { + $result = null; + + $file = $path.'.png'; + if (FileGen::fileExists($file)) + $result = imagecreatefrompng($file); + + if (!$result) + { + $file = $path.'.blp'; + if (FileGen::fileExists($file)) + $result = imagecreatefromblp($file); + } + + return $result; + }; + + $assembleImage = function($baseName, $order, $w, $h) use ($loadImageFile) + { + $dest = imagecreatetruecolor($w, $h); + imagesavealpha($dest, true); + imagealphablending($dest, false); + + $_h = $h; + foreach ($order as $y => $row) + { + $_w = $w; + foreach ($row as $x => $suffix) + { + $src = $loadImageFile($baseName.$suffix); + if (!$src) + { + FileGen::status(' - complexImg: tile '.$baseName.$suffix.'.blp missing.', MSG_LVL_ERROR); + unset($dest); + return null; + } + + imagecopyresampled($dest, $src, 256 * $x, 256 * $y, 0, 0, min($_w, 256), min($_h, 256), min($_w, 256), min($_h, 256)); + $_w -= 256; + + unset($src); + } + $_h -= 256; + } + + return $dest; + }; + + $writeImage = function($name, $ext, $src, $w, $h, $done) + { + $ok = false; + $dest = imagecreatetruecolor($w, $h); + imagesavealpha($dest, true); + imagealphablending($dest, false); + imagecopyresampled($dest, $src, 0, 0, 0, 0, $w, $h, imagesx($src), imagesy($src)); + + switch ($ext) + { + case 'jpg': + $ok = imagejpeg($dest, $name.'.'.$ext, 85); + break; + case 'png': + $ok = imagepng($dest, $name.'.'.$ext); + break; + default: + FileGen::status($done.' - unsupported file fromat: '.$ext, MSG_LVL_WARN); + } + + imagedestroy($dest); + + if ($ok) + { + chmod($name.'.'.$ext, FileGen::$accessMask); + FileGen::status($done.' - image '.$name.'.'.$ext.' written', MSG_LVL_OK); + } + else + FileGen::status($done.' - could not create image '.$name.'.'.$ext, MSG_LVL_ERROR); + + return $ok; + }; + + $createSpawnMap = function($img, $zoneId) use ($mapHeight, $mapWidth) + { + FileGen::status(' - creating spawn map'); + + $tmp = imagecreate(1000, 1000); + $cbg = imagecolorallocate($tmp, 255, 255, 255); + $cfg = imagecolorallocate($tmp, 0, 0, 0); + + for ($y = 0; $y < 1000; $y++) + { + for ($x = 0; $x < 1000; $x++) + { + $a = imagecolorat($img, ($x * $mapWidth) / 1000, ($y * $mapHeight) / 1000) >> 24; + imagesetpixel($tmp, $x, $y, $a < 30 ? $cfg : $cbg); + } + } + + imagepng($tmp, 'cache/alphaMaps/' . $zoneId . '.png'); + + imagecolordeallocate($tmp, $cbg); + imagecolordeallocate($tmp, $cfg); + imagedestroy($tmp); + }; + + $checkSourceDirs = function($sub, &$missing = []) use ($imgPath, $dbcPath, $paths, &$modeMask) + { + $incomplete = false; + foreach ($paths as $idx => $subDir) + { + if ($idx == 0 && !($modeMask & 0x16)) // map related + continue; + else if ($idx == 1 && !($modeMask & 0x1)) // talentBGs + continue; + else if ($idx == 2 && !($modeMask & 0x8)) // artwork + continue; + + $p = sprintf($imgPath, $sub).$subDir; + if (!FileGen::fileExists($p)) + { + $missing[] = $p; + $incomplete = true; + } + } + + if ($modeMask & 0x17) + { + $p = sprintf($dbcPath, $sub); + if (!FileGen::fileExists($p)) + { + $missing[] = $p; + $incomplete = true; + } + } + + return !$incomplete; + }; + + + // do not change order of params! + if ($_ = FileGen::hasOpt('talentbgs', 'maps', 'spawn-maps', 'artwork', 'area-maps')) + $modeMask = $_; + + foreach (FileGen::$expectedPaths as $xp => $__) + { + if ($xp) // if sun subDir add trailing slash + $xp .= '/'; + + if ($checkSourceDirs($xp, $missing)) + { + $locStr = $xp; + break; + } + } + + // if no subdir had sufficient data, diaf + if ($locStr === null) + { + FileGen::status('one or more required directories are missing:', MSG_LVL_ERROR); + foreach ($missing as $m) + FileGen::status(' - '.$m, MSG_LVL_ERROR); + + return; + } + + + /**************/ + /* TalentTabs */ + /**************/ + + if ($modeMask & 0x01) + { + if (FileGen::writeDir($destDir.'hunterpettalents/') && FileGen::writeDir($destDir.'talents/backgrounds/')) + { + // [classMask, creatureFamilyMask, tabNr, textureStr] + $talentTab = (new DBC('TalentTab'))->readArbitrary(); + $chrClass = (new DBC('ChrClasses'))->readArbitrary(); + $order = array( + ['-TopLeft', '-TopRight'], + ['-BottomLeft', '-BottomRight'] + ); + + if ($chrClass && $talentTab) + { + $sum = 0; + $total = count($talentTab); + FileGen::status('Processing '.$total.' files from TalentFrame/ ...'); + + foreach ($talentTab as $tt) + { + ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time) + $sum++; + $done = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9); + + if ($tt['creatureFamilyMask']) // is PetCalc + { + $size = [244, 364]; + $name = $destDir.'hunterpettalents/bg_'.(log($tt['creatureFamilyMask'], 2) + 1); + } + else + { + $size = [204, 554]; + $name = $destDir.'talents/backgrounds/'.strtolower($chrClass[log($tt['classMask'], 2) + 1]['nameINT']).'_'.($tt['tabNumber'] + 1); + + } + + if (!isset(FileGen::$cliOpts['force']) && file_exists($name.'.jpg')) + { + FileGen::status($done.' - file '.$name.'.jpg was already processed'); + continue; + } + + $im = $assembleImage(sprintf($imgPath, $locStr).'TalentFrame/'.$tt['textureFile'], $order, 256 + 44, 256 + 75); + if (!$im) + { + FileGen::status(' - could not assemble file '.$tt['textureFile'], MSG_LVL_ERROR); + continue; + } + + if (!$writeImage($name, 'jpg', $im, $size[0], $size[1], $done)) + $success = false; + } + } + else + $success = false; + + ini_set('max_execution_time', $runTime); + } + else + $success = false; + } + + /************/ + /* Worldmap */ + /************/ + + if ($modeMask & 0x16) + { + $mapDirs = array( + ['maps/%snormal/', 'jpg', 488, 325], + ['maps/%soriginal/', 'jpg', 0, 0], // 1002, 668 + ['maps/%ssmall/', 'jpg', 224, 163], + ['maps/%szoom/', 'jpg', 772, 515] + ); + + // as the js expects them + $baseLevelFix = array( + // WotLK maps + // Halls of Stone; The Nexus; Violet Hold; Gundrak; Obsidian Sanctum; Eye of Eternity; Vault of Archavon; Trial of the Champion; The Forge of Souls; Pit of Saron; Halls of Reflection + 4264 => 1, 4265 => 1, 4415 => 1, 4416 => 1, 4493 => 0, 4500 => 1, 4603 => 1, 4723 => 1, 4809 => 1, 4813 => 1, 4820 => 1, + // Cata maps for WotLK instances + // TheStockade; TheBloodFurnace; Ragefire; TheUnderbog; TheBotanica; WailingCaverns; TheSlavePens; TheShatteredHalls; HellfireRamparts; RazorfenDowns; RazorfenKraul; ManaTombs + // ShadowLabyrinth; TheTempleOfAtalHakkar (simplified layout); BlackTemple; TempestKeep; MoltenCore; GruulsLair; CoilfangReservoir; MagtheridonsLair; OnyxiasLair; SunwellPlateau; + 717 => 1, 3713 => 1, 2437 => 1, 3716 => 1, 3847 => 1, 718 => 1, 3717 => 1, 3714 => 1, 3562 => 1, 722 => 1, 491 => 1, 3792 => 1, + 3789 => 1, 1477 => 1, 3959 => 0, 3845 => 1, 2717 => 1, 3923 => 1, 3607 => 1, 3836 => 1, 2159 => 1, 4075 => 0 + ); + + $tmp = (new DBC('WorldMapArea'))->readArbitrary(); + $wma = []; + foreach ($tmp as $row) + { + // fixups... + if (!$row['areaId']) + { + switch ($row['Id']) + { + case 13: $row['areaId'] = -6; break; // Kalimdor + case 14: $row['areaId'] = -3; break; // Eastern Kingdoms + case 466: $row['areaId'] = -2; break; // Outland + case 485: $row['areaId'] = -5; break; // Northrend + } + } + $wma[] = $row; + } + + $tmp = (new DBC('WorldMapOverlay'))->readFiltered(function(&$val) { return !empty($val['textureString']); }); + $wmo = []; + foreach ($tmp as $row) + $wmo[$row['worldMapAreaId']][] = $row; + + if (!$wma || !$wmo) + { + $success = false; + FileGen::status(' - could not read required dbc files: WorldMapArea.dbc ['.count($wma).' entries]; WorldMapOverlay.dbc ['.count($wmo).' entries]', MSG_LVL_ERROR); + return; + } + + // more fixups to WorldMapArea + array_unshift($wma, ['Id' => -1, 'areaId' => -1, 'nameINT' => 'World'], ['Id' => -4, 'areaId' => -4, 'nameINT' => 'Cosmic']); + + $sumMaps = count(FileGen::$localeIds) * count($wma); + + FileGen::status('Processing '.$sumMaps.' files from WorldMap/ ...'); + + foreach (FileGen::$localeIds as $progressLoc => $l) + { + // create destination directories + $dirError = false; + foreach ($mapDirs as $md) + if (!FileGen::writeDir($destDir . sprintf($md[0], strtolower(Util::$localeStrings[$l]).'/'))) + $dirError = true; + + if ($modeMask & 0x04) + if (!FileGen::writeDir('cache/alphaMaps')) + $dirError = true; + + if ($dirError) + { + $success = false; + FileGen::status(' - complexImg: could not create map directories for locale '.$l.'. skipping...', MSG_LVL_ERROR); + continue; + } + + + // source for mapFiles + $mapSrcDir = null; + $locDirs = array_filter(FileGen::$expectedPaths, function($var) use ($l) { return !$var || $var == $l; }); + foreach ($locDirs as $mapLoc => $__) + { + if ($mapLoc) // and trailing slash again + $mapLoc .= '/'; + + $p = sprintf($imgPath, $mapLoc).$paths[0]; + if (FileGen::fileExists($p)) + { + FileGen::status(' - using files from '.($mapLoc ?: '/').' for locale '.Util::$localeStrings[$l], MSG_LVL_WARN); + $mapSrcDir = $p.'/'; + break; + } + } + + if ($mapSrcDir === null) + { + $success = false; + FileGen::status(' - no suitable localized map files found for locale '.$l, MSG_LVL_ERROR); + continue; + } + + + foreach ($wma as $progressArea => $areaEntry) + { + $curMap = $progressArea + count($wma) * $progressLoc; + $progress = ' - ' . str_pad($curMap.'/'.($sumMaps), 10) . str_pad('('.number_format($curMap * 100 / $sumMaps, 2).'%)', 9); + + $wmaId = $areaEntry['Id']; + $zoneId = $areaEntry['areaId']; + $textureStr = $areaEntry['nameINT']; + + $path = $mapSrcDir.$textureStr; + if (!FileGen::fileExists($path)) + { + $success = false; + FileGen::status('worldmap file '.$path.' missing for selected locale '.Util::$localeStrings[$l], MSG_LVL_ERROR); + continue; + } + + $fmt = array( + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12] + ); + + FileGen::status($textureStr . " [" . $zoneId . "]"); + + $overlay = $createAlphaImage($mapWidth, $mapHeight); + + // zone has overlays (is in open world; is not multiLeveled) + if (isset($wmo[$wmaId])) + { + FileGen::status(' - area has '.count($wmo[$wmaId]).' overlays'); + + foreach ($wmo[$wmaId] as &$row) + { + $i = 1; + $y = 0; + while ($y < $row['h']) + { + $x = 0; + while ($x < $row['w']) + { + $img = $loadImageFile($path . '/' . $row['textureString'] . $i); + if (!$img) + { + FileGen::status(' - complexImg: tile '.$path.'/'.$row['textureString'].$i.'.blp missing.', MSG_LVL_ERROR); + break 2; + } + + imagecopy($overlay, $img, $row['x'] + $x, $row['y'] + $y, 0, 0, imagesx($img), imagesy($img)); + + // prepare subzone image + if ($modeMask & 0x10) + { + if (!isset($row['maskimage'])) + { + $row['maskimage'] = $createAlphaImage($row['w'], $row['h']); + $row['maskcolor'] = imagecolorallocatealpha($row['maskimage'], 255, 64, 192, 64); + } + + for ($my = 0; $my < imagesy($img); $my++) + for ($mx = 0; $mx < imagesx($img); $mx++) + if ((imagecolorat($img, $mx, $my) >> 24) < 30) + imagesetpixel($row['maskimage'], $x + $mx, $y + $my, $row['maskcolor']); + } + + imagedestroy($img); + + $x += 256; + $i++; + } + $y += 256; + } + } + + // create spawn-maps if wanted + if ($modeMask & 0x04) + $createSpawnMap($overlay, $zoneId); + } + + // check, if the current zone is multiLeveled + // if there are also files present without layer-suffix assume them as layer: 0 + $multiLeveled = false; + $multiLevel = 0; + do + { + if (!FileGen::filesInPath('/'.$textureStr.'\/'.$textureStr.($multiLevel + 1).'_\d\.blp/i', true)) + break; + + $multiLevel++; + $multiLeveled = true; + } + while ($multiLevel < 18); // Karazhan has 17 frickin floors + + // check if we can create base map anyway + $file = $path.'/'.$textureStr.'1.blp'; + $hasBaseMap = FileGen::fileExists($file); + + FileGen::status(' - area has '.($multiLeveled ? $multiLevel . ' levels' : 'only base level')); + + $map = null; + for ($i = 0; $i <= $multiLevel; $i++) + { + ini_set('max_execution_time', 120); // max 120sec per image + + $file = $path.'/'.$textureStr; + + if (!$i && !$hasBaseMap) + continue; + + // if $multiLeveled also suffix -0 to baseMap if it exists + if ($i && $multiLeveled) + $file .= $i.'_'; + + $doSkip = 0x0; + $outFile = []; + + foreach ($mapDirs as $idx => $info) + { + $outFile[$idx] = $destDir . sprintf($info[0], strtolower(Util::$localeStrings[$l]).'/') . $zoneId; + + $floor = $i; + if ($zoneId == 4100) // ToCStratholme: map order fix + $floor += 1; + + if ($multiLeveled && !(isset($baseLevelFix[$zoneId]) && $i == $baseLevelFix[$zoneId])) + $outFile[$idx] .= '-'.$floor; + + if (!isset(FileGen::$cliOpts['force']) && file_exists($outFile[$idx].'.'.$info[1])) + { + FileGen::status($progress.' - file '.$outFile[$idx].'.'.$info[1].' was already processed'); + $doSkip |= (1 << $idx); + } + } + + if ($doSkip == 0xF) + continue; + + $map = $assembleImage($file, $fmt, $mapWidth, $mapHeight); + if (!$map) + { + $success = false; + FileGen::status(' - could not create image resource for map '.$zoneId.($multiLevel ? ' level '.$i : '')); + continue; + } + + if (!$multiLeveled) + { + imagecolortransparent($overlay, imagecolorat($overlay, imagesx($overlay)-1, imagesy($overlay)-1)); + imagecopymerge($map, $overlay, 0, 0, 0, 0, imagesx($overlay), imagesy($overlay), 100); + imagedestroy($overlay); + } + + // create map + if ($modeMask & 0x02) + { + foreach ($mapDirs as $idx => $info) + { + if ($doSkip & (1 << $idx)) + continue; + + if (!$writeImage($outFile[$idx], $info[1], $map, $info[2] ?: $mapWidth, $info[3] ?: $mapHeight, $progress)) + $success = false; + } + } + } + + // also create subzone-maps + if ($map && isset($wmo[$wmaId]) && $modeMask & 0x10) + { + foreach ($wmo[$wmaId] as &$row) + { + $doSkip = 0x0; + $outFile = []; + + foreach ($mapDirs as $idx => $info) + { + $outFile[$idx] = $destDir . sprintf($info[0], strtolower(Util::$localeStrings[$l]).'/') . $row['areaTableId']; + if (!isset(FileGen::$cliOpts['force']) && file_exists($outFile[$idx].'.'.$info[1])) + { + FileGen::status($progress.' - file '.$outFile[$idx].'.'.$info[1].' was already processed'); + $doSkip |= (1 << $idx); + } + } + + if ($doSkip == 0xF) + continue; + + $subZone = imagecreatetruecolor($mapWidth, $mapHeight); + imagecopy($subZone, $map, 0, 0, 0, 0, imagesx($map), imagesy($map)); + imagecopy($subZone, $row['maskimage'], $row['x'], $row['y'], 0, 0, imagesx($row['maskimage']), imagesy($row['maskimage'])); + + foreach ($mapDirs as $idx => $info) + { + if ($doSkip & (1 << $idx)) + continue; + + if (!$writeImage($outFile[$idx], $info[1], $subZone, $info[2] ?: $mapWidth, $info[3] ?: $mapHeight, $progress)) + $success = false; + } + + imagedestroy($subZone); + } + } + + if ($map) + imagedestroy($map); + } + } + } + + /***********/ + /* Credits */ + /***********/ + + if ($modeMask & 0x08) // optional tidbits (not used by default) + { + if (FileGen::writeDir($destDir.'interface/Glues/Credits/')) + { + // tile ordering + $order = array( + 1 => array( + [1] + ), + 2 => array( + [1], + [2] + ), + 4 => array( + [1, 2], + [3, 4] + ), + 6 => array( + [1, 2, 3], + [4, 5, 6] + ), + 8 => array( + [1, 2, 3, 4], + [5, 6, 7, 8] + ) + ); + + $imgGroups = []; + $srcPath = sprintf($imgPath, $locStr).'Glues/Credits/'; + $files = FileGen::filesInPath($srcPath); + foreach ($files as $f) + { + if (preg_match('/([^\/]+)(\d).blp/i', $f, $m)) + { + if ($m[1] && $m[2]) + { + if (!isset($imgGroups[$m[1]])) + $imgGroups[$m[1]] = $m[2]; + else if ($imgGroups[$m[1]] < $m[2]) + $imgGroups[$m[1]] = $m[2]; + } + } + } + + // errör-korrekt + $imgGroups['Desolace'] = 4; + $imgGroups['BloodElf_Female'] = 6; + + $total = count($imgGroups); + $sum = 0; + + FileGen::status('Processing '.$total.' files from Glues/Credits/...'); + + foreach ($imgGroups as $file => $fmt) + { + ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time) + + $sum++; + $done = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9); + $name = $destDir.'interface/Glues/Credits/'.$file; + + if (!isset(FileGen::$cliOpts['force']) && file_exists($name.'.png')) + { + FileGen::status($done.' - file '.$name.'.png was already processed'); + continue; + } + + if (!isset($order[$fmt])) + { + FileGen::status(' - pattern for file '.$name.' not set. skipping', MSG_LVL_WARN); + continue; + } + + $im = $assembleImage($srcPath.$file, $order[$fmt], count($order[$fmt][0]) * 256, count($order[$fmt]) * 256); + if (!$im) + { + FileGen::status(' - could not assemble file '.$name, MSG_LVL_ERROR); + continue; + } + + if (!$writeImage($name, 'png', $im, count($order[$fmt][0]) * 256, count($order[$fmt]) * 256, $done)) + $success = false; + } + + ini_set('max_execution_time', $runTime); + } + else + $success = false; + } + + return $success; + } + +?> diff --git a/setup/tools/filegen/enchants.func.php b/setup/tools/filegen/enchants.func.php index 2974a393..61a5bc4b 100644 --- a/setup/tools/filegen/enchants.func.php +++ b/setup/tools/filegen/enchants.func.php @@ -52,18 +52,17 @@ if (!defined('AOWOW_REVISION')) }, */ - function enchants(&$log, $locales) + function enchants() { // from g_item_slots: 13:"One-Hand", 26:"Ranged", 17:"Two-Hand", $slotPointer = [13, 17, 26, 26, 13, 17, 17, 13, 17, null, 17, null, null, 13, null, 13, null, null, null, null, 17]; - $locales = [LOCALE_EN, LOCALE_FR, LOCALE_DE, LOCALE_ES, LOCALE_RU]; $castItems = []; $successs = true; $enchantSpells = new SpellList([['effect1Id', 53], ['name_loc0', 'QA%', '!'], CFG_SQL_LIMIT_NONE]); // enchantItemPermanent && !qualityAssurance // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; $enchIds = []; @@ -73,7 +72,7 @@ if (!defined('AOWOW_REVISION')) $enchMisc = []; $enchJSON = Util::parseItemEnchantment($enchIds, false, $enchMisc); - foreach ($locales as $lId) + foreach (FileGen::$localeIds as $lId) { set_time_limit(120); @@ -135,11 +134,11 @@ if (!defined('AOWOW_REVISION')) $ench['jsonequip']['requiredLevel'] = $enchMisc[$eId]['requiredLevel']; // check if the spell has an entry in skill_line_ability -> Source:Profession - if ($skill = DB::Aowow()->SelectCell('SELECT skillLineId FROM dbc.skilllineability WHERE spellId = ?d', $enchantSpells->id)) + if ($skills = $enchantSpells->getField('skillLines')) { $ench['name'][] = $enchantSpells->getField('name', true); $ench['source'][] = $enchantSpells->id; - $ench['skill'] = $skill; + $ench['skill'] = $skills[0]; $ench['slots'][] = $slot; } @@ -209,12 +208,10 @@ if (!defined('AOWOW_REVISION')) if (is_array($v) && count($v) == 1 && $k != 'jsonequip') $ench[$k] = $v[0]; - $toFile = "var g_enchants = "; - $toFile .= json_encode($enchantsOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK); - $toFile .= ";"; - $file = 'datasets/'.User::$localeString.'/enchants'; + $toFile = "var g_enchants = ".Util::toJSON($enchantsOut).";"; + $file = 'datasets/'.User::$localeString.'/enchants'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } diff --git a/setup/tools/filegen/fileGen.class.php b/setup/tools/filegen/fileGen.class.php new file mode 100644 index 00000000..41021881 --- /dev/null +++ b/setup/tools/filegen/fileGen.class.php @@ -0,0 +1,423 @@ + 2, + 'deDE' => 3, + 'esES' => 6, 'esMX' => 6, + 'ruRU' => 8, + '' => 0, 'enGB' => 0, 'enUS' => 0, + ); + + public static $cliOpts = []; + private static $shortOpts = 'fh'; + private static $longOpts = array( + 'build:', 'log::', 'help', 'locales::', 'force', 'mpqDataDir::', // general + 'icons', 'glyphs', 'pagetexts', 'loadingscreens', // whole images + 'artwork', 'talentbgs', 'maps', 'spawn-maps', 'area-maps' // images from image parts + ); + + public static $subScripts = []; + public static $tplFiles = array( + 'searchplugin' => ['aowow.xml', 'static/download/searchplugins/'], + 'power' => ['power.js', 'static/widgets/' ], + 'searchboxScript' => ['searchbox.js', 'static/widgets/' ], + 'demo' => ['demo.html', 'static/widgets/power/' ], + 'searchboxBody' => ['searchbox.html', 'static/widgets/searchbox/' ], + 'realmMenu' => ['profile_all.js', 'static/js/' ], + 'locales' => ['locale.js', 'static/js/' ], + // 'itemScaling => ['item-scaling', 'datasets/' ], # provided 'as is', as dbc-content doesn't usualy change + ); + public static $datasets = array( + 'pets', 'simpleImg', 'complexImg', + 'realms', 'statistics', 'profiler', // profiler related + 'talents', 'talentIcons', 'glyphs', // talentCalc related + 'itemsets', 'enchants', 'gems' // comparison related + ); + + public static $defaultExecTime = 30; + + public static $accessMask = 0755; + + private static $logFile = ''; + private static $logHandle = null; + + private static $mpqFiles = []; + + private static $locales = []; + public static $localeIds = []; + + private static $reqDirs = array( + 'static/uploads/screenshots/normal', + 'static/uploads/screenshots/pending', + 'static/uploads/screenshots/resized', + 'static/uploads/screenshots/temp', + 'static/uploads/screenshots/thumb', + 'static/uploads/temp/' + ); + + public static $txtConstants = array( + 'CFG_NAME' => CFG_NAME, + 'CFG_NAME_SHORT' => CFG_NAME_SHORT, + 'HOST_URL' => HOST_URL, + 'STATIC_URL' => STATIC_URL + ); + + public static function init($scriptList = '') + { + self::$defaultExecTime = ini_get('max_execution_time'); + $doScripts = []; + if (CLI) + { + if (getopt(self::$shortOpts, self::$longOpts)) + self::handleCLIOpts($doScripts); + else + { + self::printCLIHelp(array_merge(array_keys(self::$tplFiles), self::$datasets)); + exit; + } + } + else + { + $doScripts = explode(',', $scriptList); + self::$locales = Util::$localeStrings; + } + + // check passed subscript names; limit to real scriptNames + self::$subScripts = array_merge(array_keys(self::$tplFiles), self::$datasets); + if ($doScripts) + self::$subScripts = array_intersect($doScripts, self::$subScripts); + + // restrict actual locales + foreach (self::$locales as $idx => $str) + { + if (!$str) + continue; + + if (!defined('CFG_LOCALES')) + self::$localeIds[] = $idx; + else if (CFG_LOCALES & (1 << $idx)) + self::$localeIds[] = $idx; + } + + if (!self::$localeIds) + { + self::status('No valid locale specified. Check your config or --locales parameter, if used', MSG_LVL_ERROR); + exit; + } + + // create directory structure + self::status('FileGen::init() - creating required directories'); + $pathOk = 0; + foreach (self::$reqDirs as $rd) + if (self::writeDir($rd)) + $pathOk++; + + FileGen::status('created '.$pathOk.' extra paths'.($pathOk == count(self::$reqDirs) ? '' : ' with errors')); + FileGen::status(); + } + + // shared funcs + public static function writeFile($file, $content) + { + $success = false; + if ($handle = @fOpen($file, "w")) + { + if (fWrite($handle, $content)) + { + $success = true; + self::status(sprintf(ERR_NONE, $file), MSG_LVL_OK); + } + else + self::status(sprintf(ERR_WRITE_FILE, $file), MSG_LVL_ERROR); + + fClose($handle); + } + else + self::status(sprintf(ERR_CREATE_FILE, $file), MSG_LVL_ERROR); + + if ($success) + @chmod($file, FileGen::$accessMask); + + return $success; + } + + public static function writeDir($dir) + { + if (is_dir($dir)) + { + if (!is_writable($dir) && !@chmod($dir, FileGen::$accessMask)) + self::status('cannot write into output directory '.$dir, MSG_LVL_ERROR); + + return is_writable($dir); + } + + if (@mkdir($dir, FileGen::$accessMask, true)) + return true; + + self::status('could not create output directory '.$dir, MSG_LVL_ERROR); + return false; + } + + public static function status($txt = '', $lvl = -1) + { + if (isset(self::$cliOpts['help'])) + return; + + $cliColor = "\033[%sm%s\033[0m"; + $htmlColor = '%s'; + + if (self::$logFile && !self::$logHandle) + { + if (!file_exists(self::$logFile)) + self::$logHandle = fopen(self::$logFile, 'w'); + else + { + $logFileParts = pathinfo(self::$logFile); + + $i = 1; + while (file_exists($logFileParts['dirname'].'/'.$logFileParts['filename'].$i.'.'.$logFileParts['extension'])) + $i++; + + self::$logFile = $logFileParts['dirname'].'/'.$logFileParts['filename'].$i.'.'.$logFileParts['extension']; + self::$logHandle = fopen(self::$logFile, 'w'); + } + } + + $msg = $raw = "\n"; + if ($txt) + { + $msg = $raw = str_pad(date('H:i:s'), 10); + switch ($lvl) + { + case MSG_LVL_ERROR: // red error + $str = 'Error: '; + $raw .= $str; + $msg .= CLI ? sprintf($cliColor, '0;31', $str) : sprintf($htmlColor, 'darkred', $str); + break; + case MSG_LVL_WARN: // yellow warn + $str = 'Notice: '; + $raw .= $str; + $msg .= CLI ? sprintf($cliColor, '0;33', $str) : sprintf($htmlColor, 'orange', $str); + break; + case MSG_LVL_OK: // green success + $str = 'Success:'; + $raw = $raw . $str; + $msg .= CLI ? sprintf($cliColor, '0;32', $str) : sprintf($htmlColor, 'darkgreen', $str); + break; + default: + $msg .= ' '; + $raw .= ' '; + } + + $msg .= ' '.$txt."\n"; + $raw .= ' '.$txt."\n"; + } + + if (CLI) + { + // maybe for future use: writing \x08 deletes the last char, use to repeatedly update single line (and even WIN should be able to handle it) + + echo substr(PHP_OS, 0, 3) == 'WIN' ? $raw : $msg; + + if (self::$logHandle) + fwrite(self::$logHandle, $raw); + + @ob_flush(); + flush(); + @ob_end_flush(); + } + else + echo "
".$msg."
\n"; + } + + private static function handleCLIOpts(&$doScripts) + { + $_ = getopt(self::$shortOpts, self::$longOpts); + + if ((isset($_['help']) || isset($_['h'])) && empty($_['build'])) + { + self::printCLIHelp(array_merge(array_keys(self::$tplFiles), self::$datasets)); + exit; + } + + // required subScripts + if (!empty($_['build'])) + $doScripts = explode(',', $_['build']); + + // optional logging + if (!empty($_['log'])) + self::$logFile = trim($_['log']); + + // optional, overwrite existing files + if (isset($_['f'])) + self::$cliOpts['force'] = true; + + // alternative data source (no quotes, use forward slash) + if (!empty($_['mpqDataDir'])) + self::$srcDir = str_replace(['\\', '"', '\''], ['/', '', ''], $_['mpqDataDir']); + + if (isset($_['h'])) + self::$cliOpts['help'] = true; + + // optional limit handled locales + if (!empty($_['locales'])) + { + // engb and enus are identical for all intents and purposes + $from = ['engb', 'esmx']; + $to = ['enus', 'eses']; + $_['locales'] = str_replace($from, $to, strtolower($_['locales'])); + + self::$locales = array_intersect(Util::$localeStrings, explode(',', $_['locales'])); + } + else + self::$locales = Util::$localeStrings; + + // mostly build-instructions from longOpts + foreach (self::$longOpts as $opt) + if (!strstr($opt, ':') && isset($_[$opt])) + self::$cliOpts[$opt] = true; + } + + public static function hasOpt(/* ...$opt */) + { + $result = 0x0; + foreach (func_get_args() as $idx => $arg) + { + if (!is_string($arg)) + continue; + + if (isset(self::$cliOpts[$arg])) + $result |= (1 << $idx); + } + + return $result; + } + + /* 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 lowecase + lookups will be done in lowercase. A successfull match will return the real path. + */ + private static function buildFileList() + { + self::status('FileGen::init() - reading MPQdata from '.self::$srcDir.' to list for first time use...'); + + $setupDirs = glob('setup/*'); + foreach ($setupDirs as $sd) + { + if (substr(self::$srcDir, -1) == '/') + self::$srcDir = substr(self::$srcDir, 0, -1); + + if (substr($sd, -1) == '/') + $sd = substr($sd, 0, -1); + + if (strtolower($sd) == strtolower(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($_)] = $_; + } + + self::status('done'); + self::status(); + } + catch (UnexpectedValueException $e) { self::status('- mpqData dir '.self::$srcDir.' does not exist', MSG_LVL_ERROR); } + } + + public static function fileExists(&$file) + { + // read mpq source file structure to tree + if (!self::$mpqFiles) + self::buildFileList(); + + // backslash to forward slash + $_ = strtolower(str_replace('\\', '/', $file)); + + // remove trailing slash + if (substr($_, -1, 1) == '/') + $_ = 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) + self::buildFileList(); + + // 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; + } + + public static function printCLIHelp($scripts) + { + echo "usage: php index.php --build= [-h --help] [--log logfile] [-f --force] [--mpqDataDir=path/to/mpqData/] [--locales=]\n\n"; + echo "build -> available subScripts:\n"; + foreach ($scripts as $s) + echo " - ".str_pad($s, 20).(isset(self::$tplFiles[$s]) ? self::$tplFiles[$s][1].self::$tplFiles[$s][0] : 'static data file')."\n"; + + echo "help -> shows this info\n"; + echo "log -> writes ouput to file\n"; + echo "force -> enforces overwriting existing files\n"; + echo "locales -> limits setup to enUS, frFR, deDE, esES and/or ruRU (does not override config settings)\n"; + echo "mpqDataDir -> manually point to directory with extracted mpq data (default: setup/mpqData/)\n"; + } +} + +?> \ No newline at end of file diff --git a/setup/tools/filegen/gems.func.php b/setup/tools/filegen/gems.func.php index 6edc0f68..50bbc9bd 100644 --- a/setup/tools/filegen/gems.func.php +++ b/setup/tools/filegen/gems.func.php @@ -21,7 +21,7 @@ if (!defined('AOWOW_REVISION')) }, */ - function gems(&$log, $locales) + function gems() { // sketchy, but should work // Id < 36'000 || ilevel < 70 ? BC : WOTLK @@ -41,7 +41,7 @@ if (!defined('AOWOW_REVISION')) // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; $enchIds = []; @@ -51,7 +51,7 @@ if (!defined('AOWOW_REVISION')) $enchMisc = []; $enchJSON = Util::parseItemEnchantment($enchIds, false, $enchMisc); - foreach ($locales as $lId) + foreach (FileGen::$localeIds as $lId) { set_time_limit(5); @@ -72,12 +72,10 @@ if (!defined('AOWOW_REVISION')) ); } - $toFile = "var g_gems = "; - $toFile .= json_encode($gemsOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK); - $toFile .= ";"; - $file = 'datasets/'.User::$localeString.'/gems'; + $toFile = "var g_gems = ".Util::toJSON($gemsOut).";"; + $file = 'datasets/'.User::$localeString.'/gems'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } diff --git a/setup/tools/filegen/glyphs.func.php b/setup/tools/filegen/glyphs.func.php index a75a81d0..dbaf8b3a 100644 --- a/setup/tools/filegen/glyphs.func.php +++ b/setup/tools/filegen/glyphs.func.php @@ -20,7 +20,7 @@ if (!defined('AOWOW_REVISION')) }, */ - function glyphs(&$log, $locales) + function glyphs() { $success = true; $glyphList = DB::Aowow()->Select( @@ -42,12 +42,12 @@ if (!defined('AOWOW_REVISION')) // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; $glyphSpells = new SpellList(array(['s.id', array_keys($glyphList)], CFG_SQL_LIMIT_NONE)); - foreach ($locales as $lId) + foreach (FileGen::$localeIds as $lId) { set_time_limit(30); @@ -76,12 +76,10 @@ if (!defined('AOWOW_REVISION')) ); } - $toFile = "var g_glyphs = "; - $toFile .= json_encode($glyphsOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK); - $toFile .= ";"; - $file = 'datasets/'.User::$localeString.'/glyphs'; + $toFile = "var g_glyphs = ".Util::toJSON($glyphsOut).";"; + $file = 'datasets/'.User::$localeString.'/glyphs'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } diff --git a/setup/tools/filegen/itemsets.func.php b/setup/tools/filegen/itemsets.func.php index c177420e..14820b00 100644 --- a/setup/tools/filegen/itemsets.func.php +++ b/setup/tools/filegen/itemsets.func.php @@ -29,7 +29,7 @@ if (!defined('AOWOW_REVISION')) }, */ - function itemsets(&$log, $locales) + function itemsets() { $success = true; $setList = DB::Aowow()->Select('SELECT * FROM ?_itemset ORDER BY refSetId DESC'); @@ -37,10 +37,10 @@ if (!defined('AOWOW_REVISION')) // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; - foreach ($locales as $lId) + foreach (FileGen::$localeIds as $lId) { User::useLocale($lId); Lang::load(Util::$localeStrings[$lId]); @@ -120,12 +120,10 @@ if (!defined('AOWOW_REVISION')) $itemsetOut[$setOut['id']] = $setOut; } - $toFile = "var g_itemsets = "; - $toFile .= json_encode($itemsetOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK); - $toFile .= ";"; - $file = 'datasets/'.User::$localeString.'/itemsets'; + $toFile = "var g_itemsets = ".Util::toJSON($itemsetOut).";"; + $file = 'datasets/'.User::$localeString.'/itemsets'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } diff --git a/setup/tools/filegen/locales.func.php b/setup/tools/filegen/locales.func.php index 1ce9f2d7..24eb2a31 100644 --- a/setup/tools/filegen/locales.func.php +++ b/setup/tools/filegen/locales.func.php @@ -7,7 +7,7 @@ if (!defined('AOWOW_REVISION')) // Create 'locale.js'-file in static/js // available locales have to be set in aowow.aowow_config - function locales(&$log, $locales) + function locales() { $result = []; $available = array( @@ -43,7 +43,7 @@ if (!defined('AOWOW_REVISION')) " }", ); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) if (isset($available[$l])) $result[] = $available[$l]; diff --git a/setup/tools/filegen/pets.func.php b/setup/tools/filegen/pets.func.php index 415ad9ab..af06d3ac 100644 --- a/setup/tools/filegen/pets.func.php +++ b/setup/tools/filegen/pets.func.php @@ -5,8 +5,6 @@ if (!defined('AOWOW_REVISION')) // builds 'pets'-file for available locales - // this script requires the following dbc-files to be parsed and available - // CreatureFamily, CreatureDisplayInfo, FactionTemplate, AreaTable /* Example data 30: { @@ -25,7 +23,7 @@ if (!defined('AOWOW_REVISION')) }, */ - function pets(&$log, $locales) + function pets() { $success = true; $locations = []; @@ -38,22 +36,21 @@ if (!defined('AOWOW_REVISION')) cr.rank AS classification, cr.family, cr.displayId1 AS displayId, - cdi.skin1 AS skin, - SUBSTRING_INDEX(cf.iconFile, "\\\\", -1) AS icon, - cf.petTalentType AS type + cr.textureString AS skin, + p.iconString AS icon, + p.type FROM ?_creature cr JOIN ?_factiontemplate ft ON ft.Id = cr.faction - JOIN dbc.creaturefamily cf ON cf.Id = cr.family - JOIN dbc.creaturedisplayinfo cdi ON cdi.id = cr.displayId1 - WHERE cf.petTalentType <> -1 AND cr.typeFlags & 0x1 AND (cr.cuFlags & 0x2) = 0 + JOIN ?_pet p ON p.id = cr.family + WHERE cr.typeFlags & 0x1 AND (cr.cuFlags & 0x2) = 0 ORDER BY cr.id ASC'); // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; - foreach ($locales as $lId) + foreach (FileGen::$localeIds as $lId) { User::useLocale($lId); Lang::load(Util::$localeStrings[$lId]); @@ -82,12 +79,10 @@ if (!defined('AOWOW_REVISION')) ); } - $toFile = "var g_pets = "; - $toFile .= json_encode($petsOut, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK); - $toFile .= ";"; - $file = 'datasets/'.User::$localeString.'/pets'; + $toFile = "var g_pets = ".Util::toJSON($petsOut).";"; + $file = 'datasets/'.User::$localeString.'/pets'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } diff --git a/setup/tools/filegen/profiler.func.php b/setup/tools/filegen/profiler.func.php index 69654048..7ea99bc1 100644 --- a/setup/tools/filegen/profiler.func.php +++ b/setup/tools/filegen/profiler.func.php @@ -7,7 +7,7 @@ if (!defined('AOWOW_REVISION')) // gatheres quasi-static data used in profiler: all available quests, achievements, titles, mounts, companions, factions, recipes // this script requires a fully set up database and is expected to be run last - function profiler(&$log, $locales) + function profiler() { $success = true; $scripts = []; @@ -15,7 +15,7 @@ if (!defined('AOWOW_REVISION')) /**********/ /* Quests */ /**********/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { $success = true; $condition = [ @@ -35,7 +35,7 @@ if (!defined('AOWOW_REVISION')) $relCurr = new CurrencyList(array(['id', $_])); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(20); @@ -44,15 +44,15 @@ if (!defined('AOWOW_REVISION')) $buff = "var _ = g_gatheredcurrencies;\n"; foreach ($relCurr->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; $buff .= "\n\nvar _ = g_quests;\n"; foreach ($questz->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; $buff .= "\ng_quest_catorder = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n"; - if (!writeFile('datasets/'.User::$localeString.'/p-quests', $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-quests', $buff)) $success = false; } @@ -62,7 +62,7 @@ if (!defined('AOWOW_REVISION')) /****************/ /* Achievements */ /****************/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { $success = true; $condition = array( @@ -72,7 +72,7 @@ if (!defined('AOWOW_REVISION')) ); $achievez = new AchievementList($condition); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(5); @@ -84,7 +84,7 @@ if (!defined('AOWOW_REVISION')) foreach ($achievez->getListviewData(ACHIEVEMENTINFO_PROFILE) as $id => $data) { $sumPoints += $data['points']; - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; } // categories to sort by @@ -92,7 +92,7 @@ if (!defined('AOWOW_REVISION')) // sum points $buff .= "\ng_achievement_points = [".$sumPoints."];\n"; - if (!writeFile('datasets/'.User::$localeString.'/p-achievements', $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-achievements', $buff)) $success = false; } @@ -102,7 +102,7 @@ if (!defined('AOWOW_REVISION')) /**********/ /* Titles */ /**********/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { $success = true; $condition = array( @@ -111,7 +111,7 @@ if (!defined('AOWOW_REVISION')) ); $titlez = new TitleList($condition); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(5); @@ -125,10 +125,10 @@ if (!defined('AOWOW_REVISION')) { $data['name'] = Util::localizedString($titlez->getEntry($id), $g ? 'female' : 'male'); unset($data['namefemale']); - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; } - if (!writeFile('datasets/'.User::$localeString.'/p-titles-'.$g, $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-titles-'.$g, $buff)) $success = false; } } @@ -139,7 +139,7 @@ if (!defined('AOWOW_REVISION')) /**********/ /* Mounts */ /**********/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { $success = true; $condition = array( @@ -149,7 +149,7 @@ if (!defined('AOWOW_REVISION')) ); $mountz = new SpellList($condition); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(5); @@ -161,10 +161,10 @@ if (!defined('AOWOW_REVISION')) { $data['quality'] = $data['name'][0]; $data['name'] = substr($data['name'], 1); - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; } - if (!writeFile('datasets/'.User::$localeString.'/p-mounts', $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-mounts', $buff)) $success = false; } @@ -174,7 +174,7 @@ if (!defined('AOWOW_REVISION')) /**************/ /* Companions */ /**************/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { $success = true; $condition = array( @@ -184,7 +184,7 @@ if (!defined('AOWOW_REVISION')) ); $companionz = new SpellList($condition); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(5); @@ -196,10 +196,10 @@ if (!defined('AOWOW_REVISION')) { $data['quality'] = $data['name'][0]; $data['name'] = substr($data['name'], 1); - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; } - if (!writeFile('datasets/'.User::$localeString.'/p-companions', $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-companions', $buff)) $success = false; } @@ -209,7 +209,7 @@ if (!defined('AOWOW_REVISION')) /************/ /* Factions */ /************/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { $success = true; $condition = array( // todo (med): exclude non-gaining reputation-header @@ -218,7 +218,7 @@ if (!defined('AOWOW_REVISION')) ); $factionz = new FactionList($condition); - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(5); @@ -227,11 +227,11 @@ if (!defined('AOWOW_REVISION')) $buff = "var _ = g_factions;\n"; foreach ($factionz->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; $buff .= "\ng_faction_order = [0, 469, 891, 1037, 1118, 67, 1052, 892, 936, 1117, 169, 980, 1097];\n"; - if (!writeFile('datasets/'.User::$localeString.'/p-factions', $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-factions', $buff)) $success = false; } @@ -241,7 +241,7 @@ if (!defined('AOWOW_REVISION')) /***********/ /* Recipes */ /***********/ - $scripts[] = function(&$log) use ($locales) + $scripts[] = function() { // special case: secondary skills are always requested, so put them in one single file (185, 129, 356); it also contains g_skill_order $skills = [171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, [185, 129, 356]]; @@ -269,7 +269,7 @@ if (!defined('AOWOW_REVISION')) } } - foreach ($locales as $l) + foreach (FileGen::$localeIds as $l) { set_time_limit(10); @@ -278,12 +278,12 @@ if (!defined('AOWOW_REVISION')) $buff = ''; foreach ($recipez->getListviewData() as $id => $data) - $buff .= '_['.$id.'] = '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).";\n"; + $buff .= '_['.$id.'] = '.Util::toJSON($data).";\n"; if (!$buff) { // this behaviour is intended, do not create an error - $log[] = [time(), ' notice: profiler - file datasets/'.User::$localeString.'/p-recipes-'.$file.' has no content => skipping']; + FileGen::status('profiler - file datasets/'.User::$localeString.'/p-recipes-'.$file.' has no content => skipping', MSG_LVL_WARN); continue; } @@ -292,7 +292,7 @@ if (!defined('AOWOW_REVISION')) if (is_array($s)) $buff .= "\ng_skill_order = [171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356];\n"; - if (!writeFile('datasets/'.User::$localeString.'/p-recipes-'.$file, $buff, $log)) + if (!FileGen::writeFile('datasets/'.User::$localeString.'/p-recipes-'.$file, $buff)) $success = false; } } @@ -302,12 +302,12 @@ if (!defined('AOWOW_REVISION')) // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; // run scripts foreach ($scripts as $func) - if (!$func($log)) + if (!$func()) $success = false; return $success; diff --git a/setup/tools/filegen/mnProfiles.func.php b/setup/tools/filegen/realmMenu.func.php similarity index 51% rename from setup/tools/filegen/mnProfiles.func.php rename to setup/tools/filegen/realmMenu.func.php index 8e68a3c4..ba9b90fb 100644 --- a/setup/tools/filegen/mnProfiles.func.php +++ b/setup/tools/filegen/realmMenu.func.php @@ -33,41 +33,39 @@ if (!defined('AOWOW_REVISION')) ]; */ -function mnProfiles(/* &$log */) -{ - $menu = [ - ["us", "US & Oceanic", null,[ - [Util::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, []] - ]], - ["eu", "Europe", null,[ - [Util::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, []] - ]] - ]; - - $rows = DB::Auth()->select('SELECT name, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0'); - $set = 0x0; - - foreach ($rows as $row) + function realmMenu() { - if ($row['region'] == 'eu') + $subEU = []; + $subUS = []; + $menu = [ + ['us', 'US & Oceanic', null,[[Util::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, &$subEU]]], + ['eu', 'Europe', null,[[Util::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP, null, &$subUS]]] + ]; + + $rows = DB::Auth()->select('SELECT name, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0'); + $set = 0x0; + + foreach ($rows as $row) { - $set |= 0x1; - $menu[1][3][0][3][] = [Util::urlize($row['name']),$row['name']]; - } - else if ($row['region'] == 'us') - { - $set |= 0x2; - $menu[0][3][0][3][] = [Util::urlize($row['name']),$row['name']]; + if ($row['region'] == 'eu') + { + $set |= 0x1; + $subEU[] = [Util::urlize($row['name']), $row['name']]; + } + else if ($row['region'] == 'us') + { + $set |= 0x2; + $subUS[] = [Util::urlize($row['name']), $row['name']]; + } } + + if (!($set & 0x1)) + array_shift($menu); + + if (!($set & 0x2)) + array_pop($menu); + + return Util::toJSON($menu); } - if (!($set & 0x1)) - array_pop($menu); - - if (!($set & 0x2)) - array_shift($menu); - - return json_encode($menu, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); -} - ?> diff --git a/setup/tools/filegen/realms.func.php b/setup/tools/filegen/realms.func.php index 201a0dd5..af251468 100644 --- a/setup/tools/filegen/realms.func.php +++ b/setup/tools/filegen/realms.func.php @@ -23,13 +23,14 @@ if (!defined('AOWOW_REVISION')) }, */ - function realms(&$log) + function realms() { - $file = 'datasets/realms'; - $rows = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, ? AS battlegroup, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0', CFG_BATTLEGROUP); - $str = 'var g_realms = '.json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).';'; + $realms = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, ? AS battlegroup, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0', CFG_BATTLEGROUP); - return writeFile($file, $str, $log); + $toFile = "var g_realms = ".Util::toJSON($realms).";"; + $file = 'datasets/realms'; + + return FileGen::writeFile($file, $toFile); } ?> diff --git a/setup/tools/filegen/scriptGen.php b/setup/tools/filegen/scriptGen.php deleted file mode 100644 index 52482d56..00000000 --- a/setup/tools/filegen/scriptGen.php +++ /dev/null @@ -1,211 +0,0 @@ - CFG_NAME, - 'CFG_NAME_SHORT' => CFG_NAME_SHORT, - 'HOST_URL' => HOST_URL, - 'STATIC_URL' => STATIC_URL -); -$tplFiles = array( - 'searchplugin' => ['aowow.xml', 'static/download/searchplugins/'], - 'power' => ['power.js', 'static/widgets/' ], - 'searchboxScript' => ['searchbox.js', 'static/widgets/' ], - 'demo' => ['demo.html', 'static/widgets/power/' ], - 'searchboxBody' => ['searchbox.html', 'static/widgets/searchbox/' ], - 'realmMenu' => ['profile_all.js', 'static/js/' ], - 'locales' => ['locale.js', 'static/js/' ], -// 'itemScaling => ['item-scaling', 'datasets/' ], # provided 'as is', as dbc-content doesn't usualy change -); -$datasets = array( - 'realms', 'statistics', 'profiler', // profiler related - 'talents', 'talentIcons', 'glyphs', // talentCalc related - 'itemsets', 'enchants', 'gems', // comparison related - 'pets', -); -$reqDirs = array( - 'static/uploads/screenshots/normal', - 'static/uploads/screenshots/pending', - 'static/uploads/screenshots/resized', - 'static/uploads/screenshots/temp', - 'static/uploads/screenshots/thumb', - 'static/uploads/temp/' -); - -// restrict actual locales -foreach (Util::$localeStrings as $idx => $str) - if ($str && (CFG_LOCALES & (1 << $idx))) - $locales[] = $idx; - - -// check $pageParam; limit to real scriptNames -$scList = array_merge(array_keys($tplFiles), $datasets); -if ($pageParam) - $scList = array_intersect(explode(';', $pageParam), $scList); - - -if ($scList) -{ - // create directory structure - $log[] = [time(), 'begin creation of directory structure']; - $pathOk = 0; - foreach ($reqDirs as $d) - if (writeDir($d, $log)) - $pathOk++; - - $log[] = [time(), 'finished directory structure.']; - $log[] = [time(), 'created '.$pathOk.' extra paths'.($pathOk == count($reqDirs) ? '' : ' with errors')]; - $log[] = null; - - // start file generation - $log[] = [time(), 'begin generation of '. implode(', ', $scList)]; - $log[] = null; - - // files with template - foreach ($tplFiles as $name => list($file, $destPath)) - { - if ($scList && !in_array($name, $scList)) - continue; - - if (!file_exists($tplPath.$file.'.in')) - { - $log[] = [time(), sprintf(ERR_MISSING_FILE, $tplPath.$file.'.in')]; - continue; - } - - if (!writeDir($destPath, $log)) - continue; - - if ($content = file_get_contents($tplPath.$file.'.in')) - { - if ($dest = @fOpen($destPath.$file, "w")) - { - // replace constants - $content = strtr($content, $pairs); - - // must generate content - // PH format: /*setup:*/ - if (preg_match('/\/\*setup:([\w\d_-]+)\*\//i', $content, $m)) - { - $res = ''; - if (file_exists('setup/tools/filegen/'.$m[1].'.func.php')) - { - include 'setup/tools/filegen/'.$m[1].'.func.php'; - $res = $m[1]($log, $locales); - } - else - $log[] = [time(), sprintf(ERR_MISSING_INCL, $m[1], 'setup/tools/filegen/'.$m[1].'.func.php')]; - - $content = str_replace('/*setup:'.$m[1].'*/', $res, $content); - } - - if (fWrite($dest, $content)) - $log[] = [time(), sprintf(ERR_NONE, $destPath.$file)]; - else - $log[] = [time(), sprintf(ERR_WRITE_FILE, $destPath.$file)]; - - fClose($dest); - } - else - $log[] = [time(), sprintf(ERR_CREATE_FILE, $destPath.$file)]; - } - else - $log[] = [time(), sprintf(ERR_READ_FILE, $tplPath.$file.'.in')]; - } - - // files without template - foreach ($datasets as $file) - { - if ($scList && !in_array($file, $scList)) - continue; - - if (file_exists('setup/tools/filegen/'.$file.'.func.php')) - { - include 'setup/tools/filegen/'.$file.'.func.php'; - if ($file($log, $locales)) - $log[] = [time(), ' - subscript returned sucessfully']; - else - $log[] = [time(), ' - subscript returned with errors']; - - set_time_limit(30); // reset to default for the next script - } - else - $log[] = [time(), sprintf(ERR_MISSING_INCL, $file, 'setup/tools/filegen/'.$file.'.func.php')]; - } - - - // end - $log[] = null; - $log[] = [time(), 'finished file generation']; -} -else - $log[] = [time(), 'no valid script names supplied']; - - -// print accumulated log -echo "
\n";
-foreach ($log as $l)
-    if ($l)
-        echo date('H:i:s', $l[0]) . ' ' . $l[1]."\n";
-    else
-        echo "\n";
-echo "
\n"; - - -?> \ No newline at end of file diff --git a/setup/tools/filegen/simpleImg.func.php b/setup/tools/filegen/simpleImg.func.php new file mode 100644 index 00000000..c19f36ab --- /dev/null +++ b/setup/tools/filegen/simpleImg.func.php @@ -0,0 +1,428 @@ + ['Icons/', $iconDirs, '/*.[bB][lL][pP]', true, 0], + 1 => ['Spellbook/', [['Interface/Spellbook/', 'png', 0, 0, 0]], '/UI-Glyph-Rune*.blp', true, 0], + 2 => ['PaperDoll/', array_slice($iconDirs, 0, 3), '/UI-{Backpack,PaperDoll}-*.blp', true, 0], + 3 => ['GLUES/CHARACTERCREATE/UI-CharacterCreate-Races.blp', $iconDirs, '', true, 64], + 4 => ['GLUES/CHARACTERCREATE/UI-CharacterCreate-CLASSES.blp', $iconDirs, '', true, 64], + 5 => ['GLUES/CHARACTERCREATE/UI-CharacterCreate-Factions.blp', $iconDirs, '', true, 64], + // 6 => ['Minimap/OBJECTICONS.BLP', [['icons/tiny/', 'gif', 0, 16, 2]], '', true, 32], + 7 => ['FlavorImages/', [['Interface/FlavorImages/', 'png', 0, 0, 0]], '/*.[bB][lL][pP]', false, 0], + 8 => ['Pictures/', [['Interface/Pictures/', 'png', 0, 0, 0]], '/*.[bB][lL][pP]', false, 0], + 9 => ['PvPRankBadges/', [['Interface/PvPRankBadges/', 'png', 0, 0, 0]], '/*.[bB][lL][pP]', false, 0], + 10 => ['Calendar/Holidays/', $calendarDirs, '/*{rt,a,y,h,s}.[bB][lL][pP]', true, 0], + 11 => ['GLUES/LOADINGSCREENS/', $loadScreenDirs, '/[lL][oO]*.[bB][lL][pP]', false, 0] + ); + // textures are composed of 64x64 icons + // numeric indexed arrays mimick the position on the texture + $cuNames = array( + 2 => array( + 'ui-paperdoll-slot-chest' => 'inventoryslot_chest', + 'ui-backpack-emptyslot' => 'inventoryslot_empty', + 'ui-paperdoll-slot-feet' => 'inventoryslot_feet', + 'ui-paperdoll-slot-finger' => 'inventoryslot_finger', + 'ui-paperdoll-slot-hands' => 'inventoryslot_hands', + 'ui-paperdoll-slot-head' => 'inventoryslot_head', + 'ui-paperdoll-slot-legs' => 'inventoryslot_legs', + 'ui-paperdoll-slot-mainhand' => 'inventoryslot_mainhand', + 'ui-paperdoll-slot-neck' => 'inventoryslot_neck', + 'ui-paperdoll-slot-secondaryhand' => 'inventoryslot_offhand', + 'ui-paperdoll-slot-ranged' => 'inventoryslot_ranged', + 'ui-paperdoll-slot-relic' => 'inventoryslot_relic', + 'ui-paperdoll-slot-shirt' => 'inventoryslot_shirt', + 'ui-paperdoll-slot-shoulder' => 'inventoryslot_shoulder', + 'ui-paperdoll-slot-tabard' => 'inventoryslot_tabard', + 'ui-paperdoll-slot-trinket' => 'inventoryslot_trinket', + 'ui-paperdoll-slot-waist' => 'inventoryslot_waist', + 'ui-paperdoll-slot-wrists' => 'inventoryslot_wrists' + ), + 3 => array( + ['race_human_male', 'race_dwarf_male', 'race_gnome_male', 'race_nightelf_male', 'race_draenai_male' ], + ['race_tauren_male', 'race_undead_male', 'race_troll_male', 'race_orc_male', 'race_bloodelf_male' ], + ['race_human_female', 'race_dwarf_female', 'race_gnome_female', 'race_nightelf_female', 'race_draenai_female' ], + ['race_tauren_female', 'race_undead_female', 'race_troll_female', 'race_orc_female', 'race_bloodelf_female'] + ), + 4 => array( + ['class_warrior', 'class_mage', 'class_rogue', 'class_druid' ], + ['class_hunter', 'class_shaman', 'class_priest', 'class_warlock'], + ['class_paladin', 'class_deathknight' ] + ), + 5 => array( + ['faction_alliance', 'faction_horde'] + ), + 6 => array( + [], + [null, 'quest_start', 'quest_end', 'quest_start_daily', 'quest_end_daily'] + ), + 10 => array( // really should have read holidays.dbc... + 'calendar_winterveilstart' => 'calendar_winterveilstart', + 'calendar_noblegardenstart' => 'calendar_noblegardenstart', + 'calendar_childrensweekstart' => 'calendar_childrensweekstart', + 'calendar_fishingextravaganza' => 'calendar_fishingextravaganzastart', + 'calendar_harvestfestivalstart' => 'calendar_harvestfestivalstart', + 'calendar_hallowsendstart' => 'calendar_hallowsendstart', + 'calendar_lunarfestivalstart' => 'calendar_lunarfestivalstart', + 'calendar_loveintheairstart' => 'calendar_loveintheairstart', + 'calendar_midsummerstart' => 'calendar_midsummerstart', + 'calendar_brewfeststart' => 'calendar_brewfeststart', + 'calendar_darkmoonfaireelwynnstart' => 'calendar_darkmoonfaireelwynnstart', + 'calendar_darkmoonfairemulgorestart' => 'calendar_darkmoonfairemulgorestart', + 'calendar_darkmoonfaireterokkarstart' => 'calendar_darkmoonfaireterokkarstart', + 'calendar_piratesday' => 'calendar_piratesdaystart', + 'calendar_wotlklaunch' => 'calendar_wotlklaunchstart', + 'calendar_dayofthedeadstart' => 'calendar_dayofthedeadstart', + 'calendar_fireworks' => 'calendar_fireworksstart' + ) + ); + + $writeImage = function($name, $ext, $src, $srcDims, $destDims, $done) + { + $ok = false; + $dest = imagecreatetruecolor($destDims['w'], $destDims['h']); + imagesavealpha($dest, true); + imagealphablending($dest, false); + imagecopyresampled($dest, $src, $destDims['x'], $destDims['x'], $srcDims['x'], $srcDims['y'], $destDims['w'], $destDims['h'], $srcDims['w'], $srcDims['h']); + + switch ($ext) + { + case 'jpg': + $ok = imagejpeg($dest, $name.'.'.$ext, 85); + break; + case 'gif': + $ok = imagegif($dest, $name.'.'.$ext); + break; + case 'png': + $ok = imagepng($dest, $name.'.'.$ext); + break; + default: + FileGen::status($done.' - unsupported file fromat: '.$ext, MSG_LVL_WARN); + } + + imagedestroy($dest); + + if ($ok) + { + chmod($name.'.'.$ext, FileGen::$accessMask); + FileGen::status($done.' - image '.$name.'.'.$ext.' written', MSG_LVL_OK); + } + else + FileGen::status($done.' - could not create image '.$name.'.'.$ext, MSG_LVL_ERROR); + + return $ok; + }; + + $checkSourceDirs = function($sub, &$missing = []) use ($imgPath, $dbcPath, $paths) + { + foreach (array_column($paths, 0) as $subDir) + { + $p = sprintf($imgPath, $sub).$subDir; + if (!FileGen::fileExists($p)) + $missing[] = $p; + } + + $p = sprintf($dbcPath, $sub); + if (!FileGen::fileExists($p)) + $missing[] = $p; + + return !$missing; + }; + + if (isset(FileGen::$cliOpts['icons'])) + array_push($groups, 0, 2, 3, 4, 5, 10); + if (isset(FileGen::$cliOpts['glyphs'])) + $groups[] = 1; + if (isset(FileGen::$cliOpts['pagetexts'])) + array_push($groups, 7, 8, 9); + if (isset(FileGen::$cliOpts['loadingscreens'])) + $groups[] = 11; + + // filter by pasaed options + if (!$groups) // by default do not generate loadingscreens + unset($paths[11]); + else + foreach (array_keys($paths) as $k) + if (!in_array($k, $groups)) + unset($paths[$k]); + + foreach (FileGen::$localeIds as $l) + { + if ($checkSourceDirs(Util::$localeStrings[$l].'/')) + { + $locStr = Util::$localeStrings[$l].'/'; + break; + } + } + + // manually check for enGB + if (!$locStr && $checkSourceDirs('enGB/')) + $locStr = 'enGB/'; + + // if no subdir had sufficient data, check mpq-root + if (!$locStr && !$checkSourceDirs('', $missing)) + { + FileGen::status('one or more required directories are missing:', MSG_LVL_ERROR); + foreach ($missing as $m) + FileGen::status(' - '.$m, MSG_LVL_ERROR); + + return; + } + + // init directories + foreach (array_column($paths, 1) as $subDirs) + foreach ($subDirs as $sd) + if (!FileGen::writeDir($destDir.$sd[0])) + $success = false; + + // ok, departure from std::procedure here + // scan ItemDisplayInfo.dbc and SpellIcon.dbc for expected images and save them to an array + // load all icon paths into another array and xor these two + // excess entries for the directory are fine, excess entries for the dbc's are not + $dbcEntries = []; + + if (isset($paths[0]) || isset($paths[1])) // generates icons or glyphs + { + $spellIcon = new DBC('SpellIcon'); + if (isset($paths[0]) && !isset($paths[1])) + $siRows = $spellIcon->readFiltered(function(&$val) { return !stripos($val['iconPath'], 'glyph-rune'); }); + else if (!isset($paths[0]) && isset($paths[1])) + $siRows = $spellIcon->readFiltered(function(&$val) { return stripos($val['iconPath'], 'glyph-rune'); }); + else + $siRows = $spellIcon->readArbitrary(); + + foreach ($siRows as $row) + $dbcEntries[] = sprintf('setup/mpqdata/%s', $locStr).strtr($row['iconPath'], ['\\' => '/']).'.blp'; + } + + if (isset($paths[0])) + { + $itemDisplayInfo = new DBC('ItemDisplayInfo'); + foreach ($itemDisplayInfo->readArbitrary() as $row) + $dbcEntries[] = sprintf($imgPath, $locStr).'Icons/'.$row['inventoryIcon1'].'.blp'; + + $holidays = new DBC('Holidays'); + $holiRows = $holidays->readFiltered(function(&$val) { return !empty($val['textureString']); }); + foreach ($holiRows as $row) + $dbcEntries[] = sprintf($imgPath, $locStr).'Calendar/Holidays/'.$row['textureString'].'Start.blp'; + } + + // case-insensitive array_unique *vomits silently into a corner* + $dbcEntries = array_intersect_key($dbcEntries, array_unique(array_map('strtolower',$dbcEntries))); + + $allPaths = []; + foreach ($paths as $i => $p) + { + $path = sprintf($imgPath, $locStr).$p[0]; + if (!FileGen::fileExists($path)) + continue; + + $files = glob($path.$p[2], GLOB_BRACE); + $allPaths = array_merge($allPaths, $files); + + FileGen::status('processing '.count($files).' files in '.$path.'...'); + + $j = 0; + foreach ($files as $f) + { + ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time) + + $src = null; + $img = explode('.', array_pop(explode('/', $f))); + array_pop($img); // there are a hand full of images with multiple file endings or random dots in the name + $img = implode('.', $img); + + // file not from dbc -> name from array or skip file + if (!empty($cuNames[$i])) + { + if (!empty($cuNames[$i][strtolower($img)])) + $img = $cuNames[$i][strtolower($img)]; + else if (!$p[4]) + { + $j += count($p[1]); + FileGen::status('skipping extraneous file '.$img.' (+'.count($p[1]).')'); + continue; + } + } + + $nFiles = count($p[1]) * ($p[4] ? array_sum(array_map('count', $cuNames[$i])) : count($files)); + + foreach ($p[1] as $info) + { + if ($p[4]) + { + foreach ($cuNames[$i] as $y => $row) + { + foreach ($row as $x => $name) + { + $j++; + $img = $p[3] ? strtolower($name) : $name; + $done = ' - '.str_pad($j.'/'.$nFiles, 12).str_pad('('.number_format($j * 100 / $nFiles, 2).'%)', 9); + + if (!isset(FileGen::$cliOpts['force']) && file_exists($destDir.$info[0].$img.'.'.$info[1])) + { + FileGen::status($done.' - file '.$info[0].$img.'.'.$info[1].' was already processed'); + continue; + } + + if (!$src) + $src = imagecreatefromblp($f); + + if (!$src) // error should be created by imagecreatefromblp + continue; + + $from = array( + 'x' => $info[4] + $p[4] * $x, + 'y' => $info[4] + $p[4] * $y, + 'w' => $p[4] - $info[4] * 2, + 'h' => $p[4] - $info[4] * 2 + ); + $to = array( + 'x' => 0, + 'y' => 0, + 'w' => $info[3], + 'h' => $info[3] + ); + + if (!$writeImage($destDir.$info[0].$img, $info[1], $src, $from, $to, $done)) + $success = false; + } + } + + // custom handle for combined icon 'quest_startend' + /* not used due to alphaChannel issues + if ($p[4] == 32) + { + $dest = imagecreatetruecolor(19, 16); + imagesavealpha($dest, true); + imagealphablending($dest, true); + + // excalmationmark, questionmark + imagecopyresampled($dest, $src, 0, 1, 32 + 5, 32 + 2, 8, 15, 18, 30); + imagecopyresampled($dest, $src, 5, 0, 64 + 1, 32 + 1, 10, 16, 18, 28); + + if (imagegif($dest, $destDir.$info[0].'quest_startend.gif')) + FileGen::status(' extra - image '.$destDir.$info[0].'quest_startend.gif written', MSG_LVL_OK); + else + { + FileGen::status(' extra - could not create image '.$destDir.$info[0].'quest_startend.gif', MSG_LVL_ERROR); + $success = false; + } + + imagedestroy($dest); + } + */ + } + else + { + // icon -> lowercase + if ($p[3]) + $img = strtolower($img); + + $j++; + $done = ' - '.str_pad($j.'/'.$nFiles, 12).str_pad('('.number_format($j * 100 / $nFiles, 2).'%)', 9); + + if (!isset(FileGen::$cliOpts['force']) && file_exists($destDir.$info[0].$img.'.'.$info[1])) + { + FileGen::status($done.' - file '.$info[0].$img.'.'.$info[1].' was already processed'); + continue; + } + + if (!$src) + $src = imagecreatefromblp($f); + + if (!$src) // error should be created by imagecreatefromblp + continue; + + $from = array( + 'x' => $info[4], + 'y' => $info[4], + 'w' => ($info[2] ?: imagesx($src)) - $info[4] * 2, + 'h' => ($info[2] ?: imagesy($src)) - $info[4] * 2 + ); + $to = array( + 'x' => 0, + 'y' => 0, + 'w' => $info[3] ?: imagesx($src), + 'h' => $info[3] ?: imagesy($src) + ); + + if (!$writeImage($destDir.$info[0].$img, $info[1], $src, $from, $to, $done)) + $success = false; + } + } + + unset($src); + } + } + + // reset execTime + ini_set('max_execution_time', FileGen::$defaultExecTime); + + if ($missing = array_diff(array_map('strtolower', $dbcEntries), array_map('strtolower', $allPaths))) + { + asort($missing); + FileGen::status('the following '.count($missing).' images where referenced by DBC but not in the mpqData directory. They may need to be converted by hand later on.', MSG_LVL_WARN); + foreach ($missing as $m) + FileGen::status(' - '.$m); + } + + return $success; + } diff --git a/setup/tools/filegen/statistics.func.php b/setup/tools/filegen/statistics.func.php index 288fa571..084cdac7 100644 --- a/setup/tools/filegen/statistics.func.php +++ b/setup/tools/filegen/statistics.func.php @@ -5,11 +5,25 @@ if (!defined('AOWOW_REVISION')) // Create 'statistics'-file in datasets - // this script requires the following dbcs to be parsed and available - // gtChanceToMeleeCrit.dbc, gtChanceToSpellCrit.dbc, gtChanceToMeleeCritBase.dbc, gtChanceToSpellCritBase.dbc, gtOCTRegenHP, gtRegenHPPperSpt.dbc, gtRegenMPPerSpt.dbc + // this script requires the following dbcs to be available + // gtChanceToMeleeCrit.dbc, gtChanceToSpellCrit.dbc, gtChanceToMeleeCritBase.dbc, gtChanceToSpellCritBase.dbc, gtOCTRegenHP.dbc, gtRegenMPPerSpt.dbc, gtRegenHPPerSpt.dbc - function statistics(&$log) + function statistics() { + // expected dbcs + $req = ['dbc_gtchancetomeleecrit', 'dbc_gtchancetomeleecritbase', 'dbc_gtchancetospellcrit', 'dbc_gtchancetospellcritbase', 'dbc_gtoctregenhp', 'dbc_gtregenmpperspt', 'dbc_gtregenhpperspt']; + $found = DB::Aowow()->selectCol('SHOW TABLES LIKE "dbc_%"'); + if ($missing = array_diff($req, $found)) + { + foreach ($missing as $m) + { + $file = explode('_', $m)[1]; + $dbc = new DBC($file); + if ($dbc->readFromFile()) + $dbc->writeToDB(); + } + } + $classs = function() { // constants and mods taken from TrinityCore (Player.cpp, StatSystem.cpp) @@ -42,7 +56,7 @@ if (!defined('AOWOW_REVISION')) ); foreach ($dataz as $class => &$data) - $data[2] = array_values(DB::Aowow()->selectRow('SELECT mle.chance*100 cMle, spl.chance*100 cSpl FROM dbc.gtchancetomeleecritbase mle, dbc.gtchancetospellcritbase spl WHERE mle.idx = spl.idx AND mle.idx = ?d', $class - 1)); + $data[2] = array_values(DB::Aowow()->selectRow('SELECT mle.chance*100 cMle, spl.chance*100 cSpl FROM dbc_gtchancetomeleecritbase mle, dbc_gtchancetospellcritbase spl WHERE mle.idx = spl.idx AND mle.idx = ?d', $class - 1)); return $dataz; }; @@ -89,10 +103,10 @@ if (!defined('AOWOW_REVISION')) $rows = DB::Aowow()->select('SELECT pls.level AS ARRAY_KEY, str-?d, agi-?d, sta-?d, inte-?d, spi-?d, basehp, IF(basemana <> 0, basemana, 100), mlecrt.chance*100, splcrt.chance*100, mlecrt.chance*100 * ?f, baseHP5.ratio*1, extraHP5.ratio*1 ' . 'FROM player_levelstats pls JOIN player_classlevelstats pcls ON pls.level = pcls.level AND pls.class = pcls.class JOIN' . - ' dbc.gtchancetomeleecrit mlecrt ON mlecrt.idx = ((pls.class - 1) * 100) + (pls.level - 1) JOIN' . - ' dbc.gtchancetospellcrit splcrt ON splcrt.idx = ((pls.class - 1) * 100) + (pls.level - 1) JOIN' . - ' dbc.gtoctregenhp baseHP5 ON baseHP5.idx = ((pls.class - 1) * 100) + (pls.level - 1) JOIN' . - ' dbc.gtregenhpperspt extraHP5 ON extraHP5.idx = ((pls.class - 1) * 100) + (pls.level - 1) ' . + ' dbc_gtchancetomeleecrit mlecrt ON mlecrt.idx = ((pls.class - 1) * 100) + (pls.level - 1) JOIN' . + ' dbc_gtchancetospellcrit splcrt ON splcrt.idx = ((pls.class - 1) * 100) + (pls.level - 1) JOIN' . + ' dbc_gtoctregenhp baseHP5 ON baseHP5.idx = ((pls.class - 1) * 100) + (pls.level - 1) JOIN' . + ' dbc_gtregenhpperspt extraHP5 ON extraHP5.idx = ((pls.class - 1) * 100) + (pls.level - 1) ' . 'WHERE pls.race = ?d AND pls.class = ?d ORDER BY pls.level ASC', $offset[0], $offset[1], $offset[2], $offset[3], $offset[4], $mod, @@ -114,7 +128,7 @@ if (!defined('AOWOW_REVISION')) // identical across classes (just use one, that acutally has mana (offset: 100)) // content of gtRegenMPPerSpt.dbc - return DB::Aowow()->selectCol('SELECT idx-99 AS ARRAY_KEY, ratio FROM dbc.gtregenmpperspt WHERE idx >= 100 AND idx < 100 + ?d', MAX_LEVEL); + return DB::Aowow()->selectCol('SELECT idx-99 AS ARRAY_KEY, ratio FROM dbc_gtregenmpperspt WHERE idx >= 100 AND idx < 100 + ?d', MAX_LEVEL); }; $skills = function() @@ -135,15 +149,14 @@ if (!defined('AOWOW_REVISION')) $out[$s] = $res; if (!$res) { - $log[] = [time(), ' error: statistics - generator $'.$s.'() returned empty']; + FileGen::status('statistics - generator $'.$s.'() returned empty', MSG_LVL_WARN); $success = false; } } - $json = json_encode($out, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - $toFile = 'g_statistics = '.preg_replace('/"\$([^$"]+)"/', '\1', $json).';'; + $toFile = 'g_statistics = '.preg_replace('/"\$([^$"]+)"/', '\1', Util::toJSON($out)).';'; - if (!writeFile('datasets/statistics', $toFile, $log)) + if (!FileGen::writeFile('datasets/statistics', $toFile)) $success = false; return $success; diff --git a/setup/tools/filegen/talentIcons.func.php b/setup/tools/filegen/talentIcons.func.php index ca8a26b5..30546328 100644 --- a/setup/tools/filegen/talentIcons.func.php +++ b/setup/tools/filegen/talentIcons.func.php @@ -6,21 +6,35 @@ if (!defined('AOWOW_REVISION')) // builds image-textures for the talent-calculator // spellIcons must be extracted and converted to at least medium size - // this script requires the following dbc-files to be parsed and available - // Talent, TalentTab, Spell + // this script requires the following dbc-files to be available + // Talent.dbc, TalentTab.dbc - function talentIcons(&$log) + function talentIcons() { + // expected dbcs + $req = ['dbc_talenttab', 'dbc_talent']; + $found = DB::Aowow()->selectCol('SHOW TABLES LIKE "dbc_%"'); + if ($missing = array_diff($req, $found)) + { + foreach ($missing as $m) + { + $file = explode('_', $m)[1]; + $dbc = new DBC($file); + if ($dbc->readFromFile($file == 'talenttab')) + $dbc->writeToDB(); + } + } + $success = true; - $query = 'SELECT s.iconString FROM ?_spell s JOIN dbc.talent t ON t.rank1 = s.Id JOIN dbc.talenttab tt ON tt.Id = t.tabId WHERE tt.?# = ?d AND tt.tabNumber = ?d ORDER BY t.row, t.column, t.petCategory1 ASC;'; + $query = 'SELECT s.iconString FROM ?_spell s JOIN dbc_talent t ON t.rank1 = s.Id JOIN dbc_talenttab tt ON tt.Id = t.tabId WHERE tt.?# = ?d AND tt.tabNumber = ?d ORDER BY t.row, t.column, t.petCategory1 ASC;'; $dims = 36; //v-pets $filenames = ['icons', 'warrior', 'paladin', 'hunter', 'rogue', 'priest', 'deathknight', 'shaman', 'mage', 'warlock', null, 'druid']; // create directory if missing - if (!writeDir('static/images/wow/talents/icons', $log)) + if (!FileGen::writeDir('static/images/wow/talents/icons')) $success = false; - if (!writeDir('static/images/wow/hunterpettalents', $log)) + if (!FileGen::writeDir('static/images/wow/hunterpettalents')) $success = false; foreach ($filenames as $k => $v) @@ -41,7 +55,7 @@ if (!defined('AOWOW_REVISION')) if (empty($icons)) { - $log[] = [time(), ' error: talentIcons - query for '.$v.' tree: '.$k.' empty']; + FileGen::status('talentIcons - query for '.$v.' tree: '.$k.' returned empty', MSG_LVL_ERROR); $success = false; continue; } @@ -53,7 +67,7 @@ if (!defined('AOWOW_REVISION')) $imgFile = 'static/images/wow/icons/medium/'.strtolower($icons[$i]).'.jpg'; if (!file_exists($imgFile)) { - $log[] = [time(), ' error: talentIcons - raw image '.$imgFile. ' not found']; + FileGen::status('talentIcons - raw image '.$imgFile. ' not found', MSG_LVL_ERROR); $success = false; break; } @@ -77,17 +91,17 @@ if (!defined('AOWOW_REVISION')) } if (@imagejpeg($res, $outFile)) - $log[] = [time(), sprintf(ERR_NONE, $outFile)]; + FileGen::status(sprintf(ERR_NONE, $outFile), MSG_LVL_OK); else { $success = false; - $log[] = [time(), ' error: talentIcons - '.$outFile.'.jpg could not be written!']; + FileGen::status('talentIcons - '.$outFile.'.jpg could not be written', MSG_LVL_ERROR); } } else { $success = false; - $log[] = [time(), ' error: talentIcons - image resource not created']; + FileGen::status('talentIcons - image resource not created', MSG_LVL_ERROR); continue; } } diff --git a/setup/tools/filegen/talents.func.php b/setup/tools/filegen/talents.func.php index efc656c7..6cbda54b 100644 --- a/setup/tools/filegen/talents.func.php +++ b/setup/tools/filegen/talents.func.php @@ -5,8 +5,8 @@ if (!defined('AOWOW_REVISION')) // builds talent-tree-data for the talent-calculator - // this script requires the following dbc-files to be parsed and available - // Talent, TalentTab, Spell, CreatureFamily + // this script requires the following dbc-files to be available + // Talent.dbc, TalentTab.dbc // talents // i - int talentId (id of aowow_talent) @@ -25,8 +25,22 @@ if (!defined('AOWOW_REVISION')) // t - array of talent-objects // f - array:int [pets only] creatureFamilies in that category - function talents(&$log, $locales) + function talents() { + // expected dbcs + $req = ['dbc_talenttab', 'dbc_talent']; + $found = DB::Aowow()->selectCol('SHOW TABLES LIKE "dbc_%"'); + if ($missing = array_diff($req, $found)) + { + foreach ($missing as $m) + { + $file = explode('_', $m)[1]; + $dbc = new DBC($file); + if ($dbc->readFromFile($file == 'talenttab')) + $dbc->writeToDB(); + } + } + $success = true; $buildTree = function ($class) use (&$petFamIcons, &$tSpells) { @@ -35,19 +49,12 @@ if (!defined('AOWOW_REVISION')) $mask = $class ? 1 << ($class - 1) : 0; // All "tabs" of a given class talent - $tabs = DB::Aowow()->select('SELECT * FROM dbc.talenttab WHERE classMask = ?d ORDER BY `tabNumber`, `creatureFamilyMask`', $mask); + $tabs = DB::Aowow()->select('SELECT * FROM dbc_talenttab WHERE classMask = ?d ORDER BY `tabNumber`, `creatureFamilyMask`', $mask); $result = []; for ($l = 0; $l < count($tabs); $l++) { - $talents = DB::Aowow()->select(' - SELECT t.id AS tId, t.*, s.* - FROM dbc.talent t, ?_spell s - WHERE t.`tabId`= ?d AND s.`Id` = t.`rank1` - ORDER by t.`row`, t.`column`', - $tabs[$l]['Id'] - ); - + $talents = DB::Aowow()->select('SELECT t.id AS tId, t.*, s.* FROM dbc_talent t, ?_spell s WHERE t.`tabId`= ?d AND s.`Id` = t.`rank1` ORDER by t.`row`, t.`column`', $tabs[$l]['Id']); $result[$l] = array( 'n' => Util::localizedString($tabs[$l], 'name'), 't' => [] @@ -163,13 +170,13 @@ if (!defined('AOWOW_REVISION')) // check directory-structure foreach (Util::$localeStrings as $dir) - if (!writeDir('datasets/'.$dir, $log)) + if (!FileGen::writeDir('datasets/'.$dir)) $success = false; - $tSpellIds = DB::Aowow()->selectCol('SELECT rank1 FROM dbc.talent UNION SELECT rank2 FROM dbc.talent UNION SELECT rank3 FROM dbc.talent UNION SELECT rank4 FROM dbc.talent UNION SELECT rank5 FROM dbc.talent'); + $tSpellIds = DB::Aowow()->selectCol('SELECT rank1 FROM dbc_talent UNION SELECT rank2 FROM dbc_talent UNION SELECT rank3 FROM dbc_talent UNION SELECT rank4 FROM dbc_talent UNION SELECT rank5 FROM dbc_talent'); $tSpells = new SpellList(array(['s.id', $tSpellIds], CFG_SQL_LIMIT_NONE)); - foreach ($locales as $lId) + foreach (FileGen::$localeIds as $lId) { User::useLocale($lId); Lang::load(Util::$localeStrings[$lId]); @@ -181,9 +188,9 @@ if (!defined('AOWOW_REVISION')) $cId = log($cMask, 2) + 1; $file = 'datasets/'.User::$localeString.'/talents-'.$cId; - $toFile = '$WowheadTalentCalculator.registerClass('.$cId.', '.json_encode($buildTree($cId), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).')'; + $toFile = '$WowheadTalentCalculator.registerClass('.$cId.', '.Util::toJSON($buildTree($cId)).')'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } @@ -191,14 +198,14 @@ if (!defined('AOWOW_REVISION')) if (empty($petIcons)) { $pets = DB::Aowow()->SelectCol('SELECT Id AS ARRAY_KEY, iconString FROM ?_pet'); - $petIcons = json_encode($pets, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK); + $petIcons = Util::toJSON($pets); } $toFile = "var g_pet_icons = ".$petIcons.";\n\n"; - $toFile .= 'var g_pet_talents = '.json_encode($buildTree(0), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK).';'; + $toFile .= 'var g_pet_talents = '.Util::toJSON($buildTree(0)).';'; $file = 'datasets/'.User::$localeString.'/pet-talents'; - if (!writeFile($file, $toFile, $log)) + if (!FileGen::writeFile($file, $toFile)) $success = false; } diff --git a/setup/tools/filegen/templates/profile_all.js.in b/setup/tools/filegen/templates/profile_all.js.in index f7ca0eda..d4e003e6 100644 --- a/setup/tools/filegen/templates/profile_all.js.in +++ b/setup/tools/filegen/templates/profile_all.js.in @@ -1,4 +1,4 @@ -var mn_profiles = /*setup:mnProfiles*/; +var mn_profiles = /*setup:realmMenu*/; var mn_guilds = $.extend(true,[],mn_profiles); var mn_arenateams = $.extend(true,[],mn_profiles); diff --git a/setup/tools/imagecreatefromblp.php b/setup/tools/imagecreatefromblp.php new file mode 100644 index 00000000..4c319b56 --- /dev/null +++ b/setup/tools/imagecreatefromblp.php @@ -0,0 +1,318 @@ + + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + // Usage example: + // $img = imagecreatefromblp("fileName.blp"); + // imagejpeg($img); + // imagedestroy($img); + + function imagecreatefromblp($fileName, $imgId = 0) + { + if (!FileGen::fileExists($fileName)) + { + FileGen::status('file '.$fileName.' could not be found', MSG_LVL_ERROR); + return; + } + + $file = fopen($fileName, 'rb'); + + if (!$file) + { + FileGen::status('could not open file '.$fileName, MSG_LVL_ERROR); + return; + } + + $fileSize = fileSize($fileName); + if ($fileSize < 16) + { + FileGen::status('file '.$fileName.' is too small for a BLP file', MSG_LVL_ERROR); + return; + } + + $data = fread($file, $fileSize); + fclose($file); + + // predict replacement patch files + // ref: http://www.zezula.net/en/mpq/patchfiles.html + if (substr($data, 0x0, 0x4) == "PTCH") + { + // strip patch header + if (substr($data, 0x40, 0x43) == "COPY") + $data = substr($data, 0x44); + else + { + FileGen::status('file '.$fileName.' is an incremental patch file and cannot be used by this script.', MSG_LVL_ERROR); + return; + } + } + + if (substr($data, 0, 4) != "BLP2") + { + FileGen::status('file '.$fileName.' has incorrect/unsupported magic bytes', MSG_LVL_ERROR); + return; + } + + $header = unpack("Vformat/Ctype/CalphaBits/CalphaType/Cmips/Vwidth/Vheight", substr($data, 4, 16)); + $header['mipsOffs'] = unpack("V16", substr($data, 20, 64)); + $header['mipsSize'] = unpack("V16", substr($data, 84, 64)); + + $debugStr = ' header = '.print_r($header, true); + + if ($header['format'] != 1) + { + FileGen::status('file '.$fileName.' has unsupported format'.$debugStr, MSG_LVL_ERROR); + return; + } + + $offs = $header['mipsOffs'][$imgId + 1]; + $size = $header['mipsSize'][$imgId + 1]; + + while ($imgId > 0) + { + $header['width'] /= 2; + $header['height'] /= 2; + $imgId--; + } + + if ($size == 0) + { + FileGen::status('file '.$fileName.' contains zeroes in a mips table'.$debugStr, MSG_LVL_ERROR); + return; + } + if ($offs + $size > $fileSize) + { + FileGen::status('file '.$fileName.' is corrupted/incomplete'.$debugStr, MSG_LVL_ERROR); + return; + } + + if ($header['type'] == 1) + $img = icfb1($header['width'], $header['height'], substr($data, 148, 1024), substr($data, $offs, $size)); + else if ($header['type'] == 2) + $img = icfb2($header['width'], $header['height'], substr($data, $offs, $size), $header['alphaBits'], $header['alphaType']); + else if ($header['type'] == 3) + $img = icfb3($header['width'], $header['height'], substr($data, $offs, $size)); + else + { + FileGen::status('file '.$fileName.' has unsupported type'.$debugStr, MSG_LVL_ERROR); + return; + } + + return $img; + } + + // uncompressed + function icfb1($width, $height, $palette, $data) + { + $img = imagecreatetruecolor($width, $height); + imagesavealpha($img, true); + imagealphablending($img, false); + + $t = unpack("V256", $palette); + $i = unpack("C*", $data); + + for ($y = 0; $y < $height; $y++) + { + for ($x = 0; $x < $width; $x++) + { + $c = $t[$i[$x + $y * $width+ 1 ] + 1]; + $c = imagecolorallocatealpha($img, ($c >> 16) & 255, ($c >> 8) & 255, $c & 255, (($c >> 24) & 255) >> 1); + imagesetpixel($img, $x, $y, $c); + imagecolordeallocate($img, $c); + } + } + + return $img; + } + + // DXTC + function icfb2($width, $height, $data, $alphaBits, $alphaType) + { + if (!in_array($alphaBits * 10 + $alphaType, [0, 10, 41, 81, 87, 88])) + { + FileGen::status('unsupported compression type', MSG_LVL_ERROR); + return; + } + + $img = imagecreatetruecolor($width, $height); + imagesavealpha($img, true); + imagealphablending($img, false); + + $offset = 0; + for ($offy = 0; $offy < $height; $offy += 4) + { + for ($offx = 0; $offx < $width; $offx += 4) + { + $alpha = []; + if ($alphaBits > 1) + { + if ($alphaType <= 1) + { + $a = unpack("V2", substr($data, $offset, 8)); + $a1 = $a[1]; + $a2 = $a[2]; + + for ($i = 0; $i < 8; $i++, $a1 >>= 4) + $alpha[$i] = (($a1 & 15) << 4) | ($a1 & 15); + + for ($i = 8; $i < 16; $i++, $a2 >>= 4) + $alpha[$i] = (($a2 & 15) << 4) | ($a2 & 15); + } + else + { + $c = unpack("C2", substr($data, $offset, 2)); + $t = [$c[1], $c[2]]; + + if ($t[0] <= $t[1]) + { + $t[2] = (4 * $t[0] + $t[1]) / 5; + $t[3] = (3 * $t[0] + 2 * $t[1]) / 5; + $t[4] = (2 * $t[0] + 3 * $t[1]) / 5; + $t[5] = ( $t[0] + 4 * $t[1]) / 5; + $t[6] = 0; + $t[7] = 255; + } + else + { + $t[2] = (6 * $t[0] + $t[1]) / 7; + $t[3] = (5 * $t[0] + 2 * $t[1]) / 7; + $t[4] = (4 * $t[0] + 3 * $t[1]) / 7; + $t[5] = (3 * $t[0] + 4 * $t[1]) / 7; + $t[6] = (2 * $t[0] + 5 * $t[1]) / 7; + $t[7] = ( $t[0] + 6 * $t[1]) / 7; + } + + $a = unpack("C6", substr($data, $offset + 2, 6)); + $a1 = $a[1] | ($a[2] << 8) | ($a[3] << 16); + $a2 = $a[4] | ($a[5] << 8) | ($a[6] << 16); + + for ($i = 0; $i < 8; $i++, $a1 >>= 3) + $alpha[$i] = $t[$a1 & 7]; + + for ($i = 8; $i < 16; $i++, $a2 >>= 3) + $alpha[$i] = $t[$a2 & 7]; + } + + $offset += 8; + } + + $c0 = unpack("v", substr($data, $offset, 2))[1]; + + $t = []; + $t[0] = array( + 'r' => (($c0 >> 8) & 0xF8) | (($c0 >> 13) & 7), + 'g' => (($c0 >> 3) & 0xFC) | (($c0 >> 9) & 3), + 'b' => (($c0 << 3) & 0xF8) | (($c0 >> 2) & 7), + 'a' => 0 + ); + + $c1 = unpack("v", substr($data, $offset + 2, 2))[1]; + + $t[1] = array( + 'r' => (($c1 >> 8) & 0xF8) | (($c1 >> 13) & 7), + 'g' => (($c1 >> 3) & 0xFC) | (($c1 >> 9) & 3), + 'b' => (($c1 << 3) & 0xF8) | (($c1 >> 2) & 7), + 'a' => 0 + ); + + if (($c0 <= $c1) && ($alphaBits <= 1)) + { + $t[2] = array( + 'r' => ($t[0]['r'] + $t[1]['r']) / 2, + 'g' => ($t[0]['g'] + $t[1]['g']) / 2, + 'b' => ($t[0]['b'] + $t[1]['b']) / 2, + 'a' => 0 + ); + + if ($alphaBits == 1) + $t[3] = ['r' => 0, 'g' => 0, 'b' => 0, 'a' => 255]; + else + $t[3] = ['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]; + } + else + { + $t[2] = array( + 'r' => (2 * $t[0]['r'] + $t[1]['r']) / 3, + 'g' => (2 * $t[0]['g'] + $t[1]['g']) / 3, + 'b' => (2 * $t[0]['b'] + $t[1]['b']) / 3, + 'a' => 0 + ); + $t[3] = array( + 'r' => ($t[0]['r'] + 2 * $t[1]['r']) / 3, + 'g' => ($t[0]['g'] + 2 * $t[1]['g']) / 3, + 'b' => ($t[0]['b'] + 2 * $t[1]['b']) / 3, + 'a' => 0 + ); + } + + if ($alphaBits > 1) + { + $i = unpack("V", substr($data, $offset + 4, 4))[1]; + + for ($y = 0; $y < 4; $y++) + { + for ($x = 0; $x < 4; $x++, $i >>= 2) + { + $color = imagecolorallocatealpha($img, $t[$i & 3]['r'], $t[$i & 3]['g'], $t[$i & 3]['b'], (255 - $alpha[$x + $y * 4]) / 2); + imagesetpixel($img, $offx + $x, $offy + $y, $color); + imagecolordeallocate($img, $color); + } + } + } + else + { + $c = []; + for ($i = 0; $i < 4; $i++) + $c[$i] = imagecolorallocatealpha($img, $t[$i]['r'], $t[$i]['g'], $t[$i]['b'], $t[$i]['a'] / 2); + + $i = unpack("V", substr($data, $offset + 4, 4))[1]; + for ($y = 0; $y < 4; $y++) + for ($x = 0; $x < 4; $x++, $i >>= 2) + imagesetpixel($img, $offx + $x, $offy + $y, $c[$i & 3]); + + for ($i = 0; $i < 4; $i++) + imagecolordeallocate($img, $c[$i]); + } + + $offset += 8; + } + } + + return $img; + } + + // plain + function icfb3($width, $height, $data) + { + $img = imagecreatetruecolor($width, $height); + $i = unpack("V*", $data); + + for ($y = 0; $y < $height; $y++) + { + for ($x = 0; $x < $width; $x++) + { + $c = $i[$x + $y * $width + 1]; + $c = imagecolorallocate($img, ($c >> 16) & 255, ($c >> 8) & 255, $c & 255); + imagesetpixel($img, $x, $y, $c); + imagecolordeallocate($img, $c); + } + } + + return $img; + } +?>