Files
TankIC/3DTank/tools/server/db.js
T
TermiNexus b6f2cb71ee feat(3dtank): 协议逆向工程与自研服务器入库(工具链 + 协议表 + 空间回放 + MySQL)
工具链(3DTank/tools/):extract(模块提取)、mine_evidence/apply_names(命名证据与别名回填)、extract_codec_fields(字段表 + 全库槽位类型 + PrimClassWire/ResourceClasses)、mine_downlink(下行字段表)、extract_space_init/rebase_space_init/audit_space_init(空间模板与记录切分审计)、extract_space_session(把真实会话切成按上行触发的回放模板)、pair_flow(请求→应答配对)、probe_containers、ingest/decrypt_frames(抓包入库与解密)、serve/fetch_assets(本地站点镜像)、import_db(抓包对象仓库导入 MySQL)。

服务器(3DTank/tools/server/):link/entry/codec/container/dispatch/world/space/semantic(协议层与 lt/pt/zt 三个容器布局)、session(会话回放,顺序推进)、db + schema.sql(MySQL 连接层与对象仓库表结构)、store/responder(查库组帧与定向应答;接管范围默认只开 ba.lcr)。

同时修 pair_flow 的时间轴偏移:timeline_c2s 与 timeline_s2c 的 0 点相差 144~4453ms,改前配对整体偏约 2 秒(实测 space-100073070 偏 2084ms),改后 22.4% 的触发者被纠正。

src/、src_named/、packets/、3DTank/local/(含数据库口令)不进库,可用上述工具重新生成。
2026-09-17 20:00:41 +08:00

126 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// db.js - MySQL 连接层(mysql2 连接池)+ schema 装载
//
// 配置来源优先级(高 → 低):
// 1. 调用方显式传入的配置对象
// 2. 环境变量 TANK_DB_HOST / TANK_DB_USER / TANK_DB_PASSWORD / TANK_DB_PORT / TANK_DB_NAME
// 3. local/db.json(未跟踪目录,凭据不进 git)
// 4. 内置默认(127.0.0.1:3306 / root / 库名 tanki)
//
// 关键取数口径:协议里的 id 是 i64,且**确实存在负数**(-2147481788 一族合成对象 id),
// 所以这里必须 supportBigNumbers + bigNumberStrings —— 大整数一律以**十进制字符串**返回,
// 与 tools/ 里既有代码「id 全部当字符串比较」的口径一致(改用 Number 会在大 id 上静默丢精度)。
'use strict';
const fs = require('fs');
const path = require('path');
const PROJECT = path.resolve(__dirname, '..', '..');
const CONFIG_PATH = path.join(PROJECT, 'local', 'db.json');
const SCHEMA_PATH = path.join(__dirname, 'schema.sql');
const DEFAULTS = {
Host: '127.0.0.1',
User: 'root',
Password: '',
Port: 3306,
Database: 'tanki',
};
function ReadConfigFile(File) {
const P = File || CONFIG_PATH;
if (!fs.existsSync(P)) return {};
try {
return JSON.parse(fs.readFileSync(P, 'utf8'));
} catch (Err) {
throw new Error('读不到数据库配置 ' + P + ':' + Err.message);
}
}
// 合出最终配置;缺项一律落到 DEFAULTS,不做隐式猜测
function LoadConfig(Explicit) {
const FromFile = ReadConfigFile(Explicit && Explicit.ConfigFile);
const Env = {
Host: process.env.TANK_DB_HOST,
User: process.env.TANK_DB_USER,
Password: process.env.TANK_DB_PASSWORD,
Port: process.env.TANK_DB_PORT ? Number(process.env.TANK_DB_PORT) : undefined,
Database: process.env.TANK_DB_NAME,
};
const Pick = (Key) => {
const Sources = [Explicit && Explicit[Key], Env[Key], FromFile[Key], DEFAULTS[Key]];
for (const V of Sources) if (V !== undefined && V !== null && V !== '') return V;
return DEFAULTS[Key];
};
return {
Host: String(Pick('Host')),
User: String(Pick('User')),
Password: String(Pick('Password')),
Port: Number(Pick('Port')) || DEFAULTS.Port,
Database: String(Pick('Database')),
};
}
// 建连接池。mysql2 是可选依赖:没装时给出可操作的报错,而不是一句 module not found
function Connect(Config) {
let mysql;
try {
mysql = require('mysql2/promise');
} catch (Err) {
throw new Error('没装 mysql2(在 tools/ 下执行 npm install mysql2)');
}
const C = Config || LoadConfig();
return mysql.createPool({
host: C.Host,
user: C.User,
password: C.Password,
port: C.Port,
database: C.Database,
waitForConnections: true,
connectionLimit: 8,
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: true,
multipleStatements: true, // EnsureSchema 要整份 schema.sql 一次执行
});
}
// 把 schema.sql 跑一遍(幂等:全部 CREATE TABLE IF NOT EXISTS)
async function EnsureSchema(Pool, SchemaFile) {
const Sql = fs.readFileSync(SchemaFile || SCHEMA_PATH, 'utf8');
await Pool.query(Sql);
const [Rows] = await Pool.query(
'SELECT TABLE_NAME AS Name FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() ORDER BY TABLE_NAME',
);
return Rows.map((R) => R.Name);
}
// 取一行(没有则 null)
async function One(Pool, Sql, Params) {
const [Rows] = await Pool.query(Sql, Params || []);
return Rows.length ? Rows[0] : null;
}
// 取多行
async function Many(Pool, Sql, Params) {
const [Rows] = await Pool.query(Sql, Params || []);
return Rows;
}
// 批量插入(Values 是数组的数组),自动按列切片拼多条 VALUES
async function InsertMany(Pool, Table, Columns, Values, ChunkSize) {
if (!Values.length) return 0;
const Size = ChunkSize || 500;
const Cols = Columns.map((C) => '`' + C + '`').join(', ');
let Done = 0;
for (let I = 0; I < Values.length; I += Size) {
const Chunk = Values.slice(I, I + Size);
const Placeholders = Chunk.map(() => '(' + Columns.map(() => '?').join(', ') + ')').join(', ');
const Flat = [];
for (const Row of Chunk) for (const V of Row) Flat.push(V);
await Pool.query('INSERT INTO `' + Table + '` (' + Cols + ') VALUES ' + Placeholders, Flat);
Done += Chunk.length;
}
return Done;
}
module.exports = { LoadConfig, Connect, EnsureSchema, One, Many, InsertMany, CONFIG_PATH, SCHEMA_PATH, DEFAULTS };