fix(protocol): 适配 e98d0216 版编解码 API 并重生成协议表
- extract_codec_fields:字段工厂 n7f→r7g、读写方法 p7g/o7g→t7h/s7h、槽位初始化 q7g→u7h - 资源类注册 u83→y84,核心类型按 模块::导出键 解析(Vector3/Long/实体引用) - mine_downlink:读取器映射按当前 ByteBuffer 修正(t2u=f32、u2u=f64 等),容器槽位泛化 - 重生成 codec_fields/uplink_table/downlink_table:762 下行字段 0 未解,361 上行命令全量有类型
This commit is contained in:
1 parent
78c02345c2
commit
2ef424929b
5 files changed
+42700
-43117
No files matched your search
+37965
-38489
File diff suppressed because it is too large.
Load diff
+2111
-2092
File diff suppressed because it is too large.
Load diff
+2518
-2506
File diff suppressed because it is too large.
Load diff
@@ -136,19 +136,16 @@ const PrimTable = {
|
||||
const PrimName = (K) => PrimTable[K] || 'prim:' + K;
|
||||
|
||||
// 读取器方法名 → 线级数值宽度(module_72834 的读取器)
|
||||
// 注意:这是「读」方法。e98d0216 版里 p2u..u2u 是读、n2u/o2u 是写,旧版口径整体右移了,
|
||||
// 所以这里以当前源码为准(h(F).p2u→xy、q2u→u2t、r2u→v2t、s2u→w2t、t2u→x2t、u2u→y2t)。
|
||||
const ReaderType = {
|
||||
xy: 'i8',
|
||||
n2u: 'i8',
|
||||
s2t: 'i16',
|
||||
o2u: 'i16',
|
||||
t2t: 'i32',
|
||||
p2u: 'i32',
|
||||
u2t: 'i64',
|
||||
q2u: 'i64',
|
||||
v2t: 'f32',
|
||||
r2u: 'f32',
|
||||
w2t: 'f64',
|
||||
s2u: 'f64',
|
||||
p2u: 'i8',
|
||||
q2u: 'i16',
|
||||
r2u: 'i32',
|
||||
s2u: 'i64',
|
||||
t2u: 'f32',
|
||||
u2u: 'f64',
|
||||
};
|
||||
|
||||
// 类型描述符表达式 → {Type, Raw}
|
||||
@@ -226,7 +223,9 @@ const ROLE_KEYS = {
|
||||
[CODEC_INFO_MODULE + '::b']: 'Enum',
|
||||
[CODEC_INFO_MODULE + '::d']: 'Map',
|
||||
[KOLIB_MODULE + '::w4']: 'Prim',
|
||||
// kClass 助手:旧包导出键 mf,新包(e98d0216)为 nf;两个都收,换版时不必再改
|
||||
[KOLIB_MODULE + '::mf']: 'KClass',
|
||||
[KOLIB_MODULE + '::nf']: 'KClass',
|
||||
};
|
||||
|
||||
// module_03569.js → '3569'
|
||||
@@ -299,8 +298,10 @@ function FindClassNames(Src) {
|
||||
// 数据类工厂:function Ut() { return ((t = M(d(Ft))), Ft.call(t), t); } → Ut 其实是造 Ft
|
||||
function FindFactories(Src) {
|
||||
const Out = {};
|
||||
// 旧包:return ((X = Y(d(Z))), Z.call(X), X);
|
||||
// 新包:return ((t = L(w(Mt))), Mt.call(t), t); ← 多包了一层工厂,故放宽为「两层单参调用」
|
||||
const Re =
|
||||
/function ([A-Za-z_$][\w$]*)\(\) \{\s*return \(\(([A-Za-z_$][\w$]*) = [A-Za-z_$][\w$]*\(d\(([A-Za-z_$][\w$]*)\)\)\), \3\.call\(\2\), \2\);/g;
|
||||
/function ([A-Za-z_$][\w$]*)\(\) \{\s*return \(\(([A-Za-z_$][\w$]*) = [A-Za-z_$][\w$]*\([A-Za-z_$][\w$]*\(([A-Za-z_$][\w$]*)\)\)\), \3\.call\(\2\), \2\);/g;
|
||||
let M;
|
||||
while ((M = Re.exec(Src))) if (!Out[M[1]]) Out[M[1]] = M[3];
|
||||
return Out;
|
||||
@@ -339,7 +340,8 @@ function FindGetters(Src) {
|
||||
// q7g 块 → [{Slot, Desc, Type, Raw}]
|
||||
function ParseSlots(Body, Roles) {
|
||||
const Out = [];
|
||||
const Re = /this\.([a-z0-9]+_\d+) = t\.n7f\(/g;
|
||||
// 字段工厂:旧包 n7f,新包 r7g
|
||||
const Re = /this\.([a-z0-9]+_\d+) = t\.r7g\(/g;
|
||||
let M;
|
||||
while ((M = Re.exec(Body))) {
|
||||
const Open = Body.indexOf('(', M.index + M[0].length - 1);
|
||||
@@ -355,7 +357,8 @@ function ParseSlots(Body, Roles) {
|
||||
// p7g 块 → [{Slot|Reader, Prop, Js, Wire}],次序即线上次序
|
||||
function ParseOrder(Body) {
|
||||
const Hits = [];
|
||||
const Re = /([A-Za-z_$][\w$]*)\(this\)\.p7g\(t\)|t\.a7g_1\.([A-Za-z0-9_$]+)\(\)/g;
|
||||
// 读序:旧包 p7g,新包 t7h;基础类型走 <缓冲槽位>.<reader>()(旧包固定 a7g_1,新包槽位名会变,故泛化)
|
||||
const Re = /([A-Za-z_$][\w$]*)\(this\)\.t7h\(t\)|t\.[a-z0-9]+_\d+\.([A-Za-z0-9_$]+)\(\)/g;
|
||||
let M;
|
||||
while ((M = Re.exec(Body))) Hits.push({ At: M.index, End: Re.lastIndex, Getter: M[1] || '', Reader: M[2] || '' });
|
||||
const Out = [];
|
||||
@@ -499,6 +502,56 @@ function ResolveClass(Name, ModId, ModuleInfo, Seen) {
|
||||
}
|
||||
|
||||
// 单个别名 → { Name, Codec }(Codec = 该类的 codec 键,供递归解码用)
|
||||
// 协议核心类 / 数学类在线上没有独立 codec,但会话双方对它们的字节形状是固定的。
|
||||
// 键 = 模块::导出键(唯一身份,避免重名),值 = 线上形状:
|
||||
// 61860::r = xr(协议实体引用)→ i64
|
||||
// 37036::sj = Kotlin Long → i64
|
||||
// 32088::j = Vector3 → 3 × f32(由生成器展开)
|
||||
const CoreIdentities = {
|
||||
'61860::r': 'i64',
|
||||
'37036::sj': 'i64',
|
||||
'32088::j': 'vector3',
|
||||
};
|
||||
|
||||
// 资源类:模块::导出键;线上只出现 i64 引用(由下面的 y84 注册块填充)
|
||||
const ResourceIdentities = new Set();
|
||||
|
||||
// 类名(模块内短名)→ 稳定身份(模块::导出键)。优先别名,其次导出反查,最后退回本地类名。
|
||||
function IdentityOf(Name, ModId, ModuleInfo) {
|
||||
const Info = ModuleInfo[ModId] || {};
|
||||
const A = (Info.Aliases || {})[Name];
|
||||
if (A) return A.Mod + '::' + A.Key;
|
||||
const ExportKey = Object.keys(Info.Exports || {}).find((K) => Info.Exports[K] === Name);
|
||||
return ModId + '::' + (ExportKey || Name);
|
||||
}
|
||||
|
||||
// 源码里「共享 codec」的登记:q7g(kClass(A), codec.t7g(kClass(B))) → A 与 B 同线形。
|
||||
// 实例:module_71105.js:12590 的 Jf 复用 qv 的 codec(两者都是资源引用 i64)。
|
||||
function AddSharedCodecIdentities(ModuleFiles, ModuleInfo) {
|
||||
for (const File of ModuleFiles) {
|
||||
let Src;
|
||||
try {
|
||||
Src = fs.readFileSync(File, 'utf8');
|
||||
} catch (Err) {
|
||||
continue;
|
||||
}
|
||||
const ModId = ModIdOf(File);
|
||||
const Roles = (ModuleInfo[ModId] || {}).Roles || {};
|
||||
const Re =
|
||||
/\.q7g\(\s*([A-Za-z_$][\w$]*)\(\s*([A-Za-z_$][\w$]*)\s*\)\s*,\s*[A-Za-z_$][\w$]*\.t7g\(\s*([A-Za-z_$][\w$]*)\(\s*([A-Za-z_$][\w$]*)\s*\)\s*\)/g;
|
||||
let M;
|
||||
while ((M = Re.exec(Src))) {
|
||||
if ((Roles[M[1]] || '') !== 'KClass' || (Roles[M[3]] || '') !== 'KClass') continue;
|
||||
const A = IdentityOf(M[2], ModId, ModuleInfo);
|
||||
const B = IdentityOf(M[4], ModId, ModuleInfo);
|
||||
if (ResourceIdentities.has(B)) ResourceIdentities.add(A);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 源码侧没有 codec、但有实测标定出处的裸类名(详见 3DTank/tools/server/container.js 的注释)
|
||||
const ManualClassWire = { Pc: 'i64' };
|
||||
|
||||
// 名字优先用 codec 的模型真名;拿不到就退回 toString 得到的名字
|
||||
function ResolveOne(Name, ModId, ModuleInfo, ClassToCodec) {
|
||||
const R = ResolveClass(Name, ModId, ModuleInfo, []);
|
||||
@@ -544,6 +597,12 @@ function ResolveType(Type, ModId, ModuleInfo, ClassToCodec, Trace) {
|
||||
const Hop = AliasChain(W, ModId, ModuleInfo).find((H) => PrimClassWire[H]);
|
||||
// 协议核心类没有 codec 时按「线上基础类型」换算(如实体引用 xr → i64),否则保留类名
|
||||
if (Hop) return PrimClassWire[Hop];
|
||||
// 核心类型 / 资源类:按 模块::导出键 的稳定身份换算
|
||||
const Chain = AliasChain(W, ModId, ModuleInfo);
|
||||
const Core = Chain.find((H) => CoreIdentities[H]);
|
||||
if (Core) return CoreIdentities[Core];
|
||||
if (Chain.some((H) => ResourceIdentities.has(H))) return 'i64';
|
||||
if (ManualClassWire[R.Name]) return ManualClassWire[R.Name];
|
||||
if (WireClasses[R.Name]) return WireClasses[R.Name];
|
||||
// 两条都没命中:这个裸类名在线上没有解码器,记进 Leftover 让调用方判「类型没推出来」
|
||||
else if (Trace) Trace.Leftover.push(W);
|
||||
@@ -636,7 +695,7 @@ for (const File of ModuleFiles) {
|
||||
}
|
||||
const ModId = ModIdOf(File);
|
||||
const Roles = (ModuleInfo[ModId] || {}).Roles || {};
|
||||
const ByCodec = new Map(FindBlocks(Src, 'o7g').map((B) => [B.Codec, B.Body]));
|
||||
const ByCodec = new Map(FindBlocks(Src, 's7h').map((B) => [B.Codec, B.Body]));
|
||||
if (!ByCodec.size) continue;
|
||||
const Re = /\.m7f\(\s*([A-Za-z_$][\w$]*)\(([A-Za-z_$][\w$]*)\)\s*,\s*new ([A-Za-z_$][\w$]*)\(/g;
|
||||
let M;
|
||||
@@ -685,16 +744,31 @@ for (const File of ModuleFiles) {
|
||||
const ModId = ModIdOf(File);
|
||||
const Roles = (ModuleInfo[ModId] || {}).Roles || {};
|
||||
const LineOf = MakeLineIndex(Src);
|
||||
const Re = /\.u83\(\s*([^,()]+),\s*([A-Za-z_$][\w$]*)\(\s*([A-Za-z_$][\w$]*)\s*\)/g;
|
||||
// 当前版注册方法:r.y84(<typeId>, oi(<类>), <工厂>);旧版是 .u83(
|
||||
const Re = /\.y84\(\s*([^,()]+),\s*([A-Za-z_$][\w$]*)\(\s*([A-Za-z_$][\w$]*)\s*\)/g;
|
||||
let M;
|
||||
while ((M = Re.exec(Src))) {
|
||||
if ((Roles[M[2]] || '') !== 'KClass') continue; // 第 2 参必须是 kClass(类)
|
||||
const Name = ResolveClass(M[3], ModId, ModuleInfo, []).Name;
|
||||
// 记录稳定身份:注册处传入的可能是本模块别名,也可能是本模块本地类名;
|
||||
// 后者要反查导出键(类名只是模块内短名,导出键才是跨模块身份)
|
||||
const Info = ModuleInfo[ModId] || {};
|
||||
const A = (Info.Aliases || {})[M[3]];
|
||||
let Identity = '';
|
||||
if (A) Identity = A.Mod + '::' + A.Key;
|
||||
else {
|
||||
const ExportKey = Object.keys(Info.Exports || {}).find((K) => Info.Exports[K] === M[3]);
|
||||
Identity = ModId + '::' + (ExportKey || M[3]);
|
||||
}
|
||||
ResourceIdentities.add(Identity);
|
||||
if (!ResourceClasses[Name])
|
||||
ResourceClasses[Name] = { Wire: 'i64', TypeId: M[1].trim(), From: path.basename(File) + ':' + LineOf(M.index) };
|
||||
}
|
||||
}
|
||||
|
||||
// 资源型 codec 共享(如 Jf 复用 qv)——必须在 codec 抽取之前填好身份集
|
||||
AddSharedCodecIdentities(ModuleFiles, ModuleInfo);
|
||||
|
||||
// ---------------------------------------------------------------- 第二遍:抽 codec
|
||||
const Codecs = {};
|
||||
let ModulesUsed = 0;
|
||||
@@ -713,8 +787,8 @@ for (const File of ModuleFiles) {
|
||||
const Roles = (ModuleInfo[ModId] || {}).Roles || {};
|
||||
const Factories = (ModuleInfo[ModId] || {}).Factories || {};
|
||||
const { SlotToName, GetterToSlot } = FindGetters(Src);
|
||||
const SlotBlocks = FindBlocks(Src, 'q7g');
|
||||
const OrderBlocks = FindBlocks(Src, 'p7g');
|
||||
const SlotBlocks = FindBlocks(Src, 'u7h');
|
||||
const OrderBlocks = FindBlocks(Src, 't7h');
|
||||
if (!SlotBlocks.length && !OrderBlocks.length) continue;
|
||||
ModulesUsed++;
|
||||
|
||||
@@ -743,8 +817,8 @@ for (const File of ModuleFiles) {
|
||||
}
|
||||
return { Order: O.Order, Name, Type, Js: O.Js, Slot, Desc, Prop: O.Prop, Wire: O.Wire };
|
||||
});
|
||||
// 模型类:优先看 o7g 里的 instanceof(直接是数据类),其次看 p7g 用的工厂函数(可能是 Ut → Ft)
|
||||
const O7g = FindBlocks(Src, 'o7g').find((B) => B.Codec === CodecName);
|
||||
// 模型类:优先看写方法(旧 o7g / 新 s7h)里的 instanceof,其次看读方法用的工厂函数
|
||||
const O7g = FindBlocks(Src, 's7h').find((B) => B.Codec === CodecName);
|
||||
const Inst = O7g && /instanceof ([A-Za-z_$][\w$]*)/.exec(O7g.Body);
|
||||
const Made = /var [A-Za-z_$][\w$]* = ([A-Za-z_$][\w$]*)\(\)/.exec(OrderBody);
|
||||
const DataClass = (Inst && Inst[1]) || (Made ? Made[1] : '');
|
||||
@@ -800,7 +874,7 @@ for (const C of Object.values(Codecs)) {
|
||||
function ParseSlotAssignments(Src, Roles, LineOf) {
|
||||
const Out = new Map();
|
||||
const Conflicts = [];
|
||||
const Re = /\.n7f\(/g;
|
||||
const Re = /\.r7g\(/g;
|
||||
let M;
|
||||
while ((M = Re.exec(Src))) {
|
||||
const Open = M.index + M[0].length - 1;
|
||||
@@ -892,7 +966,7 @@ function FindFieldInfo(Src, Roles) {
|
||||
Long[M[1]] = { Cls: M[2], Lo: Number(M[3]), Hi: Number(M[4]), Value: LongOf(Number(M[3]), Number(M[4])) };
|
||||
|
||||
const Desc = {};
|
||||
const Re = /\.n7f\(/g;
|
||||
const Re = /\.r7g\(/g;
|
||||
while ((M = Re.exec(Src))) {
|
||||
const Open = M.index + M[0].length - 1;
|
||||
const Close = MatchBracket(Src, Open, '(', ')');
|
||||
@@ -966,7 +1040,7 @@ for (const File of ModuleFiles) {
|
||||
const Fields = [];
|
||||
if (Host) {
|
||||
const Head = Src.slice(Host.BodyStart, At);
|
||||
for (const G of Head.matchAll(/this\.([a-z0-9]+_\d+)\.o7g\(/g)) {
|
||||
for (const G of Head.matchAll(/this\.([a-z0-9]+_\d+)\.s7h\(/g)) {
|
||||
const D = Desc[G[1]];
|
||||
const T = D ? ResolveType(D.Type, ModId, ModuleInfo, ClassToCodec) : { Type: '?', Codecs: [] };
|
||||
Fields.push({ Field: G[1], Type: T.Type, TypeCodec: T.Codecs[0] || '', Desc: D ? D.Raw : '' });
|
||||
@@ -1008,7 +1082,8 @@ function FindModelRegistrations(Src, Roles) {
|
||||
while ((M = ReId.exec(Src))) IdOf[M[1]] = LongOf(Number(M[3]), Number(M[4]));
|
||||
const Out = [];
|
||||
if (!Object.keys(IdOf).length) return Out;
|
||||
const Re = /j7n\(\s*this\.([a-z0-9]+_\d+)\s*,[\s\S]{0,90}?\.n7f\(\s*new ([A-Za-z_$][\w$]*)\(([A-Za-z_$][\w$]*)\(([A-Za-z_$][\w$]*)\)/g;
|
||||
// 旧包登记函数 j7n,新包 n7o;字段工厂旧 n7f / 新 r7g
|
||||
const Re = /n7o\(\s*this\.([a-z0-9]+_\d+)\s*,[\s\S]{0,90}?\.r7g\(\s*new ([A-Za-z_$][\w$]*)\(([A-Za-z_$][\w$]*)\(([A-Za-z_$][\w$]*)\)/g;
|
||||
while ((M = Re.exec(Src))) {
|
||||
const Id = IdOf[M[1]];
|
||||
if (!Id) continue;
|
||||
|
||||
@@ -49,8 +49,8 @@ const NamesDir = path.resolve(ToolsDir, '..', 'names');
|
||||
const OutPath = path.resolve(ToolsDir, Arg('out', path.join('..', 'names', 'downlink_table.json')));
|
||||
const Check = Argv.indexOf('--check') >= 0;
|
||||
|
||||
// ByteBuffer 的定长读取器:名字来自 src_named/module_72834.js(xi/n2u=xy=getInt8 … s2u=w2t=getFloat64)
|
||||
const ReaderType = { n2u: 'i8', o2u: 'i16', p2u: 'i32', q2u: 'i64', r2u: 'f32', s2u: 'f64' };
|
||||
// ByteBuffer 的定长读取器:名字来自 src/module_72834.js(p2u=xy=getInt8 … t2u=getFloat32、u2u=getFloat64)
|
||||
const ReaderType = { xy: 'i8', p2u: 'i8', q2u: 'i16', r2u: 'i32', s2u: 'i64', t2u: 'f32', u2u: 'f64' };
|
||||
|
||||
// ---------------------------------------------------------------- 载入 codec 字段表
|
||||
const CodecFields = JSON.parse(fs.readFileSync(path.join(NamesDir, 'codec_fields.json'), 'utf8'));
|
||||
@@ -93,7 +93,7 @@ for (const N of NameAmbiguous) NameType.delete(N);
|
||||
// 以及「模型类」——但模型类只有在能定位到 codec 时才解得出(TypeCodec 链接,或该真名全局唯一)。
|
||||
// 不在这个集合里的(any/number/nothing/throwable/xxArray/prim:xx/认不出的裸类名)一律算没解出来,
|
||||
// 免得把解不动的类型写进表里、到 semantic.js 那一步才炸。
|
||||
const ReaderLeaf = new Set(['bool', 'i8', 'i16', 'i32', 'i64', 'f32', 'f64', 'string', 'bytes', 'array']);
|
||||
const ReaderLeaf = new Set(['bool', 'i8', 'i16', 'i32', 'i64', 'f32', 'f64', 'string', 'bytes', 'array', 'vector3']);
|
||||
function Readable(Type, TypeCodec) {
|
||||
const T = String(Type || '');
|
||||
if (!T || T === '?' || T === '...' || T[0] === '<') return false;
|
||||
@@ -176,8 +176,9 @@ function ReadOrder(Body) {
|
||||
/\(function \(t\) \{\s*var ([A-Za-z0-9_$]+) = t\.([a-z0-9]+_\d+);\s*if \(null != \1\) return \1;\s*[A-Za-z_$][\w$]*\(\s*"([^"]*)"\s*\);\s*\}\)\(this\)/g;
|
||||
let M;
|
||||
while ((M = ReCodec.exec(Body))) Hits.push({ At: M.index, Slot: M[2], Tag: M[3] });
|
||||
const RePrim = /\.a7g_1\.([A-Za-z0-9_$]+)\(\)/g;
|
||||
while ((M = RePrim.exec(Body))) Hits.push({ At: M.index, Reader: M[1] });
|
||||
// 基础类型:旧包固定缓冲槽位 a7g_1,新包槽位名会变,故泛化
|
||||
const RePrim = /\.([a-z0-9]+_\d+)\.([A-Za-z0-9_$]+)\(\)/g;
|
||||
while ((M = RePrim.exec(Body))) Hits.push({ At: M.index, Reader: M[2] });
|
||||
Hits.sort((A, B) => A.At - B.At);
|
||||
return Hits;
|
||||
}
|
||||
@@ -208,7 +209,7 @@ for (const File of Files) {
|
||||
const Longs = FindLongs(Text);
|
||||
// 找每个 `.q7i = function (...) {` 的函数体。源码形态是 `(p(Pt).q7i = function (t, n) {` ——
|
||||
// 类名在包装函数的括号里(包装函数逐模块不同:p / ke / ee / nt / …),所以捕获 `)<方法名>` 前那个标识符。
|
||||
const ReHead = /([A-Za-z_$][\w$]*)\)\.q7i = function \(([^)]*)\) \{/g;
|
||||
const ReHead = /([A-Za-z_$][\w$]*)\)\.u7j = function \(([^)]*)\) \{/g;
|
||||
let H;
|
||||
while ((H = ReHead.exec(Text))) {
|
||||
const Owner = H[1];
|
||||
|
||||
Reference in new issue
Block a user