Skip to content

Commit 147a631

Browse files
committed
llas db diff tools
1 parent 9cb1a39 commit 147a631

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

llas/main.php

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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

Comments
 (0)