diff --git a/Minecraft.Client/Common/Media/MediaWindows64.arc b/Minecraft.Client/Common/Media/MediaWindows64.arc index 4b7cec4b..42f14e70 100644 Binary files a/Minecraft.Client/Common/Media/MediaWindows64.arc and b/Minecraft.Client/Common/Media/MediaWindows64.arc differ diff --git a/Minecraft.Client/Common/Media/MediaWindows64.arc.bak b/Minecraft.Client/Common/Media/MediaWindows64.arc.bak new file mode 100644 index 00000000..4b7cec4b Binary files /dev/null and b/Minecraft.Client/Common/Media/MediaWindows64.arc.bak differ diff --git a/tools/ExtractFromArc.java b/tools/ExtractFromArc.java new file mode 100644 index 00000000..22c5d305 --- /dev/null +++ b/tools/ExtractFromArc.java @@ -0,0 +1,42 @@ +import java.io.*; +import java.nio.file.*; + +public class ExtractFromArc { + public static void main(String[] args) throws Exception { + if (args.length < 3) { + System.out.println("Usage: ExtractFromArc "); + System.exit(1); + } + String arcPath = args[0]; + String target = args[1]; + String outPath = args[2]; + + DataInputStream dis = new DataInputStream(new FileInputStream(arcPath)); + int numFiles = dis.readInt(); + int foundOffset = -1, foundSize = -1; + for (int i = 0; i < numFiles; i++) { + String name = dis.readUTF(); + int offset = dis.readInt(); + int size = dis.readInt(); + if (name.startsWith("*")) name = name.substring(1); + if (name.equals(target)) { + foundOffset = offset; + foundSize = size; + } + } + dis.close(); + + if (foundOffset == -1) { + System.err.println("File not found in archive: " + target); + System.exit(1); + } + + RandomAccessFile raf = new RandomAccessFile(arcPath, "r"); + raf.seek(foundOffset); + byte[] data = new byte[foundSize]; + raf.readFully(data); + raf.close(); + Files.write(Paths.get(outPath), data); + System.out.println("Extracted " + target + " (" + foundSize + " bytes) -> " + outPath); + } +} diff --git a/tools/ReplaceLogo.java b/tools/ReplaceLogo.java new file mode 100644 index 00000000..adfbf115 --- /dev/null +++ b/tools/ReplaceLogo.java @@ -0,0 +1,124 @@ +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.*; +import com.jpexs.decompiler.flash.tags.base.*; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.awt.*; +import java.io.*; +import java.util.*; + +/** + * Replaces MenuTitle and MenuTitleSmall bitmaps in a skin SWF + * with a custom logo image, then saves the modified SWF. + * + * Usage: ReplaceLogo [scale] + * + * Optional scale parameter (0.0-1.0) shrinks the logo within the bitmap + * canvas, adding transparent padding. Default is 1.0 (fill canvas). + * + * Finds bitmaps by symbol name (MenuTitle, MenuTitleSmall) rather than + * hardcoded IDs, so it works with both skinHDWin.swf and skinWin.swf. + */ +public class ReplaceLogo { + public static void main(String[] args) throws Exception { + if (args.length < 3) { + System.out.println("Usage: ReplaceLogo "); + System.exit(1); + } + + String swfPath = args[0]; + String logoPath = args[1]; + String outputPath = args[2]; + double extraScale = (args.length >= 4) ? Double.parseDouble(args[3]) : 1.0; + + System.out.println("Loading SWF: " + swfPath); + SWF swf = new SWF(new FileInputStream(swfPath), false); + + System.out.println("Loading logo: " + logoPath); + BufferedImage logoOriginal = ImageIO.read(new File(logoPath)); + System.out.println("Logo dimensions: " + logoOriginal.getWidth() + "x" + logoOriginal.getHeight()); + System.out.println("Extra scale: " + extraScale); + + // Build symbol name map from SymbolClass tags + Map symbolNames = new HashMap<>(); + for (Tag tag : swf.getTags()) { + if (tag instanceof SymbolClassTag) { + SymbolClassTag sym = (SymbolClassTag) tag; + for (int i = 0; i < sym.tags.size(); i++) { + symbolNames.put(sym.tags.get(i), sym.names.get(i)); + } + } + } + + // Find and replace MenuTitle / MenuTitleSmall bitmaps by symbol name + Set targetNames = Set.of("MenuTitle", "MenuTitleSmall"); + int replaced = 0; + + for (Tag tag : swf.getTags()) { + if (tag instanceof DefineBitsLossless2Tag) { + DefineBitsLossless2Tag bmp = (DefineBitsLossless2Tag) tag; + String symName = symbolNames.getOrDefault(bmp.characterID, ""); + + if (targetNames.contains(symName)) { + int targetW = bmp.bitmapWidth; + int targetH = bmp.bitmapHeight; + + System.out.println("Replacing " + symName + " (id=" + bmp.characterID + ", " + targetW + "x" + targetH + ")"); + + // Scale logo to fit within target dimensions, maintaining aspect ratio + // Then apply extra scale factor to shrink within the canvas + int fitW = (int) Math.round(targetW * extraScale); + int fitH = (int) Math.round(targetH * extraScale); + BufferedImage scaled = scaleToFit(logoOriginal, fitW, fitH); + + // Create target-sized image with transparency, center the scaled logo + BufferedImage target = new BufferedImage(targetW, targetH, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = target.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + int xOff = (targetW - scaled.getWidth()) / 2; + int yOff = (targetH - scaled.getHeight()) / 2; + g.drawImage(scaled, xOff, yOff, null); + g.dispose(); + + // Convert BufferedImage to PNG bytes for FFDec setImage() + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(target, "PNG", baos); + bmp.setImage(baos.toByteArray()); + + System.out.println(" Replaced with " + scaled.getWidth() + "x" + scaled.getHeight() + " (centered in " + targetW + "x" + targetH + ")"); + replaced++; + } + } + } + + if (replaced == 0) { + System.err.println("ERROR: No logo bitmaps found to replace!"); + System.exit(1); + } + + System.out.println("Saving modified SWF: " + outputPath); + try (FileOutputStream fos = new FileOutputStream(outputPath)) { + swf.saveTo(fos); + } + System.out.println("Done! Replaced " + replaced + " bitmaps."); + } + + static BufferedImage scaleToFit(BufferedImage src, int maxW, int maxH) { + double scaleX = (double) maxW / src.getWidth(); + double scaleY = (double) maxH / src.getHeight(); + double scale = Math.min(scaleX, scaleY); + + int newW = (int) Math.round(src.getWidth() * scale); + int newH = (int) Math.round(src.getHeight() * scale); + + BufferedImage result = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = result.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.drawImage(src, 0, 0, newW, newH, null); + g.dispose(); + return result; + } +}