
image-20250624174137965.png (1.65 MB, 下载次数: 0)
下载附件
2025-6-24 18:11 上传
更新
刷完了剧情,就开始研究怎么解锁各种剧情了。所以又继续研究了下,大部分的数据都存在了gamePlay这个全局变量里了。所以根据这个思路,整理了一下,用chatGPT更新了注入代码。现在包含以下几个功能:
[ol]
[/ol]
说实话,更新后的会有点影响游戏体验,还是建议通关一次过后再使用。
因为代码比较多,现在分为两个文件,主要的逻辑都在inject-enhanced-tools.js中,main.loader.js文件中只是注入的入口。
有一点要注意,main.loader.js中的最后一行代码require(path.join(__dirname, './bin/main-darwin-' + arch + '.jsc')); 是需要根据自己实际的操作系统来的,所以不要照抄,要根据原文件的内容修改。
//main.loader.js
require('bytenode');
const fs = require('fs');
const path = require('node:path');
const arch = process.arch;
const { app } = require('electron');
app.commandLine.appendSwitch('remote-debugging-port', '9222');
app.commandLine.appendSwitch('remote-allow-origins', '*');
const injectionPath = path.join(__dirname, 'inject-enhanced-tools.js');
const injectionScript = fs.readFileSync(injectionPath, 'utf8');
app.on('web-contents-created', (event, contents) => {
contents.on('did-finish-load', () => {
contents.executeJavaScript(injectionScript)
.then(() => console.log('[注入] 脚本成功加载'))
.catch(console.error);
});
});
require(path.join(__dirname, './bin/main-darwin-' + arch + '.jsc'));
// inject-enhanced-tools.js
/**
* 注入脚本功能说明:
* 1. 视频播放控制(R键切换倍速,←/→ 快进快退5秒)
* 2. 左上角实时播放信息面板(当前时间/总时长,倍速)
* 3. L键切换显示人物关系面板(实时刷新)
* 4. K键切换显示当前分支选择对人物关系的影响
* 5. 捕捉 console.log 中包含 [conditionCheck] 的日志,并右上角 toast 提示
*/
(function () {
if (window.__inject_inited__) return;
window.__inject_inited__ = true;
const rateList = [1.0, 1.5, 2.0, 3.0];
let currentRateIndex = 0;
let currentRate = rateList[currentRateIndex];
// 中心 Toast 提示(可复用)
const centerToast = document.createElement('div');
Object.assign(centerToast.style, {
position: 'fixed',
top: '10px',
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(0,0,0,0.7)',
color: '#fff',
padding: '8px 16px',
borderRadius: '8px',
fontSize: '16px',
zIndex: 999999,
opacity: '0',
transition: 'opacity 0.3s ease'
});
document.body.appendChild(centerToast);
function showToast(msg, duration = 1500) {
centerToast.innerText = msg;
centerToast.style.opacity = '1';
clearTimeout(centerToast._t);
centerToast._t = setTimeout(() => {
centerToast.style.opacity = '0';
}, duration);
}
function formatTime(seconds) {
if (isNaN(seconds)) return '--:--';
const m = Math.floor(seconds / 60).toString().padStart(2, '0');
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${m}:${s}`;
}
function getPlayingVideo() {
const videos = document.querySelectorAll('.--player-video-root video');
for (const video of videos) {
const style = getComputedStyle(video);
if (style.opacity === '1' && style.zIndex === '-10000') {
return video;
}
}
return null;
}
// 视频信息面板
const infoPanel = document.createElement('div');
Object.assign(infoPanel.style, {
position: 'fixed',
top: '10px',
left: '100px',
background: 'rgba(0, 0, 0, 0.7)',
color: 'white',
padding: '6px 12px',
borderRadius: '6px',
fontSize: '14px',
fontFamily: 'monospace',
zIndex: 999999
});
document.body.appendChild(infoPanel);
setInterval(() => {
const video = getPlayingVideo();
if (video) {
if (video.playbackRate !== currentRate) {
video.playbackRate = currentRate;
}
infoPanel.innerText = `播放进度:${formatTime(video.currentTime)} / ${formatTime(video.duration)}\n播放速度:${currentRate}x`;
} else {
infoPanel.innerText = `未检测到正在播放的视频`;
}
}, 1000);
document.addEventListener('keydown', (e) => {
const video = getPlayingVideo();
if (!video) return;
if (e.key === 'ArrowLeft') {
video.currentTime = Math.max(0, video.currentTime - 5);
showToast(`⏪ 后退 5 秒:${formatTime(video.currentTime)}`);
} else if (e.key === 'ArrowRight') {
video.currentTime = Math.min(video.duration, video.currentTime + 5);
showToast(`⏩ 前进 5 秒:${formatTime(video.currentTime)}`);
} else if (e.key.toLowerCase() === 'r') {
currentRateIndex = (currentRateIndex + 1) % rateList.length;
currentRate = rateList[currentRateIndex];
showToast(`当前播放速度:${currentRate}x`);
}
});
// 监听 L 键:显示人物关系
(function () {
let isVisible = false;
let intervalId = null;
const relPanel = document.createElement('div');
Object.assign(relPanel.style, {
position: 'fixed',
top: '300px',
left: '20px',
background: 'rgba(0,0,0,0.8)',
color: 'white',
padding: '10px 16px',
borderRadius: '8px',
fontSize: '14px',
whiteSpace: 'pre',
zIndex: 999999,
fontFamily: 'monospace',
display: 'none'
});
document.body.appendChild(relPanel);
function updatePanel() {
try {
const relMap = gamePlay?.relationshipManager?.relationship;
if (!relMap) {
relPanel.innerText = '未找到人物关系数据';
return;
}
const lines = ['【人物关系】'];
for (const key in relMap) {
const rel = relMap[key];
if (rel?.displayName && rel?.value != null) {
lines.push(`${rel.displayName}:${rel.value}`);
}
}
relPanel.innerText = lines.join('\n');
} catch (err) {
relPanel.innerText = '❌ 获取人物关系失败';
}
}
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'l') {
isVisible = !isVisible;
relPanel.style.display = isVisible ? 'block' : 'none';
if (isVisible) {
updatePanel();
intervalId = setInterval(updatePanel, 1000);
} else {
clearInterval(intervalId);
}
}
});
})();
// 监听 K 键:预测人物关系变化
(function () {
let isKPanelEnabled = false;
let kPanel = null;
let updateTimer = null;
function createKPanel() {
if (kPanel) return;
kPanel = document.createElement('div');
Object.assign(kPanel.style, {
position: 'fixed',
bottom: '10px',
left: '10px',
background: 'rgba(0,0,0,0.85)',
color: '#fff',
padding: '10px 16px',
borderRadius: '8px',
fontSize: '14px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
zIndex: '999999',
maxWidth: '480px',
maxHeight: '60vh',
overflowY: 'auto',
display: 'none'
});
document.body.appendChild(kPanel);
}
function updateKPanel() {
try {
if (!isKPanelEnabled || !gamePlay?.UIShow) {
kPanel.style.display = 'none';
return;
}
const chapter = gamePlay.getCurrentChapterInfo?.();
const chapterId = chapter?.chapterId;
const currentNode = gamePlay.archiveManager?.archive?.currentNode;
const edges = gamePlay.archiveManager?.layout?.[chapterId]?.edges || [];
const targets = edges.filter(edge => edge.source === currentNode).map(edge => edge.target);
const lines = [];
targets.forEach(target => {
const nodeDetail = gamePlay.archiveManager?.nodeList?.[target];
if (!nodeDetail) {
lines.push(`${currentNode} → ${target}:未找到节点信息`);
return;
}
const relChange = nodeDetail.relationship || {};
const keys = Object.keys(relChange);
if (keys.length === 0) {
lines.push(`${currentNode} → ${target}:无影响`);
} else {
const parts = keys.map(k => {
const delta = relChange[k];
const name = gamePlay.relationshipManager?.relationship?.[k]?.displayName || k;
const sign = delta > 0 ? '+' : '';
return `${name} ${sign}${delta}`;
});
lines.push(`${currentNode} → ${target}:${parts.join(';')}`);
}
});
kPanel.innerText = lines.join('\n');
kPanel.style.display = 'block';
} catch (err) {
kPanel.innerText = '❌ 获取数据失败';
}
}
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'k') {
isKPanelEnabled = !isKPanelEnabled;
if (isKPanelEnabled) {
createKPanel();
updateKPanel();
updateTimer = setInterval(updateKPanel, 1000);
showToast('✅ 已开启人物关系预测面板');
} else {
clearInterval(updateTimer);
updateTimer = null;
if (kPanel) kPanel.style.display = 'none';
showToast('❎ 已关闭人物关系预测面板');
}
}
});
})();
// 捕捉日志输出中的关键词
(function () {
const rawLog = console.log;
const toastContainer = document.createElement('div');
Object.assign(toastContainer.style, {
position: 'fixed',
top: '10px',
right: '10px',
zIndex: '999999',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end'
});
document.body.appendChild(toastContainer);
function showLogToast(message) {
const toast = document.createElement('div');
toast.innerText = message;
Object.assign(toast.style, {
background: 'rgba(50, 50, 50, 0.9)',
color: '#fff',
padding: '8px 14px',
marginTop: '6px',
borderRadius: '6px',
fontSize: '14px',
maxWidth: '400px',
wordBreak: 'break-word',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
opacity: '0',
transition: 'opacity 0.3s ease'
});
toastContainer.appendChild(toast);
requestAnimationFrame(() => {
toast.style.opacity = '1';
});
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 10000);
}
console.log = function (...args) {
try {
const str = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
if (str.includes('[conditionCheck]')) {
showLogToast(str);
}
} catch {}
rawLog.apply(console, args);
};
})();
})();
前言
最近在Steam上有一款游戏小小的出了一下圈,叫“感情XX模拟器”,也叫“捞女游戏”。这种互动影游说白了就是播放视频,玩家选择故事走向,就当电视剧、短剧看了。
现在大家看视频都倍速看了,所以这种游戏良心点的会提供倍速播放的按钮,差一点的只有解锁后的故事章节有倍速按钮,最差的不管故事章节是否解锁,都无法倍速播放。
而这款游戏就属于中间那档,只有解锁的故事章节才能倍速播放,奈何剧情确实有些拖沓,实在不想浪费太多时间观看视频,就想着是否能有办法让未解锁的故事章节也能倍速播放视频。
本人也是小白一个,也是一点点根据chatGPT的提示来完成的,所以在此跟大家分享一下过程。
(PS.该游戏是跨平台的,以下的操作都是在MacOS中完成的,但和windows系统应该都是通用的)
打开App包内容后发现,这个游戏是用electron框架编写的,那么应该就可以跟调试网页一样来进行调试。
开启远程调试
{
"name": "RevengeOnGoldDiggers",
"version": "1.1.3",
"description": "RevengeOnGoldDiggers",
"main": "src/main.loader.js",
"author": "zuoqianfang",
"license": "ISC",
"dependencies": {
"appdata-path": "1.0.0",
"steamworks.js": "^0.3.2",
"bytenode": "1.5.3",
"electron-log": "5.1.5"
}
}
找到package.json文件,找到了程序的入口文件。

image-20250623174105147.png (213.09 KB, 下载次数: 0)
下载附件
2025-6-23 17:57 上传
require('bytenode');
const path = require('node:path');
const arch = process.arch;
require(path.join(__dirname, './bin/main-darwin-' + arch + '.jsc'));
原来所有的逻辑都是在jsc文件中,那就不考虑了。但好在main.loader.js文件是可以修改的。
尝试了各种各样的方法,都无法在游戏主窗口中打开DevTools,应该是开发者做了相关的屏蔽,那么就只能尝试开启远程调试了。
直接找到程序的二进制文件,使用 --remote-debugging-port=9222 来开启远程调试。该方法会被检测到,并且几秒后游戏就会自动关闭。
后来在chatGPT的帮助下,通过修改main.loader.js的方式,成功绕开了检测,并开启了远程调试。
require('bytenode');
const path = require('node:path');
const arch = process.arch;
const { app } = require('electron');
app.commandLine.appendSwitch('remote-debugging-port', '9222');//开启远程调试
app.commandLine.appendSwitch('remote-allow-origins', '*');//允许所有源访问
require(path.join(__dirname, './bin/main-darwin-' + arch + '.jsc'));
然后使用chrome浏览器直接访问 http://localhost:9222 按照提示就能开始远程调试。

image-20250623174313283.png (1.26 MB, 下载次数: 0)
下载附件
2025-6-23 17:58 上传
JS注入

image-20250623174440070.png (300.8 KB, 下载次数: 0)
下载附件
2025-6-23 17:58 上传
既然顺利开启了远程调试,那么一切都在掌握中了。
页面也并不复杂,主要就是四层,分别是:视频层,互动层,UI层,语音层(应该是用来播放游戏里的微信语音)。
我们就直奔主题,看看他的视频层吧。这里用的都是标签,那么就直接通过 document.querySelectorAll('.--player-video-root video') 遍历所有video标签,设置 playbackRate 属性就可以了。
在控制台中尝试后,确实实现了视频倍速播放,但当更换视频的时候,倍速的效果就消失了。

image-20250623174642759.png (807.42 KB, 下载次数: 0)
下载附件
2025-6-23 17:58 上传
仔细观察一下就会发现,player-video-root 下面有15个video标签轮流播放视频,当轮到当前video标签播放视频时,style中的 opacity 会变为 1 ;z-index会变为-10000,当播放结束时,opacity 会变为 0 ;z-index会变为-10002。
那么就简单设置一个定时器,不断地改吧。
setInterval(() => {
const videos = document.querySelectorAll('.--player-video-root video');
videos.forEach(video => {
const style = getComputedStyle(video);
const isPlaying = style.opacity === '1' && style.zIndex === '-10000';
if (isPlaying) {
video.playbackRate = 2.0;
}
});
}, 1000);
确实奏效,接着用chatGPT完善下功能。
(function () {
let rateList = [1.0, 1.5, 2.0];
let currentRateIndex = 0;
let currentRate = rateList[currentRateIndex];
// 右上角 - 播放进度面板
const infoPanel = document.createElement('div');
infoPanel.style.position = 'fixed';
infoPanel.style.top = '10px';
infoPanel.style.left = '10px';
infoPanel.style.background = 'rgba(0, 0, 0, 0.7)';
infoPanel.style.color = 'white';
infoPanel.style.padding = '6px 12px';
infoPanel.style.borderRadius = '6px';
infoPanel.style.fontSize = '14px';
infoPanel.style.fontFamily = 'monospace';
infoPanel.style.zIndex = 999999;
document.body.appendChild(infoPanel);
function formatTime(seconds) {
if (isNaN(seconds)) return '--:--';
const m = Math.floor(seconds / 60).toString().padStart(2, '0');
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${m}:${s}`;
}
// 中间提示用 Toast
const toast = document.createElement('div');
toast.style.position = 'fixed';
toast.style.top = '50px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.background = 'rgba(0, 0, 0, 0.7)';
toast.style.color = 'white';
toast.style.padding = '8px 16px';
toast.style.borderRadius = '8px';
toast.style.fontSize = '16px';
toast.style.zIndex = 999999;
toast.style.transition = 'opacity 0.3s ease';
toast.style.opacity = '0';
toast.innerText = '';
document.body.appendChild(toast);
function showToast(message) {
toast.innerText = message;
toast.style.opacity = '1';
clearTimeout(toast._timeout);
toast._timeout = setTimeout(() => {
toast.style.opacity = '0';
}, 1500);
}
function getPlayingVideo() {
const videos = document.querySelectorAll('.--player-video-root video');
for (const video of videos) {
const style = getComputedStyle(video);
if (style.opacity === '1' && style.zIndex === '-10000') {
return video;
}
}
return null;
}
// 每秒更新播放信息 + 设置倍速
setInterval(() => {
const video = getPlayingVideo();
if (video) {
// 强制 playbackRate
if (video.playbackRate !== currentRate) {
video.playbackRate = currentRate;
}
// 更新 info 面板
infoPanel.innerText = `播放进度:${formatTime(video.currentTime)} / ${formatTime(video.duration)}\n播放速度:${currentRate}x`;
} else {
infoPanel.innerText = `未检测到正在播放的视频`;
}
}, 1000);
// 快捷键:R = 切换速度,←/→ = 快进/快退
document.addEventListener('keydown', (e) => {
const video = getPlayingVideo();
if (!video) return;
if (e.key === 'ArrowLeft') {
video.currentTime = Math.max(0, video.currentTime - 5);
showToast(`⏪ 后退 5 秒:${formatTime(video.currentTime)}`);
} else if (e.key === 'ArrowRight') {
video.currentTime = Math.min(video.duration, video.currentTime + 5);
showToast(`⏩ 前进 5 秒:${formatTime(video.currentTime)}`);
} else if (e.key.toLowerCase() === 'r') {
currentRateIndex = (currentRateIndex + 1) % rateList.length;
currentRate = rateList[currentRateIndex];
showToast(`当前播放速度:${currentRate}x`);
}
});
})();
然后我们再将这些代码整合到main.loader.js中。
require('bytenode');
const path = require('node:path');
const arch = process.arch;
const { app } = require('electron');
app.commandLine.appendSwitch('remote-debugging-port', '9222');
app.commandLine.appendSwitch('remote-allow-origins', '*');
const injectionScript = `(${function () {
(function () {
let rateList = [1.0, 1.5, 2.0, 3.0];
let currentRateIndex = 0;
let currentRate = rateList[currentRateIndex];
// 右上角 - 播放进度面板
const infoPanel = document.createElement('div');
infoPanel.style.position = 'fixed';
infoPanel.style.top = '10px';
infoPanel.style.left = '10px';
infoPanel.style.background = 'rgba(0, 0, 0, 0.7)';
infoPanel.style.color = 'white';
infoPanel.style.padding = '6px 12px';
infoPanel.style.borderRadius = '6px';
infoPanel.style.fontSize = '14px';
infoPanel.style.fontFamily = 'monospace';
infoPanel.style.zIndex = 999999;
document.body.appendChild(infoPanel);
function formatTime(seconds) {
if (isNaN(seconds)) return '--:--';
const m = Math.floor(seconds / 60).toString().padStart(2, '0');
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${m}:${s}`;
}
// 中间提示用 Toast
const toast = document.createElement('div');
toast.style.position = 'fixed';
toast.style.top = '10px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.background = 'rgba(0, 0, 0, 0.7)';
toast.style.color = 'white';
toast.style.padding = '8px 16px';
toast.style.borderRadius = '8px';
toast.style.fontSize = '16px';
toast.style.zIndex = 999999;
toast.style.transition = 'opacity 0.3s ease';
toast.style.opacity = '0';
toast.innerText = '';
document.body.appendChild(toast);
function showToast(message) {
toast.innerText = message;
toast.style.opacity = '1';
clearTimeout(toast._timeout);
toast._timeout = setTimeout(() => {
toast.style.opacity = '0';
}, 1500);
}
function getPlayingVideo() {
const videos = document.querySelectorAll('.--player-video-root video');
for (const video of videos) {
const style = getComputedStyle(video);
if (style.opacity === '1' && style.zIndex === '-10000') {
return video;
}
}
return null;
}
// 每秒更新播放信息 + 设置倍速
setInterval(() => {
const video = getPlayingVideo();
if (video) {
// 强制 playbackRate
if (video.playbackRate !== currentRate) {
video.playbackRate = currentRate;
}
// 更新 info 面板
infoPanel.innerText = `播放进度:${formatTime(video.currentTime)} / ${formatTime(video.duration)}\n播放速度:${currentRate}x`;
} else {
infoPanel.innerText = `未检测到正在播放的视频`;
}
}, 1000);
// 快捷键:R = 切换速度,←/→ = 快进/快退
document.addEventListener('keydown', (e) => {
const video = getPlayingVideo();
if (!video) return;
if (e.key === 'ArrowLeft') {
video.currentTime = Math.max(0, video.currentTime - 5);
showToast(`⏪ 后退 5 秒:${formatTime(video.currentTime)}`);
} else if (e.key === 'ArrowRight') {
video.currentTime = Math.min(video.duration, video.currentTime + 5);
showToast(`⏩ 前进 5 秒:${formatTime(video.currentTime)}`);
} else if (e.key.toLowerCase() === 'r') {
currentRateIndex = (currentRateIndex + 1) % rateList.length;
currentRate = rateList[currentRateIndex];
showToast(`当前播放速度:${currentRate}x`);
}
});
})();
}})()`;
app.on('web-contents-created', (event, contents) => {
contents.on('did-finish-load', () => {
contents.executeJavaScript(injectionScript)
.then(() => console.log('[注入] 成功注入 video 控制脚本'))
.catch(err => console.error('[注入失败]', err));
});
});
require(path.join(__dirname, './bin/main-darwin-' + arch + '.jsc'));

image-20250623174818378.png (539.38 KB, 下载次数: 0)
下载附件
2025-6-23 17:58 上传
一切大功告成,直接使用steam打开游戏后,就会自动注入代码,可以随时倍速播放了!
所有功能都正常,但有时候倍速播放可能会导致比较短的循环视频黑屏,但无所谓了,那些也不影响刷剧情~