mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-08 07:32:47 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4cd69f303 | ||
|
|
c76ddd9e3f | ||
|
|
d40c456494 | ||
|
|
408169da4e | ||
|
|
9de8e957fd | ||
|
|
a287e5db43 | ||
|
|
589048b912 | ||
|
|
79517a37d2 | ||
|
|
e022f04661 | ||
|
|
d1a635be7c | ||
|
|
aa853262b7 | ||
|
|
bdf9561dfa | ||
|
|
a663fddeb0 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,5 +3,7 @@ config.json
|
||||
out.*
|
||||
node_modules
|
||||
.idea
|
||||
app*.exe
|
||||
export*.json
|
||||
secret.js
|
||||
package-lock.json
|
||||
|
||||
18
README.md
18
README.md
@@ -10,7 +10,10 @@
|
||||
**打开程序前需要关闭正在运行的原神主程序**
|
||||
第一次打开需要先设置原神主程序所在路径,支持多个路径, 使用符号'*'分隔
|
||||

|
||||
- 自定义代理: 配置文件内添加proxy字段,详细请参看[Axios-请求配置](https://axios-http.com/zh/docs/req_config)
|
||||
### Windows7
|
||||
系统变量添加名为```NODE_SKIP_PLATFORM_CHECK```的变量并将值设为```1```
|
||||
### 自定义代理
|
||||
配置文件内添加proxy字段,详细请参看[Axios-请求配置](https://axios-http.com/zh/docs/req_config)
|
||||
```json
|
||||
{
|
||||
"path": [],
|
||||
@@ -31,10 +34,15 @@
|
||||
[issues](https://github.com/HolographicHat/genshin-achievement-export/issues)或[QQ群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
|
||||
|
||||
## FAQ
|
||||
1. Q: 为什么需要管理员权限
|
||||
0. Q: 程序异常退出或被强行终止后,原神启动时报错: 无法连接网络(4201)
|
||||
A: 用文本编辑器打开```C:\Windows\System32\drivers\etc\hosts```,删除```127.0.0.1 dispatch**global.yuanshen.com```后保存,重启原神
|
||||
|
||||
1. Q: 原神启动时报错: 数据异常(31-4302)
|
||||
A: 不要把软件和原神主程序放一起
|
||||
|
||||
2. Q: 为什么需要管理员权限
|
||||
A: 临时修改Hosts和启动原神
|
||||
2. Q: 报毒?
|
||||
|
||||
3. Q: 报毒?
|
||||
A: 执行命令或修改hosts引发,相关代码可在./utils.js,./appcenter.js下找到
|
||||
|
||||
## TODO
|
||||
- [ ] GUI
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
23
app.js
23
app.js
@@ -5,16 +5,24 @@ const rs = require("./regionServer")
|
||||
const appcenter = require("./appcenter")
|
||||
const { initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkCDN, checkUpdate } = require("./utils")
|
||||
const { exportData } = require("./export")
|
||||
const { exitHook } = require("./exitHook.js");
|
||||
|
||||
const onExit = () => {
|
||||
setupHost(true)
|
||||
console.log("按任意键退出")
|
||||
cp.execSync("pause > nul", { stdio: "inherit" })
|
||||
};
|
||||
|
||||
// TODO: use kotlin rewrite it
|
||||
(async () => {
|
||||
try {
|
||||
exitHook(() => {
|
||||
setupHost(true)
|
||||
console.log("按任意键退出")
|
||||
cp.execSync("pause > nul", { stdio: "inherit" })
|
||||
process.on("uncaughtException", (err, origin) => {
|
||||
appcenter.uploadError(err, true)
|
||||
console.log(err)
|
||||
console.log(`Origin: ${origin}`)
|
||||
process.exit(1)
|
||||
})
|
||||
process.once("exit", onExit)
|
||||
process.once("SIGINT", onExit)
|
||||
appcenter.init()
|
||||
let conf = await initConfig()
|
||||
try {
|
||||
@@ -112,6 +120,8 @@ const { exitHook } = require("./exitHook.js");
|
||||
login = true
|
||||
}
|
||||
})
|
||||
server.on("error", err => console.log(`Proxy error: ${err.message}` + err.message))
|
||||
server.on("proxyError", err => console.log(`Proxy error: ${err.message}` + err.message))
|
||||
server.on("proxyMsg", (msg, _) => {
|
||||
lastRecvTimestamp = parseInt(Date.now() / 1000)
|
||||
let buf = Buffer.from(msg)
|
||||
@@ -121,6 +131,9 @@ const { exitHook } = require("./exitHook.js");
|
||||
createMonitor()
|
||||
debug("服务端握手应答")
|
||||
break
|
||||
case 404:
|
||||
lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333
|
||||
break
|
||||
default:
|
||||
console.log(`Unhandled: ${buf.toString("hex")}`)
|
||||
process.exit(2)
|
||||
|
||||
52
appcenter.js
52
appcenter.js
@@ -8,17 +8,21 @@ const getTimestamp = (d = new Date()) => {
|
||||
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}T${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}.${p(d.getUTCMilliseconds())}Z`
|
||||
}
|
||||
|
||||
const readRegistry = (path, key) => {
|
||||
const i = cp.execSync(`reg query "${path}" /v ${key}`, {
|
||||
encoding: "utf-8"
|
||||
}).split("\n")[2].split(" ").filter(s => s.length > 0).map(s => s.trim())
|
||||
switch (i[1]) {
|
||||
case "REG_SZ":
|
||||
return i[2]
|
||||
case "REG_DWORD":
|
||||
return parseInt(i[2])
|
||||
default:
|
||||
throw "Unsupported"
|
||||
const readRegistry = (path, key, def) => {
|
||||
try {
|
||||
const i = cp.execSync(`reg query "${path}" /v ${key}`, {
|
||||
encoding: "utf-8"
|
||||
}).split("\n")[2].split(" ").filter(s => s.length > 0).map(s => s.trim())
|
||||
switch (i[1]) {
|
||||
case "REG_SZ":
|
||||
return i[2]
|
||||
case "REG_DWORD":
|
||||
return parseInt(i[2])
|
||||
default:
|
||||
return def
|
||||
}
|
||||
} catch (e) {
|
||||
return def
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +31,7 @@ const session = crypto.randomUUID()
|
||||
const key = "648b83bf-d439-49bd-97f4-e1e506bdfe39"
|
||||
|
||||
const install = (() => {
|
||||
const s = readRegistry("HKCU\\SOFTWARE\\miHoYoSDK", "MIHOYOSDK_DEVICE_ID")
|
||||
const s = readRegistry("HKCU\\SOFTWARE\\miHoYoSDK", "MIHOYOSDK_DEVICE_ID", crypto.randomUUID())
|
||||
return `${s.substring(0, 8)}-${s.substring(8, 12)}-${s.substring(12, 16)}-${s.substring(16, 20)}-${s.substring(20, 32)}`
|
||||
})()
|
||||
|
||||
@@ -44,8 +48,8 @@ const device = (() => {
|
||||
timeZoneOffset: parseInt(osi[1]),
|
||||
osBuild: `${osi[2]}.${readRegistry("HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "UBR")}`,
|
||||
osVersion: osi[2],
|
||||
locale: readRegistry("HKCU\\Control Panel\\International", "LocaleName"),
|
||||
carrierCountry: readRegistry("HKCU\\Control Panel\\International\\Geo", "Name"),
|
||||
locale: readRegistry("HKCU\\Control Panel\\International", "LocaleName", "zh-CN"),
|
||||
carrierCountry: readRegistry("HKCU\\Control Panel\\International\\Geo", "Name", "CN"),
|
||||
sdkName: "appcenter.wpf.netcore",
|
||||
sdkVersion: "4.5.0",
|
||||
osName: "WINDOWS",
|
||||
@@ -57,17 +61,15 @@ const device = (() => {
|
||||
|
||||
const upload = () => {
|
||||
if (queue.length > 0) {
|
||||
try {
|
||||
const data = JSON.stringify({ "logs": queue })
|
||||
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
|
||||
headers: {
|
||||
"App-Secret": key,
|
||||
"Install-ID": install
|
||||
}
|
||||
}).then(_ => {
|
||||
queue.length = 0
|
||||
})
|
||||
} catch (e) {}
|
||||
const data = JSON.stringify({ "logs": queue })
|
||||
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
|
||||
headers: {
|
||||
"App-Secret": key,
|
||||
"Install-ID": install
|
||||
}
|
||||
}).then(_ => {
|
||||
queue.length = 0
|
||||
}).catch(_ => {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
50
exitHook.js
50
exitHook.js
@@ -1,50 +0,0 @@
|
||||
// https://github.com/sindresorhus/exit-hook
|
||||
|
||||
const callbacks = new Set();
|
||||
let isCalled = false;
|
||||
let isRegistered = false;
|
||||
|
||||
function exit(shouldManuallyExit, signal) {
|
||||
if (isCalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isCalled = true;
|
||||
|
||||
for (const callback of callbacks) {
|
||||
callback();
|
||||
}
|
||||
|
||||
if (shouldManuallyExit === true) {
|
||||
process.exit(128 + signal);
|
||||
}
|
||||
}
|
||||
|
||||
function exitHook(onExit) {
|
||||
callbacks.add(onExit);
|
||||
|
||||
if (!isRegistered) {
|
||||
isRegistered = true;
|
||||
|
||||
process.once('exit', exit);
|
||||
process.once('SIGINT', exit.bind(undefined, true, 2));
|
||||
process.once('SIGTERM', exit.bind(undefined, true, 15));
|
||||
|
||||
// PM2 Cluster shutdown message. Caught to support async handlers with pm2, needed because
|
||||
// explicitly calling process.exit() doesn't trigger the beforeExit event, and the exit
|
||||
// event cannot support async handlers, since the event loop is never called after it.
|
||||
process.on('message', message => {
|
||||
if (message === 'shutdown') {
|
||||
exit(true, -128);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
callbacks.delete(onExit);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
exitHook
|
||||
}
|
||||
18
export.js
18
export.js
@@ -1,6 +1,6 @@
|
||||
const fs = require("fs")
|
||||
const readline = require("readline")
|
||||
const { exec } = require("child_process")
|
||||
const { spawnSync } = require("child_process")
|
||||
const { loadCache } = require("./utils")
|
||||
|
||||
const exportToSeelie = proto => {
|
||||
@@ -14,7 +14,7 @@ const exportToSeelie = proto => {
|
||||
}
|
||||
|
||||
const exportToPaimon = async proto => {
|
||||
const out = { achievements: {} }
|
||||
const out = { achievement: {} }
|
||||
const achTable = new Map()
|
||||
const excel = await loadCache("ExcelBinOutput/AchievementExcelConfigData.json")
|
||||
excel.forEach(({GoalId, Id}) => {
|
||||
@@ -22,10 +22,10 @@ const exportToPaimon = async proto => {
|
||||
})
|
||||
proto.list.filter(achievement => achievement.status === 3).forEach(({id}) => {
|
||||
const gid = achTable.get(id)
|
||||
if (out.achievements[gid] === undefined) {
|
||||
out.achievements[gid] = {}
|
||||
if (out.achievement[gid] === undefined) {
|
||||
out.achievement[gid] = {}
|
||||
}
|
||||
out.achievements[gid][id] = true
|
||||
out.achievement[gid][id] = true
|
||||
})
|
||||
const fp = `./export-${Date.now()}-paimon.json`
|
||||
fs.writeFileSync(fp, JSON.stringify(out))
|
||||
@@ -60,8 +60,12 @@ const exportToCocogoat = async proto => {
|
||||
date: getDate(finishTimestamp)
|
||||
})
|
||||
})
|
||||
exec("clip").stdin.end(JSON.stringify(out,null,2))
|
||||
console.log("导出内容已复制到剪贴板")
|
||||
const ts = Date.now()
|
||||
const json = JSON.stringify(out,null,2)
|
||||
spawnSync("clip", { input: json })
|
||||
const fp = `./export-${ts}-cocogoat.json`
|
||||
fs.writeFileSync(fp, json)
|
||||
console.log(`导出内容已复制到剪贴板,若拷贝失败请手动复制 export-${ts}-cocogoat.json 文件内容`)
|
||||
}
|
||||
|
||||
const exportToCsv = async proto => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const version = {
|
||||
code: 2,
|
||||
name: "1.0.1"
|
||||
code: 4,
|
||||
name: "1.3"
|
||||
}
|
||||
|
||||
module.exports = { version }
|
||||
|
||||
Reference in New Issue
Block a user