LCE-Emerald-Launcher/src/hooks/useSkinSync.ts
/home/neo d536a6590b
Some checks failed
Nix / build (push) Has been cancelled
feat: xbox360+ps3+java->windows64 world conversion (#145)
Co-authored-by: str1k3r <115313679+S1l3ntStr1ke87@users.noreply.github.com>
2026-08-07 19:01:24 +03:00

206 lines
6.5 KiB
TypeScript

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<string | null>(null);
const [capeUrl, setCapeUrl] = useLocalStorage<string | null>(
"lce-cape",
null,
);
const [capeBase64, setCapeBase64] = useState<string | null>(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<void>((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,
};
}