['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( // uses nameINT from ChrRaces.dbc ['race_human_male', 'race_dwarf_male', 'race_gnome_male', 'race_nightelf_male', 'race_draenei_male' ], ['race_tauren_male', 'race_scourge_male', 'race_troll_male', 'race_orc_male', 'race_bloodelf_male' ], ['race_human_female', 'race_dwarf_female', 'race_gnome_female', 'race_nightelf_female', 'race_draenei_female' ], ['race_tauren_female', 'race_scourge_female', 'race_troll_female', 'race_orc_female', 'race_bloodelf_female'] ), 4 => array( // uses nameINT from ChrClasses.dbc ['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); if ($ext == 'png') 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: CLISetup::log($done.' - unsupported file fromat: '.$ext, CLISetup::LOG_WARN); } imagedestroy($dest); if ($ok) { chmod($name.'.'.$ext, Util::FILE_ACCESS); CLISetup::log($done.' - image '.$name.'.'.$ext.' written', CLISetup::LOG_OK); } else CLISetup::log($done.' - could not create image '.$name.'.'.$ext, CLISetup::LOG_ERROR); return $ok; }; $checkSourceDirs = function($sub, &$missing = []) use ($imgPath, $dbcPath, $paths) { $hasMissing = false; foreach (array_column($paths, 0) as $subDir) { $p = sprintf($imgPath, $sub).$subDir; if (!CLISetup::fileExists($p)) { $hasMissing = true; $missing[] = $p; } } $p = sprintf($dbcPath, $sub); if (!CLISetup::fileExists($p)) { $hasMissing = true; $missing[] = $p; } return !$hasMissing; }; 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 (CLISetup::$expectedPaths as $xp => $__) { if ($xp) // if in subDir add trailing slash $xp .= '/'; if ($checkSourceDirs($xp, $missing)) { $locStr = $xp; break; } } // if no subdir had sufficient data, diaf if ($locStr === null) { CLISetup::log('one or more required directories are missing:', CLISetup::LOG_ERROR); foreach ($missing as $m) CLISetup::log(' - '.$m, CLISetup::LOG_ERROR); return; } // init directories foreach (array_column($paths, 1) as $subDirs) foreach ($subDirs as $sd) if (!CLISetup::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 { if (isset($paths[0]) && !isset($paths[1])) $siRows = DB::Aowow()->selectCol('SELECT iconPath FROM dbc_spellicon WHERE iconPath NOT LIKE "%glyph-rune%"'); else if (!isset($paths[0]) && isset($paths[1])) $siRows = DB::Aowow()->selectCol('SELECT iconPath FROM dbc_spellicon WHERE iconPath LIKE "%glyph-rune%"'); else $siRows = DB::Aowow()->selectCol('SELECT iconPath FROM dbc_spellicon'); foreach ($siRows as $icon) $dbcEntries[] = strtolower(sprintf('setup/mpqdata/%s', $locStr).strtr($icon, ['\\' => '/']).'.blp'); } if (isset($paths[0])) { $itemIcons = DB::Aowow()->selectCol('SELECT inventoryIcon1 FROM dbc_itemdisplayinfo WHERE inventoryIcon1 <> ""'); foreach ($itemIcons as $icon) $dbcEntries[] = strtolower(sprintf($imgPath, $locStr).'Icons/'.$icon.'.blp'); $eventIcons = DB::Aowow()->selectCol('SELECT textureString FROM dbc_holidays WHERE textureString <> ""'); foreach ($eventIcons as $icon) $dbcEntries[] = strtolower(sprintf($imgPath, $locStr).'Calendar/Holidays/'.$icon.'Start.blp'); } // case-insensitive array_unique *vomits silently into a corner* $dbcEntries = array_intersect_key($dbcEntries, array_unique($dbcEntries)); $allPaths = []; foreach ($paths as $i => $p) { $path = sprintf($imgPath, $locStr).$p[0]; if (!CLISetup::fileExists($path)) continue; $files = glob($path.$p[2], GLOB_BRACE); $allPaths = array_merge($allPaths, $files); CLISetup::log('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]); CLISetup::log('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])) { CLISetup::log($done.' - file '.$info[0].$img.'.'.$info[1].' was already processed'); continue; } if (!$src) $src = $loadImageFile($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')) CLISetup::log(' extra - image '.$destDir.$info[0].'quest_startend.gif written', CLISetup::LOG_OK); else { CLISetup::log(' extra - could not create image '.$destDir.$info[0].'quest_startend.gif', CLISetup::LOG_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])) { CLISetup::log($done.' - file '.$info[0].$img.'.'.$info[1].' was already processed'); continue; } if (!$src) $src = $loadImageFile($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); CLISetup::log('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.', CLISetup::LOG_WARN); foreach ($missing as $m) CLISetup::log(' - '.$m); } return $success; }