mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-11 17:08:12 +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.*
|
out.*
|
||||||
node_modules
|
node_modules
|
||||||
.idea
|
.idea
|
||||||
|
app*.exe
|
||||||
|
export*.json
|
||||||
secret.js
|
secret.js
|
||||||
package-lock.json
|
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
|
```json
|
||||||
{
|
{
|
||||||
"path": [],
|
"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)
|
[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
|
## 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和启动原神
|
A: 临时修改Hosts和启动原神
|
||||||
2. Q: 报毒?
|
|
||||||
|
3. Q: 报毒?
|
||||||
A: 执行命令或修改hosts引发,相关代码可在./utils.js,./appcenter.js下找到
|
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 appcenter = require("./appcenter")
|
||||||
const { initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkCDN, checkUpdate } = require("./utils")
|
const { initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkCDN, checkUpdate } = require("./utils")
|
||||||
const { exportData } = require("./export")
|
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
|
// TODO: use kotlin rewrite it
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
exitHook(() => {
|
process.on("uncaughtException", (err, origin) => {
|
||||||
setupHost(true)
|
appcenter.uploadError(err, true)
|
||||||
console.log("按任意键退出")
|
console.log(err)
|
||||||
cp.execSync("pause > nul", { stdio: "inherit" })
|
console.log(`Origin: ${origin}`)
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
process.once("exit", onExit)
|
||||||
|
process.once("SIGINT", onExit)
|
||||||
appcenter.init()
|
appcenter.init()
|
||||||
let conf = await initConfig()
|
let conf = await initConfig()
|
||||||
try {
|
try {
|
||||||
@@ -112,6 +120,8 @@ const { exitHook } = require("./exitHook.js");
|
|||||||
login = true
|
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, _) => {
|
server.on("proxyMsg", (msg, _) => {
|
||||||
lastRecvTimestamp = parseInt(Date.now() / 1000)
|
lastRecvTimestamp = parseInt(Date.now() / 1000)
|
||||||
let buf = Buffer.from(msg)
|
let buf = Buffer.from(msg)
|
||||||
@@ -121,6 +131,9 @@ const { exitHook } = require("./exitHook.js");
|
|||||||
createMonitor()
|
createMonitor()
|
||||||
debug("服务端握手应答")
|
debug("服务端握手应答")
|
||||||
break
|
break
|
||||||
|
case 404:
|
||||||
|
lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
console.log(`Unhandled: ${buf.toString("hex")}`)
|
console.log(`Unhandled: ${buf.toString("hex")}`)
|
||||||
process.exit(2)
|
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`
|
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 readRegistry = (path, key, def) => {
|
||||||
const i = cp.execSync(`reg query "${path}" /v ${key}`, {
|
try {
|
||||||
encoding: "utf-8"
|
const i = cp.execSync(`reg query "${path}" /v ${key}`, {
|
||||||
}).split("\n")[2].split(" ").filter(s => s.length > 0).map(s => s.trim())
|
encoding: "utf-8"
|
||||||
switch (i[1]) {
|
}).split("\n")[2].split(" ").filter(s => s.length > 0).map(s => s.trim())
|
||||||
case "REG_SZ":
|
switch (i[1]) {
|
||||||
return i[2]
|
case "REG_SZ":
|
||||||
case "REG_DWORD":
|
return i[2]
|
||||||
return parseInt(i[2])
|
case "REG_DWORD":
|
||||||
default:
|
return parseInt(i[2])
|
||||||
throw "Unsupported"
|
default:
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return def
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +31,7 @@ const session = crypto.randomUUID()
|
|||||||
const key = "648b83bf-d439-49bd-97f4-e1e506bdfe39"
|
const key = "648b83bf-d439-49bd-97f4-e1e506bdfe39"
|
||||||
|
|
||||||
const install = (() => {
|
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)}`
|
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]),
|
timeZoneOffset: parseInt(osi[1]),
|
||||||
osBuild: `${osi[2]}.${readRegistry("HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "UBR")}`,
|
osBuild: `${osi[2]}.${readRegistry("HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "UBR")}`,
|
||||||
osVersion: osi[2],
|
osVersion: osi[2],
|
||||||
locale: readRegistry("HKCU\\Control Panel\\International", "LocaleName"),
|
locale: readRegistry("HKCU\\Control Panel\\International", "LocaleName", "zh-CN"),
|
||||||
carrierCountry: readRegistry("HKCU\\Control Panel\\International\\Geo", "Name"),
|
carrierCountry: readRegistry("HKCU\\Control Panel\\International\\Geo", "Name", "CN"),
|
||||||
sdkName: "appcenter.wpf.netcore",
|
sdkName: "appcenter.wpf.netcore",
|
||||||
sdkVersion: "4.5.0",
|
sdkVersion: "4.5.0",
|
||||||
osName: "WINDOWS",
|
osName: "WINDOWS",
|
||||||
@@ -57,17 +61,15 @@ const device = (() => {
|
|||||||
|
|
||||||
const upload = () => {
|
const upload = () => {
|
||||||
if (queue.length > 0) {
|
if (queue.length > 0) {
|
||||||
try {
|
const data = JSON.stringify({ "logs": queue })
|
||||||
const data = JSON.stringify({ "logs": queue })
|
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
|
||||||
axios.post("https://in.appcenter.ms/logs?api-version=1.0.0", data,{
|
headers: {
|
||||||
headers: {
|
"App-Secret": key,
|
||||||
"App-Secret": key,
|
"Install-ID": install
|
||||||
"Install-ID": install
|
}
|
||||||
}
|
}).then(_ => {
|
||||||
}).then(_ => {
|
queue.length = 0
|
||||||
queue.length = 0
|
}).catch(_ => {})
|
||||||
})
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 fs = require("fs")
|
||||||
const readline = require("readline")
|
const readline = require("readline")
|
||||||
const { exec } = require("child_process")
|
const { spawnSync } = require("child_process")
|
||||||
const { loadCache } = require("./utils")
|
const { loadCache } = require("./utils")
|
||||||
|
|
||||||
const exportToSeelie = proto => {
|
const exportToSeelie = proto => {
|
||||||
@@ -14,7 +14,7 @@ const exportToSeelie = proto => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exportToPaimon = async proto => {
|
const exportToPaimon = async proto => {
|
||||||
const out = { achievements: {} }
|
const out = { achievement: {} }
|
||||||
const achTable = new Map()
|
const achTable = new Map()
|
||||||
const excel = await loadCache("ExcelBinOutput/AchievementExcelConfigData.json")
|
const excel = await loadCache("ExcelBinOutput/AchievementExcelConfigData.json")
|
||||||
excel.forEach(({GoalId, Id}) => {
|
excel.forEach(({GoalId, Id}) => {
|
||||||
@@ -22,10 +22,10 @@ const exportToPaimon = async proto => {
|
|||||||
})
|
})
|
||||||
proto.list.filter(achievement => achievement.status === 3).forEach(({id}) => {
|
proto.list.filter(achievement => achievement.status === 3).forEach(({id}) => {
|
||||||
const gid = achTable.get(id)
|
const gid = achTable.get(id)
|
||||||
if (out.achievements[gid] === undefined) {
|
if (out.achievement[gid] === undefined) {
|
||||||
out.achievements[gid] = {}
|
out.achievement[gid] = {}
|
||||||
}
|
}
|
||||||
out.achievements[gid][id] = true
|
out.achievement[gid][id] = true
|
||||||
})
|
})
|
||||||
const fp = `./export-${Date.now()}-paimon.json`
|
const fp = `./export-${Date.now()}-paimon.json`
|
||||||
fs.writeFileSync(fp, JSON.stringify(out))
|
fs.writeFileSync(fp, JSON.stringify(out))
|
||||||
@@ -60,8 +60,12 @@ const exportToCocogoat = async proto => {
|
|||||||
date: getDate(finishTimestamp)
|
date: getDate(finishTimestamp)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
exec("clip").stdin.end(JSON.stringify(out,null,2))
|
const ts = Date.now()
|
||||||
console.log("导出内容已复制到剪贴板")
|
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 => {
|
const exportToCsv = async proto => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const version = {
|
const version = {
|
||||||
code: 2,
|
code: 4,
|
||||||
name: "1.0.1"
|
name: "1.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { version }
|
module.exports = { version }
|
||||||
|
|||||||
Reference in New Issue
Block a user