'use strict'; /* * icons.js – echte Minecraft-Item-Icons * ------------------------------------- * Die Original-Texturen werden aus der bereits heruntergeladenen client.jar * des Nutzers extrahiert (also aus seinen eigenen Spieldateien) und lokal * als Icons abgelegt. Es werden keine fremden Assets mitgeliefert. */ const fs = require('fs'); const path = require('path'); const forge = require('./forge'); // Auswahl gut erkennbarer 16x16-Texturen (Pfade im client.jar) const WANTED = [ ['grass', 'block/grass_block_side.png', 'Grasblock'], ['dirt', 'block/dirt.png', 'Erde'], ['stone', 'block/cobblestone.png', 'Bruchstein'], ['planks', 'block/oak_planks.png', 'Holzbretter'], ['tnt', 'block/tnt_side.png', 'TNT'], ['obsidian', 'block/obsidian.png', 'Obsidian'], ['netherrack', 'block/netherrack.png', 'Netherrack'], ['crafting', 'block/crafting_table_front.png', 'Werkbank'], ['furnace', 'block/furnace_front.png', 'Ofen'], ['bookshelf', 'block/bookshelf.png', 'Bücherregal'], ['diamondblock', 'block/diamond_block.png', 'Diamantblock'], ['goldblock', 'block/gold_block.png', 'Goldblock'], ['emeraldblock', 'block/emerald_block.png', 'Smaragdblock'], ['redstoneblock', 'block/redstone_block.png', 'Redstoneblock'], ['diamond', 'item/diamond.png', 'Diamant'], ['emerald', 'item/emerald.png', 'Smaragd'], ['gold', 'item/gold_ingot.png', 'Goldbarren'], ['iron', 'item/iron_ingot.png', 'Eisenbarren'], ['redstone', 'item/redstone.png', 'Redstone'], ['coal', 'item/coal.png', 'Kohle'], ['potion', 'item/potion.png', 'Trank'], ['paper', 'item/paper.png', 'Papier'], ['feather', 'item/feather.png', 'Feder'], ['slimeball', 'item/slime_ball.png', 'Schleimball'], ['magma', 'item/magma_cream.png', 'Magmacreme'], ['quartz', 'item/quartz.png', 'Netherquarz'], ['brick', 'item/brick.png', 'Ziegel'], ['cookie', 'item/cookie.png', 'Keks'], ['snowball', 'item/snowball.png', 'Schneeball'], ['sword', 'item/diamond_sword.png', 'Diamantschwert'], ['pickaxe', 'item/diamond_pickaxe.png', 'Diamantspitzhacke'], ['axe', 'item/diamond_axe.png', 'Diamantaxt'], ['bow', 'item/bow.png', 'Bogen'], ['apple', 'item/apple.png', 'Apfel'], ['goldenapple', 'item/golden_apple.png', 'Goldener Apfel'], ['book', 'item/book.png', 'Buch'], ['enchantedbook', 'item/enchanted_book.png', 'Verzaubertes Buch'], ['map', 'item/map.png', 'Karte'], ['star', 'item/nether_star.png', 'Netherstern'], ['endereye', 'item/ender_eye.png', 'Enderauge'], ['enderpearl', 'item/ender_pearl.png', 'Enderperle'], ['blaze', 'item/blaze_powder.png', 'Lohenstaub'], ['xp', 'item/experience_bottle.png', 'Erfahrungsfläschchen'], ['cake', 'item/cake.png', 'Kuchen'], ['bucket', 'item/water_bucket.png', 'Wassereimer'], ['lava', 'item/lava_bucket.png', 'Lavaeimer'], ['chest', 'item/chest.png', 'Truhe'], ['creeper', 'item/creeper_head.png', 'Creeper-Kopf'], ['tnt_item', 'item/gunpowder.png', 'Schwarzpulver'], ]; // sucht die neueste vorhandene client.jar (auch im Datenordner vor der Umbenennung) function findClientJars(sharedDirs) { const jars = []; for (const shared of sharedDirs) { const vdir = path.join(shared, 'versions'); if (!fs.existsSync(vdir)) continue; for (const entry of fs.readdirSync(vdir, { withFileTypes: true })) { if (!entry.isDirectory()) continue; const jar = path.join(vdir, entry.name, entry.name + '.jar'); try { if (fs.existsSync(jar) && fs.statSync(jar).size > 1024 * 1024) { jars.push({ version: entry.name, path: jar, mtime: fs.statSync(jar).mtimeMs }); } } catch { /* überspringen */ } } } jars.sort((a, b) => b.mtime - a.mtime); return jars; } /* * Extrahiert die Texturen in . Gibt die Liste der gefundenen Icons zurück. */ function extractIcons(sharedDirs, outDir) { const jars = findClientJars(sharedDirs); if (!jars.length) return { ok: false, reason: 'no-jar' }; const jar = jars[0]; const buf = fs.readFileSync(jar.path); const zip = forge.readZipIndex(buf); fs.mkdirSync(outDir, { recursive: true }); const found = []; for (const [key, rel, label] of WANTED) { const entry = zip['assets/minecraft/textures/' + rel]; if (!entry) continue; try { const png = forge.readZipEntry(buf, entry); // sehr große Dateien überspringen (animierte Texturen sind gestreckt) if (png.length > 64 * 1024) continue; const dest = path.join(outDir, key + '.png'); fs.writeFileSync(dest, png); found.push({ key, label, file: dest }); } catch { /* Textur überspringen */ } } return { ok: true, version: jar.version, count: found.length, icons: found }; } // liest die extrahierten Icons als data:-URIs (für die Anzeige) function listIcons(outDir) { if (!fs.existsSync(outDir)) return []; const labels = Object.fromEntries(WANTED.map(([k, , l]) => [k, l])); return fs.readdirSync(outDir) .filter((f) => f.endsWith('.png')) .map((f) => { const key = path.basename(f, '.png'); try { const data = fs.readFileSync(path.join(outDir, f)).toString('base64'); return { key, label: labels[key] || key, dataUri: 'data:image/png;base64,' + data }; } catch { return null; } }) .filter(Boolean); } module.exports = { extractIcons, listIcons, findClientJars, WANTED };