133 lines
5.2 KiB
JavaScript
133 lines
5.2 KiB
JavaScript
'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 <outDir>. 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 };
|