import { useState, useEffect } from "react"; import { useLocalStorage } from "./useLocalStorage"; import { PckService } from "../services/PckService"; import { TauriService } from "../services/TauriService"; import { PCKAsset, PCKAssetType, PCKProperty } from "../types/pck"; interface Edition { id: string; supportsSlimSkins?: boolean; } interface UseSkinSyncProps { username: string; profile: string; editions: Edition[]; } export function useSkinSync({ username, profile, editions }: UseSkinSyncProps) { const [skinUrl, setSkinUrl] = useLocalStorage( "lce-skin", "/images/Default.png", ); const [skinIsSlim, setSkinIsSlim] = useLocalStorage("lce-skin-slim", false); const [skinBase64, setSkinBase64] = useState(null); const [capeUrl, setCapeUrl] = useLocalStorage( "lce-cape", null, ); const [capeBase64, setCapeBase64] = useState(null); useEffect(() => { let cancelled: Boolean = false; let editionNotSupportSlimSkins: Boolean = !editions.find( (edition: Edition) => edition.id === profile, )?.supportsSlimSkins; //neo: the bang (!) for "not" here is not a typo. its a fix because neoLegacy supports 64x64 now, no need to remodel the arms if (!skinUrl) return; const img = new Image(); img.crossOrigin = "anonymous"; img.onload = async () => { if (cancelled) return; const cvs = document.createElement("canvas"); cvs.width = 64; cvs.height = editionNotSupportSlimSkins ? 32 : 64; const ctx = cvs.getContext("2d"); if (ctx) { ctx.drawImage(img, 0, 0); const b64 = cvs.toDataURL("image/png"); setSkinBase64(b64); try { const res = await fetch(b64); const skinBuf = await res.arrayBuffer(); const isModernHeight = img.height === 64; const animValue = skinIsSlim ? editionNotSupportSlimSkins ? "0x00041800" : "0x00080000" : isModernHeight ? "0x00040000" : "0x00000000"; let boxes: PCKProperty[] = []; if (skinIsSlim && editionNotSupportSlimSkins) { boxes.push({ key: "BOX", value: "ARM0 -2 -2 -2 3 12 4 40 16 0 0 0", }); boxes.push({ key: "BOX", value: "ARM1 -1 -2 -2 3 12 4 40 16 0 1 0", }); } let capeBuf: ArrayBuffer | null = null; if (capeUrl) { const capeImg = new Image(); capeImg.crossOrigin = "anonymous"; await new Promise((resolve) => { capeImg.onload = () => resolve(); capeImg.onerror = () => resolve(); capeImg.src = capeUrl; }); if (!cancelled && capeImg.complete && capeImg.naturalWidth > 0) { const capeCanvas = document.createElement("canvas"); capeCanvas.width = capeImg.naturalWidth; capeCanvas.height = capeImg.naturalHeight; const capeCtx = capeCanvas.getContext("2d"); if (capeCtx) { capeCtx.drawImage(capeImg, 0, 0); const capeDataUrl = capeCanvas.toDataURL("image/png"); const capeRes = await fetch(capeDataUrl); capeBuf = await capeRes.arrayBuffer(); setCapeBase64(capeDataUrl); } } } else { setCapeBase64(null); } const getSeededId = (name: string) => { let hash = 0; for (let i = 0; i < name.length; i++) { hash = (hash << 5) - hash + name.charCodeAt(i); hash |= 0; } return Math.abs(hash).toString().padStart(8, "0").slice(-8); }; const seededId = getSeededId(username); //neo: (comment reason stated below) const packId = seededId.slice(-4); const files: PCKAsset[] = [ /*neo: commented because it causes neoLegacy to think its a Texture Pack, while it does nothing when removed. preserved for future reference. { id: "0", path: "0", type: PCKAssetType.INFO, size: 0, data: new Uint8Array(0), properties: [ { key: "PACKID", value: packId, }, ], },*/ { id: `dlcskin${seededId}`, path: `dlcskin${seededId}.png`, type: PCKAssetType.SKIN, size: skinBuf.byteLength, data: new Uint8Array(skinBuf), properties: [ { key: "DISPLAYNAME", value: username, }, { key: "GAME_FLAGS", value: "0x18", }, { key: "FREE", value: "1", }, { key: "ANIM", value: animValue, }, ...boxes, { key: "THEMENAME", value: "Emerald Launcher", }, { key: "CAPEPATH", value: `dlccape${seededId}.png`, }, ], }, ]; if (capeBuf) { files.push({ id: `dlccape${seededId}`, path: `dlccape${seededId}.png`, type: PCKAssetType.CAPE, size: capeBuf.byteLength, data: new Uint8Array(capeBuf), properties: [], }); } const pckBuf = PckService.serializePCK({ version: 3, endianness: "little", xmlSupport: false, properties: [ "ANIM", "DISPLAYNAME", "THEMENAME", "GAME_FLAGS", "FREE", "BOX", ], files, }); await TauriService.saveGlobalSkinPck(new Uint8Array(pckBuf)); } catch (e) { console.error("Failed to generate and save Skin PCK", e); } } }; img.src = skinUrl; return () => { cancelled = true; }; }, [skinUrl, capeUrl, username, profile, editions, skinIsSlim]); return { skinUrl, setSkinUrl, skinIsSlim, setSkinIsSlim, skinBase64, capeUrl, setCapeUrl, capeBase64, }; }