package ar.com.companeros.identity; import ar.com.companeros.Companeros; import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import java.nio.charset.StandardCharsets; import java.util.*; import net.minecraft.gametest.framework.*; import net.minecraftforge.gametest.*; @GameTestHolder(Companeros.ID) @PrefixGameTestTemplate(false) public final class SkinProfileTests { private static final String TEXTURE = "https://textures.minecraft.net/texture/" + "a".repeat(64); @GameTest(template = "empty") public void steveOverridePersistsBeforeInfectionAndHostAppearanceCanBeRestored(GameTestHelper helper) { UUID host = PlayerProfileIds.offline("Xenon"); var state = new ar.com.companeros.horror.HorrorState(0); state.appearanceOverride(SkinSources.parasiteOverride("Steve")); var restored = ar.com.companeros.horror.HorrorState.load(state.save(new net.minecraft.nbt.CompoundTag())); helper.assertTrue(!restored.active() && restored.appearanceOverride().equals("steve") && SkinSources.parasiteOverride("default").equals("steve") && SkinSources.parasiteSource(restored.appearanceOverride(), host, TEXTURE).equals("steve"), "Steve local se conserva antes de infectar y prevalece sobre otra skin"); restored.appearanceOverride(SkinSources.parasiteOverride("host")); helper.assertTrue(SkinSources.parasiteSource(restored.appearanceOverride(), host, TEXTURE).equals(TEXTURE) && SkinSources.parasiteSource("", host, "").equals(host.toString()) && SkinSources.parasiteSource("", null, "").equals("steve"), "Restaurar huésped recupera selección o vanilla por UUID; sin huésped es Steve"); helper.assertTrue(rejected(() -> state.appearanceOverride("https://example.com")) && rejected(() -> SkinSources.parasiteOverride("../../skin")), "El comando local no puede elegir endpoints ni archivos"); helper.succeed(); } @GameTest(template = "empty") public void skinSourcesAllowOnlyMojangAccountsAndExactOfficialTextures(GameTestHelper helper) { helper.assertTrue(SkinSources.profile("Neon").equals("Neon") && SkinSources.profile("68e79ab569dd350ba1d373b2f9b89fb1").equals("68e79ab5-69dd-350b-a1d3-73b2f9b89fb1") && SkinSources.profile("DEFAULT").equals("default") && SkinSources.texture(TEXTURE.replace("https:", "http:")).equals(TEXTURE), "Nombres y UUID canónicos; CDN siempre HTTPS"); for (String input : List.of("../secret", "https://example.com/skin.png", " Neon", "Neon\n", "a".repeat(37))) helper.assertTrue(rejected(() -> SkinSources.profile(input)), "La skin no elige rutas ni endpoints"); for (String input : List.of(TEXTURE + "?token=secret", TEXTURE + "#fragment", TEXTURE.replace("textures.minecraft.net", "textures.minecraft.net.evil.example"), TEXTURE.replace("textures.minecraft.net", "user:secret@textures.minecraft.net"), TEXTURE.replace(".net/", ".net:443/"), "file:///skin.png")) helper.assertTrue(rejected(() -> SkinSources.texture(input)), "CDN sin credenciales, query ni puertos alternativos"); helper.succeed(); } @GameTest(template = "empty") public void skinSelectionsPersistAcrossWorldsWithoutReplacingPlayerIdentity(GameTestHelper helper) { UUID actor = PlayerProfileIds.offline("Neon"), owner = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"); SkinProfiles.Selection selected = new SkinProfiles.Selection("Example", owner, TEXTURE, property(), "test-signature", 123456L); Map restored = SkinProfiles.decode(SkinProfiles.encode(Map.of(actor, selected))); GameProfile profile = new GameProfile(actor, "Neon"); profile.getProperties().put("textures", restored.get(actor).property()); helper.assertTrue(restored.get(actor).equals(selected) && profile.getId().equals(actor) && profile.getName().equals("Neon") && !actor.equals(selected.profileId()) && profile.getProperties().containsKey("textures"), "Skin de otra cuenta mantiene UUID, nombre y fecha propia"); helper.assertTrue(rejected(() -> SkinProfiles.decode("{\"schema\":2,\"agents\":{}}")) && rejected(() -> SkinProfiles.decode("{\"schema\":1,\"agents\":{},\"endpoint\":\"http://localhost\"}")) && rejected(() -> new SkinProfiles.Selection("Example", owner, TEXTURE.replace('a', 'b'), property(), "test-signature", 0)), "Esquema cerrado y URL coincidente con propiedad"); helper.succeed(); } @GameTest(template = "empty") public void hostAtlasPreservesFaceHairAndClothesAtEveryMutationStage(GameTestHelper helper) { int[] source = new int[64 * 64]; for (int y = 8; y < 16; y++) for (int x = 8; x < 16; x++) source[y * 64 + x] = 0xFFCC9977; source[9 * 64 + 9] = 0xFF302018; // El huésped ya tenía este peinado. source[8 * 64 + 41] = 0xFF4F321F; // Capa exterior de pelo. source[23 * 64 + 21] = 0xFF246080; // Camisa. int[] before = source.clone(); int[] initial = HostSkinAtlas.compose(source, 64, 0); helper.assertTrue(initial.length == 128 * 128 && initial[9 * 128 + 9] == 0xFF302018 && initial[8 * 128 + 9] == 0xFF4F321F && initial[23 * 128 + 21] == 0xFF246080, "Cara, pelo superior y ropa pertenecen al huésped"); for (int stage = 1; stage <= 4; stage++) { int[] mutation = HostSkinAtlas.compose(source, 64, stage); helper.assertTrue(mutation[9 * 128 + 9] == initial[9 * 128 + 9] && mutation[8 * 128 + 9] == initial[8 * 128 + 9] && mutation[23 * 128 + 21] == initial[23 * 128 + 21] && mutation[10 * 128 + 14] != initial[10 * 128 + 14] && mutation[96 * 128 + 96] == 0xFF271A18, "Cicatrices localizadas sin borrar identidad; boca usa UV aparte"); } helper.assertTrue(Arrays.equals(source, before) && rejected(() -> HostSkinAtlas.compose(new int[1], 64, 0)), "Composición acotada sin mutar la imagen del huésped"); helper.succeed(); } @GameTest(template = "empty") public void skinPngDimensionsRejectCompressedOversizedImages(GameTestHelper helper) { byte[] png = new byte[33]; byte[] signature = {(byte)137, 80, 78, 71, 13, 10, 26, 10}; System.arraycopy(signature, 0, png, 0, 8); png[12] = 'I'; png[13] = 'H'; png[14] = 'D'; png[15] = 'R'; java.nio.ByteBuffer.wrap(png, 16, 8).putInt(64).putInt(64); helper.assertTrue(SkinSources.playerPng(png), "Cabecera de skin moderna"); java.nio.ByteBuffer.wrap(png, 20, 4).putInt(32); helper.assertTrue(SkinSources.playerPng(png), "Skin antigua 64×32"); java.nio.ByteBuffer.wrap(png, 16, 4).putInt(16_384); helper.assertTrue(!SkinSources.playerPng(png) && !SkinSources.playerPng(new byte[131_073]), "Dimensiones y transferencia acotadas antes del decodificador"); helper.succeed(); } private static String property() { JsonObject skin = new JsonObject(); skin.addProperty("url", TEXTURE); JsonObject textures = new JsonObject(); textures.add("SKIN", skin); JsonObject root = new JsonObject(); root.add("textures", textures); return Base64.getEncoder().encodeToString(root.toString().getBytes(StandardCharsets.UTF_8)); } private static boolean rejected(Runnable action) { try { action.run(); return false; } catch (RuntimeException expected) { return true; } } }