import * as e from './extract.mjs'; import * as s from './stages.mjs'; import {table_bindings} from './rounds.mjs'; // Client tables behind the Elite branch (binary node rule, enemy difficulty) and // the SpecialGoods-gated projections. Every table is decoded byte-exactly. export const TYPES = [ 'GridFightBinaryNodeRuleConfigRow', 'GridFightBinaryDiffAddRuleConfigRow', 'GridFightSpecialGoodsConfigRow', 'GridFightProjectionConfigRow', 'GridFightEnemyDifficultyLvConfigRow' ]; // GridFightProjectionUnlockType: 0 TraitUnlock, 1 SpecialGoods. export const UNLOCK_SPECIAL_GOODS = 1; // Update-union case 1684 (CDKJHJNFDIH) -> GridFightModule.UpdateUnlockProjectionID (DC17B10). export const UNLOCK_PROJECTION_CASE = 1684; // SpecialGoods JsonPath folder names, keyed by GroupID (1 Cyrene, 2 Aha, 3 Aha upper). export const SERIES = ['Cyrene', 'Aha']; // FixPoint wire is signed Q32.32. export function fixed(p) { return Math.round(Number(BigInt.asIntN(64, BigInt(p?.wire ?? 0))) / 4294967296 * 1e6) / 1e6; } export function eliteResource(t) { const node_rules = t.GridFightBinaryNodeRuleConfigRow.map(r => ({ id: r.ID, // Elite nodes carry ParamList[0] = family; option id = family * 100 + quality. family: r.ID >= 100 ? Math.floor(r.ID / 100) : 0, quality: r.Quality ?? 0, perform_level: r.PerformLevel ?? 0 })).sort((a, b) => a.id - b.id); for (const r of node_rules) { if (r.id >= 100 && r.id !== r.family * 100 + r.quality) e.fail('Binary node rule id does not follow family*100+quality: ' + r.id); } const diff_add = t.GridFightBinaryDiffAddRuleConfigRow.map(r => ({ rule: r.ID, quality: r.Quality, add: r.EnemyDifficultyAddValue ?? 0 })).sort((a, b) => a.rule - b.rule || a.quality - b.quality); const difficulty = t.GridFightEnemyDifficultyLvConfigRow.map(r => ({ chapter: r.ChapterID ?? 0, level: r.EnemyDifficultyLevel ?? 0, attack: fixed(r.AttackRatio), defence: fixed(r.DefenceRatio), hp: fixed(r.HPRatio), speed: fixed(r.SpeedRatio), stance: fixed(r.StanceRatio), damage_reduce: fixed(r.AllDamageReduce) })).sort((a, b) => a.chapter - b.chapter || a.level - b.level); return {schema: 1, node_rules, diff_add, difficulty, extra_rewards: []}; } export function goodsResource(t) { const projections = t.GridFightProjectionConfigRow.filter(r => r.UnlockType === UNLOCK_SPECIAL_GOODS).map(r => ({ id: r.ID, role_id: r.RoleID, rarity: r.Rarity, mazebuff_id: r.MazebuffID, params: (r.ParamList ?? []).map(fixed) })).sort((a, b) => a.id - b.id); const special_goods = t.GridFightSpecialGoodsConfigRow.map(g => { const series = SERIES.find(n => g.JsonPath.includes('/' + n + '/')); if (!series) e.fail('Unknown SpecialGoods series: ' + g.JsonPath); return { id: g.ID, group: g.GroupID, series, cost: g.Cost ?? 0, quality: g.Quality ?? 0, lower_good_id: g.LowerGoodID ?? 0, params: (g.EffectParamList ?? []).map(fixed) }; }).sort((a, b) => a.id - b.id); // The good -> projection entitlement lives in the SpecialGoods JsonPath assets, // which no installed table or tool decodes. Kept empty until proven. return {schema: 1, unlock_case: UNLOCK_PROJECTION_CASE, projections, special_goods, grants: []}; } export function main() { const a = e.cli(['client', 'dump', 'identity', 'literals', 'output']); const ctx = s.extractionContext(a); const literals = new Map(e.loadJSON(a.literals).map(r => [Number(BigInt(r.Address)), r.Value])); const tables = {}, proofs = []; for (const name of TYPES) { const assets = table_bindings(ctx.codec, literals, 'RPG.GameCore.' + name, null); tables[name] = []; for (const asset of assets) { const match = asset.match(/^BakedConfig\/ExcelOutput(GameCore)?\/(\w+)\.bytes$/); if (!match) e.fail('Unsupported table path: ' + asset); const [rows, proof] = s.table(ctx.assets, ctx.codec, name, match[1] ?? '', match[2]); tables[name].push(...rows); proofs.push(proof); console.log(`${name}: ${rows.length} byte-exact rows`); } } const source = {dump_sha256: ctx.identity.dump_sha256, game_assembly_sha256: ctx.identity.game_assembly_sha256, tables: TYPES}; s.publish(a, ctx, tables, proofs, { 'rewards.raw.json': tables, 'elite_rewards.json': {...eliteResource(tables), source}, 'projection_goods.json': {...goodsResource(tables), source} }, 'rewards', {gameplay_verified: false}); } e.runMain(import.meta.url, main);