// ==UserScript==
// @name 仙山小农 油猴砍树挖矿 + 农场 + 抓包
// @namespace xsxn.capture
// @version 3.0
// @description 自动砍树/挖矿/开垦/播种/收获;保留抓包导出
// @match https://xsxncdn.xdyouxi.net/*
// @run-at document-start
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// @connect 127.0.0.1
// @connect localhost
// ==/UserScript==
(function () {
'use strict';
const PW = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; // 页面真实 window
// ============================================================
// 第一部分:document-start 钩 cc._RF,抓 AppMain.app(模块注册表)
// ============================================================
window.__appMod = null; // AppMain 模块对象(.exports.AppMain.app 即注册表宿主)
let __realCC = undefined;
function hookRF(cc) {
try {
if (!cc || !cc._RF || cc._RF.__xsxn_hooked) return;
cc._RF.__xsxn_hooked = true;
const push = cc._RF.push;
cc._RF.push = function (mod, uuid, name) {
if (name === 'AppMain' && mod && !window.__appMod) window.__appMod = mod;
return push.apply(this, arguments);
};
log('[hook] cc._RF.push 已挂钩');
} catch (e) { }
}
// 拦截 window.cc 赋值(引擎脚本晚于本脚本执行)
try {
Object.defineProperty(window, 'cc', {
configurable: true,
get: function () { return __realCC; },
set: function (v) {
__realCC = v;
hookRF(v);
},
});
} catch (e) { }
function getApp() {
// 首选:browserify 全局 __require(bundle 头部暴露 window.__require)
try {
const rq = PW.__require;
if (typeof rq === 'function') {
let mmObj = null;
try { mmObj = rq('./A_ModuleManager/ModuleMap'); } catch (e) { }
if (mmObj && mmObj.m) return { moduleMap: mmObj.m };
try {
const A = rq('AppMain');
if (A && A.AppMain && A.AppMain.app && A.AppMain.app.moduleMap) return A.AppMain.app;
} catch (e) { }
}
} catch (e) { }
// 备选:cc._RF 钩子抓到的模块
const m = PW.__appMod || window.__appMod;
if (!m) return null;
const A = (m.exports && m.exports.AppMain) || m.AppMain;
if (A && A.app && A.app.moduleMap) return A.app;
return null;
}
// ============================================================
// 第二部分:日志与悬浮面板
// ============================================================
const LOG_LINES = [];
let logBox = null;
function log(s) {
const t = new Date().toTimeString().slice(0, 8);
LOG_LINES.push('[' + t + '] ' + s);
if (LOG_LINES.length > 200) LOG_LINES.shift();
console.log('[仙山小农]', s);
if (logBox) logBox.textContent = LOG_LINES.slice(-14).join('\n');
}
function addUI() {
if (document.getElementById('xsxn_panel')) return;
const p = document.createElement('div');
p.id = 'xsxn_panel';
p.style.cssText = 'position:fixed;right:6px;bottom:34px;z-index:999999;background:rgba(0,0,0,.82);color:#0f0;' +
'font:12px/1.5 monospace;padding:8px;border:1px solid #0f0;border-radius:6px;max-width:290px;touch-action:none;';
p.innerHTML =
'
≡ 拖动 ≡
' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'' +
'' +
'' +
'' +
'' +
'
';
document.body.appendChild(p);
logBox = p.querySelector('#xsxn_log');
logBox.textContent = LOG_LINES.slice(-14).join('\n');
document.getElementById('xsxn_scan').onclick = function () { log('资源: ' + JSON.stringify(listChores())); };
document.getElementById('xsqn_export').onclick = doExport;
// 内存修改器面板:输入数值 -> 搜 / 滤 / 写 / 清
function memVal() {
const v = document.getElementById('xsxn_memv').value;
if (v === '') { log('内存修改器:请先在输入框填数值'); return null; }
return +v;
}
document.getElementById('xsxn_memsearch').onclick = function () { const v = memVal(); if (v !== null) memSearch(v); };
document.getElementById('xsxn_memfilter').onclick = function () { const v = memVal(); if (v !== null) memFilter(v); };
document.getElementById('xsxn_memwrite').onclick = function () { const v = memVal(); if (v !== null) memWrite(v); };
document.getElementById('xsxn_memclear').onclick = function () { memClear(); };
// 一键领奖:邮件全部领取 + 可提交任务全部提交
function claimAll() {
const mm = MM();
let mailOk = 0, mailSkip = 0;
const md = mm.MAINDATA || mm.MAILDATA || mm.MailData;
if (md && md.getMailList && md.getMailAward) {
let list = [];
try { list = md.getMailList() || []; } catch (e) { }
if (list.length && Array.isArray(list[0])) list = list[0]; // 兼容分组结构
for (const m of list) {
try {
if (!m || !m.award || m.award === '' || m.state === 2) { mailSkip++; continue; } // 2=READGET
const r = md.getMailAward(m, false);
if (r !== false) mailOk++; else mailSkip++;
} catch (e) { mailSkip++; }
}
}
let taskOk = 0, taskSkip = 0;
const tm = mm.TASKMANAGER;
if (tm && typeof tm.getHaves === 'function') {
let tasks = [];
try { tasks = tm.getHaves() || []; } catch (e) { }
for (const t of tasks) {
try {
if (!t || !t.task_id) continue;
if (typeof t.canSubmit === 'function' && !t.canSubmit()) { taskSkip++; continue; }
if (typeof t.submit === 'function') { t.submit(); taskOk++; }
else if (typeof tm.submitTask === 'function' && tm.submitTask(t.task_id)) taskOk++; else taskSkip++;
} catch (e) { taskSkip++; }
}
}
log('一键领奖: 邮件 +' + mailOk + ' (跳' + mailSkip + '),任务 +' + taskOk + ' (跳' + taskSkip + ')' +
((!md && !tm) ? '(本场景无邮件/任务模块)' : ''));
}
document.getElementById('xsxn_claim').onclick = function () { claimAll(); };
// ==================== 守卫飞艇(TowerDefense)日常 ====================
// 协议(逆向自 main_bundle.js):
// getChallengeList -> 关卡列表 {stage_id, lv, type, is_passed, star}
// getDailyChances -> 领每日挑战次数(奖励发 10189 门票)
// saodangChallenge -> {stage_id, saodang_count} 仅 3 星通关关可扫
// getBattlePassRecord / claimBattlePassReward -> 令牌领取(status==2 可领)
function tdNotice(mm) {
return mm.NEW_NET_NOTICE || (mm.EnumClass && mm.EnumClass.NEW_NET_NOTICE) || null;
}
function tdPost(mm) {
const rm = mm.REQ_METHOD || (mm.EnumClass && mm.EnumClass.REQ_METHOD);
return (rm && rm.POST) || 'POST';
}
function tdActId(mm) {
const a = mm.CONFIG && Number(mm.CONFIG.TFActId);
return (a > 0) ? a : 4161;
}
// 接口数字 id(逆向自 EnumClass.NEW_NET_NOTICE,moduleMap 上拿不到枚举,直接用数字)
const TD_NOTICE = { GETDAILYCHANCES: 494, GETDAILYCHANCESRECORD: 495, SAODANG: 496, REPORT: 497, CHALLENGELIST: 498, BATTLEPASS: 499, CLAIMBATTLEPASS: 500 };
const TC_NOTICE = { ZAOTAI: 60, DAILY: 61, REPORT: 63, SHUFFLE: 64, PROGRESS: 70, FINISH: 69 };
function tdApi(mm, noticeId, params, cb) {
try {
mm.APIHANDLER.apiRequestByURL(Object.assign({ activity_id: tdActId(mm), useJSONType: 1 }, params),
noticeId, tdPost(mm), function (r) {
log('飞艇 ' + noticeId + ': ret=' + (r && r.ret) + fmtReward(r));
cb && cb(r);
});
} catch (e) { log('飞艇 err: ' + e.message); }
}
function fmtReward(r) {
try {
const d = r && r.data;
const wc = d && d.waiting_collect;
if (wc && Object.keys(wc).length) return ' 奖励:' + JSON.stringify(wc);
} catch (e) { }
return '';
}
function tdDaily(count) {
const mm = MM();
if (!mm || !mm.APIHANDLER) { log('飞艇: 游戏模块未就绪,稍后再试'); return; }
count = count || 3;
tdApi(mm, TD_NOTICE.GETDAILYCHANCES, {}, function (r1) { // 1. 领每日次数
tdApi(mm, TD_NOTICE.CHALLENGELIST, {}, function (r2) { // 2. 关卡列表
let best = null;
const list = (r2 && r2.ret === 1 && r2.data) || [];
for (const s of list) {
if (Number(s.type) === 0 && Number(s.is_passed) === 1 && Number(s.star) >= 3
&& (!best || Number(s.lv) > Number(best.lv))) best = s;
}
if (!best) { log('飞艇: 暂无可扫荡关卡(需3星通关)'); return; }
log('飞艇: 扫荡 ' + best.stage_id + ' x' + count);
tdApi(mm, TD_NOTICE.SAODANG, // 3. 扫荡最高3星关
{ stage_id: Number(best.stage_id), saodang_count: count }, function (r3) {
if (!r3 || r3.ret !== 1) return;
tdApi(mm, TD_NOTICE.BATTLEPASS, {}, function (r4) { // 4. 令牌奖励
const zs = (r4 && r4.ret === 1 && r4.data && r4.data.zhiling_list) || [];
const claimable = zs.filter(function (z) { return z.status === 2; });
log('飞艇令牌: 可领 ' + claimable.length + ' 档');
claimable.forEach(function (z, i) {
setTimeout(function () {
tdApi(mm, TD_NOTICE.CLAIMBATTLEPASS, {});
}, i * 800);
});
});
});
});
});
}
document.getElementById('xsxn_td').onclick = function () { tdDaily(3); };
// ==================== 仙山食韵(ActTwoCombine 合成)自动 ====================
// 协议:getZaotaiLayOut -> {layout:[等级], next_ball_level, current_score, ...}
// reportChallenge {ball_lv, insert_side, is_closed, location:{x,y}, reference_index} -> 服务器权威新布局
// 每次发射耗 1 灶火;getDailyChances 领每日次数
function tcActId(mm) {
// 从活动数据表里按 type=MERGE_GAME(40) 找 activity_id
try {
const ad = mm.ACTIVITYDATA;
const src = (ad.activity_datas || ad.activity_temp_datas) || {};
for (const k in src) {
for (const k2 in src[k]) {
const a = src[k][k2];
if (a && a.getActivityType && Number(a.getActivityType()) === 40) {
const id = a.getActivityId && Number(a.getActivityId());
if (id > 0) return id;
}
}
}
} catch (e) { }
return (mm.CONFIG && Number(mm.CONFIG.ActTwoCombineActId)) || 0;
}
function tcApi(mm, noticeId, params, cb) {
try {
mm.APIHANDLER.apiRequestByURL(Object.assign({ activity_id: tcActId(mm), useJSONType: 1 }, params),
noticeId, tdPost(mm), function (r) {
let extra = fmtReward(r);
if (r && r.ret !== 1) extra += ' raw=' + JSON.stringify(r).slice(0, 260); // 失败时带出 msg
log('食韵 ' + noticeId + ': ret=' + (r && r.ret) + extra);
cb && cb(r);
});
} catch (e) { log('食韵 err: ' + e.message); }
}
const TC = { running: false, count: 0, finishes: 0, finishFails: 0 };
function tcStamina(mm) {
try { return Number(mm.BALANCEDATA.getBalanceNum(10102)) || 0; } catch (e) { return -1; }
}
function tcShot(mm) {
if (!TC.running) return;
if (TC.count >= 120) { log('食韵: 达到单轮上限120次,停止'); TC.running = false; return; }
const stam = tcStamina(mm);
if (stam === 0) { log('食韵: 灶火(10102)耗尽,停止'); TC.running = false; return; }
TC.count++;
tcApi(mm, TC_NOTICE.ZAOTAI, {}, function (r) {
if (!TC.running) return;
if (!r || r.ret !== 1 || !r.data) { log('食韵: 布局获取失败,停止'); TC.running = false; return; }
const d = r.data;
const layout = Array.isArray(d.layout) ? d.layout.map(Number) : [];
if (!layout.length) {
// 本局结束:finishGame 结算(返回新布局),再继续
if (TC.finishes >= 20) { log('食韵: 结算次数达上限,停止'); TC.running = false; return; }
TC.finishes++;
log('食韵: 盘面空(第' + TC.finishes + '局完),finishGame…');
tcApi(mm, TC_NOTICE.FINISH, {}, function (rf) {
if (!TC.running) return;
if (!rf || rf.ret !== 1) {
TC.finishFails++;
if (TC.finishFails >= 2) { log('食韵: finishGame 连续被拒,停止'); TC.running = false; return; }
} else TC.finishFails = 0;
setTimeout(function () { tcShot(mm); }, 500);
});
return;
}
const ballLv = Number(d.next_ball_level) || 1;
// 贪心:找一个与发射球同级的水果,贴它插入并尝试闭环;否则插在下标0旁
let refIdx = 0, closed = 0;
for (let i = 0; i < layout.length; i++) {
if (layout[i] === ballLv) { refIdx = i; closed = 1; break; }
}
tcApi(mm, TC_NOTICE.REPORT, {
ball_lv: ballLv,
insert_side: 1,
is_closed: closed,
location: JSON.stringify({ x: 0, y: 0 }),
reference_index: refIdx,
}, function (r2) {
if (!TC.running) return;
if (!r2 || r2.ret !== 1) {
log('食韵: 上报被拒 ret=' + (r2 && r2.ret) + ',重查布局');
setTimeout(function () { tcShot(mm); }, 900); // 可能是灶火耗尽/盘面已空,交给下一轮判断
return;
}
const nd = r2.data || {};
const merged = Number(nd.last_merge_count) || 0;
log('食韵 第' + TC.count + '手: 合并x' + merged + ' 得分+' + (nd.score_delta || 0) +
' 总分' + (nd.total_score != null ? nd.total_score : nd.current_score) + ' 灶火' + tcStamina(mm));
setTimeout(function () { tcShot(mm); }, 700);
});
});
}
document.getElementById('xsxn_tc').onclick = function () {
if (TC.running) { TC.running = false; this.textContent = '食韵自动:关'; this.style.color = '#f80'; log('食韵自动: 停止'); return; }
const mm = MM();
if (!mm || !mm.APIHANDLER) { log('食韵: 游戏模块未就绪'); return; }
TC.running = true; TC.count = 0; TC.finishes = 0;
this.textContent = '食韵自动:开'; this.style.color = '#0f0';
log('食韵自动: 开始(领次数->循环发射,再点一次停止)');
tcApi(mm, TC_NOTICE.DAILY, {}, function () { tcShot(mm); });
};
// 飞艇战斗内 建塔/升级 0 消耗
// 实际扣费链路(main_bundle @~11538874):h = scene.getTowerDefenseBuildCost(cost_num,type)
// → h>0 且 !CURDATA.hasItem(10192,h) 直接 return → CURDATA.useItem(10192,h)
// 故需两处 patch:① Economy.normalizeCost(配置价归一)② Scene.prototype.getTowerDefenseBuildCost(折扣后实价)
// 后者置 0 后 hasItem/useItem 均被跳过
const TDFREE = { on: false, patched: [] };
function tdFreeApply() {
const mm = MM();
if (!mm) return 0;
let hit = 0;
// 还原
for (const p of TDFREE.patched) {
try { Object.defineProperty(p.target, p.fn, p.desc || { value: p.orig, writable: true, configurable: true }); } catch (e) { }
}
TDFREE.patched = [];
if (!TDFREE.on) return 0;
const FNS = ['getTowerDefenseBuildCost', 'getUpgradeCost', 'getBuildCost']; // 不动 normalizeCost,保留卖塔回收
const seen = new Set();
function tryPatch(target) {
if (!target || (typeof target !== 'object' && typeof target !== 'function')) return;
if (seen.has(target)) return;
seen.add(target);
for (const fn of FNS) {
const desc = Object.getOwnPropertyDescriptor(target, fn);
if (!desc || typeof desc.value !== 'function' || !desc.configurable) continue;
const nd = Object.assign({}, desc);
nd.value = function () { return 0; };
try {
Object.defineProperty(target, fn, nd);
TDFREE.patched.push({ target: target, fn: fn, orig: desc.value, desc: desc });
hit++;
} catch (e) { }
}
}
for (const key in mm) {
try {
const mod = mm[key];
tryPatch(mod);
tryPatch(Object.getPrototypeOf(mod));
if (mod && mod.prototype) tryPatch(mod.prototype);
} catch (e) { }
}
return hit;
}
document.getElementById('xsxn_tdfree').onclick = function () {
TDFREE.on = !TDFREE.on;
const hit = tdFreeApply();
this.textContent = '飞艇0消耗:' + (TDFREE.on ? '开' : '关');
this.style.color = TDFREE.on ? '#0f0' : '#0cf';
log('飞艇建塔/升级 0消耗: ' + (TDFREE.on ? '开' : '关') + ',补丁 ' + hit + ' 处' + (hit ? '' : '(进守卫飞艇场景后再开)'));
};
// 自动宝箱 / 自动渔网 / 自动捕捉
const AUTO2 = { box: { on: false }, net: { on: false }, catch: { on: false } };
const ONCE_IN = {}; // 低频提示计数
const TPED = {}; // 已拉到面前/已捕获的动物 id 记录
const FISH_ST = {}; // 钓鱼状态计时 {since:抛竿时间, get:咬钩时间}
function onceIn(n, key, fn) { ONCE_IN[key] = (ONCE_IN[key] || 0) + 1; if (ONCE_IN[key] >= n) { ONCE_IN[key] = 0; fn(); } }
function mkAutoLoop(key, label, tick) {
return function loop() {
const st = AUTO2[key];
if (!st.on) return;
try { tick(); } catch (e) { log(label + ' err: ' + e.message); }
setTimeout(loop, 3000);
};
}
function isApiMatch(b, kws) {
try {
const mm = MM();
const api = mm.ADATA.getItemApi(b.build_id) || {};
const name = String(api.name || '');
const ap = String(api.api || '');
return kws.some(function (k) { return name.indexOf(k) >= 0 || ap.indexOf(k) >= 0; });
} catch (e) { return false; }
}
// 自动宝箱:扫描场景建筑数据,treasureBOXes 里且未开(open!=1)的调 MAPOPERATION.openTreasure(真实开箱链路)
const boxLoop = mkAutoLoop('box', '自动宝箱', function () {
const mm = MM();
const mo = mm.MAPOPERATION;
if (!mo || !mo.openTreasure) return;
const tbl = (mm.EnumClass && mm.EnumClass.treasureBOXes) || mm.treasureBOXes || [];
const pools = [];
try { if (mm.IHOUSEDATA && mm.IHOUSEDATA.sceneBuildData) pools.push(mm.IHOUSEDATA.sceneBuildData.getBuildingData()); } catch (e) { }
try { if (mm.ISLANDDATA && mm.ISLANDDATA.sceneBuildData) pools.push(mm.ISLANDDATA.sceneBuildData.getBuildingData()); } catch (e) { }
if (!pools.length) { try { const sc = mm.INFO.getCurrScene(); if (sc && sc.sceneBuildData) pools.push(sc.sceneBuildData.getBuildingData()); } catch (e) { } }
const boxes = [];
for (const pd of pools) {
if (!pd) continue;
for (const id in pd) {
const b = pd[id];
if (!b || !b.locate || !b.build_id || b.open == 1) continue;
if (tbl.length) { if (tbl.includes(Number(b.build_id))) boxes.push(b); }
else {
const api = mm.ADATA && mm.ADATA.getItemApi && mm.ADATA.getItemApi(b.build_id);
const nm = (api && api.name) || '';
if (nm.indexOf('箱') >= 0 || nm.toLowerCase().indexOf('box') >= 0) boxes.push(b);
}
}
}
if (!boxes.length) { onceIn(8, 'box0', function () { log('自动宝箱: 本场景无未开宝箱'); }); return; }
log('自动宝箱: ' + boxes.length + ' 个');
boxes.forEach(function (b, i) {
setTimeout(function () {
try { mo.openTreasure(b); } catch (e) { }
}, i * 500);
});
});
// 自动钓鱼:必须玩家已走到水边(用面向格子 currFaceTile),自动 抛竿->咬钩提竿;顺带收渔网
const netLoop = mkAutoLoop('net', '自动钓鱼', function () {
const mm = MM();
const sc = mm.INFO.getCurrScene();
if (!sc) return;
const fd = sc.fishMain && sc.fishMain.getFishData && sc.fishMain.getFishData();
if (fd) {
const face = (sc.CURDATA && sc.CURDATA.currFaceTile) || (mm.ISLANDDATA && mm.ISLANDDATA.currFaceTile) || null;
const mp = sc.CURDATA && sc.CURDATA.sceneTileData && sc.CURDATA.sceneTileData.map_pool;
function isPoolTile(t) {
if (!t || !mp || !mp.getPoolIds) return false;
try {
const ids = mp.getPoolIds();
for (const k in ids) {
const arr = ids[k];
if (!arr || !arr.length) continue;
for (const it of arr) {
const s = (typeof it === 'string') ? it : (it.x + '|' + it.y);
if (s === (t.x + '|' + t.y)) return true;
}
}
} catch (e) { }
return false;
}
if (!fd.FISHING) {
FISH_ST.since = 0; FISH_ST.get = 0;
if (isPoolTile(face)) {
log('钓鱼: 抛竿 @' + face.x + '|' + face.y);
try { mm.MAPOPERATION.deal_pool(face); } catch (e) { }
FISH_ST.since = Date.now();
} else {
onceIn(10, 'fishhint', function () { log('钓鱼: 请先走到水边面向水面(我方不乱走位),到位后自动抛竿'); });
}
} else if (mm.SCENEDATA.IF_FISHGET || fd.getFIshNode) {
if (!FISH_ST.get) FISH_ST.get = Date.now();
log('钓鱼: 提竿!');
try { mm.MAPOPERATION.deal_pool(face || null, true, function () { }); } catch (e) { }
if (Date.now() - FISH_ST.get > 6000) {
log('钓鱼: 提竿无效,强制结算');
try { mm.MAPOPERATION.deal_pool(null, true, function () { }); } catch (e) { }
FISH_ST.get = 0;
}
} else {
FISH_ST.get = 0;
if (!FISH_ST.since) FISH_ST.since = Date.now();
if (Date.now() - FISH_ST.since > 15000) {
log('钓鱼: 久不咬钩,强制收竿重抛');
try { mm.MAPOPERATION.deal_pool(null, true, function () { }); } catch (e) { }
FISH_ST.since = Date.now();
}
}
}
// 顺带收渔网
const bs = allBuildings().filter(function (b) { return isApiMatch(b, ['网', 'fishnet']); });
bs.forEach(function (b, i) {
setTimeout(function () {
try { mm.MAPOPERATION.deal_fishingnet({ id: b.id, locate: b.locate }); } catch (e) { }
}, i * 500);
});
});
// 自动捕捉:复刻游戏抓捕回调逻辑(deal_animal 的 ifgacha 对话框内代码)
// 入库 addCapturedAnimalBag + 移除野怪节点 + 清 wildAnimals
const catchLoop = mkAutoLoop('catch', '自动捕捉', function () {
const mm = MM();
const sc = mm.INFO.getCurrScene();
const am = sc && sc.animalManager;
if (!am || !am.getAnimalData) return;
const ad = am.getAnimalData();
const orgin = am.orginData || ad;
const targets = [];
for (const k in ad) {
const v = ad[k];
const isWildPool = (k === 'wildAnimals' || k === 'gachaAnimal');
if (!isWildPool) continue;
if (Array.isArray(v)) {
for (const a of v) if (a && a.id !== undefined && a.animal_id !== undefined) targets.push({ a: a, pool: v });
} else if (v && typeof v === 'object') {
for (const id in v) {
const a = v[id];
if (a && a.id !== undefined && a.animal_id !== undefined) targets.push({ a: a, pool: v });
}
}
}
if (!targets.length) {
onceIn(6, 'catch0', function () { log('自动捕捉: 无野动物目标;昆虫/飞鸟需走近它们(面向后自动捕捉)'); });
} else {
// 第一步:把野动物拉到角色面前(触发游戏的走近/面向可捕捉状态)
const pnode = (function () {
try {
const mv = mm.MAINVIEW || (sc.CURVIEW || {});
const r = mv.role_AI || mv.role || sc.CURVIEW && (sc.CURVIEW.role_AI || sc.CURVIEW.role);
return r && (r.node || r);
} catch (e) { return null; }
})();
if (pnode && pnode.setPosition) {
for (const t of targets) {
if (TPED[t.a.id]) continue;
try {
const n = am.getNodeById(t.a.id);
if (n && cc.isValid && cc.isValid(n)) {
n.setPosition(pnode.x, pnode.y);
TPED[t.a.id] = 1;
log('自动捕捉: 已把动物 ' + t.a.id + ' 拉到面前');
}
} catch (e) { }
}
}
}
// 第二步:面向的动物 currAnimal 出现后确认抓捕(复刻对话确认回调:入库+清理)
const ca = sc.CURDATA && sc.CURDATA.currAnimal;
if (ca && ca.id !== undefined && ca.animal_id !== undefined && !TPED['done' + ca.id]) {
try {
const api = mm.ADATA.getItemApi(ca.animal_id);
const o = (mm.UTILS && mm.UTILS.objCopy) ? mm.UTILS.objCopy(ca) : Object.assign({}, ca);
o.ifgacha = 0; o.pack = 0; o.id = orgin.getAutoAnimalId ? orgin.getAutoAnimalId() : o.id; o.wild = 0;
const s = mm.ANIMALDATA.getAnimalBagItemId
? mm.ANIMALDATA.getAnimalBagItemId(o.animal_id, !!o.blood_id)
: mm.ANIMALDATA.getAnimalBag(api.sub_type);
if (!s || mm.PACKDATA.checkPackLimit({ item_id: s, num: 1, rare: 0, ip: -1, box: 0, relate_id: 0 })) {
log('自动捕捉: 动物背包已满,跳过');
} else if (mm.GDATA.addCapturedAnimalBag(o, 'reststop')) {
TPED['done' + ca.id] = 1;
log('自动捕捉: 已捕获 ' + ((api && api.name) || ca.animal_id));
try { const n = am.getNodeById(ca.id); if (n) { if (n.removeFromParent) n.removeFromParent(true); if (n.destroy) n.destroy(); } } catch (e) { }
try { for (const key in ad) if (ad[key] && typeof ad[key] === 'object' && ad[key][ca.id] !== undefined) delete ad[key][ca.id]; } catch (e) { }
}
} catch (e) { }
}
// 昆虫/飞鸟:走近后面向目标时 currInsect/currBird 会被置位,直接走游戏自己的捕捉流程
const cd = (sc.CURDATA && sc.CURDATA.currInsect) || (mm.ISLANDDATA && mm.ISLANDDATA.currInsect);
if (cd) { log('自动捕捉: 昆虫'); try { mm.MAPOPERATION.deal_insect(cd); } catch (e) { } }
const bd = (sc.CURDATA && sc.CURDATA.currBird) || (mm.ISLANDDATA && mm.ISLANDDATA.currBird);
if (bd) { log('自动捕捉: 飞鸟'); try { mm.MAPOPERATION.deal_bird(bd); } catch (e) { } }
});
document.getElementById('xsxn_autobox').onclick = function () {
AUTO2.box.on = !AUTO2.box.on;
this.textContent = '自动宝箱:' + (AUTO2.box.on ? '开' : '关');
if (AUTO2.box.on) boxLoop();
};
document.getElementById('xsxn_autonet').onclick = function () {
AUTO2.net.on = !AUTO2.net.on;
this.textContent = '自动渔网:' + (AUTO2.net.on ? '开' : '关');
if (AUTO2.net.on) netLoop();
};
document.getElementById('xsxn_autocatch').onclick = function () {
AUTO2.catch.on = !AUTO2.catch.on;
this.textContent = '自动捕捉:' + (AUTO2.catch.on ? '开' : '关');
if (AUTO2.catch.on) catchLoop();
};
document.getElementById('xsxn_diag3').onclick = function () {
try {
const mm = MM();
if (!mm) { log('诊断: moduleMap 未就绪'); return; }
// 1) 建筑与配置名
const bs = allBuildings();
const seen = {};
for (const b of bs) {
if (seen[b.build_id]) continue;
seen[b.build_id] = 1;
const api = (mm.ADATA.getItemApi(b.build_id) || {});
log('建筑 ' + b.build_id + ' name=' + (api.name || '?') + ' api=' + (api.api || '?') + ' sub_type=' + (api.sub_type !== undefined ? api.sub_type : '?') + ' x' + bs.filter(function (x) { return x.build_id === b.build_id; }).length);
}
// 2) 动物数据结构
const sc = mm.INFO.getCurrScene && mm.INFO.getCurrScene();
const am = sc && sc.animalManager;
if (am && am.getAnimalData) {
const ad = am.getAnimalData();
log('动物数据 keys=' + Object.keys(ad).join(','));
for (const k in ad) {
if (Array.isArray(ad[k]) && ad[k].length) {
let s; try { s = JSON.stringify(ad[k][0]); } catch (e) { s = '[circular]'; }
log('动物 ' + k + '[' + ad[k].length + '] 样例=' + String(s).slice(0, 200));
} else if (ad[k] && typeof ad[k] === 'object') {
const ks = Object.keys(ad[k]).slice(0, 5);
if (ks.length) { let s; try { s = JSON.stringify(ad[k][ks[0]]); } catch (e) { s = '[circular]'; } log('动物 ' + k + '(obj) 子键=' + ks.join(',') + ' 样例=' + String(s).slice(0, 200)); }
}
}
} else log('诊断: 无 animalManager');
// 3) 鱼相关
log('fishMain=' + (sc && sc.fishMain ? '有' : '无') + ' birdManager=' + (sc && sc.birdManager ? '有' : '无') + ' enemyManager=' + (sc && sc.enemyManager ? '有' : '无'));
if (sc && sc.fishMain) log('fishMain keys=' + Object.keys(sc.fishMain).slice(0, 20).join(','));
} catch (e) { log('诊断 err: ' + (e && e.message)); }
};
document.getElementById('xsxn_copylog').onclick = function () {
const txt = LOG_LINES.join('\n');
try { GM_setClipboard(txt); } catch (e) { }
try {
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(txt);
} catch (e) { }
log('日志已复制 ' + LOG_LINES.length + ' 行');
};
const autoBtn = document.getElementById('xsxn_auto');
autoBtn.onclick = function () {
AUTO.on = !AUTO.on;
autoBtn.textContent = '自动砍树:' + (AUTO.on ? '开' : '关');
if (AUTO.on) autoLoop();
};
document.getElementById('xsxn_reap').onclick = function () { reapAll(); };
document.getElementById('xsxn_sow').onclick = function () { sowAll(); };
document.getElementById('xsxn_dig').onclick = function () { digAll(); };
document.getElementById('xsxn_water').onclick = function () { waterAll(true); };
const dbgBtn = document.getElementById('xsxn_putdbg');
dbgBtn.onclick = function () {
const on = !PUTDBG.on;
const n = debugPut(on);
this.textContent = '摆放诊断:' + (on ? '开' : '关');
log('摆放诊断 ' + (on ? '开启' : '关闭') + ',包装 ' + n + ' 个函数,现在去放一次建筑,看日志');
};
const farmBtn = document.getElementById('xsxn_farmauto');
farmBtn.onclick = function () {
FARM.on = !FARM.on;
farmBtn.textContent = '自动农场:' + (FARM.on ? '开' : '关');
if (FARM.on) farmLoop();
};
document.getElementById('xsxn_god').onclick = function () {
try {
GOD.on = !GOD.on;
const hit = setNoDamage(GOD.on);
this.textContent = '无敌:' + (GOD.on ? '开' : '关');
log('无敌 ' + (GOD.on ? '开启' : '关闭') + ',补丁 ' + hit + ' 处' + (hit ? '' : '(进战斗场景后再试)'));
} catch (e) {
log('无敌 err: ' + (e && e.stack ? e.stack.split('\n').slice(0, 3).join(' | ') : e.message));
}
};
document.getElementById('xsxn_dmg').onclick = function () {
try {
DMG.on = !DMG.on;
const hit = setBigDamage(DMG.on);
this.textContent = '伤害99999:' + (DMG.on ? '开' : '关');
log('伤害99999 ' + (DMG.on ? '开启' : '关闭') + ',补丁 ' + hit + ' 处' + (hit ? '' : '(进战斗场景后再试)'));
} catch (e) {
log('伤害99999 err: ' + (e && e.stack ? e.stack.split('\n').slice(0, 3).join(' | ') : e.message));
}
};
document.getElementById('xsxn_aoe').onclick = function () {
AOE.on = !AOE.on;
this.textContent = '全屏攻击:' + (AOE.on ? '开' : '关');
if (AOE.on) { log('全屏攻击 开启(每 400ms 对全屏敌人结算一次伤害)'); aoeLoop(); }
else log('全屏攻击 关闭');
};
document.getElementById('xsxn_fast').onclick = function () {
try {
FAST.on = !FAST.on;
const hit = setFastAtk(FAST.on);
this.textContent = '高速攻击:' + (FAST.on ? '开' : '关');
log('高速攻击 ' + (FAST.on ? '开启' : '关闭') + ',补丁 ' + hit + ' 处' + (hit ? '' : '(进战斗场景后再试)'));
} catch (e) {
log('高速攻击 err: ' + (e && e.stack ? e.stack.split('\n').slice(0, 3).join(' | ') : e.message));
}
};
document.getElementById('xsxn_terr').onclick = function () {
try {
TERR.on = !TERR.on;
const hit = setNoTerrain(TERR.on);
this.textContent = '无视地形:' + (TERR.on ? '开' : '关');
const detail = TERR.lastNames && TERR.lastNames.length ? ' [' + TERR.lastNames.join(',') + ']' : '';
log('无视地形 ' + (TERR.on ? '开启' : '关闭') + ',补丁 ' + hit + ' 处' + detail + (hit ? '' : '(进主场景/摆放状态后再试)'));
} catch (e) {
log('无视地形 err: ' + (e && e.stack ? e.stack.split('\n').slice(0, 3).join(' | ') : e.message));
}
};
// 拖动(避开按钮/日志区域,仅标题条触发)
const dragBar = p.querySelector('#xsxn_drag');
let sx = 0, sy = 0, sl = 0, st = 0, dragging = false;
dragBar.addEventListener('pointerdown', function (e) {
dragging = true;
const r = p.getBoundingClientRect();
sl = r.left; st = r.top;
sx = e.clientX; sy = e.clientY;
p.style.left = sl + 'px'; p.style.top = st + 'px';
p.style.right = 'auto'; p.style.bottom = 'auto';
dragBar.setPointerCapture(e.pointerId);
e.preventDefault();
});
dragBar.addEventListener('pointermove', function (e) {
if (!dragging) return;
p.style.left = Math.max(0, Math.min(window.innerWidth - 60, sl + e.clientX - sx)) + 'px';
p.style.top = Math.max(0, Math.min(window.innerHeight - 30, st + e.clientY - sy)) + 'px';
});
dragBar.addEventListener('pointerup', function () { dragging = false; });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', addUI);
else addUI();
// ============================================================
// 第三部分:资源扫描与砍树(调用游戏自身 deal_chores)
// ============================================================
const AUTO = { on: false };
function MM() { const app = getApp(); return app && app.moduleMap; }
function listChores() {
const mm = MM();
if (!mm) { log('moduleMap 未就绪'); return []; }
const out = [];
try {
// 路径1: INFO.getCurrScene().CURDATA.sceneBuildData.getBuildingData()
const sc = mm.INFO && mm.INFO.getCurrScene && mm.INFO.getCurrScene();
if (!sc) { log('getCurrScene 无场景'); }
else {
const cd = sc.CURDATA;
if (!out.length) log('场景=' + (sc.constructor && sc.constructor.name || '?') +
' CURDATA=' + (cd ? Object.keys(cd).join(',') : '无'));
const bd = cd && cd.sceneBuildData;
const data = bd && bd.getBuildingData && bd.getBuildingData();
if (data) {
for (const id in data) {
const b = data[id];
if (!b || !b.build_id) continue;
const cfg = mm.ADATA.API_CHORES[b.build_id];
if (!cfg) continue;
const isTree = !!(mm.ADATA.API_TREE && mm.ADATA.API_TREE[b.build_id]);
out.push({
id: +id, build_id: b.build_id, hp: b.hp, locate: b.locate,
kind: isTree ? 'tree' : (cfg.api || 'chore'),
tools: cfg.tools, name: cfg.name || '',
});
}
}
// 路径2: sceneTileData.build_datas["x|y"] = [建筑数组]
if (!out.length && cd && cd.sceneTileData && cd.sceneTileData.build_datas) {
const tds = cd.sceneTileData.build_datas;
for (const k in tds) {
const arr = tds[k];
if (!Array.isArray(arr)) continue;
for (const b of arr) {
if (!b || !b.build_id) continue;
const cfg = mm.ADATA.API_CHORES[b.build_id];
if (!cfg) continue;
const isTree = !!(mm.ADATA.API_TREE && mm.ADATA.API_TREE[b.build_id]);
out.push({
id: b.id, build_id: b.build_id, hp: b.hp, locate: k,
kind: isTree ? 'tree' : (cfg.api || 'chore'),
tools: cfg.tools, name: cfg.name || '',
});
}
}
}
}
// 路径3: 全 moduleMap 扫描含 getBuildingData 的模块(终极兜底)
if (!out.length) {
let hit = [];
for (const key in mm) {
const mod = mm[key];
if (mod && typeof mod.getBuildingData === 'function') {
try {
const d = mod.getBuildingData();
const n = d ? Object.keys(d).length : 0;
hit.push(key + '(' + n + ')');
if (n && !out.length) {
for (const id in d) {
const b = d[id];
if (b && b.build_id && mm.ADATA.API_CHORES[b.build_id]) {
const cfg = mm.ADATA.API_CHORES[b.build_id];
const isTree = !!(mm.ADATA.API_TREE && mm.ADATA.API_TREE[b.build_id]);
out.push({ id: +id, build_id: b.build_id, hp: b.hp, locate: b.locate, kind: isTree ? 'tree' : (cfg.api || 'chore'), tools: cfg.tools, name: cfg.name || '' });
}
}
}
} catch (e) { }
}
}
if (hit.length) log('兜底扫描 getBuildingData 模块: ' + hit.join(' '));
else log('所有模块均无 getBuildingData(可能在山体/特殊场景)');
}
} catch (e) { log('scan err: ' + e.message); }
return out;
}
// kind: 'tree' | 'mine' | null(全部)
// mine 的判定:非 tree 且 tools 不是斧头的杂役建筑(石头/矿石)
let CHOP_RUNNING = false;
function chopAll(kind) {
if (CHOP_RUNNING) { log('上一轮还在跑'); return; }
const list = listChores();
const targets = list.filter(function (b) {
if (kind === 'tree') return b.kind === 'tree';
if (kind === 'mine') return b.kind !== 'tree';
return true;
});
log((kind || '全部') + ' 目标 ' + targets.length + '/' + list.length);
if (!targets.length) return;
CHOP_RUNNING = true;
const mm = MM();
let i = 0, done = 0, fail = 0;
(function next() {
if (i >= targets.length) {
CHOP_RUNNING = false;
log('完成: 成功' + done + ' 失败' + fail);
return;
}
const b = targets[i++];
try {
const parts = String(b.locate || '').split('|');
const tile = { x: +parts[0], y: +parts[1] };
if (isNaN(tile.x)) { fail++; log('跳过 #' + b.id + ' locate=' + b.locate); return next(); }
log('砍 #' + b.id + ' build=' + b.build_id + ' hp=' + b.hp + ' @' + b.locate);
mm.MAPOPERATION.deal_chores(tile, { first: true, multi_chore: true, check_permi: false }, function () {
done++;
});
} catch (e) {
fail++; log('err #' + b.id + ': ' + e.message);
}
setTimeout(next, 500);
})();
}
function autoLoop() {
if (!AUTO.on) return;
try {
const list = listChores();
if (list.length) {
log('自动模式: 剩余资源 ' + list.length);
chopAll(null);
}
} catch (e) { log('auto err: ' + e.message); }
setTimeout(autoLoop, 5000);
}
// 调试快捷方式
const __xsxn = {
app: function () { return getApp(); },
mm: function () { return MM(); },
scan: listChores,
chop: chopAll,
farm: { scan: scanFarm, reap: reapAll, sow: sowAll, dig: digAll, water: waterAll },
terr: function (on) { TERR.on = on === undefined ? !TERR.on : !!on; const hit = setNoTerrain(TERR.on); log('无视地形 ' + TERR.on + ' 补丁' + hit + '处 [' + (TERR.lastNames || []).join(',') + ']'); return hit; },
god: function (on) { GOD.on = on === undefined ? !GOD.on : !!on; const hit = setNoDamage(GOD.on); log('无敌 ' + GOD.on + ' 补丁' + hit + '处'); return hit; },
dmg: function (on) { DMG.on = on === undefined ? !DMG.on : !!on; const hit = setBigDamage(DMG.on); log('伤害99999 ' + DMG.on + ' 补丁' + hit + '处'); return hit; },
aoe: function (on) { AOE.on = on === undefined ? !AOE.on : !!on; if (AOE.on) aoeLoop(); log('全屏攻击 ' + AOE.on); return AOE.on; },
fast: function (on) { FAST.on = on === undefined ? !FAST.on : !!on; const hit = setFastAtk(FAST.on); log('高速攻击 ' + FAST.on + ' 补丁' + hit + '处'); return hit; },
diag: function () {
const mm = MM();
if (!mm) { log('diag: 无 moduleMap'); return; }
const sc = mm.INFO && mm.INFO.getCurrScene && mm.INFO.getCurrScene();
if (!sc) { log('diag: 无场景'); return; }
log('diag 场景=' + (sc.constructor && sc.constructor.name || '?') + ' keys=' + Object.keys(sc).slice(0, 25).join(','));
const em = sc.enemyManager;
log('diag enemyManager=' + (em ? '有 keys=' + Object.keys(em).slice(0, 15).join(',') : '无'));
let nodes = null;
try { nodes = em && em.getAllEnemyNode && em.getAllEnemyNode(); } catch (e) { log('diag getAllEnemyNode err: ' + e.message); }
log('diag 敌人节点=' + (nodes ? nodes.length : 'getAllEnemyNode 不存在'));
if (nodes && nodes.length) {
const nd = nodes[0];
const comps = (nd._components || []).map(function (c) { return c && c.constructor && c.constructor.name; });
log('diag 敌人组件=' + comps.join(','));
for (const c of nd._components || []) {
if (c && typeof c.dealDamage === 'function') {
log('diag dealDamage@' + (c.constructor && c.constructor.name || '?') + ' src=' + Function.prototype.toString.call(c.dealDamage).slice(0, 150));
}
}
}
const rv = sc.CURVIEW && sc.CURVIEW.role_ATK;
log('diag role_ATK=' + (rv ? (rv.constructor && rv.constructor.name || '?') : '无'));
if (rv) {
log('diag role_ATK keys=' + Object.keys(rv).slice(0, 25).join(','));
let p = Object.getPrototypeOf(rv), depth = 0;
while (p && depth++ < 6) {
if (Object.getOwnPropertyDescriptor(p, 'robotAutoAttack')) { log('diag robotAutoAttack 在原型链第' + depth + '层'); break; }
p = Object.getPrototypeOf(p);
}
if (!p) log('diag robotAutoAttack 未找到(原型链 6 层内)');
}
// 深扫 moduleMap 找 dealDamage / robotAutoAttack 挂载点
const hits = [], seen = new Set();
function scan(obj, path, depth) {
if (!obj || depth > 3) return;
if (typeof obj === 'function') {
try {
if (obj.prototype && !seen.has(obj.prototype)) {
seen.add(obj.prototype);
for (const k of ['dealDamage', 'robotAutoAttack']) {
if (Object.getOwnPropertyDescriptor(obj.prototype, k)) hits.push(path + '.proto.' + k);
}
}
} catch (e) { }
return;
}
if (typeof obj !== 'object' || seen.has(obj)) return;
seen.add(obj);
for (const k in obj) {
let v; try { v = obj[k]; } catch (e) { continue; }
if (!v) continue;
const np = path + '.' + k;
if (k === 'dealDamage' || k === 'robotAutoAttack') hits.push(np);
else if (typeof v === 'object' || typeof v === 'function') scan(v, np, depth + 1);
}
}
for (const key in mm) { try { scan(mm[key], key, 0); } catch (e) { } }
log('diag moduleMap 命中: ' + (hits.slice(0, 15).join(' ') || '无'));
},
mem: { search: memSearch, filter: memFilter, write: memWrite, list: memList, clear: memClear },
debugPut: function (on) { return debugPut(on); },
};
// 同时挂到页面真实 window:油猴沙箱的 window 在 DevTools 控制台里不可见
try { window.__xsxn = __xsxn; } catch (e) { }
try { PW.__xsxn = __xsxn; } catch (e) { }
try { PW.top.__xsxn = __xsxn; } catch (e) { }
// ============================================================
// 摆放诊断:包装摆放链路函数,记录调用与返回值,定位拦截点
// ============================================================
const PUTDBG = { on: false, wrapped: [] };
const DBG_FNS = ['normalPut', 'set_building', 'put_failed', 'put_finish', 'checkArea',
'checkEmptyTile', 'checkOverObstruct', 'checkSpecialTile', 'checkTileUnlock',
'checkFaceTiles', 'layOutCheck', 'checkValidTileOrFallback', 'check_can_build'];
function debugPut(on) {
const mm = MM();
if (!mm) return 0;
let n = 0;
// 还原
for (const w of PUTDBG.wrapped) {
try { Object.defineProperty(w.proto, w.fn, w.desc || { value: w.orig, writable: true, configurable: true }); } catch (e) { }
}
PUTDBG.wrapped = [];
if (!on) { log('摆放诊断关闭'); return 0; }
const seen = new Set();
for (const key in mm) {
try {
const mod = mm[key];
if (!mod || typeof mod !== 'object') continue;
const proto = Object.getPrototypeOf(mod);
if (!proto || seen.has(proto)) continue;
seen.add(proto);
for (const fn of DBG_FNS) {
const desc = Object.getOwnPropertyDescriptor(proto, fn);
if (!desc || typeof desc.value !== 'function' || !desc.configurable) continue;
const orig = desc.value;
const nd = Object.assign({}, desc);
nd.value = function () {
let ret;
try { ret = orig.apply(this, arguments); } catch (e) { log('[PUT] ' + fn + ' throw ' + e.message); throw e; }
try {
const a0 = arguments[0];
const brief = a0 && typeof a0 === 'object' ? (a0.x !== undefined ? a0.x + '|' + a0.y : 'obj') : String(a0).slice(0, 20);
log('[PUT] ' + fn + '(' + brief + ') -> ' + ret);
} catch (e) { }
return ret;
};
Object.defineProperty(proto, fn, nd);
PUTDBG.wrapped.push({ proto: proto, fn: fn, orig: orig, desc: desc });
n++;
}
} catch (e) { }
}
log('摆放诊断开启,包装 ' + n + ' 个函数。现在尝试摆放一次建筑,然后把日志发我');
return n;
}
// ============================================================
// 第三部分半:农场(开垦 / 播种 / 收获)
// 收获 = MAPOPERATION.reap_crop(tile)
// 播种 = MAPOPERATION.plant_crop(tile, seedApi)
// 开垦 = 对 FARM_RUIN(1066) 走 deal_chores(锄头杂役 -> 耕地)
// ============================================================
const FARM = { on: false };
// ============================================================
// 无视地形摆放:运行时补丁所有摆放校验函数(按钮开关,可还原)
// true=可放/通过: checkEmptyTile checkTileUnlock checkTileWalk judge_tile_border
// false=无阻挡/非特殊: checkOverObstruct checkSpecialTile checkOutBorderWalk
// ============================================================
const TERR = { on: false, patched: [] };
// 最小集:只保留极性从源码确认过的;checkArea 是 set_building 的总闸(false=拦截)
const TERR_RULES = {
checkArea: function () { return true; }, // 摆放总闸
checkEmptyTile: function () { return true; }, // true=空地可放
checkTileUnlock: function () { return true; }, // true=已解锁
checkOverObstruct: function () { return false; }, // false=无阻挡
checkSpecialTile: function () { return false; }, // false=非特殊禁放格
// —— 以下为 set_building 里补充确认的拦截点 ——
check_cropLand: function () { return true; }, // 与 checkArea && 在一起,false=整个不放
checkInUseSpecial: function () { return false; }, // true=建筑使用中拦截
self_behave_judge: function () { return 2; }, // 移动路径要求返回 2(2!=x → return 0)
setCheck: function () { return { state: -1 }; }, // state 2/3/4 会 toast 拦截;-1=跳过校验
};
function setNoTerrain(on) {
const mm = MM();
if (!mm) return 0;
let hit = 0;
const seen = new Set();
const names = [];
// 候补对象: 模块本身 / 模块原型 / 类导出 prototype / 模块一级嵌套对象及其原型
function tryPatch(target) {
if (!target || (typeof target !== 'object' && typeof target !== 'function')) return;
if (seen.has(target)) return;
seen.add(target);
for (const fn in TERR_RULES) {
const desc = Object.getOwnPropertyDescriptor(target, fn);
if ((!desc || typeof desc.value !== 'function') && typeof target[fn] !== 'function') continue;
if (desc && !desc.configurable) continue;
const tag = fn + '@' + (target.constructor && target.constructor.name || '?');
if (on) {
if (TERR.patched.some(function (p) { return p.tag === tag; })) { hit++; continue; }
TERR.patched.push({ tag: tag, target: target, fn: fn, orig: desc ? desc.value : target[fn], desc: desc });
try {
if (desc) { desc.value = TERR_RULES[fn]; Object.defineProperty(target, fn, desc); }
else target[fn] = TERR_RULES[fn];
hit++; names.push(fn);
} catch (e) { }
} else {
const i = TERR.patched.findIndex(function (p) { return p.tag === tag; });
if (i >= 0) {
try { Object.defineProperty(target, fn, TERR.patched[i].desc || { value: TERR.patched[i].orig, writable: true, configurable: true }); } catch (e) { try { target[fn] = TERR.patched[i].orig; } catch (e2) { } }
TERR.patched.splice(i, 1); hit++;
}
}
}
}
for (const key in mm) {
try {
const mod = mm[key];
tryPatch(mod);
tryPatch(Object.getPrototypeOf(mod));
if (mod && mod.prototype) tryPatch(mod.prototype);
if (mod && typeof mod === 'object') {
for (const k2 in mod) {
const sub = mod[k2];
if (sub && (typeof sub === 'object')) {
tryPatch(sub);
tryPatch(Object.getPrototypeOf(sub));
}
}
}
} catch (e) { }
}
TERR.lastNames = names;
return hit;
}
// ============================================================
// 无敌:补丁角色受击 dealDamage(函数源码含 role_under_atk),强制伤害为 0
// 敌人受击走 enemy_under_atk,不受影响
// ============================================================
const GOD = { on: false, patched: [] };
function setNoDamage(on) {
const mm = MM();
if (!mm) return 0;
let hit = 0;
// 还原
for (const p of GOD.patched) {
try { Object.defineProperty(p.proto, 'dealDamage', p.desc || { value: p.orig, writable: true, configurable: true }); } catch (e) { }
}
GOD.patched = [];
if (!on) return 0;
const seen = new Set();
function tryPatch(target) {
if (!target || (typeof target !== 'object' && typeof target !== 'function')) return;
if (seen.has(target)) return;
seen.add(target);
const desc = Object.getOwnPropertyDescriptor(target, 'dealDamage');
if (!desc || typeof desc.value !== 'function' || !desc.configurable) return;
const orig = desc.value;
// 只补角色受击:函数源码里引用了 role_under_atk(敌人是 enemy_under_atk)
let src = '';
try { src = Function.prototype.toString.call(orig); } catch (e) { }
if (src.indexOf('role_under_atk') < 0) return;
const nd = Object.assign({}, desc);
nd.value = function (t) {
return orig.call(this, 0, ...Array.prototype.slice.call(arguments, 1));
};
Object.defineProperty(target, 'dealDamage', nd);
GOD.patched.push({ proto: target, orig: orig, desc: desc });
hit++;
}
for (const key in mm) {
try {
const mod = mm[key];
tryPatch(mod);
tryPatch(Object.getPrototypeOf(mod));
if (mod && mod.prototype) tryPatch(mod.prototype);
if (mod && typeof mod === 'object') {
for (const k2 in mod) {
const sub = mod[k2];
if (sub && (typeof sub === 'object' || typeof sub === 'function')) {
tryPatch(sub);
tryPatch(Object.getPrototypeOf(sub));
if (sub.prototype) tryPatch(sub.prototype);
}
}
}
} catch (e) { }
}
return hit;
}
// ============================================================
// 伤害99999:补丁敌人受击 dealDamage(函数源码含 enemy_under_atk),伤害强制 99999
// 角色受击走 role_under_atk,不受影响
// ============================================================
const DMG = { on: false, patched: [] };
const BIG_DMG = 99999;
function wrapDamageFn(orig) {
return function (t) {
return orig.call(this, BIG_DMG, ...Array.prototype.slice.call(arguments, 1));
};
}
// 通用:沿原型链查找并对实例原型打一个函数补丁(marker 用于源码识别),成功返回 1
function patchInstanceFn(proto, fnName, marker, wrapperMaker, store) {
try {
let p = proto, depth = 0;
while (p && depth++ < 5) {
const desc = Object.getOwnPropertyDescriptor(p, fnName);
if (desc) {
if (typeof desc.value !== 'function' || !desc.configurable) return 0;
let src = '';
try { src = Function.prototype.toString.call(desc.value); } catch (e) { }
if (src.indexOf(marker) < 0) return 0;
const nd = Object.assign({}, desc);
nd.value = wrapperMaker(desc.value);
Object.defineProperty(p, fnName, nd);
store.push({ proto: p, orig: desc.value, desc: desc });
return 1;
}
p = Object.getPrototypeOf(p);
}
return 0;
} catch (e) { return 0; }
}
// 懒加载补丁循环:
// 主方案:直接改角色属性 ATK=99999 / ATKSPEED=0(走正常攻击管线,主场景即可生效)
// 备用:出现敌人后按实例原型补丁 dealDamage(保险)
const LAZY = { running: false };
function lazyBattlePatches() {
if (!DMG.on && !FAST.on) { LAZY.running = false; return; }
try {
const mm = MM();
const sc = mm && mm.INFO && mm.INFO.getCurrScene && mm.INFO.getCurrScene();
if (sc) {
const rv = sc.CURVIEW && sc.CURVIEW.role_ATK;
if (rv && rv.battle_pros) {
if (DMG.on && rv.battle_pros.ATK !== BIG_DMG) {
rv.battle_pros.ATK = BIG_DMG;
log('伤害99999:角色 ATK 已改为 ' + BIG_DMG);
}
if (FAST.on && rv.battle_pros.ATKSPEED !== 0) {
rv.battle_pros.ATKSPEED = 0;
log('高速攻击:角色 ATKSPEED 已置 0');
}
}
if (DMG.on && !DMG.patched.length) {
const em = sc.enemyManager;
const nodes = em && em.getAllEnemyNode && em.getAllEnemyNode();
if (nodes) {
let done = false;
for (const nd of nodes) {
const comps = (nd && nd._components) || [];
for (const comp of comps) {
if (!comp) continue;
if (patchInstanceFn(Object.getPrototypeOf(comp), 'dealDamage', 'enemy_under_atk', wrapDamageFn, DMG.patched)) {
log('伤害99999:已补丁敌人原型 (' + (comp.constructor && comp.constructor.name || '?') + ')');
done = true; break;
}
}
if (done) break;
}
}
}
}
} catch (e) { }
setTimeout(lazyBattlePatches, 800);
}
function ensureLazy() { if (!LAZY.running) { LAZY.running = true; lazyBattlePatches(); } }
function wrapFastFn(orig) {
return function () {
try { if (this.battle_pros) this.battle_pros.ATKSPEED = 0; } catch (e) { }
return orig.apply(this, arguments);
};
}
function setBigDamage(on) {
const mm = MM();
if (!mm) return 0;
let hit = 0;
// 还原
for (const p of DMG.patched) {
try { Object.defineProperty(p.proto, 'dealDamage', p.desc || { value: p.orig, writable: true, configurable: true }); } catch (e) { }
}
DMG.patched = [];
if (!on) return 0;
const seen = new Set();
function tryPatch(target) {
if (!target || (typeof target !== 'object' && typeof target !== 'function')) return;
if (seen.has(target)) return;
seen.add(target);
const desc = Object.getOwnPropertyDescriptor(target, 'dealDamage');
if (!desc || typeof desc.value !== 'function' || !desc.configurable) return;
const orig = desc.value;
// 只补敌人受击:函数源码里引用了 enemy_under_atk(角色是 role_under_atk)
let src = '';
try { src = Function.prototype.toString.call(orig); } catch (e) { }
if (src.indexOf('enemy_under_atk') < 0) return;
const nd = Object.assign({}, desc);
nd.value = function (t) {
return orig.call(this, BIG_DMG, ...Array.prototype.slice.call(arguments, 1));
};
Object.defineProperty(target, 'dealDamage', nd);
DMG.patched.push({ proto: target, orig: orig, desc: desc });
hit++;
}
for (const key in mm) {
try {
const mod = mm[key];
tryPatch(mod);
tryPatch(Object.getPrototypeOf(mod));
if (mod && mod.prototype) tryPatch(mod.prototype);
if (mod && typeof mod === 'object') {
for (const k2 in mod) {
const sub = mod[k2];
if (sub && (typeof sub === 'object' || typeof sub === 'function')) {
tryPatch(sub);
tryPatch(Object.getPrototypeOf(sub));
if (sub.prototype) tryPatch(sub.prototype);
}
}
}
} catch (e) { }
}
if (on && !hit) { log('伤害99999:moduleMap 扫描未命中,进入战斗后自动补丁'); ensureLazy(); }
return hit;
}
// ============================================================
// 全屏攻击:定时对全屏所有敌人结算一次伤害(走敌人自身 dealDamage,动画/死亡流程正常)
// ============================================================
const AOE = { on: false };
function aoeLoop() {
if (!AOE.on) return;
try {
const mm = MM();
const sc = mm && mm.INFO && mm.INFO.getCurrScene && mm.INFO.getCurrScene();
const em = sc && sc.enemyManager;
const nodes = em && em.getAllEnemyNode && em.getAllEnemyNode();
if (nodes && nodes.length) {
const rv = sc.CURVIEW && sc.CURVIEW.role_ATK;
const atk = (rv && rv.battle_pros && rv.battle_pros.ATK) || 1000;
const dmg = DMG.on ? BIG_DMG : atk;
for (const nd of nodes) {
try {
if (!nd || (typeof cc !== 'undefined' && cc.isValid && !cc.isValid(nd))) continue;
const comps = nd._components || [];
for (const en of comps) {
if (en && en.battle_pros && en.battle_pros.HP > 0 && typeof en.dealDamage === 'function') {
en.dealDamage(dmg, 0, 0, true);
break;
}
}
} catch (e) { }
}
}
} catch (e) { }
setTimeout(aoeLoop, 400);
}
// ============================================================
// 高速攻击:补丁 robotAutoAttack,每次把攻击间隔 ATKSPEED 清零(判定 t-last= MEM_MAX_HITS) break;
} else if (d < MEM_MAX_DEPTH && !isSkipObj(v) && !visited.has(v)) {
visited.add(v);
queue.push({ o: v, p: p + '.' + k, d: d + 1 });
}
nodes++;
if (nodes >= MEM_MAX_NODES) break;
}
}
log('内存搜索 ' + val + ':命中 ' + MEM.hits.length + ' 处(扫描 ' + nodes + ' 节点)');
memList();
return MEM.hits.length;
}
function memFilter(val) {
const before = MEM.hits.length;
MEM.hits = MEM.hits.filter(function (h) {
try { return h.obj[h.key] === val; } catch (e) { return false; }
});
log('内存过滤 ' + val + ':' + before + ' -> ' + MEM.hits.length + ' 处');
memList();
return MEM.hits.length;
}
function memWrite(val) {
let ok = 0;
for (const h of MEM.hits) {
try {
const desc = Object.getOwnPropertyDescriptor(h.obj, h.key);
if (desc && desc.get && !desc.set) continue; // 只读 getter 跳过
h.obj[h.key] = val;
ok++;
} catch (e) { }
}
log('内存写入 ' + val + ':成功 ' + ok + '/' + MEM.hits.length + ' 处');
return ok;
}
function memList() {
for (const h of MEM.hits.slice(0, 30)) {
let cur;
try { cur = h.obj[h.key]; } catch (e) { cur = '?'; }
log(' ' + h.path + ' = ' + cur);
}
if (MEM.hits.length > 30) log(' ... 其余 ' + (MEM.hits.length - 30) + ' 处省略');
return MEM.hits.length;
}
function memClear() { MEM.hits = []; log('内存结果已清空'); }
// 全量建筑扫描(多路径兜底),返回 [{id, build_id, locate, hp, time, regrow}]
function allBuildings() {
const mm = MM();
let data = null;
try {
const sc = mm.INFO.getCurrScene();
const cd = sc && sc.CURDATA;
if (cd && cd.sceneBuildData && cd.sceneBuildData.getBuildingData) data = cd.sceneBuildData.getBuildingData();
} catch (e) { }
if (!data) { // 兜底:扫 moduleMap 里所有 getBuildingData
for (const key in mm) {
const mod = mm[key];
if (mod && typeof mod.getBuildingData === 'function') {
try { const d = mod.getBuildingData(); if (d && Object.keys(d).length) { data = d; break; } } catch (e) { }
}
}
}
const out = [];
if (data) for (const id in data) {
const b = data[id];
if (b && b.build_id) out.push({ id: +id, build_id: b.build_id, locate: String(b.locate || ''), hp: b.hp, time: b.time, regrow: b.regrow });
}
return out;
}
// 扫描农场: {crops, emptyLand, ruins, seeds}
function scanFarm() {
const mm = MM();
if (!mm) { log('moduleMap 未就绪'); return null; }
const out = { crops: [], emptyLand: [], ruins: [], seeds: [] };
try {
const bs = allBuildings();
if (!bs.length) { log('拿不到建筑数据(场景未加载完?)'); return out; }
const now = Date.now() / 1000;
const landTiles = {}, cropTiles = {};
for (const b of bs) {
if (mm.ADATA.API_SEED[b.build_id]) { // 已种作物
cropTiles[b.locate] = 1;
out.crops.push({ tile: b.locate, id: b.id, build_id: b.build_id, mature: !b.time || b.time <= now, time: b.time });
} else if (b.build_id === 1070) { // CROPLAND
landTiles[b.locate] = 1;
} else if (b.build_id === 1066) { // FARM_RUIN 荒地
out.ruins.push({ tile: b.locate, id: b.id, hp: b.hp });
}
}
for (const k in landTiles) if (!cropTiles[k]) out.emptyLand.push(k);
} catch (e) { log('scanFarm err: ' + e.message); }
// 背包种子(仅提示用,plant_crop 自己会校验库存)
try {
for (const sid in mm.ADATA.API_SEED) {
let num = 0;
try { const n = mm.GDATA.itemData.getItem(+sid); num = (typeof n === 'number') ? n : (n && n.num) || 0; } catch (e) { }
if (num > 0) out.seeds.push({ id: +sid, num: num, name: (mm.ADATA.getItemApi(+sid) || {}).name || sid });
}
} catch (e) { }
return out;
}
// 浇水 + 打水: 先对水井(1074) deal_chores 打水,再对所有作物 water_crop
function waterAll(withWell) {
const mm = MM();
if (!mm) return;
const f = scanFarm();
if (!f.crops.length) { log('没有作物可浇'); return; }
const run = function () {
let i = 0, done = 0;
(function next() {
if (i >= f.crops.length) { log('浇水完成 ' + done + '/' + f.crops.length); return; }
const k = f.crops[i++].tile;
const p = k.split('|');
try { if (mm.MAPOPERATION.water_crop({ x: +p[0], y: +p[1] }, true, 0)) done++; } catch (e) { }
setTimeout(next, 250);
})();
};
if (withWell) {
let well = null;
try { for (const b of allBuildings()) if (b.build_id === 1074) { well = b.locate; break; } } catch (e) { }
if (well) {
const wp = well.split('|');
log('打水 @' + well);
try {
// 游戏自身打水流程:deal_water(井坐标, true, 回调),自动把水桶补满
mm.MAPOPERATION.deal_water({ x: +wp[0], y: +wp[1] }, true, function () { });
} catch (e) { log('打水 err: ' + e.message); }
setTimeout(run, 2500); // 等打水动画/补满完成
return;
}
log('附近没有水井(1074),直接浇水');
}
run();
}
// 收获全部成熟作物
let REAP_RUNNING = false;
function reapAll() {
if (REAP_RUNNING) return;
const f = scanFarm();
const ripe = (f.crops || []).filter(function (c) { return c.mature; });
log('成熟作物 ' + ripe.length + '/' + (f.crops || []).length);
if (!ripe.length) return 0;
REAP_RUNNING = true;
const mm = MM();
let i = 0, done = 0;
(function next() {
if (i >= ripe.length) { REAP_RUNNING = false; log('收获完成 ' + done); return; }
const c = ripe[i++];
try {
const p = c.tile.split('|');
mm.MAPOPERATION.reap_crop({ x: +p[0], y: +p[1] });
done++;
} catch (e) { log('reap err @' + c.tile + ': ' + e.message); }
setTimeout(next, 400);
})();
return ripe.length;
}
// 播种: 空耕地 + 背包第一个有库存的种子
let SOW_RUNNING = false;
function sowAll(seedId) {
if (SOW_RUNNING) return;
const f = scanFarm();
const seeds = f.seeds || [];
if (!seeds.length) { log('背包没有种子'); return 0; }
const seed = seedId ? seeds.find(function (s) { return s.id === seedId; }) : seeds[0];
if (!seed) { log('找不到种子 ' + seedId); return 0; }
const mm = MM();
const seedApi = mm.ADATA.getItemApi(seed.id) || mm.ADATA.API_SEED[seed.id];
log('播种: ' + (seed.name || seed.id) + ' x' + seed.num + ' -> 空地 ' + f.emptyLand.length);
if (!f.emptyLand.length) return 0;
SOW_RUNNING = true;
let i = 0, done = 0;
(function next() {
if (i >= f.emptyLand.length || done >= seed.num) {
SOW_RUNNING = false;
log('播种完成 ' + done);
if (done) setTimeout(function () { waterAll(true); }, 800); // 播种后自动浇水打水
return;
}
const k = f.emptyLand[i++];
try {
const p = k.split('|');
if (mm.MAPOPERATION.plant_crop({ x: +p[0], y: +p[1] }, seedApi, false)) done++;
} catch (e) { log('sow err @' + k + ': ' + e.message); }
setTimeout(next, 400);
})();
return f.emptyLand.length;
}
// 开垦: 缺多少开多少 = 种子总数 - 现有地块(耕地+已种),直接在空位创建 CROPLAND(1070)
let DIG_RUNNING = false;
function digAll(maxN) {
if (DIG_RUNNING) return 0;
const mm = MM();
const f = scanFarm();
const seedTotal = f.seeds.reduce(function (s, x) { return s + x.num; }, 0);
const landCount = f.crops.length + f.emptyLand.length;
let need = (maxN !== undefined) ? maxN : (seedTotal - landCount);
if (need <= 0) { log('地够用: 地块' + landCount + ' >= 种子' + seedTotal); return 0; }
// 已有耕地/作物位置作为锚点,螺旋找空位
const anchors = [];
for (const c of f.crops) anchors.push(c.tile);
try {
const bs = allBuildings();
for (const b of bs) if (b.build_id === 1070) anchors.push(b.locate);
} catch (e) { }
if (!anchors.length) { log('没有锚点:先在农场里种一块地'); return 0; }
const occ = {};
try { for (const b of allBuildings()) if (b.locate) occ[b.locate] = 1; } catch (e) { }
// 收集锚点周围空位
const free = [];
for (const a of anchors) {
const p = a.split('|'), cx = +p[0], cy = +p[1];
for (let dx = -1; dx <= 1; dx++) for (let dy = -1; dy <= 1; dy++) {
if (!dx && !dy) continue;
const k = (cx + dx) + '|' + (cy + dy);
if (!occ[k]) { free.push(k); occ[k] = 1; }
}
}
log('开垦: 种子' + seedTotal + ' 地块' + landCount + ' -> 开 ' + Math.min(need, free.length));
if (!free.length) return 0;
DIG_RUNNING = true;
const sc = mm.INFO.getCurrScene();
const cd = sc && sc.CURDATA;
const bd = cd && cd.sceneBuildData;
const td = cd && cd.sceneTileData;
let i = 0, done = 0;
(function next() {
if (i >= free.length || done >= need || !bd) { DIG_RUNNING = false; log('开垦完成 ' + done); return; }
const k = free[i++];
try {
const p = k.split('|');
const o = bd.createBuildingData(1070, true); // CROPLAND
o.locate = k;
bd.setBuildingData(o.id, o);
if (td && td.setTileBuilding) td.setTileBuilding(o.locate, o);
if (bd.cloud_save) bd.cloud_save();
try {
const ms = mm.INFO.mainscene;
if (ms && ms.buildUIManager && ms.buildUIManager.create_node_by_data) ms.buildUIManager.create_node_by_data(o);
} catch (e) { }
done++;
} catch (e) { log('dig err @' + k + ': ' + e.message); }
setTimeout(next, 300);
})();
return free.length;
}
// 自动农场循环: 收获 -> 播种 -> (间隔回合)开垦,每轮只打一条摘要
let farmTick = 0;
function farmLoop() {
if (!FARM.on) return;
farmTick++;
try {
const f = scanFarm();
if (f) {
const ripe = f.crops.filter(function (c) { return c.mature; }).length;
const msg = '[' + farmTick + '] 作物' + f.crops.length + '(熟' + ripe + ') 空地' + f.emptyLand.length +
' 荒地' + f.ruins.length + ' 种子' + f.seeds.map(function (s) { return (s.name || s.id) + 'x' + s.num; }).join(',') ;
log(msg);
if (ripe) reapAll();
if (f.seeds.length) {
if (f.emptyLand.length) sowAll();
else digAll(); // 缺多少开多少
}
}
} catch (e) { log('farm err: ' + e.message); }
setTimeout(farmLoop, 10000);
}
// ============================================================
// 第四部分:抓包(WebSocket + XHR)与导出、登录参数推送
// ============================================================
window.__pkts = [];
const MAX = 20000;
function logB(dir, data) {
if (window.__pkts.length >= MAX) return;
try {
const b = new Uint8Array(data);
let s = '';
for (let i = 0; i < b.length; i++) s += String.fromCharCode(b[i]);
window.__pkts.push({ dir: dir, t: Date.now(), len: b.length, b64: btoa(s) });
} catch (e) { }
}
function logS(dir, s) {
if (window.__pkts.length < MAX) window.__pkts.push({ dir: dir, t: Date.now(), s: String(s).slice(0, 4000) });
}
const OrigWS = window.WebSocket;
function HookedWS(url, protocols) {
const ws = protocols !== undefined ? new OrigWS(url, protocols) : new OrigWS(url);
window.__pkts.push({ dir: 'OPEN', t: Date.now(), url: String(url) });
ws.addEventListener('message', function (ev) {
(ev.data instanceof ArrayBuffer) ? logB('R', ev.data) : logS('R', ev.data);
});
const origSend = ws.send.bind(ws);
ws.send = function (d) {
(d instanceof ArrayBuffer) ? logB('T', d) : logS('T', d);
return origSend(d);
};
return ws;
}
HookedWS.prototype = OrigWS.prototype;
HookedWS.CONNECTING = 0; HookedWS.OPEN = 1; HookedWS.CLOSING = 2; HookedWS.CLOSED = 3;
Object.defineProperty(HookedWS, 'name', { value: 'WebSocket' });
window.WebSocket = HookedWS;
const oo = XMLHttpRequest.prototype.open, osn = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (m, u) { this.__u = u; return oo.apply(this, arguments); };
XMLHttpRequest.prototype.send = function (b) {
const x = this;
if (String(x.__u || '').search(/report|stat|hm\.baidu|cnzz|talkingdata|gif|assets\/|\.bin|\.png|\.jpg|\.mp3|\.plist|\.json$/i) < 0
&& String(x.__u || '').indexOf('xdyouxi.net') >= 0) {
if (b) (typeof b === 'string') ? logS('T ' + x.__u, b) : logB('T ' + x.__u, b);
const ol = x.onload;
x.onload = function (ev) {
try {
const r = (x.responseType === 'arraybuffer' || x.responseType === 'blob') ? x.response : (x.responseText || x.response || '');
(typeof r === 'string') ? logS('R ' + x.__u, r) : (r && logB('R ' + x.__u, r));
} catch (e) { }
return ol ? ol.call(x, ev) : undefined;
};
}
return osn.apply(this, arguments);
};
function pushLogin(body) {
try {
GM_xmlhttpRequest({
method: 'POST',
url: 'http://127.0.0.1:17933/pkt',
data: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
onload: function () { log('登录参数已推送本地 bot'); },
onerror: function () { log('本地 bot 未启动(可忽略)'); },
});
} catch (e) { }
}
const _open2 = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (m, u) {
const x = this;
if (String(u).indexOf('thirdAuthLoginNew') >= 0) {
const _ol = x.onload;
x.onload = function (ev) {
try { if (x.__u_body) pushLogin(x.__u_body); } catch (e) { }
return _ol ? _ol.call(x, ev) : undefined;
};
}
return _open2.apply(this, arguments);
};
const _send2 = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (b) {
const x = this;
if (String(x.__u || '').indexOf('thirdAuthLoginNew') >= 0 && typeof b === 'string') x.__u_body = b;
return _send2.apply(this, arguments);
};
function doExport() {
const out = JSON.stringify(window.__pkts);
log('抓包共 ' + window.__pkts.length + ' 条');
try { GM_setClipboard(out); log('已复制到剪贴板'); } catch (e) { }
const blob = new Blob([out], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'xsxn_pkts_' + Date.now() + '.json';
a.click();
}
GM_registerMenuCommand('导出抓包数据', doExport);
window.__dump = doExport;
// ============================================================
// 第五部分:等待游戏就绪
// ============================================================
let readyTries = 0;
(function waitReady() {
const app = getApp();
if (app && app.moduleMap && app.moduleMap.MAPOPERATION) {
try { PW.__xsxn = window.__xsxn; } catch (e) { }
log('游戏模块已就绪(moduleMap ' + Object.keys(app.moduleMap).length + ' 项),可点「扫资源」');
return;
}
if (++readyTries > 600) { log('等待游戏模块超时(游戏可能未登录进主场景)'); return; }
setTimeout(waitReady, 500);
})();
})();