|
| 1 | +<?php |
| 2 | +require_once 'UnityBundle.php'; |
| 3 | + |
| 4 | +chdir(__DIR__); |
| 5 | +$logFile = fopen('llas.log', 'a'); |
| 6 | +function _log($s) { |
| 7 | + global $logFile; |
| 8 | + fwrite($logFile, date('[m/d H:i] ').$s."\n"); |
| 9 | + echo date('[m/d H:i] ').$s."\n"; |
| 10 | +} |
| 11 | + |
| 12 | +$curl = curl_init(); |
| 13 | +curl_setopt_array($curl, array( |
| 14 | + CURLOPT_URL => 'https://allstars.kirara.ca/api/v1/master_version.json', |
| 15 | + CURLOPT_HEADER=>0, |
| 16 | + CURLOPT_RETURNTRANSFER=>1, |
| 17 | + CURLOPT_SSL_VERIFYPEER=>false |
| 18 | +)); |
| 19 | + |
| 20 | +$verInfo = json_decode(curl_exec($curl), true); |
| 21 | +if (empty($verInfo) || empty($verInfo['version'])) { |
| 22 | + _log('fetch version info failed'); |
| 23 | + exit; |
| 24 | +} |
| 25 | +$version = $verInfo['version']; |
| 26 | +if ($version == trim(file_get_contents('data/!masterdata_version.txt'))) { |
| 27 | + _log('no update'); |
| 28 | + exit; |
| 29 | +} |
| 30 | +_log("new version: $version"); |
| 31 | +$urlBase = "https://jp-real-prod-v4tadlicuqeeumke.api.game25.klabgames.net/ep1001/static/$version"; |
| 32 | +curl_setopt($curl, CURLOPT_URL, "$urlBase/masterdata_i_ja"); |
| 33 | +$masterdata = curl_exec($curl); |
| 34 | + |
| 35 | +$f = new MemoryStream($masterdata); |
| 36 | +$f->position = 20; |
| 37 | +$hash = ReadString($f); |
| 38 | +$lang = ReadString($f); |
| 39 | +$rows = ReadInt($f); |
| 40 | + |
| 41 | +$out = []; |
| 42 | +for ($i=0; $i < $rows; $i++) { |
| 43 | + $v15 = ReadString($f); // db name |
| 44 | + $v16 = ReadString($f); // db keys |
| 45 | + $out[] = [$v15, $v16, GetKeyData($v16)]; |
| 46 | +} |
| 47 | +for ($i=0; $i < $rows; $i++) { |
| 48 | + $out[$i][] = bin2hex($f->readData(20)); // download sha1 hash |
| 49 | + $out[$i][] = ReadInt($f); // download file size |
| 50 | +} |
| 51 | +$db = new PDO('sqlite::memory:'); |
| 52 | +$db->query('pragma JOURNAL_MODE=MEMORY'); |
| 53 | +$db->query('CREATE TABLE masterdata(name TEXT NOT NULL, keys TEXT NOT NULL, hash TEXT NOT NULL, size INTEGER NOT NULL)'); |
| 54 | +$insert = $db->prepare('INSERT INTO masterdata (name,keys,hash,size) VALUES (?,?,?,?)'); |
| 55 | +foreach ($out as $row) { |
| 56 | + $insert->execute([$row[0], $row[1], $row[3], $row[4]]); |
| 57 | +} |
| 58 | +chdir('data'); |
| 59 | +$output = []; |
| 60 | +exec('git rm -rf --ignore-unmatch -q */', $output); |
| 61 | +chdir('..'); |
| 62 | +exportSqlite($db, 'data'); |
| 63 | + |
| 64 | +$constKeys = [ |
| 65 | + 0x3039, // 12345 |
| 66 | + 0x10932,// 67890 |
| 67 | + 0x7AB7 // 31415 |
| 68 | +]; |
| 69 | +$retry = 0; |
| 70 | +for ($i=0; $i<count($out); $i++) { |
| 71 | + $row = $out[$i]; |
| 72 | + $keys = [ |
| 73 | + $constKeys[0] ^ $row[2][0], |
| 74 | + $constKeys[1] ^ $row[2][1], |
| 75 | + $constKeys[2] ^ $row[2][2], |
| 76 | + ]; |
| 77 | + $name = $row[0]; |
| 78 | + $hash = $row[3]; |
| 79 | + $size = $row[4]; |
| 80 | + _log("download $name, size: $size, hash: $hash"); |
| 81 | + curl_setopt($curl, CURLOPT_URL, "$urlBase/$name"); |
| 82 | + $data = curl_exec($curl); |
| 83 | + $recv = strlen($data); |
| 84 | + $recv_hash = hash('sha1', $data); |
| 85 | + if ($recv !== $size || $recv_hash !== $hash) { |
| 86 | + _log("download $name failed, received: $recv $recv_hash"); |
| 87 | + if ($retry++ < 3) $i--; |
| 88 | + continue; |
| 89 | + } |
| 90 | + |
| 91 | + $data = gzinflate(SIFAS_Decrypt_Stream($data, ...$keys)); |
| 92 | + file_put_contents('temp', $data); |
| 93 | + _log("export $name"); |
| 94 | + exportSqlite(new PDO('sqlite:temp'), "data/$name"); |
| 95 | + unlink('temp'); |
| 96 | +} |
| 97 | +file_put_contents('data/!masterdata_version.txt', "$version\n"); |
| 98 | + |
| 99 | +chdir('data'); |
| 100 | +exec('git add *', $output); |
| 101 | +exec('git commit -m "'.$version.'"', $output); |
| 102 | +exec('git push origin master', $output); |
| 103 | + |
| 104 | + |
| 105 | +function ReadByte(Stream $f) { |
| 106 | + return hexdec(bin2hex($f->byte)); |
| 107 | +} |
| 108 | +function ReadInt(Stream $f) { |
| 109 | + $val = ReadByte($f); |
| 110 | + if ($val >= 128) { |
| 111 | + $b2 = ReadByte($f); |
| 112 | + $b3 = ReadByte($f); |
| 113 | + $b4 = ReadByte($f); |
| 114 | + $val = $val + (($b2 + (($b3 + ($b4 << 8)) << 8)) << 7); |
| 115 | + } |
| 116 | + return $val; |
| 117 | +} |
| 118 | +function ReadString(Stream $f) { |
| 119 | + $strlen = ReadInt($f); |
| 120 | + return $f->readData($strlen); |
| 121 | +} |
| 122 | +function GetKeyData($k) { |
| 123 | + $keys = []; |
| 124 | + for ($i=0; $i<strlen($k); $i+=8) { |
| 125 | + $keys[] = hexdec(substr($k, $i, 8)); |
| 126 | + } |
| 127 | + return $keys; |
| 128 | +} |
| 129 | +function SIFAS_Decrypt_Stream(string $data, int $key0, int $key1, int $key2) { |
| 130 | + for ($j=0; $j<strlen($data); $j++) { |
| 131 | + $data[$j] = $data[$j] ^ chr((($key1 ^ $key0 ^ $key2) >> 24) & 0xff); |
| 132 | + $key0 = (0x343fd * $key0 + 0x269ec3) & 0xFFFFFFFF; |
| 133 | + $key1 = (0x343fd * $key1 + 0x269ec3) & 0xFFFFFFFF; |
| 134 | + $key2 = (0x343fd * $key2 + 0x269ec3) & 0xFFFFFFFF; |
| 135 | + } |
| 136 | + return $data; |
| 137 | +} |
| 138 | + |
| 139 | +function encodeValue($value) { |
| 140 | + $arr = []; |
| 141 | + foreach ($value as $key=>$val) { |
| 142 | + $arr[] = '/*'.$key.'*/' . (is_numeric($val) ? $val : ('"'.str_replace('"','\\"',$val).'"')); |
| 143 | + } |
| 144 | + return implode(", ", $arr); |
| 145 | +} |
| 146 | +function exportSqlite(PDO $db, string $path) { |
| 147 | + if (!is_dir($path)) { |
| 148 | + if (file_exists($path)) throw new Exception('output path is not directory'); |
| 149 | + mkdir($path, 0777, true); |
| 150 | + } |
| 151 | + |
| 152 | + $tables = $db->query('SELECT * FROM sqlite_master')->fetchAll(PDO::FETCH_ASSOC); |
| 153 | + foreach ($tables as $entry) { |
| 154 | + if ($entry['name'] == 'sqlite_stat1') continue; |
| 155 | + if ($entry['type'] == 'table') { |
| 156 | + $tblName = $entry['name']; |
| 157 | + $f = fopen("$path/${tblName}.sql", 'w'); |
| 158 | + fwrite($f, $entry['sql'].";\n"); |
| 159 | + $values = $db->query("SELECT * FROM ${tblName}"); |
| 160 | + while($value = $values->fetch(PDO::FETCH_ASSOC)) { |
| 161 | + fwrite($f, "INSERT INTO `${tblName}` VALUES (".encodeValue($value).");\n"); |
| 162 | + } |
| 163 | + fclose($f); |
| 164 | + } else if ($entry['type'] == 'index' && $entry['sql']) { |
| 165 | + $tblName = $entry['tbl_name']; |
| 166 | + file_put_contents("$path/${tblName}.sql", $entry['sql'].";\n", FILE_APPEND); |
| 167 | + } |
| 168 | + //echo "\r".++$i.'/'.count($tables); |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +/* |
| 173 | +Read assets: (not tested) |
| 174 | +SIFAS_Decrypt_Stream($encrypted_asset_data, 12345, $key1, $key2) |
| 175 | +$key1 & $key2 from asset_i_ja_0.db |
| 176 | +*/ |
0 commit comments