forked from xziino/ff14-mitigator
236 lines
6.4 KiB
PHP
236 lines
6.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
ini_set('memory_limit', '1024M');
|
|
|
|
const ACTION_SOURCE_URL = 'https://ff14.akurosiakamo.de/extras/json/xivapi_data/Action.json';
|
|
|
|
$rootDir = dirname(__DIR__);
|
|
$mitigationSource = $rootDir . '/api/analysis.php';
|
|
$outputFile = $rootDir . '/assets/jsons/Action.json';
|
|
|
|
function fail(string $message, int $code = 1): void
|
|
{
|
|
fwrite(STDERR, $message . PHP_EOL);
|
|
exit($code);
|
|
}
|
|
|
|
function extract_constant_array_literal(string $php, string $constantName): string
|
|
{
|
|
$needle = 'const ' . $constantName . ' =';
|
|
$start = strpos($php, $needle);
|
|
|
|
if ($start === false) {
|
|
fail('Could not find const ' . $constantName . ' in api/analysis.php');
|
|
}
|
|
|
|
$arrayStart = strpos($php, '[', $start);
|
|
if ($arrayStart === false) {
|
|
fail('Could not find array literal for ' . $constantName);
|
|
}
|
|
|
|
$depth = 0;
|
|
$length = strlen($php);
|
|
$inString = false;
|
|
$stringQuote = '';
|
|
$escaped = false;
|
|
|
|
for ($i = $arrayStart; $i < $length; $i++) {
|
|
$char = $php[$i];
|
|
|
|
if ($inString) {
|
|
if ($escaped) {
|
|
$escaped = false;
|
|
continue;
|
|
}
|
|
|
|
if ($char === '\\') {
|
|
$escaped = true;
|
|
continue;
|
|
}
|
|
|
|
if ($char === $stringQuote) {
|
|
$inString = false;
|
|
$stringQuote = '';
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($char === '\'' || $char === '"') {
|
|
$inString = true;
|
|
$stringQuote = $char;
|
|
continue;
|
|
}
|
|
|
|
if ($char === '[') {
|
|
$depth++;
|
|
continue;
|
|
}
|
|
|
|
if ($char === ']') {
|
|
$depth--;
|
|
|
|
if ($depth === 0) {
|
|
return substr($php, $arrayStart, $i - $arrayStart + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fail('Could not parse array literal for ' . $constantName);
|
|
}
|
|
|
|
function read_mitigation_action_ids(string $sourceFile): array
|
|
{
|
|
if (!is_file($sourceFile)) {
|
|
fail('Missing mitigation source file: ' . $sourceFile);
|
|
}
|
|
|
|
$php = file_get_contents($sourceFile);
|
|
if ($php === false) {
|
|
fail('Could not read mitigation source file: ' . $sourceFile);
|
|
}
|
|
|
|
$literal = extract_constant_array_literal($php, 'MITIGATION_ABILITIES');
|
|
$abilities = eval('return ' . $literal . ';');
|
|
|
|
if (!is_array($abilities)) {
|
|
fail('MITIGATION_ABILITIES did not parse as an array');
|
|
}
|
|
|
|
$ids = [];
|
|
foreach ($abilities as $name => $ability) {
|
|
$id = (int)($ability['extraAbilityGameID'] ?? 0);
|
|
if ($id <= 0) {
|
|
fwrite(STDERR, 'Skipping mitigation without extraAbilityGameID: ' . $name . PHP_EOL);
|
|
continue;
|
|
}
|
|
|
|
$ids[$id] = true;
|
|
}
|
|
|
|
if (!$ids) {
|
|
fail('No extraAbilityGameID values found in MITIGATION_ABILITIES');
|
|
}
|
|
|
|
ksort($ids, SORT_NUMERIC);
|
|
return array_keys($ids);
|
|
}
|
|
|
|
function download_url(string $url): string
|
|
{
|
|
$lastError = '';
|
|
$allowInsecureDownload = getenv('FF14_MITIGATOR_INSECURE_DOWNLOAD') === '1';
|
|
|
|
if (function_exists('curl_init')) {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_CONNECTTIMEOUT => 15,
|
|
CURLOPT_TIMEOUT => 120,
|
|
CURLOPT_USERAGENT => 'ff14-mitigator-action-cache/1.0',
|
|
CURLOPT_SSL_VERIFYPEER => !$allowInsecureDownload,
|
|
CURLOPT_SSL_VERIFYHOST => $allowInsecureDownload ? 0 : 2,
|
|
]);
|
|
|
|
$body = curl_exec($ch);
|
|
$error = curl_error($ch);
|
|
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($body !== false && $status >= 200 && $status < 300) {
|
|
return $body;
|
|
}
|
|
|
|
$lastError = 'cURL HTTP ' . $status . ($error ? ': ' . $error : '');
|
|
}
|
|
|
|
$wrappers = stream_get_wrappers();
|
|
if (in_array('https', $wrappers, true)) {
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'timeout' => 120,
|
|
'user_agent' => 'ff14-mitigator-action-cache/1.0',
|
|
],
|
|
'ssl' => [
|
|
'verify_peer' => !$allowInsecureDownload,
|
|
'verify_peer_name' => !$allowInsecureDownload,
|
|
],
|
|
]);
|
|
|
|
$body = file_get_contents($url, false, $context);
|
|
if ($body !== false) {
|
|
return $body;
|
|
}
|
|
|
|
$lastError = trim($lastError . '; file_get_contents failed', '; ');
|
|
}
|
|
|
|
if ($lastError !== '') {
|
|
fail('Could not download Action.json. ' . $lastError);
|
|
}
|
|
|
|
fail('This PHP installation has neither cURL nor the HTTPS stream wrapper enabled.');
|
|
}
|
|
|
|
function action_field(array $action, string $field): ?int
|
|
{
|
|
if (array_key_exists($field, $action) && is_numeric($action[$field])) {
|
|
return (int)$action[$field];
|
|
}
|
|
|
|
$fields = $action['fields'] ?? null;
|
|
if (is_array($fields) && array_key_exists($field, $fields) && is_numeric($fields[$field])) {
|
|
return (int)$fields[$field];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
$actionIds = read_mitigation_action_ids($mitigationSource);
|
|
$wanted = array_fill_keys(array_map('strval', $actionIds), true);
|
|
|
|
$json = download_url(ACTION_SOURCE_URL);
|
|
$actions = json_decode($json, true);
|
|
|
|
if (!is_array($actions)) {
|
|
fail('Downloaded Action.json is not valid JSON: ' . json_last_error_msg());
|
|
}
|
|
|
|
$filtered = [];
|
|
foreach ($wanted as $id => $_) {
|
|
$action = $actions[$id] ?? null;
|
|
if (!is_array($action)) {
|
|
fwrite(STDERR, 'Missing action in downloaded Action.json: ' . $id . PHP_EOL);
|
|
continue;
|
|
}
|
|
|
|
$filtered[$id] = [
|
|
'cast' => action_field($action, 'Cast100ms'),
|
|
'recast' => action_field($action, 'Recast100ms'),
|
|
];
|
|
}
|
|
|
|
if (!$filtered) {
|
|
fail('No matching mitigation actions found in downloaded Action.json');
|
|
}
|
|
|
|
ksort($filtered, SORT_NUMERIC);
|
|
|
|
$outputDir = dirname($outputFile);
|
|
if (!is_dir($outputDir) && !mkdir($outputDir, 0775, true) && !is_dir($outputDir)) {
|
|
fail('Could not create output directory: ' . $outputDir);
|
|
}
|
|
|
|
$encoded = json_encode($filtered, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
if ($encoded === false) {
|
|
fail('Could not encode filtered Action.json: ' . json_last_error_msg());
|
|
}
|
|
|
|
if (file_put_contents($outputFile, $encoded . PHP_EOL, LOCK_EX) === false) {
|
|
fail('Could not write output file: ' . $outputFile);
|
|
}
|
|
|
|
echo 'Saved ' . count($filtered) . ' actions to ' . $outputFile . PHP_EOL;
|