Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee0e6dec32 | ||
|
|
3b54217989 | ||
|
|
425b94fd93 | ||
|
|
e0bdcb01fb | ||
|
|
c840a49f75 | ||
|
|
c535d8b830 | ||
|
|
248a387f01 | ||
|
|
87fded4cb6 |
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# beim Bauen erzeugt, enthält Adresse und Token der CurseForge-Weiterleitung
|
||||||
|
src/build-config.json
|
||||||
|
|
||||||
|
# Zugangsdaten des Weiterleitungs-Servers
|
||||||
|
server/curseforge-proxy/.env
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Ein Minecraft-Launcher für Windows mit getrennten Instanzen, Mod-Unterstützung und Microsoft-Anmeldung.
|
Ein Minecraft-Launcher für Windows mit getrennten Instanzen, Mod-Unterstützung und Microsoft-Anmeldung.
|
||||||
|
|
||||||
Aktueller Stand: Version 1.0.8
|
Aktueller Stand: Version 1.0.9
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Generated
+222
-3
@@ -1,15 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "aeromc",
|
"name": "aeromc",
|
||||||
"version": "1.0.7",
|
"version": "1.1.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "aeromc",
|
"name": "aeromc",
|
||||||
"version": "1.0.7",
|
"version": "1.1.2",
|
||||||
"license": "MIT",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xhayper/discord-rpc": "^1.3.4",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
|
"loopback-capture": "^3.0.2",
|
||||||
|
"node-window-manager": "^2.2.4",
|
||||||
"skinview3d": "^3.4.2",
|
"skinview3d": "^3.4.2",
|
||||||
"undici": "^6.28.0",
|
"undici": "^6.28.0",
|
||||||
"unzipper": "^0.12.5"
|
"unzipper": "^0.12.5"
|
||||||
@@ -37,6 +40,56 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@discordjs/collection": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/rest": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/collection": "^2.1.1",
|
||||||
|
"@discordjs/util": "^1.2.0",
|
||||||
|
"@sapphire/async-queue": "^1.5.3",
|
||||||
|
"@sapphire/snowflake": "^3.5.5",
|
||||||
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||||
|
"discord-api-types": "^0.38.50",
|
||||||
|
"magic-bytes.js": "^1.13.0",
|
||||||
|
"tslib": "^2.6.3",
|
||||||
|
"undici": "^6.27.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/util": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"discord-api-types": "^0.38.33"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@electron/asar": {
|
"node_modules/@electron/asar": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
|
||||||
@@ -494,6 +547,26 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sapphire/async-queue": {
|
||||||
|
"version": "1.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
||||||
|
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v14.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sapphire/snowflake": {
|
||||||
|
"version": "3.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
|
||||||
|
"integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v14.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sindresorhus/is": {
|
"node_modules/@sindresorhus/is": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||||
@@ -662,6 +735,31 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vladfrangu/async_event_emitter": {
|
||||||
|
"version": "2.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
|
||||||
|
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v14.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xhayper/discord-rpc": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xhayper/discord-rpc/-/discord-rpc-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-ff0uEXuibh9wi+l4vOj7xInLUjtlTaQBje/SCyQkeXZ0j2V0y+Zge5PQIQFRHH9TjjGaYJkTofEcQhncM2q7/w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/rest": "^2.6.1",
|
||||||
|
"@vladfrangu/async_event_emitter": "^2.4.7",
|
||||||
|
"discord-api-types": "^0.38.47",
|
||||||
|
"ws": "^8.20.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.9.10",
|
"version": "0.9.10",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
|
||||||
@@ -1128,6 +1226,15 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bl": {
|
"node_modules/bl": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||||
@@ -1799,6 +1906,15 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/discord-api-types": {
|
||||||
|
"version": "0.38.53",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.53.tgz",
|
||||||
|
"integrity": "sha512-HL1zz/UuZ+bbJjA/X8Kbxx9gk8v9rJAbTeWRNYKmIdjwJ7EovjlHgoJTxcLpATfNJ+AonOtMdy3Y5MVIJAAd/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"scripts/actions/documentation"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/dmg-builder": {
|
"node_modules/dmg-builder": {
|
||||||
"version": "24.13.3",
|
"version": "24.13.3",
|
||||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz",
|
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz",
|
||||||
@@ -2493,6 +2609,22 @@
|
|||||||
"bare-events": "^2.7.0"
|
"bare-events": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/extract-file-icon": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/extract-file-icon/-/extract-file-icon-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-We5/HaMDlioOx3c9zDjit8RtKWEvT94sJF8jTujaKVOyWL8Kkqu4QGnwDr5fID6XsbKvWT0JJNuWuV2Tmr7yiw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-addon-api": "1.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/extract-file-icon/node_modules/node-addon-api": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/extract-zip": {
|
"node_modules/extract-zip": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||||
@@ -2561,6 +2693,12 @@
|
|||||||
"integrity": "sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==",
|
"integrity": "sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/filelist": {
|
"node_modules/filelist": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
||||||
@@ -3340,6 +3478,25 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/loopback-capture": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/loopback-capture/-/loopback-capture-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-65qyF9L7e+InnbEZP3Ic4C4J+SszoxEq84XpIZWJYH3/IgUpTKqBzJ4wCF+ShL3bvqV7I2wOEgQv9VfpaO2AMw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"os": [
|
||||||
|
"linux",
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lowercase-keys": {
|
"node_modules/lowercase-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||||
@@ -3363,6 +3520,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/magic-bytes.js": {
|
||||||
|
"version": "1.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.1.tgz",
|
||||||
|
"integrity": "sha512-x5sn4UX2k5gCWlcfmoFwG4TPie8+dctESyqOBdhB5p6MsgWXdBKGmt9nXPObj/JI50TTL928lc5Yt1WntMn1bw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/matcher": {
|
"node_modules/matcher": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||||
@@ -3525,12 +3688,41 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-gyp-build": {
|
||||||
|
"version": "4.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||||
|
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build": "bin.js",
|
||||||
|
"node-gyp-build-optional": "optional.js",
|
||||||
|
"node-gyp-build-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-int64": {
|
"node_modules/node-int64": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-window-manager": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-window-manager/-/node-window-manager-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-Dgq9a7qx6+Gi+BifWM79leteNz76QUtwSsdiqEe7SjvoJsc8TYQsRl1IA5uxqz5iSLeHS1iA/8YI/snoq0DblA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"extract-file-icon": "^0.3.2",
|
||||||
|
"node-addon-api": "2.0.0",
|
||||||
|
"node-gyp-build": "^4.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-window-manager/node_modules/node-addon-api": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
@@ -4318,6 +4510,12 @@
|
|||||||
"utf8-byte-length": "^1.0.1"
|
"utf8-byte-length": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-fest": {
|
"node_modules/type-fest": {
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||||
@@ -4517,6 +4715,27 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
|
||||||
|
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xmlbuilder": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "15.1.1",
|
"version": "15.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
|
|||||||
+10
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "aeromc",
|
"name": "aeromc",
|
||||||
"productName": "AeroMC",
|
"productName": "AeroMC",
|
||||||
"version": "1.0.7",
|
"version": "1.1.2",
|
||||||
"description": "AeroMC – Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)",
|
"description": "AeroMC – Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)",
|
||||||
"homepage": "https://m-viper.de",
|
"homepage": "https://m-viper.de",
|
||||||
"launcherWebsite": "https://aeromc.viper.ipv64.net",
|
"launcherWebsite": "https://aeromc.viper.ipv64.net",
|
||||||
@@ -23,7 +23,12 @@
|
|||||||
"compression": "store",
|
"compression": "store",
|
||||||
"files": [
|
"files": [
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
"package.json"
|
"package.json",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"asarUnpack": [
|
||||||
|
"node_modules/loopback-capture/**/*",
|
||||||
|
"node_modules/node-window-manager/**/*"
|
||||||
],
|
],
|
||||||
"win": {
|
"win": {
|
||||||
"target": "nsis",
|
"target": "nsis",
|
||||||
@@ -39,7 +44,10 @@
|
|||||||
"electron-builder": "^24.0.0"
|
"electron-builder": "^24.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xhayper/discord-rpc": "^1.3.4",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
|
"loopback-capture": "^3.0.2",
|
||||||
|
"node-window-manager": "^2.2.4",
|
||||||
"skinview3d": "^3.4.2",
|
"skinview3d": "^3.4.2",
|
||||||
"undici": "^6.28.0",
|
"undici": "^6.28.0",
|
||||||
"unzipper": "^0.12.5"
|
"unzipper": "^0.12.5"
|
||||||
|
|||||||
+21
-9
@@ -354,6 +354,10 @@ async function restoreGlobalBackup(options) {
|
|||||||
const settingsFile = path.join(userDataDir, 'settings.json');
|
const settingsFile = path.join(userDataDir, 'settings.json');
|
||||||
const rollbackSettings = path.join(rollbackDir, 'settings.json');
|
const rollbackSettings = path.join(rollbackDir, 'settings.json');
|
||||||
let settingsPreviouslyExisted = false;
|
let settingsPreviouslyExisted = false;
|
||||||
|
// wird erst true, sobald der aktuelle Stand tatsächlich weggesichert wurde -
|
||||||
|
// vorher darf der Rollback nichts anfassen, sonst löscht ein früher Fehler
|
||||||
|
// (z. B. ungültiges Archiv) den unveränderten, intakten Originalbestand
|
||||||
|
let originalMovedAway = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ensureDir(extractDir);
|
ensureDir(extractDir);
|
||||||
@@ -382,6 +386,7 @@ async function restoreGlobalBackup(options) {
|
|||||||
endPercent: 40,
|
endPercent: 40,
|
||||||
detailPrefix: 'Sichere aktuelle Instanzen',
|
detailPrefix: 'Sichere aktuelle Instanzen',
|
||||||
});
|
});
|
||||||
|
originalMovedAway = true;
|
||||||
settingsPreviouslyExisted = fs.existsSync(settingsFile);
|
settingsPreviouslyExisted = fs.existsSync(settingsFile);
|
||||||
if (settingsPreviouslyExisted) fs.copyFileSync(settingsFile, rollbackSettings);
|
if (settingsPreviouslyExisted) fs.copyFileSync(settingsFile, rollbackSettings);
|
||||||
|
|
||||||
@@ -414,16 +419,23 @@ async function restoreGlobalBackup(options) {
|
|||||||
restoredSettings,
|
restoredSettings,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
// Nur zurückrollen, wenn wir den ursprünglichen Bestand überhaupt schon
|
||||||
if (fs.existsSync(instancesDir)) fs.rmSync(instancesDir, { recursive: true, force: true });
|
// angefasst haben (siehe originalMovedAway oben). Schlägt die
|
||||||
if (fs.existsSync(rollbackInstances)) fs.cpSync(rollbackInstances, instancesDir, { recursive: true });
|
// Wiederherstellung vorher fehl (ungültiges/beschädigtes Archiv o. Ä.),
|
||||||
else ensureDir(instancesDir);
|
// ist instancesDir noch der unveränderte Originalbestand - den lassen wir
|
||||||
|
// dann bewusst in Ruhe, statt ihn zu löschen.
|
||||||
|
if (originalMovedAway) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(instancesDir)) fs.rmSync(instancesDir, { recursive: true, force: true });
|
||||||
|
if (fs.existsSync(rollbackInstances)) fs.cpSync(rollbackInstances, instancesDir, { recursive: true });
|
||||||
|
else ensureDir(instancesDir);
|
||||||
|
|
||||||
if (includeSettings) {
|
if (includeSettings) {
|
||||||
if (fs.existsSync(rollbackSettings)) fs.copyFileSync(rollbackSettings, settingsFile);
|
if (fs.existsSync(rollbackSettings)) fs.copyFileSync(rollbackSettings, settingsFile);
|
||||||
else if (!settingsPreviouslyExisted && fs.existsSync(settingsFile)) fs.rmSync(settingsFile, { force: true });
|
else if (!settingsPreviouslyExisted && fs.existsSync(settingsFile)) fs.rmSync(settingsFile, { force: true });
|
||||||
}
|
}
|
||||||
} catch { /* ignore rollback errors */ }
|
} catch { /* ignore rollback errors */ }
|
||||||
|
}
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
|
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* discordpresence.js – Discord Rich Presence ("Spielt Minecraft über AeroMC")
|
||||||
|
* -----------------------------------------------------------------------------
|
||||||
|
* Verbindet sich rein lokal über die IPC-Pipe des laufenden Discord-Clients.
|
||||||
|
* SET_ACTIVITY braucht dafür bewusst KEINE Authentifizierung/OAuth (anders als
|
||||||
|
* der Rest der RPC-API) – daher hier absichtlich client.request(...) direkt
|
||||||
|
* statt client.user.setActivity(...), das erst nach einem Login funktioniert.
|
||||||
|
*
|
||||||
|
* Immer aktiv, ohne eigene Einstellung – braucht also keine Zustimmung/kein
|
||||||
|
* Häkchen. Läuft trotzdem komplett unauffällig: ist Discord nicht installiert
|
||||||
|
* oder nicht gestartet, bleibt es einfach inaktiv, ohne den Launcher oder den
|
||||||
|
* Spielstart zu stören – jeder Fehler hier wird bewusst verschluckt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Client } = require('@xhayper/discord-rpc');
|
||||||
|
|
||||||
|
// Von M_Viper unter discord.com/developers/applications angelegte AeroMC-App.
|
||||||
|
// Öffentliche Anwendungs-ID, kein Geheimnis (genau wie DEFAULT_CLIENT_ID bei
|
||||||
|
// der Microsoft-Anmeldung in auth.js) – gilt für alle AeroMC-Nutzer gleich.
|
||||||
|
const DEFAULT_CLIENT_ID = '1538173087730630686';
|
||||||
|
|
||||||
|
const LAUNCHER_WEBSITE = 'https://aeromc.viper.ipv64.net/';
|
||||||
|
|
||||||
|
let clientId = DEFAULT_CLIENT_ID;
|
||||||
|
let client = null;
|
||||||
|
let connecting = null;
|
||||||
|
|
||||||
|
function resolveClientId() {
|
||||||
|
return (clientId && String(clientId).trim()) || DEFAULT_CLIENT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connect() {
|
||||||
|
if (client && client.isConnected) return client;
|
||||||
|
if (connecting) return connecting;
|
||||||
|
|
||||||
|
const id = resolveClientId();
|
||||||
|
const c = new Client({ clientId: id });
|
||||||
|
c.on('disconnected', () => { if (client === c) client = null; });
|
||||||
|
connecting = c.connect()
|
||||||
|
.then(() => { client = c; connecting = null; return c; })
|
||||||
|
.catch((err) => { connecting = null; throw err; });
|
||||||
|
return connecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* activity: { details, state, startTimestamp }
|
||||||
|
* details/state -> die beiden Zeilen unter dem Profilbild in Discord
|
||||||
|
* startTimestamp -> Discord zeigt die Spielzeit dann selbst laufend an
|
||||||
|
*/
|
||||||
|
async function setActivity(activity) {
|
||||||
|
try {
|
||||||
|
const c = await connect();
|
||||||
|
await c.request('SET_ACTIVITY', {
|
||||||
|
pid: process.pid,
|
||||||
|
activity: {
|
||||||
|
details: activity.details,
|
||||||
|
state: activity.state,
|
||||||
|
timestamps: activity.startTimestamp ? { start: activity.startTimestamp } : undefined,
|
||||||
|
assets: { large_image: 'aeromc_logo', large_text: 'AeroMC Launcher' },
|
||||||
|
// Discord zeigt Buttons Betrachtern des Profils an (nicht sich selbst) -
|
||||||
|
// maximal 2 sind erlaubt, hier reicht einer.
|
||||||
|
buttons: [{ label: 'AeroMC herunterladen', url: LAUNCHER_WEBSITE }],
|
||||||
|
instance: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Discord nicht installiert/nicht gestartet o. Ä. - bewusst lautlos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearActivity() {
|
||||||
|
try {
|
||||||
|
if (!client || !client.isConnected) return;
|
||||||
|
await client.request('SET_ACTIVITY', { pid: process.pid });
|
||||||
|
} catch { /* egal */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shutdown() {
|
||||||
|
const c = client;
|
||||||
|
client = null;
|
||||||
|
connecting = null;
|
||||||
|
if (!c) return;
|
||||||
|
try { await c.destroy(); } catch { /* egal */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verbindet direkt beim Start. settings.discordClientId erlaubt weiterhin
|
||||||
|
* (rein optional, per Hand in settings.json) eine eigene App-ID zu setzen -
|
||||||
|
* ohne eigenes Einstellungsfeld, ohne dass es dafür einen Schalter braucht.
|
||||||
|
*/
|
||||||
|
function configure(settings) {
|
||||||
|
const nextId = (settings && settings.discordClientId) || DEFAULT_CLIENT_ID;
|
||||||
|
const idChanged = nextId !== clientId;
|
||||||
|
clientId = nextId;
|
||||||
|
|
||||||
|
if (idChanged) { shutdown().then(() => connect().catch(() => {})); }
|
||||||
|
else if (!client) { connect().catch(() => {}); }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { configure, setActivity, clearActivity, shutdown, DEFAULT_CLIENT_ID };
|
||||||
+172
-6
@@ -51,6 +51,7 @@
|
|||||||
<button id="p-mods" class="sbtn">Mods anzeigen</button>
|
<button id="p-mods" class="sbtn">Mods anzeigen</button>
|
||||||
<button id="p-worlds" class="sbtn">Welten verwalten</button>
|
<button id="p-worlds" class="sbtn">Welten verwalten</button>
|
||||||
<button id="p-screens" class="sbtn">Bildschirmfotos verwalten</button>
|
<button id="p-screens" class="sbtn">Bildschirmfotos verwalten</button>
|
||||||
|
<button id="p-record" class="sbtn">Spiel aufzeichnen</button>
|
||||||
<button id="p-modpack" class="sbtn hidden">Modpack aktualisieren</button>
|
<button id="p-modpack" class="sbtn hidden">Modpack aktualisieren</button>
|
||||||
<button id="p-logs" class="sbtn">Logdateien ansehen</button>
|
<button id="p-logs" class="sbtn">Logdateien ansehen</button>
|
||||||
<button id="p-servers" class="sbtn">Server verwalten</button>
|
<button id="p-servers" class="sbtn">Server verwalten</button>
|
||||||
@@ -130,6 +131,7 @@
|
|||||||
<option value="forge">Forge</option>
|
<option value="forge">Forge</option>
|
||||||
<option value="neoforge">NeoForge</option>
|
<option value="neoforge">NeoForge</option>
|
||||||
<option value="quilt">Quilt</option>
|
<option value="quilt">Quilt</option>
|
||||||
|
<option value="optifine">OptiFine</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
@@ -139,6 +141,23 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="f-optifine-tools" class="field-row hidden" style="align-items:flex-start">
|
||||||
|
<div class="field" style="flex:1">
|
||||||
|
<span>OptiFine einrichten</span>
|
||||||
|
<p class="hint" style="margin:2px 0 8px">
|
||||||
|
OptiFine bietet kein automatisches Download-API (Downloads laufen bei OptiFine bewusst über eine Werbeseite).
|
||||||
|
Lade den Installer zuerst selbst von <b>optifine.net</b> herunter, wähle ihn dann hier aus – AeroMC bereitet
|
||||||
|
die passende Minecraft-Version vor und startet den echten OptiFine-Installer. Wähle darin als Zielordner den
|
||||||
|
automatisch in die Zwischenablage kopierten Pfad, klicke „Install" und bestätige danach hier.
|
||||||
|
</p>
|
||||||
|
<div class="path-row">
|
||||||
|
<button type="button" id="f-optifine-install" class="btn">Installer wählen und starten…</button>
|
||||||
|
<button type="button" id="f-optifine-confirm" class="btn btn-primary hidden">Fertig – Version übernehmen</button>
|
||||||
|
</div>
|
||||||
|
<small id="f-optifine-status" class="hint"></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Notizen <small class="opt">(optional)</small></span>
|
<span>Notizen <small class="opt">(optional)</small></span>
|
||||||
<textarea id="f-notes" rows="2" placeholder="Modliste, Server-IP, To-dos …"></textarea>
|
<textarea id="f-notes" rows="2" placeholder="Modliste, Server-IP, To-dos …"></textarea>
|
||||||
@@ -642,6 +661,7 @@
|
|||||||
<option value="mod">Mods</option>
|
<option value="mod">Mods</option>
|
||||||
<option value="resourcepack">Ressourcenpakete</option>
|
<option value="resourcepack">Ressourcenpakete</option>
|
||||||
<option value="shader">Shader</option>
|
<option value="shader">Shader</option>
|
||||||
|
<option value="datapack">Datenpakete</option>
|
||||||
</select>
|
</select>
|
||||||
<input id="m-query" type="text" placeholder="Mods durchsuchen … (z. B. Sodium, JEI, Create)" />
|
<input id="m-query" type="text" placeholder="Mods durchsuchen … (z. B. Sodium, JEI, Create)" />
|
||||||
<button id="m-search-btn" class="btn btn-primary">Suchen</button>
|
<button id="m-search-btn" class="btn btn-primary">Suchen</button>
|
||||||
@@ -690,6 +710,11 @@
|
|||||||
<button id="m-add-file" class="btn btn-mini" data-ic="plus">Datei hinzufügen…</button>
|
<button id="m-add-file" class="btn btn-mini" data-ic="plus">Datei hinzufügen…</button>
|
||||||
<button id="m-check-updates" class="btn btn-mini">Nach Updates suchen</button>
|
<button id="m-check-updates" class="btn btn-mini">Nach Updates suchen</button>
|
||||||
<button id="m-apply-updates" class="btn btn-primary btn-mini hidden">Alle aktualisieren</button>
|
<button id="m-apply-updates" class="btn btn-primary btn-mini hidden">Alle aktualisieren</button>
|
||||||
|
<select id="m-installed-sort" title="Sortierung">
|
||||||
|
<option value="name-asc">Name (A-Z)</option>
|
||||||
|
<option value="name-desc">Name (Z-A)</option>
|
||||||
|
<option value="status">Status (aktiv zuerst)</option>
|
||||||
|
</select>
|
||||||
<span id="m-update-status" class="hint"></span>
|
<span id="m-update-status" class="hint"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="m-installed" class="mods-list"><div class="mods-empty">Noch keine Mods.</div></div>
|
<div id="m-installed" class="mods-list"><div class="mods-empty">Noch keine Mods.</div></div>
|
||||||
@@ -781,6 +806,123 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ===================== Dialog: Spielaufzeichnung ===================== -->
|
||||||
|
<div id="modal-record" class="modal hidden">
|
||||||
|
<div class="modal-card modal-lg">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3 id="record-title">Spielaufzeichnung</h3>
|
||||||
|
<button class="modal-close" data-close>✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="record-search" class="record-search">
|
||||||
|
<p id="record-search-text">Suche Spielfenster …</p>
|
||||||
|
<button id="record-search-retry" class="btn hidden">Erneut suchen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="record-picker" class="screens-grid hidden"></div>
|
||||||
|
|
||||||
|
<div id="record-stage" class="record-stage hidden">
|
||||||
|
<video id="record-preview" class="record-video" autoplay muted></video>
|
||||||
|
<div class="record-audio-bar">
|
||||||
|
<span class="hint">Video: <span id="record-video-summary">Nativ · Mittel</span></span>
|
||||||
|
<button id="record-video-edit" class="btn btn-mini">Video-Einstellungen…</button>
|
||||||
|
</div>
|
||||||
|
<div class="record-audio-bar">
|
||||||
|
<span class="hint">Audio: <span id="record-audio-summary">Systemton</span></span>
|
||||||
|
<button id="record-audio-edit" class="btn btn-mini">Audioquellen wählen…</button>
|
||||||
|
</div>
|
||||||
|
<div class="record-toolbar">
|
||||||
|
<div id="record-live-badge" class="record-live-badge hidden">
|
||||||
|
<span class="rec-dot"></span>
|
||||||
|
<span>Aufnahme läuft · <span id="record-timer">00:00</span></span>
|
||||||
|
</div>
|
||||||
|
<button id="record-toggle" class="btn btn-primary">Aufnahme starten</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<button id="record-open-folder" class="btn" data-ic="folder">Ordner öffnen</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button class="btn" data-close>Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ===================== Dialog: Video-Einstellungen der Aufzeichnung ===================== -->
|
||||||
|
<div id="modal-record-video" class="modal hidden">
|
||||||
|
<div class="modal-card modal-sm">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3>Video-Einstellungen</h3>
|
||||||
|
<button class="modal-close" data-close>✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="field-row">
|
||||||
|
<label class="field">
|
||||||
|
<span>Auflösung</span>
|
||||||
|
<select id="record-resolution">
|
||||||
|
<option value="native">Nativ (Fenstergröße)</option>
|
||||||
|
<option value="1080">1080p</option>
|
||||||
|
<option value="720">720p</option>
|
||||||
|
<option value="480">480p</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Qualität</span>
|
||||||
|
<select id="record-quality">
|
||||||
|
<option value="low">Niedrig (kleinere Datei)</option>
|
||||||
|
<option value="medium" selected>Mittel</option>
|
||||||
|
<option value="high">Hoch (größere Datei)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Bildrate</span>
|
||||||
|
<select id="record-fps">
|
||||||
|
<option value="30" selected>30 fps</option>
|
||||||
|
<option value="60">60 fps (größere Datei)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p id="record-video-hint" class="hint hidden">Gilt erst für die nächste Aufnahme – während einer laufenden Aufnahme nicht änderbar.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button class="btn btn-primary" data-close>Fertig</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ===================== Dialog: Audioquellen der Aufzeichnung ===================== -->
|
||||||
|
<div id="modal-record-audio" class="modal hidden">
|
||||||
|
<div class="modal-card modal-sm">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3>Audioquellen wählen</h3>
|
||||||
|
<button class="modal-close" data-close>✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="hint">Mehrfachauswahl möglich – alle angehakten Quellen werden zu einer Tonspur gemischt, Lautstärke je Quelle einstellbar.</p>
|
||||||
|
<div class="record-source-row">
|
||||||
|
<label class="checkbox-field">
|
||||||
|
<input id="record-src-system" type="checkbox" checked /> <span>Systemton</span>
|
||||||
|
</label>
|
||||||
|
<input id="record-vol-system" class="record-vol" type="range" min="0" max="100" value="100" title="Lautstärke" />
|
||||||
|
</div>
|
||||||
|
<div class="record-source-row">
|
||||||
|
<label class="checkbox-field">
|
||||||
|
<input id="record-src-game" type="checkbox" disabled /> <span>Nur Minecraft (isoliert)</span>
|
||||||
|
</label>
|
||||||
|
<input id="record-vol-game" class="record-vol" type="range" min="0" max="100" value="100" title="Lautstärke" />
|
||||||
|
</div>
|
||||||
|
<p id="record-src-game-hint" class="hint hidden">Kein laufender Minecraft-Prozess erkannt – bitte über den Launcher starten.</p>
|
||||||
|
<div id="record-mic-list" class="record-mic-list"></div>
|
||||||
|
<p class="hint" style="margin-top:10px">Systemton ist immer der komplette Mix (Spiel, Discord, Musik, Benachrichtigungen usw.). "Nur Minecraft" nimmt ausschließlich den Ton des Spiels auf (z. B. wenn nebenbei Musik läuft, die nicht mit rein soll) – beides lässt sich auch kombiniert mit einem Mikrofon anhaken.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button class="btn btn-primary" data-close>Fertig</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ===================== Dialog: Screenshots ===================== -->
|
<!-- ===================== Dialog: Screenshots ===================== -->
|
||||||
<div id="modal-screens" class="modal hidden">
|
<div id="modal-screens" class="modal hidden">
|
||||||
<div class="modal-card modal-lg">
|
<div class="modal-card modal-lg">
|
||||||
@@ -837,7 +979,7 @@
|
|||||||
<div class="field-row" style="margin-top:12px">
|
<div class="field-row" style="margin-top:12px">
|
||||||
<label class="field"><span>Name</span><input id="srv-name" type="text" placeholder="z. B. Mein Server" /></label>
|
<label class="field"><span>Name</span><input id="srv-name" type="text" placeholder="z. B. Mein Server" /></label>
|
||||||
<label class="field"><span>Adresse</span><input id="srv-ip" type="text" placeholder="mc.example.de oder 192.168.1.50:25566" /></label>
|
<label class="field"><span>Adresse</span><input id="srv-ip" type="text" placeholder="mc.example.de oder 192.168.1.50:25566" /></label>
|
||||||
<button id="srv-add" class="btn btn-primary" style="align-self:flex-end">Hinzufügen</button>
|
<div class="field" style="flex:0 0 auto"><span> </span><button id="srv-add" class="btn btn-primary">Hinzufügen</button></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="srv-list" class="mods-list" style="margin-top:12px"></div>
|
<div id="srv-list" class="mods-list" style="margin-top:12px"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -880,19 +1022,19 @@
|
|||||||
<div class="skin-spalte">
|
<div class="skin-spalte">
|
||||||
<div class="skin-label">Aktuell</div>
|
<div class="skin-label">Aktuell</div>
|
||||||
<div class="skin-bild">
|
<div class="skin-bild">
|
||||||
<img id="skin-aktuell" alt="" class="hidden" />
|
<canvas id="skin-aktuell" class="hidden"></canvas>
|
||||||
<span id="skin-aktuell-leer" class="hint">wird geladen …</span>
|
<span id="skin-aktuell-leer" class="hint">wird geladen …</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="skin-spalte">
|
<div class="skin-spalte">
|
||||||
<div class="skin-label">Neu</div>
|
<div class="skin-label">Neu</div>
|
||||||
<div class="skin-bild">
|
<div class="skin-bild">
|
||||||
<img id="skin-neu" alt="" class="hidden" />
|
<canvas id="skin-neu" class="hidden"></canvas>
|
||||||
<span id="skin-neu-leer" class="hint">noch keine Datei gewählt</span>
|
<span id="skin-neu-leer" class="hint">noch keine Datei gewählt</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="hint">Die Vorschau zeigt die Figur so, wie sie im Spiel aussieht. Die Armform wirkt sich auf die Darstellung aus.</small>
|
<small class="hint">Mit der Maus ziehen dreht die Figur, Mausrad zoomt. Die Armform wirkt sich auf die Darstellung aus.</small>
|
||||||
<div class="path-row" style="margin-top:12px">
|
<div class="path-row" style="margin-top:12px">
|
||||||
<input id="skin-pfad" type="text" readonly placeholder="PNG mit 64×64 Pixeln" />
|
<input id="skin-pfad" type="text" readonly placeholder="PNG mit 64×64 Pixeln" />
|
||||||
<button id="skin-pick" class="btn">Datei wählen…</button>
|
<button id="skin-pick" class="btn">Datei wählen…</button>
|
||||||
@@ -966,6 +1108,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ===================== Dialog: Lizenz ===================== -->
|
||||||
|
<div id="modal-license" class="modal hidden">
|
||||||
|
<div class="modal-card modal-lg">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3>Lizenz</h3>
|
||||||
|
<button class="modal-close" data-close>✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<pre id="license-text" class="crash-voll">Lade …</pre>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<button class="btn" data-close>Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ===================== Dialog: Update ===================== -->
|
<!-- ===================== Dialog: Update ===================== -->
|
||||||
<div id="modal-update" class="modal hidden">
|
<div id="modal-update" class="modal hidden">
|
||||||
<div class="modal-card modal-sm">
|
<div class="modal-card modal-sm">
|
||||||
@@ -1019,11 +1177,18 @@
|
|||||||
|
|
||||||
<div id="import-pane-modpack" class="import-pane hidden">
|
<div id="import-pane-modpack" class="import-pane hidden">
|
||||||
<p class="import-copy">Modpack aus dem Katalog installieren oder als Datei importieren.</p>
|
<p class="import-copy">Modpack aus dem Katalog installieren oder als Datei importieren.</p>
|
||||||
<p class="hint">Suche läuft über Modrinth. Datei-Import unterstützt zusätzlich CurseForge-ZIP (manifest.json) – dafür ist ein API-Key oder eine Weiterleitung in den Netzwerk-Einstellungen nötig.</p>
|
<p class="hint">Katalog-Suche läuft über Modrinth oder CurseForge. Für CurseForge wird ein eigener API-Key in den Netzwerk-Einstellungen gebraucht (die Weiterleitung reicht nur für den Datei-Import). Datei-Import unterstützt zusätzlich CurseForge-ZIP (manifest.json).</p>
|
||||||
|
|
||||||
<div class="field-row" style="margin-top:12px;align-items:flex-end">
|
<div class="field-row" style="margin-top:12px;align-items:flex-end">
|
||||||
|
<label class="field" style="width:150px">
|
||||||
|
<span>Quelle</span>
|
||||||
|
<select id="mp-source">
|
||||||
|
<option value="modrinth">Modrinth</option>
|
||||||
|
<option value="curseforge">CurseForge</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="field" style="flex:1">
|
<label class="field" style="flex:1">
|
||||||
<span>Suche (Modrinth)</span>
|
<span>Suche</span>
|
||||||
<input id="mp-query" type="text" placeholder="z. B. Fabulously Optimized, Create …" autocomplete="off" />
|
<input id="mp-query" type="text" placeholder="z. B. Fabulously Optimized, Create …" autocomplete="off" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field" style="width:140px">
|
<label class="field" style="width:140px">
|
||||||
@@ -1233,6 +1398,7 @@
|
|||||||
<button class="ctx-item" data-act="p-mods">Mods anzeigen</button>
|
<button class="ctx-item" data-act="p-mods">Mods anzeigen</button>
|
||||||
<button class="ctx-item" data-act="p-worlds">Welten verwalten</button>
|
<button class="ctx-item" data-act="p-worlds">Welten verwalten</button>
|
||||||
<button class="ctx-item" data-act="p-screens">Bildschirmfotos verwalten</button>
|
<button class="ctx-item" data-act="p-screens">Bildschirmfotos verwalten</button>
|
||||||
|
<button class="ctx-item" data-act="p-record">Spiel aufzeichnen</button>
|
||||||
<button class="ctx-item" data-act="p-logs">Logdateien ansehen</button>
|
<button class="ctx-item" data-act="p-logs">Logdateien ansehen</button>
|
||||||
<button class="ctx-item" data-act="p-servers">Server verwalten</button>
|
<button class="ctx-item" data-act="p-servers">Server verwalten</button>
|
||||||
<div class="ctx-sep"></div>
|
<div class="ctx-sep"></div>
|
||||||
|
|||||||
@@ -464,6 +464,37 @@ async function prepareAndLaunch(opts, onProgress) {
|
|||||||
classpath.unshift(path.join(libDir, rel.replace(/\//g, path.sep)));
|
classpath.unshift(path.join(libDir, rel.replace(/\//g, path.sep)));
|
||||||
}
|
}
|
||||||
effectiveVj = mergeLoader(vj, fvj);
|
effectiveVj = mergeLoader(vj, fvj);
|
||||||
|
|
||||||
|
} else if (opts.loader === 'optifine') {
|
||||||
|
/*
|
||||||
|
* OptiFine hat keine öffentliche Installations-API (Downloads laufen bewusst
|
||||||
|
* über eine Werbeseite). AeroMC lädt hier nichts selbst herunter – der Nutzer
|
||||||
|
* hat den offiziellen Installer bereits einmal sichtbar laufen lassen (siehe
|
||||||
|
* optifine:setup/optifine:confirm in main.js), der dabei ein normales
|
||||||
|
* Versions-Profil unter <sharedDir>/versions/<loaderVersion>/ abgelegt hat,
|
||||||
|
* genau wie Fabric/Forge es tun. Wir lesen es nur noch ein und mergen es.
|
||||||
|
*/
|
||||||
|
const ofId = opts.loaderVersion;
|
||||||
|
if (!ofId) {
|
||||||
|
return { ok: false, message: 'Für diese Instanz ist keine OptiFine-Version hinterlegt. Bitte im Bearbeiten-Dialog einrichten.' };
|
||||||
|
}
|
||||||
|
const ofJsonPath = path.join(opts.sharedDir, 'versions', ofId, ofId + '.json');
|
||||||
|
if (!fs.existsSync(ofJsonPath)) {
|
||||||
|
return { ok: false, message: `OptiFine-Profil "${ofId}" wurde nicht gefunden. Bitte die Einrichtung im Bearbeiten-Dialog wiederholen.` };
|
||||||
|
}
|
||||||
|
let ofvj;
|
||||||
|
try { ofvj = JSON.parse(fs.readFileSync(ofJsonPath, 'utf8')); }
|
||||||
|
catch (err) { return { ok: false, message: 'OptiFine-Profil ist beschädigt: ' + err.message }; }
|
||||||
|
for (const lib of ofvj.libraries || []) {
|
||||||
|
const art = lib.downloads && lib.downloads.artifact;
|
||||||
|
const rel = (art && art.path) || forge.mavenPath(lib.name);
|
||||||
|
const dest = path.join(libDir, rel.replace(/\//g, path.sep));
|
||||||
|
if (!fs.existsSync(dest) && art && art.url) {
|
||||||
|
await downloadIfNeeded(art.url, dest, art.sha1 || null);
|
||||||
|
}
|
||||||
|
classpath.unshift(dest);
|
||||||
|
}
|
||||||
|
effectiveVj = mergeLoader(vj, ofvj);
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalClasspath = dedupeClasspath(classpath, opts.sharedDir);
|
const finalClasspath = dedupeClasspath(classpath, opts.sharedDir);
|
||||||
|
|||||||
+581
-91
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
// AudioWorklet-Prozessor für die isolierte "Nur Minecraft"-Tonaufnahme.
|
||||||
|
//
|
||||||
|
// loopback-capture liefert PCM-Häppchen unregelmäßig getaktet über IPC (mal
|
||||||
|
// etwas früher, mal etwas später). Würde man sie direkt nacheinander als
|
||||||
|
// einzelne AudioBufferSourceNode-Aufrufe abspielen, würde jede kleine
|
||||||
|
// Verzögerung als hörbares Knacksen/Aussetzer auffallen. Stattdessen landen
|
||||||
|
// ankommende Häppchen hier in einem Ringpuffer; der Audio-Rendering-Thread
|
||||||
|
// liest in gleichmäßigem Takt (process() läuft alle 128 Samples) daraus.
|
||||||
|
// Kleine Zeitschwankungen bei der Anlieferung werden so unhörbar
|
||||||
|
// abgefedert. Läuft der Puffer doch mal leer, gibt es kurz Stille statt
|
||||||
|
// eines Knacksens.
|
||||||
|
class PcmRingBufferProcessor extends AudioWorkletProcessor {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.capacity = sampleRate * 2; // 2 Sekunden Puffer je Kanal
|
||||||
|
this.ring = [new Float32Array(this.capacity), new Float32Array(this.capacity)];
|
||||||
|
this.writeIdx = 0;
|
||||||
|
this.readIdx = 0;
|
||||||
|
this.available = 0; // gepufferte Frames (nicht Samples)
|
||||||
|
this.port.onmessage = (e) => this._enqueue(e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_enqueue([left, right]) {
|
||||||
|
const n = left.length;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const idx = (this.writeIdx + i) % this.capacity;
|
||||||
|
this.ring[0][idx] = left[i];
|
||||||
|
this.ring[1][idx] = right[i];
|
||||||
|
}
|
||||||
|
this.writeIdx = (this.writeIdx + n) % this.capacity;
|
||||||
|
this.available = Math.min(this.capacity, this.available + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(_inputs, outputs) {
|
||||||
|
const output = outputs[0];
|
||||||
|
const frames = output[0].length;
|
||||||
|
const playable = Math.min(frames, this.available);
|
||||||
|
for (let i = 0; i < playable; i++) {
|
||||||
|
const idx = (this.readIdx + i) % this.capacity;
|
||||||
|
output[0][i] = this.ring[0][idx];
|
||||||
|
output[1][i] = this.ring[1][idx];
|
||||||
|
}
|
||||||
|
for (let i = playable; i < frames; i++) { output[0][i] = 0; output[1][i] = 0; }
|
||||||
|
this.readIdx = (this.readIdx + playable) % this.capacity;
|
||||||
|
this.available -= playable;
|
||||||
|
return true; // Prozessor am Leben halten, auch wenn gerade nichts ankommt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('pcm-player', PcmRingBufferProcessor);
|
||||||
@@ -7,6 +7,15 @@
|
|||||||
|
|
||||||
const { contextBridge, ipcRenderer } = require('electron');
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
// Debug-Hilfe: JS-Fehler im Renderer landen im Terminal des Hauptprozesses,
|
||||||
|
// damit man sie auch ohne offene DevTools sieht
|
||||||
|
window.addEventListener('error', (e) => {
|
||||||
|
ipcRenderer.send('debug:rendererError', (e.error && e.error.stack) || e.message);
|
||||||
|
});
|
||||||
|
window.addEventListener('unhandledrejection', (e) => {
|
||||||
|
ipcRenderer.send('debug:rendererError', 'unhandledrejection: ' + ((e.reason && e.reason.stack) || e.reason));
|
||||||
|
});
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('api', {
|
contextBridge.exposeInMainWorld('api', {
|
||||||
// Einstellungen
|
// Einstellungen
|
||||||
getSettings: () => ipcRenderer.invoke('settings:get'),
|
getSettings: () => ipcRenderer.invoke('settings:get'),
|
||||||
@@ -19,6 +28,9 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
// Minecraft-Versionen (Mojang)
|
// Minecraft-Versionen (Mojang)
|
||||||
mcVersions: () => ipcRenderer.invoke('mc:versions'),
|
mcVersions: () => ipcRenderer.invoke('mc:versions'),
|
||||||
loaderVersions: (loader, mcVersion) => ipcRenderer.invoke('loaders:versions', loader, mcVersion),
|
loaderVersions: (loader, mcVersion) => ipcRenderer.invoke('loaders:versions', loader, mcVersion),
|
||||||
|
optifineFind: (mcVersion) => ipcRenderer.invoke('optifine:find', mcVersion),
|
||||||
|
optifineSetup: (id) => ipcRenderer.invoke('optifine:setup', id),
|
||||||
|
optifineConfirm: (id) => ipcRenderer.invoke('optifine:confirm', id),
|
||||||
|
|
||||||
// Mods (Modrinth)
|
// Mods (Modrinth)
|
||||||
modSearch: (opts) => ipcRenderer.invoke('mods:search', opts),
|
modSearch: (opts) => ipcRenderer.invoke('mods:search', opts),
|
||||||
@@ -104,6 +116,7 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
filesDrop: (filePath, targetId, targetArt) => ipcRenderer.invoke('files:drop', filePath, targetId, targetArt),
|
filesDrop: (filePath, targetId, targetArt) => ipcRenderer.invoke('files:drop', filePath, targetId, targetArt),
|
||||||
modpackSearch: (opts) => ipcRenderer.invoke('modpack:search', opts),
|
modpackSearch: (opts) => ipcRenderer.invoke('modpack:search', opts),
|
||||||
modpackInstallFromModrinth: (project) => ipcRenderer.invoke('modpack:installFromModrinth', project),
|
modpackInstallFromModrinth: (project) => ipcRenderer.invoke('modpack:installFromModrinth', project),
|
||||||
|
modpackInstallFromCurseForge: (project) => ipcRenderer.invoke('modpack:installFromCurseForge', project),
|
||||||
modpackCheck: (id) => ipcRenderer.invoke('modpack:check', id),
|
modpackCheck: (id) => ipcRenderer.invoke('modpack:check', id),
|
||||||
modpackUpdate: (id, versionId) => ipcRenderer.invoke('modpack:update', id, versionId),
|
modpackUpdate: (id, versionId) => ipcRenderer.invoke('modpack:update', id, versionId),
|
||||||
onLaunchProgress: (cb) => {
|
onLaunchProgress: (cb) => {
|
||||||
@@ -236,4 +249,58 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
pickFolder: (title) => ipcRenderer.invoke('dialog:pickFolder', title),
|
pickFolder: (title) => ipcRenderer.invoke('dialog:pickFolder', title),
|
||||||
pickJava: () => ipcRenderer.invoke('dialog:pickJava'),
|
pickJava: () => ipcRenderer.invoke('dialog:pickJava'),
|
||||||
appInfo: () => ipcRenderer.invoke('app:info'),
|
appInfo: () => ipcRenderer.invoke('app:info'),
|
||||||
|
appLicense: () => ipcRenderer.invoke('app:license'),
|
||||||
|
|
||||||
|
// Einstellungen/Konsole als eigenes, frei verschiebbares Fenster
|
||||||
|
popoutOpen: (modalId, params) => ipcRenderer.send('popout:open', modalId, params),
|
||||||
|
popoutHide: () => ipcRenderer.send('popout:hide'),
|
||||||
|
onPopoutReopen: (cb) => {
|
||||||
|
const l = (_e, params) => cb(params);
|
||||||
|
ipcRenderer.on('popout:reopen', l);
|
||||||
|
return () => ipcRenderer.removeListener('popout:reopen', l);
|
||||||
|
},
|
||||||
|
onSettingsChanged: (cb) => {
|
||||||
|
const l = (_e, s) => cb(s);
|
||||||
|
ipcRenderer.on('settings:changed', l);
|
||||||
|
return () => ipcRenderer.removeListener('settings:changed', l);
|
||||||
|
},
|
||||||
|
onInstancesChanged: (cb) => {
|
||||||
|
const l = () => cb();
|
||||||
|
ipcRenderer.on('instances:changed', l);
|
||||||
|
return () => ipcRenderer.removeListener('instances:changed', l);
|
||||||
|
},
|
||||||
|
onSkinChanged: (cb) => {
|
||||||
|
const l = () => cb();
|
||||||
|
ipcRenderer.on('skin:changed', l);
|
||||||
|
return () => ipcRenderer.removeListener('skin:changed', l);
|
||||||
|
},
|
||||||
|
updateGetPending: () => ipcRenderer.invoke('update:getPending'),
|
||||||
|
|
||||||
|
// Spielaufzeichnung
|
||||||
|
recordingListWindows: () => ipcRenderer.invoke('recording:listWindows'),
|
||||||
|
recordingGetAudioSourceId: () => ipcRenderer.invoke('recording:getAudioSourceId'),
|
||||||
|
recordingSave: (instanceName, buffer, serverLabel) => ipcRenderer.invoke('recording:save', instanceName, buffer, serverLabel),
|
||||||
|
recordingOpenFolder: (instanceName) => ipcRenderer.invoke('recording:openFolder', instanceName),
|
||||||
|
recordingGetServer: (instanceId) => ipcRenderer.invoke('recording:getServer', instanceId),
|
||||||
|
onRecordingServerDetected: (cb) => {
|
||||||
|
const l = (_e, payload) => cb(payload);
|
||||||
|
ipcRenderer.on('recording:serverDetected', l);
|
||||||
|
return () => ipcRenderer.removeListener('recording:serverDetected', l);
|
||||||
|
},
|
||||||
|
recordingGetPid: (instanceId) => ipcRenderer.invoke('recording:getPid', instanceId),
|
||||||
|
recordingSetCaptureTarget: (sourceId) => ipcRenderer.invoke('recording:setCaptureTarget', sourceId),
|
||||||
|
recordingGetWindowBounds: (instanceId, fensterTitel) => ipcRenderer.invoke('recording:getWindowBounds', instanceId, fensterTitel),
|
||||||
|
recordingStartGameAudio: (pid) => ipcRenderer.invoke('recording:startGameAudio', pid),
|
||||||
|
recordingStopGameAudio: () => ipcRenderer.invoke('recording:stopGameAudio'),
|
||||||
|
onGameAudioChunk: (cb) => {
|
||||||
|
const l = (_e, chunk) => cb(chunk);
|
||||||
|
ipcRenderer.on('recording:gameAudioChunk', l);
|
||||||
|
return () => ipcRenderer.removeListener('recording:gameAudioChunk', l);
|
||||||
|
},
|
||||||
|
recordingSetGuard: (active) => ipcRenderer.send('recording:guard', active),
|
||||||
|
onRecordCloseAttempt: (cb) => {
|
||||||
|
const l = () => cb();
|
||||||
|
ipcRenderer.on('record:closeAttempt', l);
|
||||||
|
return () => ipcRenderer.removeListener('record:closeAttempt', l);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+1034
-73
File diff suppressed because it is too large
Load Diff
+17
-2
@@ -236,6 +236,7 @@ const INHALTSARTEN = {
|
|||||||
mod: { projectType: 'mod', ordner: 'mods', label: 'Mods', loaderNoetig: true },
|
mod: { projectType: 'mod', ordner: 'mods', label: 'Mods', loaderNoetig: true },
|
||||||
resourcepack: { projectType: 'resourcepack', ordner: 'resourcepacks', label: 'Ressourcenpakete', loaderNoetig: false },
|
resourcepack: { projectType: 'resourcepack', ordner: 'resourcepacks', label: 'Ressourcenpakete', loaderNoetig: false },
|
||||||
shader: { projectType: 'shader', ordner: 'shaderpacks', label: 'Shader', loaderNoetig: false },
|
shader: { projectType: 'shader', ordner: 'shaderpacks', label: 'Shader', loaderNoetig: false },
|
||||||
|
datapack: { projectType: 'datapack', ordner: 'datapacks', label: 'Datenpakete', loaderNoetig: false },
|
||||||
modpack: { projectType: 'modpack', ordner: null, label: 'Modpacks', loaderNoetig: false },
|
modpack: { projectType: 'modpack', ordner: null, label: 'Modpacks', loaderNoetig: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -515,7 +516,9 @@ function forgeCdnUrl(fileId, fileName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CF_LOADER = { forge: 1, fabric: 4, quilt: 5, neoforge: 6 };
|
const CF_LOADER = { forge: 1, fabric: 4, quilt: 5, neoforge: 6 };
|
||||||
const CF_CLASS = { mod: 6, resourcepack: 12, shader: 6552 };
|
// classId je Inhaltsart bei gameId 432 (Minecraft). modpack=4471 ist die auf
|
||||||
|
// CurseForge selbst verwendete Kategorie-ID (siehe curseforge.com/minecraft/modpacks).
|
||||||
|
const CF_CLASS = { mod: 6, resourcepack: 12, shader: 6552, modpack: 4471 };
|
||||||
|
|
||||||
function cfLoaderType(loader) {
|
function cfLoaderType(loader) {
|
||||||
return CF_LOADER[String(loader || '').toLowerCase()] || 0;
|
return CF_LOADER[String(loader || '').toLowerCase()] || 0;
|
||||||
@@ -598,7 +601,12 @@ async function curseforgeSearch(zugang, {
|
|||||||
+ 'Die Weiterleitung reicht für Downloads, nicht für den Katalog.',
|
+ 'Die Weiterleitung reicht für Downloads, nicht für den Katalog.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const classId = CF_CLASS[art] || CF_CLASS.mod;
|
if (!CF_CLASS[art]) {
|
||||||
|
// Kein zugeordneter CurseForge-Klassencode für diese Inhaltsart -> lieber gar
|
||||||
|
// keine Treffer als falsch einsortierte (z. B. Mods unter "Datenpakete").
|
||||||
|
return { total: 0, hits: [], error: 'CurseForge unterstützt diese Inhaltsart hier nicht – bitte Modrinth nutzen.' };
|
||||||
|
}
|
||||||
|
const classId = CF_CLASS[art];
|
||||||
const url = new URL(CURSEFORGE + '/mods/search');
|
const url = new URL(CURSEFORGE + '/mods/search');
|
||||||
url.searchParams.set('gameId', '432');
|
url.searchParams.set('gameId', '432');
|
||||||
url.searchParams.set('classId', String(classId));
|
url.searchParams.set('classId', String(classId));
|
||||||
@@ -713,6 +721,12 @@ async function curseforgePickFile(zugang, modId, gameVersion, loader, art) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Beste Modpack-Datei eines CurseForge-Projekts auflösen (für den Katalog-Import,
|
||||||
|
// analog zu modrinthResolveModpack).
|
||||||
|
async function curseforgeResolveModpackFile(zugang, modId, gameVersion) {
|
||||||
|
return curseforgePickFile(zugang, modId, gameVersion, '', 'modpack');
|
||||||
|
}
|
||||||
|
|
||||||
async function curseforgeInstall({ zugang, projectId, title, gameVersion, loader, modsDir, art = 'mod' }) {
|
async function curseforgeInstall({ zugang, projectId, title, gameVersion, loader, modsDir, art = 'mod' }) {
|
||||||
if (!zugang) return { ok: false, message: 'Kein CurseForge-Zugang eingerichtet.' };
|
if (!zugang) return { ok: false, message: 'Kein CurseForge-Zugang eingerichtet.' };
|
||||||
const rawId = String(projectId || '').replace(/^cf-/, '');
|
const rawId = String(projectId || '').replace(/^cf-/, '');
|
||||||
@@ -814,4 +828,5 @@ module.exports = {
|
|||||||
modrinthSearch, modrinthResolveFile, modrinthResolveModpack, modrinthInstall, modrinthProjectTitle,
|
modrinthSearch, modrinthResolveFile, modrinthResolveModpack, modrinthInstall, modrinthProjectTitle,
|
||||||
modrinthFindUpdates, downloadFile, downloadBuffer, inhaltsArt, INHALTSARTEN,
|
modrinthFindUpdates, downloadFile, downloadBuffer, inhaltsArt, INHALTSARTEN,
|
||||||
curseforgeResolveFiles, curseforgeSearch, curseforgeInstall, curseforgeTestKey, forgeCdnUrl, curseforgeZugang,
|
curseforgeResolveFiles, curseforgeSearch, curseforgeInstall, curseforgeTestKey, forgeCdnUrl, curseforgeZugang,
|
||||||
|
curseforgeResolveModpackFile,
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-2
@@ -77,6 +77,7 @@ const DEFAULT_SETTINGS = {
|
|||||||
bedrockEnabled: true, // Bedrock-Bereich anzeigen (nur wenn auch installiert)
|
bedrockEnabled: true, // Bedrock-Bereich anzeigen (nur wenn auch installiert)
|
||||||
desktopNotifications: true, // Windows-Meldung bei fertigem Hintergrund-Download/-Backup
|
desktopNotifications: true, // Windows-Meldung bei fertigem Hintergrund-Download/-Backup
|
||||||
autostart: false, // mit Windows starten, im Infobereich statt als Fenster
|
autostart: false, // mit Windows starten, im Infobereich statt als Fenster
|
||||||
|
discordClientId: '', // optional, nur per Hand in settings.json; leer => eingebaute AeroMC-App-ID
|
||||||
bgSkin: '', // '' = aus, 'active' = aktives Konto, sonst Konto-UUID
|
bgSkin: '', // '' = aus, 'active' = aktives Konto, sonst Konto-UUID
|
||||||
bgSkinOpacity: 55, // Deckkraft des Hintergrund-Skins in %
|
bgSkinOpacity: 55, // Deckkraft des Hintergrund-Skins in %
|
||||||
bgSkinPose: 'walking', // Pose des 3D-Renders (falls der Pose-Dienst erreichbar ist)
|
bgSkinPose: 'walking', // Pose des 3D-Renders (falls der Pose-Dienst erreichbar ist)
|
||||||
@@ -89,6 +90,16 @@ const DEFAULT_SETTINGS = {
|
|||||||
proxyUser: '',
|
proxyUser: '',
|
||||||
proxyPass: '',
|
proxyPass: '',
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
|
// Spielaufzeichnung - bleibt über Neustarts erhalten (letzte Wahl je Feld)
|
||||||
|
recordingResolution: 'native',
|
||||||
|
recordingQuality: 'medium',
|
||||||
|
recordingFramerate: 30,
|
||||||
|
recordingAudioSystem: true,
|
||||||
|
recordingAudioGame: false,
|
||||||
|
recordingVolSystem: 100,
|
||||||
|
recordingVolGame: 100,
|
||||||
|
recordingMicSelected: [], // Liste gewählter Mikrofon-deviceIds
|
||||||
|
recordingMicVolumes: {}, // deviceId -> Lautstärke (0-100)
|
||||||
};
|
};
|
||||||
|
|
||||||
function settingsFile() {
|
function settingsFile() {
|
||||||
@@ -134,8 +145,12 @@ function gameDir(id) {
|
|||||||
* "mod" behält die bisherigen Namen, damit vorhandene Instanzen unverändert
|
* "mod" behält die bisherigen Namen, damit vorhandene Instanzen unverändert
|
||||||
* weiterlaufen.
|
* weiterlaufen.
|
||||||
*/
|
*/
|
||||||
const INHALT_ORDNER = { mod: 'mods', resourcepack: 'resourcepacks', shader: 'shaderpacks' };
|
const INHALT_ORDNER = {
|
||||||
const INHALT_INDEX = { mod: 'mods.json', resourcepack: 'resourcepacks.json', shader: 'shaders.json' };
|
mod: 'mods', resourcepack: 'resourcepacks', shader: 'shaderpacks', datapack: 'datapacks',
|
||||||
|
};
|
||||||
|
const INHALT_INDEX = {
|
||||||
|
mod: 'mods.json', resourcepack: 'resourcepacks.json', shader: 'shaders.json', datapack: 'datapacks.json',
|
||||||
|
};
|
||||||
|
|
||||||
function inhaltsDir(id, art = 'mod') {
|
function inhaltsDir(id, art = 'mod') {
|
||||||
return path.join(gameDir(id), INHALT_ORDNER[art] || INHALT_ORDNER.mod);
|
return path.join(gameDir(id), INHALT_ORDNER[art] || INHALT_ORDNER.mod);
|
||||||
|
|||||||
+62
-2
@@ -32,6 +32,7 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
|
|||||||
:root[data-theme="dark"] .field select,
|
:root[data-theme="dark"] .field select,
|
||||||
:root[data-theme="dark"] .field textarea,
|
:root[data-theme="dark"] .field textarea,
|
||||||
:root[data-theme="dark"] .mods-search input,
|
:root[data-theme="dark"] .mods-search input,
|
||||||
|
:root[data-theme="dark"] .path-row input,
|
||||||
:root[data-theme="dark"] .login-usercode{background:#1e1e1e;color:var(--text);border-color:var(--border)}
|
:root[data-theme="dark"] .login-usercode{background:#1e1e1e;color:var(--text);border-color:var(--border)}
|
||||||
:root[data-theme="dark"] .modal-card{background:#2d2d30;border-color:#161618}
|
:root[data-theme="dark"] .modal-card{background:#2d2d30;border-color:#161618}
|
||||||
:root[data-theme="dark"] .modal-head{background:#333337}
|
:root[data-theme="dark"] .modal-head{background:#333337}
|
||||||
@@ -67,7 +68,8 @@ body{display:grid;grid-template-rows:auto 1fr auto;height:100vh;overflow:hidden}
|
|||||||
:root[data-theme="dark"] #btn-console svg{color:#c8c8cc}
|
:root[data-theme="dark"] #btn-console svg{color:#c8c8cc}
|
||||||
:root[data-theme="dark"] .acct-av svg{color:#c8c8cc}
|
:root[data-theme="dark"] .acct-av svg{color:#c8c8cc}
|
||||||
:root[data-theme="dark"] .mods-filters select,
|
:root[data-theme="dark"] .mods-filters select,
|
||||||
:root[data-theme="dark"] .mods-search select{background:#1e1e1e;color:var(--text);border-color:var(--border)}
|
:root[data-theme="dark"] .mods-search select,
|
||||||
|
:root[data-theme="dark"] .mmc-bar select{background:#1e1e1e;color:var(--text);border-color:var(--border)}
|
||||||
:root[data-theme="dark"] .upd-notes{background:#1e1e1e;color:var(--text)}
|
:root[data-theme="dark"] .upd-notes{background:#1e1e1e;color:var(--text)}
|
||||||
:root[data-theme="dark"] .modal-close{color:#aaa}
|
:root[data-theme="dark"] .modal-close{color:#aaa}
|
||||||
:root[data-theme="dark"] .modal-close:hover{color:#fff}
|
:root[data-theme="dark"] .modal-close:hover{color:#fff}
|
||||||
@@ -185,6 +187,38 @@ body.file-drop-active::after{
|
|||||||
.modal-body{padding:16px;overflow:auto;background:#f6f6f6}
|
.modal-body{padding:16px;overflow:auto;background:#f6f6f6}
|
||||||
.modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:10px 14px;border-top:1px solid var(--border);background:#efefef}
|
.modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:10px 14px;border-top:1px solid var(--border);background:#efefef}
|
||||||
|
|
||||||
|
/* Popup-Fenster (Einstellungen/Konsole): Haupt-UI ausblenden, das eine Modal
|
||||||
|
füllt das ganze (rahmenlose) Fenster; der eigene Kopfbereich ersetzt die
|
||||||
|
native Titelleiste – per -webkit-app-region per Maus verschiebbar */
|
||||||
|
body.is-popout>header.toolbar,body.is-popout>main.workspace,body.is-popout>footer.statusbar{display:none}
|
||||||
|
.popout-host{position:static;inset:auto;background:none;display:block}
|
||||||
|
/* !important: schlägt auch die spezifischere #modal-settings-Breitenregel oben */
|
||||||
|
.popout-host .modal-card{width:100%!important;max-width:none!important;height:100vh!important;max-height:none!important;border:none;border-radius:0;box-shadow:none;margin:0}
|
||||||
|
/* Inhalt soll die restliche Fensterhöhe füllen (scrollen statt Leerraum unter dem Fuß) */
|
||||||
|
.popout-host .modal-body{flex:1 1 auto;min-height:0}
|
||||||
|
/* Konsole/Logs/Screenshots haben einen einzelnen scrollbaren Kasten mit
|
||||||
|
fester Höhe (für den normalen Dialog gedacht) – im Popup-Fenster soll der
|
||||||
|
stattdessen die volle verbleibende Höhe ausfüllen statt Leerraum darunter
|
||||||
|
zu lassen */
|
||||||
|
#modal-console.popout-host .modal-body,
|
||||||
|
#modal-logs.popout-host .modal-body,
|
||||||
|
#modal-screens.popout-host .modal-body,
|
||||||
|
#modal-servers.popout-host .modal-body,
|
||||||
|
#modal-mods.popout-host .modal-body,
|
||||||
|
#modal-record.popout-host .modal-body{display:flex;flex-direction:column}
|
||||||
|
.popout-host .console-out,
|
||||||
|
#modal-logs.popout-host .crash-voll,
|
||||||
|
.popout-host .screens-grid,
|
||||||
|
.popout-host .mods-list,
|
||||||
|
.popout-host .record-stage{flex:1 1 auto;height:auto;max-height:none;min-height:0}
|
||||||
|
/* Mods-Fenster: die aktive Ansicht (Katalog/Installiert) muss selbst ein
|
||||||
|
Flex-Container werden, damit ihre eigene Liste (.mods-list) die Regel oben
|
||||||
|
nutzen und den restlichen Platz füllen kann */
|
||||||
|
#modal-mods.popout-host #m-pane-catalog,
|
||||||
|
#modal-mods.popout-host #m-pane-installed{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}
|
||||||
|
.popout-host .modal-head{-webkit-app-region:drag;cursor:default}
|
||||||
|
.popout-host .modal-head button,.popout-host .modal-head input,.popout-host .modal-head select{-webkit-app-region:no-drag}
|
||||||
|
|
||||||
/* Formular */
|
/* Formular */
|
||||||
.field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}
|
.field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}
|
||||||
.field>span{font-size:12px;color:#333}
|
.field>span{font-size:12px;color:#333}
|
||||||
@@ -196,7 +230,8 @@ body.file-drop-active::after{
|
|||||||
.backup-select-field{margin-top:14px;margin-bottom:0}
|
.backup-select-field{margin-top:14px;margin-bottom:0}
|
||||||
.backup-select-field select{width:100%}
|
.backup-select-field select{width:100%}
|
||||||
.hint{font-size:12px}
|
.hint{font-size:12px}
|
||||||
.path-row{display:flex;gap:8px}.path-row input{flex:1}
|
.path-row{display:flex;gap:8px}
|
||||||
|
.path-row input{flex:1;padding:6px 8px;border:1px solid var(--border);border-radius:3px;background:#fff;color:var(--text);font-family:inherit;font-size:13px}
|
||||||
.advanced{border:1px solid var(--border);border-radius:3px;padding:12px 14px 2px;margin-top:4px;background:#fbfbfb}
|
.advanced{border:1px solid var(--border);border-radius:3px;padding:12px 14px 2px;margin-top:4px;background:#fbfbfb}
|
||||||
.advanced legend{color:var(--dim);font-size:12px;padding:0 6px}
|
.advanced legend{color:var(--dim);font-size:12px;padding:0 6px}
|
||||||
.version-row{display:flex;gap:8px}.version-row #f-version-type{max-width:120px}.version-row #f-version{flex:1}
|
.version-row{display:flex;gap:8px}.version-row #f-version-type{max-width:120px}.version-row #f-version{flex:1}
|
||||||
@@ -286,6 +321,8 @@ body.file-drop-active::after{
|
|||||||
/* Die gerenderte Figur soll das Feld ausfüllen; eine rohe 64x64-Textur wird
|
/* Die gerenderte Figur soll das Feld ausfüllen; eine rohe 64x64-Textur wird
|
||||||
ohne Weichzeichnen vergrößert, damit sie nicht verschwommen wirkt. */
|
ohne Weichzeichnen vergrößert, damit sie nicht verschwommen wirkt. */
|
||||||
.skin-bild img{max-width:100%;max-height:100%;image-rendering:pixelated}
|
.skin-bild img{max-width:100%;max-height:100%;image-rendering:pixelated}
|
||||||
|
.skin-bild canvas{max-width:100%;max-height:100%;cursor:grab}
|
||||||
|
.skin-bild canvas:active{cursor:grabbing}
|
||||||
.crash-details{margin-top:12px}
|
.crash-details{margin-top:12px}
|
||||||
.crash-details summary{cursor:pointer;font-size:12px;color:var(--dim)}
|
.crash-details summary{cursor:pointer;font-size:12px;color:var(--dim)}
|
||||||
.crash-voll{max-height:320px;overflow:auto;margin-top:8px;padding:10px;background:rgba(0,0,0,.25);border-radius:4px;font-family:var(--mono);font-size:11px;line-height:1.45;white-space:pre-wrap;word-break:break-word}
|
.crash-voll{max-height:320px;overflow:auto;margin-top:8px;padding:10px;background:rgba(0,0,0,.25);border-radius:4px;font-family:var(--mono);font-size:11px;line-height:1.45;white-space:pre-wrap;word-break:break-word}
|
||||||
@@ -299,6 +336,8 @@ body.file-drop-active::after{
|
|||||||
|
|
||||||
/* MultiMC-Import */
|
/* MultiMC-Import */
|
||||||
.mmc-bar{display:flex;gap:18px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
|
.mmc-bar{display:flex;gap:18px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
|
||||||
|
.mmc-bar select{padding:6px 8px;border:1px solid var(--border);border-radius:3px;background:var(--input,#fff);color:var(--text);font-family:inherit;font-size:12px}
|
||||||
|
.mmc-bar select:focus{outline:none;border-color:var(--accent)}
|
||||||
.mmc-item{display:flex;gap:10px;align-items:center;padding:7px 8px;border-radius:3px}
|
.mmc-item{display:flex;gap:10px;align-items:center;padding:7px 8px;border-radius:3px}
|
||||||
.mmc-item+.mmc-item{border-top:1px solid var(--border2)}
|
.mmc-item+.mmc-item{border-top:1px solid var(--border2)}
|
||||||
.mmc-item:hover{background:rgba(128,128,128,.10)}
|
.mmc-item:hover{background:rgba(128,128,128,.10)}
|
||||||
@@ -340,6 +379,21 @@ img.account-av{object-fit:cover;image-rendering:auto}
|
|||||||
.screens-toolbar .hint{font-size:11px}
|
.screens-toolbar .hint{font-size:11px}
|
||||||
.screens-empty{color:var(--dim);text-align:center;padding:30px;grid-column:1/-1}
|
.screens-empty{color:var(--dim);text-align:center;padding:30px;grid-column:1/-1}
|
||||||
|
|
||||||
|
/* Spielaufzeichnung */
|
||||||
|
.record-search{display:flex;flex-direction:column;align-items:center;gap:10px;padding:48px 0;color:var(--dim)}
|
||||||
|
.record-stage{display:flex;flex-direction:column;gap:10px}
|
||||||
|
.record-video{width:100%;flex:1 1 auto;min-height:360px;background:#000;border-radius:4px;object-fit:contain}
|
||||||
|
.record-toolbar{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
|
||||||
|
.record-live-badge{display:flex;align-items:center;gap:8px;color:var(--danger);font-size:13px;font-weight:600}
|
||||||
|
#record-timer{font-variant-numeric:tabular-nums}
|
||||||
|
.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--danger);animation:rec-pulse 1s infinite}
|
||||||
|
@keyframes rec-pulse{0%,100%{opacity:1}50%{opacity:.25}}
|
||||||
|
.record-audio-bar{display:flex;align-items:center;gap:10px;padding:2px 2px}
|
||||||
|
.record-mic-list{display:flex;flex-direction:column;gap:8px;margin-top:8px}
|
||||||
|
.record-source-row{display:flex;align-items:center;gap:10px;margin-bottom:8px}
|
||||||
|
.record-source-row .checkbox-field{margin-bottom:0;flex:1;min-width:0;font-size:13px;gap:6px}
|
||||||
|
.record-vol{width:110px}
|
||||||
|
|
||||||
/* Konsole (dunkel wie ein Log) */
|
/* Konsole (dunkel wie ein Log) */
|
||||||
.console-out{height:440px;overflow:auto;margin:0;background:#1e1e1e;border:1px solid var(--border);border-radius:3px;padding:10px;font-family:var(--mono);font-size:12px;color:#d4d4d4;white-space:pre-wrap;word-break:break-word;line-height:1.5}
|
.console-out{height:440px;overflow:auto;margin:0;background:#1e1e1e;border:1px solid var(--border);border-radius:3px;padding:10px;font-family:var(--mono);font-size:12px;color:#d4d4d4;white-space:pre-wrap;word-break:break-word;line-height:1.5}
|
||||||
.log-line{white-space:pre-wrap}
|
.log-line{white-space:pre-wrap}
|
||||||
@@ -413,6 +467,12 @@ img.account-av{object-fit:cover;image-rendering:auto}
|
|||||||
.path-row{flex-direction:column}
|
.path-row{flex-direction:column}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Popup-Fenster sind eigenständige, meist schmalere Fenster -- die
|
||||||
|
Zeilenumbruch-Regel oben ist für die (deutlich breitere) Haupt-App gedacht
|
||||||
|
und soll hier nicht greifen (sonst z. B. Version/Icon im Instanz-Fenster
|
||||||
|
untereinander statt nebeneinander) */
|
||||||
|
.is-popout .field-row{flex-direction:row}
|
||||||
|
|
||||||
/* Konto-Button mit 3D-Skin-Kopf */
|
/* Konto-Button mit 3D-Skin-Kopf */
|
||||||
.acct-btn{gap:8px}
|
.acct-btn{gap:8px}
|
||||||
.acct-av{display:inline-flex;align-items:center;justify-content:center}
|
.acct-av{display:inline-flex;align-items:center;justify-content:center}
|
||||||
|
|||||||
+11
-2
@@ -230,8 +230,17 @@ async function downloadAndInstall(asset, onProgress) {
|
|||||||
|
|
||||||
const dir = updateDir();
|
const dir = updateDir();
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
const dest = path.join(dir, asset.name.replace(/[^\w.\-]/g, '_'));
|
let dest = path.join(dir, asset.name.replace(/[^\w.\-]/g, '_'));
|
||||||
fs.rmSync(dest, { force: true });
|
try {
|
||||||
|
fs.rmSync(dest, { force: true });
|
||||||
|
} catch {
|
||||||
|
// Alte Datei ist gesperrt (Virenscanner prüft sie gerade, oder ein vorheriger
|
||||||
|
// Installer-Versuch läuft noch) - dann eben unter neuem Namen laden, statt mit
|
||||||
|
// EPERM abzubrechen. Der Rest wird bei nächster Gelegenheit von
|
||||||
|
// cleanupUpdateCache() aufgeräumt (ab 10 Minuten Alter).
|
||||||
|
const parsed = path.parse(dest);
|
||||||
|
dest = path.join(parsed.dir, `${parsed.name}-${Date.now()}${parsed.ext}`);
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch(asset.url, { headers: { 'User-Agent': UA } });
|
const res = await fetch(asset.url, { headers: { 'User-Agent': UA } });
|
||||||
if (!res.ok) throw new Error('Download fehlgeschlagen: HTTP ' + res.status);
|
if (!res.ok) throw new Error('Download fehlgeschlagen: HTTP ' + res.status);
|
||||||
|
|||||||
Reference in New Issue
Block a user