Upload via GUI (21 Dateien)
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* bedrock.js – Unterstützung für Minecraft Bedrock (Windows-Store-App)
|
||||
* --------------------------------------------------------------------
|
||||
* Bedrock ist keine Java-Anwendung, sondern eine UWP-App. Der Launcher kann
|
||||
* sie daher nicht herunterladen oder in Instanzen aufteilen – wohl aber
|
||||
* starten und die Spieldaten (Welten, Add-ons, Pakete, Bildschirmfotos)
|
||||
* verwalten. Die Installation selbst bleibt Sache des Microsoft Store.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execFile, spawn } = require('child_process');
|
||||
|
||||
const PKG_FAMILY = 'Microsoft.MinecraftUWP_8wekyb3d8bbwe';
|
||||
const APP_ID = PKG_FAMILY + '!App';
|
||||
|
||||
function comMojangDir() {
|
||||
return path.join(process.env.LOCALAPPDATA || '', 'Packages', PKG_FAMILY,
|
||||
'LocalState', 'games', 'com.mojang');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Erkennung
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function runPwsh(cmd) {
|
||||
return new Promise((resolve) => {
|
||||
execFile('powershell', ['-NoProfile', '-NonInteractive', '-Command', cmd],
|
||||
{ windowsHide: true, timeout: 15000 },
|
||||
(err, stdout) => resolve(err ? '' : String(stdout || '').trim()));
|
||||
});
|
||||
}
|
||||
|
||||
async function detect() {
|
||||
const dir = comMojangDir();
|
||||
const version = await runPwsh(
|
||||
"(Get-AppxPackage -Name 'Microsoft.MinecraftUWP' -ErrorAction SilentlyContinue).Version");
|
||||
const installed = !!version;
|
||||
return {
|
||||
installed,
|
||||
version: version || null,
|
||||
dataDir: fs.existsSync(dir) ? dir : null,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Start
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Startet Bedrock direkt.
|
||||
* Wichtig: Der AppsFolder-Pfad darf NICHT in Anführungszeichen stehen – sonst
|
||||
* öffnet der Explorer nur ein Ordnerfenster. Daher windowsVerbatimArguments.
|
||||
*/
|
||||
function launch() {
|
||||
const target = 'shell:appsFolder\\' + APP_ID;
|
||||
const versuche = [
|
||||
// 1) genau der Aufruf, der sich unter Windows als zuverlässig erwiesen hat
|
||||
['powershell', ['-NoProfile', '-WindowStyle', 'Hidden', '-Command',
|
||||
`Start-Process explorer.exe -ArgumentList '${target}'`], {}],
|
||||
// 2) explorer direkt, ohne zusätzliche Anführungszeichen
|
||||
['explorer.exe', [target], { windowsVerbatimArguments: true }],
|
||||
// 3) über die Eingabeaufforderung
|
||||
['cmd', ['/c', 'start', '', target], { windowsVerbatimArguments: true }],
|
||||
];
|
||||
for (const [cmd, args, extra] of versuche) {
|
||||
try {
|
||||
const p = spawn(cmd, args, Object.assign({ detached: true, stdio: 'ignore', windowsHide: true }, extra));
|
||||
p.unref();
|
||||
return { ok: true, method: cmd };
|
||||
} catch { /* nächsten Weg versuchen */ }
|
||||
}
|
||||
return { ok: false, message: 'Bedrock konnte nicht gestartet werden.' };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Welten / Pakete / Bildschirmfotos
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function readLevelName(worldDir) {
|
||||
// levelname.txt enthält den im Spiel angezeigten Namen
|
||||
try {
|
||||
const p = path.join(worldDir, 'levelname.txt');
|
||||
if (fs.existsSync(p)) return fs.readFileSync(p, 'utf8').trim();
|
||||
} catch { /* Ordnername verwenden */ }
|
||||
return path.basename(worldDir);
|
||||
}
|
||||
|
||||
function dirSize(dir, depth = 3) {
|
||||
let total = 0;
|
||||
if (depth < 0) return 0;
|
||||
let entries;
|
||||
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return 0; }
|
||||
for (const e of entries) {
|
||||
const p = path.join(dir, e.name);
|
||||
try {
|
||||
if (e.isDirectory()) total += dirSize(p, depth - 1);
|
||||
else total += fs.statSync(p).size;
|
||||
} catch { /* überspringen */ }
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function listWorlds() {
|
||||
const base = path.join(comMojangDir(), 'minecraftWorlds');
|
||||
if (!fs.existsSync(base)) return [];
|
||||
return fs.readdirSync(base, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => {
|
||||
const dir = path.join(base, e.name);
|
||||
let modified = null;
|
||||
try { modified = fs.statSync(dir).mtime.toISOString(); } catch { /* egal */ }
|
||||
return { id: e.name, name: readLevelName(dir), dir, modified, bytes: dirSize(dir) };
|
||||
})
|
||||
.sort((a, b) => String(b.modified).localeCompare(String(a.modified)));
|
||||
}
|
||||
|
||||
// Ordnerarten, die verwaltet werden können
|
||||
const FOLDERS = {
|
||||
worlds: 'minecraftWorlds',
|
||||
resource_packs: 'resource_packs',
|
||||
behavior_packs: 'behavior_packs',
|
||||
skin_packs: 'skin_packs',
|
||||
screenshots: 'Screenshots',
|
||||
};
|
||||
|
||||
function listFolder(kind) {
|
||||
const sub = FOLDERS[kind];
|
||||
if (!sub) return [];
|
||||
const base = path.join(comMojangDir(), sub);
|
||||
if (!fs.existsSync(base)) return [];
|
||||
return fs.readdirSync(base, { withFileTypes: true })
|
||||
.filter((e) => !e.name.startsWith('.'))
|
||||
.map((e) => {
|
||||
const p = path.join(base, e.name);
|
||||
let bytes = 0, modified = null;
|
||||
try {
|
||||
const st = fs.statSync(p);
|
||||
modified = st.mtime.toISOString();
|
||||
bytes = e.isDirectory() ? dirSize(p, 2) : st.size;
|
||||
} catch { /* egal */ }
|
||||
return { name: e.name, isDir: e.isDirectory(), path: p, bytes, modified };
|
||||
});
|
||||
}
|
||||
|
||||
function folderPath(kind) {
|
||||
const sub = FOLDERS[kind];
|
||||
return sub ? path.join(comMojangDir(), sub) : null;
|
||||
}
|
||||
|
||||
function removeEntry(kind, name) {
|
||||
const base = folderPath(kind);
|
||||
if (!base) return false;
|
||||
const target = path.join(base, name);
|
||||
if (!target.startsWith(base + path.sep)) return false; // Pfad-Ausbruch verhindern
|
||||
if (fs.existsSync(target)) fs.rmSync(target, { recursive: true, force: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add-on installieren: .mcpack/.mcaddon/.mcworld sind ZIP-Dateien.
|
||||
* Der Doppelklick-Weg über die Shell ist der zuverlässigste, weil Minecraft
|
||||
* die Pakete selbst einsortiert (inkl. Abhängigkeiten).
|
||||
*/
|
||||
function installAddon(filePath) {
|
||||
if (!fs.existsSync(filePath)) return { ok: false, message: 'Datei nicht gefunden.' };
|
||||
if (!/\.(mcpack|mcaddon|mcworld|mctemplate)$/i.test(filePath)) {
|
||||
return { ok: false, message: 'Nur .mcpack, .mcaddon, .mcworld oder .mctemplate möglich.' };
|
||||
}
|
||||
const child = spawn('cmd', ['/c', 'start', '', filePath], { detached: true, stdio: 'ignore' });
|
||||
child.unref();
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
detect, launch, listWorlds, listFolder, folderPath, removeEntry, installAddon,
|
||||
comMojangDir, FOLDERS,
|
||||
};
|
||||
Reference in New Issue
Block a user