// WXWork 5.0.11.6029 ia32: SendMessageInfo + 0x38 holds the original local ID.
// Native pre-save builds QUOTE_MESSAGE (1002), its locator, original content and
// kQuoteMessage flag together. Leave protocol construction to that native path.
const quoteEntries = {
service: [0x097D1740, '6690558bec'], byId: [0x0885A9B0, '6690558bec'],
canQuote: [0x08155C20, '6690558bec']
};
function quoteEntry(name) {
const value = quoteEntries[name], address = base.add(value[0]);
const bytes = Array.from(new Uint8Array(address.readByteArray(value[1].length / 2))).map(n => n.toString(16).padStart(2, '0')).join('');
if (bytes !== value[1]) throw new Error('原生引用入口不匹配:' + name);
return address;
}
const quoteService = new NativeFunction(quoteEntry('service'), 'pointer', ['pointer', 'pointer'], 'thiscall');
const quoteById = new NativeFunction(quoteEntry('byId'), 'pointer', ['pointer', 'pointer', 'uint64'], 'thiscall');
const canQuoteMessage = new NativeFunction(quoteEntry('canQuote'), 'bool', ['pointer'], 'thiscall');
function applyNativeQuote(info, conversationId, triggerMessageId, localId, selfTest, triggerSenderId) {
if (typeof localId !== 'string' || !/^[1-9][0-9]{0,18}$/.test(localId)) throw new Error('引用原消息的本地 ID 缺失。');
if (!selfTest && (typeof triggerSenderId !== 'string' || !/^[1-9][0-9]{0,19}$/.test(triggerSenderId) || triggerSenderId === selfId))
throw new Error('引用原消息的发出成员 ID 无效。');
const service = empty(8), original = empty(8);
try {
const environment = manager.add(8).readPointer();
quoteService(environment, service);
const serviceObject = service.readPointer();
if (serviceObject.isNull() || !serviceObject.readPointer().add(0x320).readPointer().equals(quoteEntry('byId')))
throw new Error('引用消息读取接口已变化。');
quoteById(serviceObject, original, uint64(localId));
const model = original.readPointer(), control = original.add(4).readPointer();
if (model.isNull() || control.isNull() || !control.readPointer().equals(base.add(0x0AF984F0)) ||
model.readU64().toString() !== localId || stringAt(model.add(0x28)) !== conversationId)
throw new Error('原消息已不可用,停止本次引用回复。');
const actualMessageId = stringAt(model.add(0x10)) || 'L:' + localId;
if (!selfTest && ((actualMessageId !== triggerMessageId && triggerMessageId !== 'L:' + localId) || model.add(0x48).readU64().toString() !== triggerSenderId))
throw new Error('引用原消息与触发事件不一致。');
if (selfTest && model.add(0x48).readU64().toString() !== selfId) throw new Error('只能引用自己的测试消息。');
if (model.add(8).readU64().toString() === '0') throw new Error('原消息尚无服务器标识,无法可靠引用。');
if ((model.add(0x128).readU32() & 0x20) !== 0 || !canQuoteMessage(model))
throw new Error('原消息已撤回或客户端不支持引用,停止本次回复。');
info.add(0x38).writeU64(uint64(localId));
return {originalLocalId: localId, originalServerId: model.add(8).readU64().toString(),
originalRawType: model.add(0x50).readU32(), originalClientId: actualMessageId};
} finally {
if (!original.readPointer().isNull()) release(original);
if (!service.readPointer().isNull()) release(service);
}
}
// WXWork 5.0.11.6029 ia32: SendMessageInfo + 0x38 holds the original local ID.
// Native pre-save builds QUOTE_MESSAGE (1002), its locator, original content and
// kQuoteMessage flag together. Leave protocol construction to that native path.
const quoteEntries = {
service: [0x097D1740, '6690558bec'], byId: [0x0885A9B0, '6690558bec'],
canQuote: [0x08155C20, '6690558bec']
};
function quoteEntry(name) {
const value = quoteEntries[name], address = base.add(value[0]);
const bytes = Array.from(new Uint8Array(address.readByteArray(value[1].length / 2))).map(n => n.toString(16).padStart(2, '0')).join('');
if (bytes !== value[1]) throw new Error('原生引用入口不匹配:' + name);
return address;
}
const quoteService = new NativeFunction(quoteEntry('service'), 'pointer', ['pointer', 'pointer'], 'thiscall');
const quoteById = new NativeFunction(quoteEntry('byId'), 'pointer', ['pointer', 'pointer', 'uint64'], 'thiscall');
const canQuoteMessage = new NativeFunction(quoteEntry('canQuote'), 'bool', ['pointer'], 'thiscall');
function applyNativeQuote(info, conversationId, triggerMessageId, localId, selfTest, triggerSenderId) {
if (typeof localId !== 'string' || !/^[1-9][0-9]{0,18}$/.test(localId)) throw new Error('引用原消息的本地 ID 缺失。');
if (!selfTest && (typeof triggerSenderId !== 'string' || !/^[1-9][0-9]{0,19}$/.test(triggerSenderId) || triggerSenderId === selfId))
throw new Error('引用原消息的发出成员 ID 无效。');
const service = empty(8), original = empty(8);
try {
const environment = manager.add(8).readPointer();
quoteService(environment, service);
const serviceObject = service.readPointer();
if (serviceObject.isNull() || !serviceObject.readPointer().add(0x320).readPointer().equals(quoteEntry('byId')))
throw new Error('引用消息读取接口已变化。');
quoteById(serviceObject, original, uint64(localId));
const model = original.readPointer(), control = original.add(4).readPointer();
if (model.isNull() || control.isNull() || !control.readPointer().equals(base.add(0x0AF984F0)) ||
model.readU64().toString() !== localId || stringAt(model.add(0x28)) !== conversationId)
throw new Error('原消息已不可用,停止本次引用回复。');
const actualMessageId = stringAt(model.add(0x10)) || 'L:' + localId;
if (!selfTest && ((actualMessageId !== triggerMessageId && triggerMessageId !== 'L:' + localId) || model.add(0x48).readU64().toString() !== triggerSenderId))
throw new Error('引用原消息与触发事件不一致。');
if (selfTest && model.add(0x48).readU64().toString() !== selfId) throw new Error('只能引用自己的测试消息。');
if (model.add(8).readU64().toString() === '0') throw new Error('原消息尚无服务器标识,无法可靠引用。');
if ((model.add(0x128).readU32() & 0x20) !== 0 || !canQuoteMessage(model))
throw new Error('原消息已撤回或客户端不支持引用,停止本次回复。');
info.add(0x38).writeU64(uint64(localId));
return {originalLocalId: localId, originalServerId: model.add(8).readU64().toString(),
originalRawType: model.add(0x50).readU32(), originalClientId: actualMessageId};
} finally {
if (!original.readPointer().isNull()) release(original);
if (!service.readPointer().isNull()) release(service);
}
}