diff --git a/PCK-Studio/Classes/Conversion/Bedrock/BedrockConverter.cs b/PCK-Studio/Classes/Conversion/Bedrock/BedrockConverter.cs deleted file mode 100644 index e00ad09f..00000000 --- a/PCK-Studio/Classes/Conversion/Bedrock/BedrockConverter.cs +++ /dev/null @@ -1,633 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Numerics; -using System.Windows.Forms; - -using ICSharpCode.SharpZipLib.Zip; -using ICSharpCode.SharpZipLib.Zip.Compression; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using OMI.Formats.Languages; -using OMI.Formats.Pck; -using OMI.Workers.Language; -using OMI.Workers.Pck; - -using PckStudio.Conversion.Bedrock.Json; -using PckStudio.Classes.Utils; -using PckStudio.Classes.Conversion.Bedrock.JsonDefinitions; - -namespace PckStudio.Classes.Bedrock -{ - internal class BedrockConverter - { - // this is just a placeholder in case the locfile entry fails - // the placeholder name can be changed at any time - string PackName = "pck_studio"; - - // Geometry.json - JObject GJSON = new JObject(); - - static Dictionary languageMap = new Dictionary() - { - {"cs-CS", "cs_CZ"}, - {"cs-CZ", "cs_CZ"}, - - {"da-CH", "da_DK"}, - {"da-DA", "da_DK"}, - {"da-DK", "da_DK"}, - - {"de-AT", "de_DE"}, - {"de-DE", "de_DE"}, - - {"el-EL", "el_GR"}, - {"el-GR", "el_GR"}, - - {"en-AU", "en_GB"}, - {"en-CA", "en_US"}, - {"en-EN", "en_US"}, - {"en-GB", "en_GB"}, - {"en-GR", "en_GB"}, - {"en-IE", "en_GB"}, - {"en-NZ", "en_GB"}, - {"en-US", "en_US"}, - - {"es-ES", "es_ES"}, - {"es-MX", "es_MX"}, - - {"fi-BE", "fi_FI"}, - {"fi-CH", "fi_FI"}, - {"fi-FI", "fi_FI"}, - - {"fr-FR", "fr_CA"}, - {"fr-CA", "fr_FR"}, - - {"it-IT", "it_IT"}, - - {"ja-JP", "ja_JP"}, - - {"ko-KR", "ko_KR"}, - - {"la-LAS", "la-LAS"}, - - {"no-NO", "no-NO"}, - - {"nb-NO", "nb_NO"}, - - {"nl-NL", "nl_NL"}, - {"nl-BE", "nl_NL"}, - - {"pl-PL", "pl_PL"}, - - {"pt-BR", "pt_BR"}, - {"pt-PT", "pt_PT"}, - - {"ru-RU", "ru_RU"}, - - {"sk-SK", "sk_SK"}, - - {"sv-SE", "sv_SE"}, - {"sv-SV", "sv_SE"}, - - {"tr-TR", "tr_TR"}, - - {"zh-CN", "zh_TW"}, - {"zh-HK", "zh_TW"}, - {"zh-SG", "zh_TW"}, - {"zh-TW", "zh_TW"}, - {"zh-CHT", "zh_TW"}, - {"zh-HANS", "zh_TW"}, - {"zh-HANT", "zh_TW"} - }; - - static List OffsetNames = new List - { - "HEAD", "HELMET", - "BODY", "CHEST", "BELT", - "ARM0", "ARMARMOR0", "SHOULDER0", "TOOL0", - "ARM1", "ARMARMOR1", "SHOULDER1", "TOOL1", - "LEG0", "LEGGING0", "BOOT0", - "LEG1", "LEGGING1", "BOOT1" - }; - - static string[,] ItemSheetArray = - { - {"leather_helmet","chainmail_helmet","iron_helmet","diamond_helmet","golden_helmet","flint_and_steel","flint","coal","string","wheat_seeds","apple","golden_apple","egg","sugar","snowball","elytra" }, - {"leather_chestplate","chainmail_chestplate","iron_chestplate","diamond_chestplate","golden_chestplate","bow","brick","iron_ingot","feather","wheat","painting","sugarcane","bone","cake","slime_ball","broken_elytra" }, - {"leather_leggings","chainmail_leggings","iron_leggings","diamond_leggings","golden_leggings","arrow","end_crystal","gold_ingot","gunpowder","bread","oak_sign","oak_door","iron_door","","fire_charge","chorus_fruit" }, - {"leather_boots","chainmail_boots","iron_boots","diamond_boots","golden_boots","stick","compass_00","diamond","redstone","clay_ball","paper","book","map","pumpkin_seeds","melon_seeds","popped_chorus_fruit" }, - {"wooden_sword","stone_sword","iron_sword","diamond_sword","golden_sword","fishing_rod","clock_00","bowl","mushroom_stew","glowstone_dust","bucket","water_bucket","lava_bucket","milk_bucket","ink_sac","gray_dye" }, - {"wooden_shovel","stone_shovel","iron_shovel","diamond_shovel","golden_shovel","fishing_rod_cast","repeater","porkchop","cooked_porkchop","cod","cooked_cod","rotten_flesh","cookie","shears","red_dye","pink_dye" }, - {"wooden_pickaxe","stone_pickaxe","iron_pickaxe","diamond_pickaxe","golden_pickaxe","bow_pulling_0","carrot_on_a_stick","leather","saddle","beef","cooked_beef","ender_pearl","blaze_rod","melon_slice","green_dye","lime_dye" }, - {"wooden_axe","stone_axe","iron_axe","diamond_axe","golden_axe","bow_pulling_1","baked_potato","potato","carrot","chicken","cooked_chicken","ghast_tear","gold_nugget","nether_wart","cocoa_beans","yellow_dye" }, - {"wooden_hoe","stone_hoe","iron_hoe","diamond_hoe","golden_hoe","bow_pulling_2","poisonous_potato","minecart","oak_boat","glistering_melon_slice","fermented_spider_eye","spider_eye","potion","potion_overlay","blue_dye","light_blue_dye" }, - {"leather_helmet_overlay","spectral_arrow","iron_horse_armor","diamond_horse_armor","golden_horse_armor","comparator","golden_carrot","chest_minecart","pumpkin_pie","spawn_egg","splash_potion","ender_eye","cauldron","blaze_powder","purple_dye","magenta_dye" }, - {"","tipped_arrow_base","dragon_breath","name_tag","lead","nether_brick","tropical_fish","furnace_minecart","charcoal","spawn_egg_overlay","","experience_bottle","brewing_stand","magma_cream","cyan_dye","orange_dye" }, - {"leather_leggings_overlay","tipped_arrow_head","lingering_potion","barrier","mutton","rabbit","pufferfish","hopper_minecart","hopper","nether_star","emerald","writable_book","written_book","flower_pot","light_gray_dye","bone_meal" }, - {"leather_boots_overlay","beetroot","beetroot_seeds","beetroot_soup","cooked_mutton","cooked_rabbit","salmon","tnt_minecart","armor_stand","firework_rocket","firework_star","firework_star_overlay","quartz","map","item_frame","enchanted_book" }, - {"acacia_door","birch_door","dark_oak_door","jungle_door","spruce_door","rabbit_stew","cooked_salmon","command_block_minecart","acacia_boat","birch_boat","dark_oak_boat","jungle_boat","spruce_boat","prismarine_shard","prismarine_crystals","leather_horse_armor" }, - {"structure_void","","totem_of_undying","shulker_shell","iron_nugget","rabbit_foot","rabbit_hide","","","","","","","","","" }, - {"music_disc_13","music_disc_cat","music_disc_blocks","music_disc_chirp","music_disc_far","music_disc_mall","music_disc_mellohi","music_disc_stal","music_disc_strad","music_disc_ward","music_disc_11","music_disc_wait","cod_bucket","salmon_bucket","pufferfish_bucket","tropical_fish_bucket" }, - {"leather_horse_armor","","","","","","","kelp","dried_kelp","sea_pickle","nautilus_shell","heart_of_the_sea","turtle_helmet","scute","trident","phantom_membrane" } - }; - - static string[,] BlockSheetArray = - { - {"grass_block_top","stone","dirt","grass_block_side","oak_planks","smooth_stone_slab_side","smooth_stone","bricks","tnt_side","tnt_top","tnt_bottom","cobweb","poppy","dandelion","blue_concrete","oak_sapling" }, - {"cobblestone","bedrock","sand","gravel","oak_log","oak_log_top","iron_block","gold_block","diamond_block","emerald_block","redstone_block","dropper_front","red_mushroom","brown_mushroom","jungle_sapling","red_concrete" }, - {"gold_ore","iron_ore","coal_ore","bookshelf","mossy_cobblestone","obsidian","grass_block_side_overlay","grass","dispenser_front_vertical","beacon","dropper_front_vertical","crafting_table_top","furnace_front","furnace_side","dispenser_front","red_concrete" }, - {"sponge","glass","diamond_ore","redstone_ore","oak_leaves","black_concrete","stone_bricks","dead_bush","fern","daylight_detector_top","daylight_detector_side","crafting_table_side","crafting_table_front","furnace_front_on","furnace_top","spruce_sapling" }, - {"white_wool","spawner","snow","ice","grass_block_snow","cactus_top","cactus_side","cactus_bottom","clay","sugar_cane","jukebox_side","jukebox_top","birch_leaves","mycelium_side","mycelium_top","birch_sapling" }, - {"torch","oak_door_top","iron_door_top","ladder","oak_trapdoor","iron_bars","farmland_wet","farmland","wheat_stage0","wheat_stage1","wheat_stage2","wheat_stage3","wheat_stage4","wheat_stage5","wheat_stage6","wheat_stage7" }, - {"lever","oak_door_bottom","iron_door_bottom","redstone_torch","mossy_stone_bricks","cracked_stone_bricks","pumpkin_top","netherrack","soul_sand","glowstone","piston_top_sticky","piston_top","piston_side","piston_bottom","piston_inner","pumpkin_stem" }, - {"rail_corner","black_wool","gray_wool","redstone_torch_off","spruce_log","birch_log","pumpkin_side","carved_pumpkin","jack_o_lantern","cake_top","cake_side","cake_inner","cake_bottom","red_mushroom_block","brown_mushroom_block","attached_pumpkin_stem" }, - {"rail","red_wool", "pink_wool","repeater","spruce_leaves","spruce_leaves","conduit","turtle_egg","melon_side","melon_top","cauldron_top","cauldron_inner","wet_sponge","mushroom_stem","mushroom_block_inside","vines" }, - {"lapis_block","green_wool","lime_wool","repeater_on","glass_pane_top","debug","debug","turtle_egg_slightly_cracked","turtle_egg_very_cracked","jungle_log","cauldron_side","cauldron_bottom","brewing_stand_base","brewing_stand","end_portal_frame_top","end_portal_frame_side" }, - {"lapis_ore","brown_wool","yellow_wool","powered_rail","redstone_dust_dot","redstone_dust_line0","enchanting_table_top","dragon_egg","cocoa_stage2","cocoa_stage1","cocoa_stage0","emerald_ore","tripwire_hook","tripwire","end_portal_frame_eye","end_stone" }, - {"sandstone_top","blue_wool","light_blue_wool","powered_rail_on","debug","debug","enchanting_table_side","enchanting_table_bottom","glide_blue","item_frame","flower_pot","comparator","comparator_on","activator_rail","activator_rail","nether_quartz_ore" }, - {"sandstone","purple_wool","magenta_wool","detector_rail","jungle_leaves","black_concrete","spruce_planks","jungle_planks","carrots_stage0","carrots_stage1","carrots_stage2","carrots_stage3","slime_block","debug","debug","debug" }, - {"sandstone_bottom","cyan_wool","orange_wool","redstone_lamp","redstone_lamp_on","chiseled_stone_bricks","birch_planks","anvil","chipped_anvil_top","chiseled_quartz_block_top","quartz_pillar_top","quartz_block_side","debug","detector_rail_on","debug","debug" }, - {"nether_bricks","light_gray_wool","nether_wart_stage0","nether_wart_stage1","nether_wart_stage2","chiseled_sandstone","cut_sandstone","anvil_top","damaged_anvil_top","chiseled_quartz_block","quartz_pillar","quartz_block_top","debug","debug","debug","debug" }, - {"destroy_stage_0","destroy_stage_1","destroy_stage_2","destroy_stage_3","destroy_stage_4","destroy_stage_5","destroy_stage_6","destroy_stage_7","destroy_stage_8","destroy_stage_9","hay_block_side","quartz_block_bottom","debug","hay_block_top","debug","debug" }, - {"coal_block","terracotta","note_block","andesite","polished_andesite","diorite","polished_diorite","granite","polished_granite","potatoes_stage0","potatoes_stage1","potatoes_stage2","potatoes_stage3","spruce_log_top","jungle_log_top","birch_log_top" }, - {"black_terracotta","blue_terracotta","brown_terracotta","cyan_terracotta","gray_terracotta","green_terracotta","light_blue_terracotta","lime_terracotta","magenta_terracotta","orange_terracotta","pink_terracotta","purple_terracotta","red_terracotta","light_gray_terracotta","white_terracotta","yellow_terracotta" }, - {"black_stained_glass","blue_stained_glass","brown_stained_glass","cyan_stained_glass","gray_stained_glass","green_stained_glass","light_blue_stained_glass","lime_stained_glass","magenta_stained_glass","orange_stained_glass","pink_stained_glass","purple_stained_glass","red_stained_glass","light_gray_stained_glass","white_stained_glass","yellow_stained_glass" }, - {"black_stained_glass_pane_top","blue_stained_glass_pane_top","brown_stained_glass_pane_top","cyan_stained_glass_pane_top","gray_stained_glass_pane_top","green_stained_glass_pane_top","light_blue_stained_glass_pane_top","lime_stained_glass_pane_top","magenta_stained_glass_pane_top","orange_stained_glass_pane_top","pink_stained_glass_pane_top","purple_stained_glass_pane_top","red_stained_glass_pane_top","light_gray_stained_glass_pane_top","white_stained_glass_pane_top","yellow_stained_glass_pane_top" }, - {"large_fern_top","tall_grass_top","peony_top","rose_bush_top","lilac_top","orange_tulip","sunflower_top","sunflower_front","acacia_log","acacia_log_top","acacia_planks","acacia_leaves","acacia_leaves","prismarine_bricks","red_sand","red_sandstone_top" }, - {"large_fern_bottom","tall_grass_bottom","peony_bottom","rose_bush_bottom","lilac_bottom","pink_tulip","sunflower_bottom","sunflower_back","dark_oak_log","dark_oak_log_top","dark_oak_planks","dark_oak_leaves","dark_oak_leaves","dark_prismarine","red_sandstone_bottom","red_sandstone" }, - {"allium","blue_orchid","azure_bluet","oxeye_daisy","red_tulip","white_tulip","acacia_sapling","dark_oak_sapling","coarse_dirt","podzol_side","podzol_top","spruce_leaves","spruce_leaves","debug","chiseled_red_sandstone","cut_red_sandstone" }, - {"acacia_door_top","birch_door_top","dark_oak_door_top","jungle_door_top","spruce_door_top","chorus_flower","chorus_flower_dead","chorus_plant","end_stone_bricks","grass_path_side","grass_path_top","debug","packed_ice","debug","daylight_detector_inverted_top","iron_trapdoor" }, - {"acacia_door_bottom","birch_door_bottom","dark_oak_door_bottom","jungle_door_bottom","spruce_door_bottom","purpur_block","purpur_pillar","purpur_pillar_top","end_rod","debug","nether_wart_block","red_nether_bricks","frosted_ice_0","frosted_ice_1","frosted_ice_2","frosted_ice_3" }, - {"beetroots_stage0","beetroots_stage1","beetroots_stage2","beetroots_stage3","debug","debug","debug","debug","debug","debug","debug","debug","debug","debug","debug","debug" }, - {"bone_block_side","bone_block_top","melon_stem","attached_melon_stem","observer_front","observer_side","observer_back","observer_back_on","observer_top","glide_yellow","glide_green","structure_block","structure_block_corner","structure_block_data","structure_block_load","structure_block_save" }, - {"black_concrete","blue_concrete","brown_concrete","cyan_concrete","gray_concrete","green_concrete","light_blue_concrete","lime_concrete","magenta_concrete","orange_concrete","pink_concrete","purple_concrete","red_concrete","light_gray_concrete","white_concrete","yellow_concrete" }, - {"black_concrete_powder","blue_concrete_powder","brown_concrete_powder","cyan_concrete_powder","gray_concrete_powder","green_concrete_powder","light_blue_concrete_powder","lime_concrete_powder","magenta_concrete_powder","orange_concrete_powder","pink_concrete_powder","purple_concrete_powder","red_concrete_powder","light_gray_concrete_powder","white_concrete_powder","yellow_concrete_powder" }, - {"black_glazed_terracotta","blue_glazed_terracotta","brown_glazed_terracotta","cyan_glazed_terracotta","gray_glazed_terracotta","green_glazed_terracotta","light_blue_glazed_terracotta","lime_glazed_terracotta","magenta_glazed_terracotta","orange_glazed_terracotta","pink_glazed_terracotta","purple_glazed_terracotta","red_glazed_terracotta","light_gray_glazed_terracotta","white_glazed_terracotta","yellow_glazed_terracotta" }, - {"white_shulker_box","","water_overlay","debug","tube_coral_block","bubble_coral_block","brain_coral_block","fire_coral_block","horn_coral_block","tube_coral","bubble_coral","brain_coral","fire_coral","horn_coral","sea_pickle","blue_ice" }, - {"dried_kelp_top","dried_kelp_side","debug","debug","dead_tube_coral_block","dead_bubble_coral_block","dead_brain_coral_block","dead_fire_coral_block","dead_horn_coral_block","tube_coral_fan","bubble_coral_fan","brain_coral_fan","fire_coral_fan","horn_coral_fan","","" }, - {"debug","debug","debug","debug","debug","debug","debug","debug","debug","dead_tube_coral_fan","dead_bubble_coral_fan","dead_brain_coral_fan","dead_fire_coral_fan","dead_horn_coral_fan","","spruce_trapdoor" }, - {"stripped_oak_log","stripped_oak_log_top","stripped_acacia_log","stripped_acacia_log_top","stripped_birch_log","stripped_birch_log_top","stripped_dark_oak_log","stripped_dark_oak_log_top","stripped_jungle_log","stripped_jungle_log_top","stripped_spruce_log","stripped_spruce_log_top","acacia_trapdoor","birch_trapdoor","dark_oak_trapdoor","jungle_trapdoor" } - }; - - static Dictionary ImgCopyLookup = new Dictionary() - { - { "res/mob", "/textures/entity" }, - { "res/art", "/textures/painting" }, - { "res/environment", "/textures/environment" }, - { "res/terrain", "/textures/environment" }, - { "res/armor", "/textures/models/armor" } - }; - - public void ConvertTexturePack(PckFile sourcePck, string exportPath) - { - Directory.CreateDirectory(exportPath); - foreach (PckFile.FileData file in sourcePck.Files) - { - switch (file.Filetype) - { - case PckFile.FileData.FileType.TextureFile: - if (file.Filename == "res/terrain.png") - SplitSheet(file.Data, exportPath, useItemsArray: false); - if (file.Filename == "res/items.png") - SplitSheet(file.Data, exportPath, useItemsArray: true); - break; - case PckFile.FileData.FileType.UIDataFile: - CopyTexture(file, exportPath); - break; - } - } - } - - void SplitSheet(byte[] data, string exportPath, bool useItemsArray) - { - int defaultHorizontalCount = 16; - int defaultVerticalCount = 34; - string[,] sheetArray = BlockSheetArray; - string outputPath = "\\textures\\blocks\\"; - if (useItemsArray) - { - defaultVerticalCount = 17; - sheetArray = ItemSheetArray; - outputPath = "\\textures\\items\\"; - } - Directory.CreateDirectory(exportPath + outputPath); - - MemoryStream ms = new MemoryStream(data); - var img = Image.FromStream(ms); - - int targetWidth = img.Width / defaultHorizontalCount; - int targetHeight = img.Height / defaultVerticalCount; - - // Start splitting the Image. - Bitmap piece = new Bitmap(targetWidth, targetHeight); - Rectangle dest_rect = new Rectangle(0, 0, targetWidth, targetHeight); - using (Graphics gr = Graphics.FromImage(piece)) - { - int num_rows = img.Height / targetHeight; - int num_cols = img.Width / targetWidth; - Rectangle source_rect = new Rectangle(0, 0, targetWidth, targetHeight); - for (int row = 0; row < num_rows; row++) - { - source_rect.X = 0; - for (int col = 0; col < num_cols; col++) - { - // Copy the piece of the image. - gr.Clear(Color.Transparent); - gr.DrawImage(img, dest_rect, source_rect, GraphicsUnit.Pixel); - - // Save the piece. - string filename = sheetArray[row, col] + ".png"; - if(!string.IsNullOrEmpty(filename) && filename != "debug") - piece.Save(exportPath + outputPath + filename, System.Drawing.Imaging.ImageFormat.Png); - - // Move to the next column. - source_rect.X += targetWidth; - } - source_rect.Y += targetHeight; - } - } - } - - [Obsolete("Wrong Behavior ?")] - void CopyTexture(PckFile.FileData file, string exportPath) - { - string exportName = file.Filename; - foreach(var kvp in ImgCopyLookup) - { - exportName = exportName.Replace(kvp.Key, kvp.Value); - } - - Directory.CreateDirectory(Path.GetDirectoryName(exportPath + exportName)); - File.WriteAllBytes(exportPath + exportName, file.Data); - } - - public void ConvertSkinPack(PckFile sourcePck, string exportFilepath) - { - SkinJSON skinJson = new SkinJSON(); // Skins.json - - string exportPath = Path.GetDirectoryName(exportFilepath); - Directory.CreateDirectory(Path.Combine(exportPath, "skin_pack")); - - if (sourcePck.TryGetFile("localisation.loc", PckFile.FileData.FileType.LocalisationFile, out PckFile.FileData locFileData) || - sourcePck.TryGetFile("languages.loc", PckFile.FileData.FileType.LocalisationFile, out locFileData)) - { - var reader = new LOCFileReader(); - - using (var ms = new MemoryStream(locFileData.Data)) - { - LOCFile locFile = reader.FromStream(ms); - string newPackName = locFile.GetLocEntry("IDS_DISPLAY_NAME", "en-EN").ToLower().Replace(":", ""); - if (!string.IsNullOrEmpty(newPackName)) - { - PackName = newPackName.Replace(" ", "_"); - } - ExportLOC(locFile, exportPath + "\\skin_pack\\texts"); - } - } - - skinJson.Skins = ConvertSkins(sourcePck, exportPath).ToArray(); - skinJson.LocalizationName = skinJson.SerializeName = PackName; - - var manifest = CreateSkinPackManifest(skinJson.LocalizationName); - - string manifest_JSON = JsonConvert.SerializeObject(manifest, Formatting.Indented); - File.WriteAllText(exportPath + "\\skin_pack\\manifest.json", manifest_JSON); - - string SKINS_JSON = JsonConvert.SerializeObject(skinJson, Formatting.Indented); - File.WriteAllText(exportPath + "\\skin_pack\\skins.json", SKINS_JSON); - - string GEO_JSON = JsonConvert.SerializeObject(GJSON, Formatting.Indented); - File.WriteAllText(exportPath + "\\skin_pack\\geometry.json", GEO_JSON); - - using (var outputStream = new ZipOutputStream(File.Create(exportFilepath))) - { - outputStream.SetLevel(Deflater.NO_COMPRESSION); - string[] files = Directory.GetFiles(exportPath + "\\skin_pack", "*", SearchOption.AllDirectories); - foreach (string file in files) - { - ZipEntry entry = new ZipEntry(file.Replace(exportPath + "\\", "")); - outputStream.PutNextEntry(entry); - - byte[] sourceBytes = File.ReadAllBytes(file); - outputStream.Write(sourceBytes, 0, sourceBytes.Length); - } - //Directory.Delete(ExportPath + "\\skin_pack", true); - } - GC.Collect(); - } - - private List ConvertSkins(PckFile sourcePck, string exportPath) - { - List skins = new List(); - foreach (PckFile.FileData file in sourcePck.Files) - { - switch (file.Filetype) - { - case PckFile.FileData.FileType.SkinFile: - skins.Add(ExportSkin(file, exportPath + "\\skin_pack")); - break; - case PckFile.FileData.FileType.CapeFile: - ExportCape(file, exportPath + "\\skin_pack"); - break; - case PckFile.FileData.FileType.SkinDataFile: - var reader = new PckFileReader(); - using (var ms = new MemoryStream(file.Data)) - { - PckFile subPack = reader.FromStream(ms); - skins.AddRange(ConvertSkins(subPack, exportPath)); - } - break; - } - } - return skins; - } - - void ExportLOC(LOCFile locFile, string exportPath) - { - Directory.CreateDirectory(exportPath); - List languages = new List(); - - foreach (var kvp in locFile.LocKeys) - { - string packString = kvp.Key.Replace("IDS_DISPLAY_NAME", "skinpack." + PackName).Replace("IDS_DLCSKIN", "skin." + PackName + ".").Replace("_DISPLAYNAME", "").Replace(" ", ""); - - if (!packString.EndsWith("_THEMENAME")) - { - foreach (KeyValuePair text in kvp.Value) - { - string bedrockLang = languageMap[text.Key]; - if (!languages.Contains(bedrockLang)) - languages.Add(bedrockLang); - if (File.Exists(exportPath + "\\" + bedrockLang + ".lang")) - { - if (!File.ReadAllText(exportPath + "\\" + bedrockLang + ".lang").Contains(packString)) - { - using (StreamWriter sw = new StreamWriter(exportPath + "\\" + bedrockLang + ".lang", true)) - { - sw.WriteLine(packString + "=" + text.Value); - } - } - } - else - { - using (StreamWriter sw = new StreamWriter(exportPath + "\\" + bedrockLang + ".lang", true)) - { - sw.WriteLine(packString + "=" + text.Value); - } - } - } - } - } - - string serializedLanguages = JsonConvert.SerializeObject(languages.ToArray(), Formatting.Indented); - File.WriteAllText(exportPath + "\\languages.json", serializedLanguages); - } - - List<(string, string)> GetSkinOffsets(PckFile.PCKProperties skinProperties) - { - List<(string, string)> skinOffsets = new List>(); - - string part_offset = ""; - - foreach (string offsetName in OffsetNames) - { - try - { - var v = skinProperties.Find(prop => prop.property == "OFFSET" && prop.value.StartsWith(offsetName)).value; - if (v != null && v.Length >= 2) part_offset = v.Split(' ')[2]; - else part_offset = "0"; - } - catch - { - part_offset = "0"; - } - - switch (offsetName) - { - case "HEAD": - skinOffsets.Add(("HEADWEAR", part_offset)); - break; - case "BODY": - skinOffsets.Add(("JACKET", part_offset)); - skinOffsets.Add(("BODYARMOR", part_offset)); - break; - case "CHEST": - skinOffsets.Add(("BELT", part_offset)); - skinOffsets.Add(("WAIST", part_offset)); - break; - case "ARM0": - skinOffsets.Add(("SLEEVE0", part_offset)); - skinOffsets.Add(("SHOULDER0", part_offset)); - break; - case "ARM1": - skinOffsets.Add(("SLEEVE1", part_offset)); - skinOffsets.Add(("SHOULDER1", part_offset)); - break; - case "LEG0": - skinOffsets.Add(("PANTS0", part_offset)); - skinOffsets.Add(("SOCK0", part_offset)); - break; - case "LEG1": - skinOffsets.Add(("PANTS1", part_offset)); - skinOffsets.Add(("SOCK1", part_offset)); - break; - } - - if(skinOffsets.Find(p => p.Item1 == offsetName) != default!) - skinOffsets.Add((offsetName, part_offset)); - } - - return skinOffsets; - } - - GeometryCube[] ConvertBoxes(string part, PckFile.FileData file, Vector3 pivot, List<(string, string)> offsets) - { - List cubes = new List(); - - var anim = new SkinANIM(); - - Console.WriteLine(part); - float offset = float.Parse(offsets.Find(o => o.Item1 == part).Item2); - - foreach (var (name, value) in file.Properties) - { - string entry = value; - switch (name) - { - case "ANIM": - anim = SkinANIM.FromString(value); - break; - case "BOX": - SkinBOX box = SkinBOX.FromString(entry); - if (box.Parent == part) - { - float y = -1 * (box.Pos.Y + offset + box.Size.Y); - cubes.Add(new GeometryCube(new Vector3(pivot.X + box.Pos.X, pivot.Y + y, pivot.Z + box.Pos.Z), - box.Size, box.UV, box.Mirror, box.Inflation)); - } - break; - default: - break; - } - } - - bool slim = anim.GetFlag(ANIM_EFFECTS.SLIM_MODEL); - bool classic_res = !(slim && anim.GetFlag(ANIM_EFFECTS.RESOLUTION_64x64)); - - switch (part) - { - case "HEAD": - if (!anim.GetFlag(ANIM_EFFECTS.HEAD_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(- 4, 24 - offset, -4), new Vector3(8, 8, 8), new Vector2(0, 0))); - break; - case "BODY": - if (!anim.GetFlag(ANIM_EFFECTS.BODY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(-4, 12 - offset, -2), new Vector3(8, 12, 4), new Vector2(16, 16))); - break; - case "ARM0": - if (!anim.GetFlag(ANIM_EFFECTS.RIGHT_ARM_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(slim ? -7 : - 8, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), new Vector2(40, 16))); - break; - case "ARM1": - if (!anim.GetFlag(ANIM_EFFECTS.LEFT_ARM_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(4, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), classic_res ? new Vector2(40, 16) : new Vector2(32, 48), classic_res)); - break; - case "LEG0": - if (!anim.GetFlag(ANIM_EFFECTS.RIGHT_LEG_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(-3.9f, 0 - offset, -2), new Vector3(4, 12, 4), new Vector2(0, 16))); - break; - case "LEG1": - if (!anim.GetFlag(ANIM_EFFECTS.LEFT_LEG_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(0.1f, 0 - offset, -2), new Vector3(4, 12, 4), classic_res ? new Vector2(0, 16) : new Vector2(16, 48), classic_res)); - break; - case "HEADWEAR": - if (!anim.GetFlag(ANIM_EFFECTS.HEAD_OVERLAY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(-4, 24 - offset, -4), new Vector3(8, 8, 8), new Vector2(32, 0), false, 0.5f)); - break; - case "JACKET": - if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.BODY_OVERLAY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(0, 24 - offset, 0), new Vector3(8, 12, 4), new Vector2(16, 32), false, 0.25f)); - break; - case "SLEEVE0": - if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.RIGHT_ARM_OVERLAY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(slim ? -7 : -8, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), new Vector2(40, 32), false, 0.25f)); - break; - case "SLEEVE1": - if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.LEFT_ARM_OVERLAY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(4, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), new Vector2(48, 48), false, 0.25f)); - break; - case "PANTS0": - if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.RIGHT_LEG_OVERLAY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(-3.9f, 0 - offset, -2), new Vector3(4, 12, 4), new Vector2(0, 32), false, 0.25f)); - break; - case "PANTS1": - if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.LEFT_LEG_OVERLAY_DISABLED)) - cubes.Add(new GeometryCube(new Vector3(0.1f, 0 - offset, -2), new Vector3(4, 12, 4), new Vector2(0, 48), false, 0.25f)); - break; - default: - break; - } - - return cubes.ToArray(); - } - - SkinObject ExportSkin(PckFile.FileData file, string exportPath) - { - if (file.Filetype != PckFile.FileData.FileType.SkinFile) - return default!; - - SkinObject skinObj = new SkinObject(); - skinObj.LocalizationName = Path.GetFileNameWithoutExtension(file.Filename); - skinObj.TextureName = Path.GetFileName(file.Filename); - skinObj.GeometryName = $"geometry.{PackName}.{skinObj.LocalizationName}"; - - Vector3 head_and_body_pivot = new Vector3(0, 24, 0); - Vector3 right_arm_pivot = new Vector3(-5, 22, 0); - Vector3 left_arm_pivot = new Vector3(5, 22, 0); - Vector3 right_leg_pivot = new Vector3(-1.9f, 12, 0); - Vector3 left_leg_pivot = new Vector3(1.9f, 12, 0); - - var offsets = GetSkinOffsets(file.Properties); - - var geometry = new Geometry((1, 2, new int[3] { 0, 1, 0 }), - new GeometryBone("head", null, head_and_body_pivot, ConvertBoxes("HEAD", file, head_and_body_pivot, offsets), "base"), - new GeometryBone("body", null, head_and_body_pivot, ConvertBoxes("BODY", file, head_and_body_pivot, offsets), "base"), - new GeometryBone("rightArm", null, right_arm_pivot, ConvertBoxes("ARM0", file, right_arm_pivot, offsets), "base"), - new GeometryBone("leftArm", null, left_arm_pivot, ConvertBoxes("ARM1", file, left_arm_pivot, offsets), "base"), - new GeometryBone("rightLeg", null, right_leg_pivot, ConvertBoxes("LEG0", file, right_leg_pivot, offsets), "base"), - new GeometryBone("leftLeg", null, left_leg_pivot, ConvertBoxes("LEG1", file, left_leg_pivot, offsets), "base"), - - new GeometryBone("hat", "head", head_and_body_pivot, ConvertBoxes("HEADWEAR", file, head_and_body_pivot, offsets), "clothing"), - new GeometryBone("jacket", "body", head_and_body_pivot, ConvertBoxes("JACKET", file, head_and_body_pivot, offsets), "clothing"), - new GeometryBone("rightSleeve", "rightArm", right_arm_pivot, ConvertBoxes("SLEEVE0", file, right_arm_pivot, offsets), "clothing"), - new GeometryBone("leftSleeve", "leftArm", left_arm_pivot, ConvertBoxes("SLEEVE1", file, left_arm_pivot, offsets), "clothing"), - new GeometryBone("rightPants", "rightLeg", right_leg_pivot, ConvertBoxes("PANTS0", file, right_leg_pivot, offsets), "clothing"), - new GeometryBone("leftPants", "leftLeg", left_leg_pivot, ConvertBoxes("PANTS1", file, left_leg_pivot, offsets), "clothing"), - new GeometryBone("rightSock", "rightLeg", right_leg_pivot, ConvertBoxes("SOCK0", file, right_leg_pivot, offsets), "clothing"), - new GeometryBone("leftSock", "leftLeg", left_leg_pivot, ConvertBoxes("SOCK1", file, left_leg_pivot, offsets), "clothing"), - - new GeometryBone("helmet", "head", head_and_body_pivot, ConvertBoxes("HELMET", file, head_and_body_pivot, offsets), "armor"), - new GeometryBone("bodyArmor", "body", head_and_body_pivot, ConvertBoxes("BODYARMOR", file, head_and_body_pivot, offsets), "armor"), - new GeometryBone("belt", "body", head_and_body_pivot, ConvertBoxes("BELT", file, head_and_body_pivot, offsets), "armor"), - new GeometryBone("rightArmArmor", "rightArm", right_arm_pivot, ConvertBoxes("ARMARMOR0", file, right_arm_pivot, offsets), "armor"), - new GeometryBone("leftArmArmor", "leftArm", left_arm_pivot, ConvertBoxes("ARMARMOR1", file, left_arm_pivot, offsets), "armor"), - new GeometryBone("rightLegArmor", "rightLeg", right_leg_pivot, ConvertBoxes("LEGGING0", file, right_leg_pivot, offsets), "armor"), - new GeometryBone("leftLegArmor", "leftLeg", left_leg_pivot, ConvertBoxes("LEGGING1", file, left_leg_pivot, offsets), "armor"), - new GeometryBone("rightBoot", "rightLeg", right_leg_pivot, ConvertBoxes("BOOT0", file, right_leg_pivot, offsets), "armor"), - new GeometryBone("leftBoot", "leftLeg", left_leg_pivot, ConvertBoxes("BOOT1", file, left_leg_pivot, offsets), "armor"), - - // calculates armor and item offsets - new GeometryBone("rightItem", "rightArm", new Vector3(-6.0f, 15.0f - float.Parse(offsets.Find(o => o.Item1 == "TOOL0").Item2), 1.0f), null, "item"), - new GeometryBone("leftItem", "leftArm", new Vector3(6.0f, 15.0f - float.Parse(offsets.Find(o => o.Item1 == "TOOL1").Item2), 1.0f), null, "item"), - - new GeometryBone("helmetArmorOffset", "head", new Vector3(0f, 24f - float.Parse(offsets.Find(o => o.Item1 == "HEAD").Item2), 0f), null, "armor_offset"), - new GeometryBone("bodyArmorOffset", "body", new Vector3(-4f, 12f - float.Parse(offsets.Find(o => o.Item1 == "BODY").Item2), -2f), null, "armor_offset"), - new GeometryBone("rightArmArmorOffset", "rightArm", new Vector3(4f, 12f - float.Parse(offsets.Find(o => o.Item1 == "ARM0").Item2), -2f), null, "armor_offset"), - new GeometryBone("leftArmArmorOffset", "leftArm", new Vector3(-8f, 12f - float.Parse(offsets.Find(o => o.Item1 == "ARM1").Item2), -2f), null, "armor_offset"), - new GeometryBone("rightLegArmorOffset", "rightLeg", new Vector3(-0.1f, float.Parse(offsets.Find(o => o.Item1 == "LEG0").Item2), -2f), null, "armor_offset"), - new GeometryBone("leftLegArmorOffset", "leftLeg", new Vector3(-4.1f, float.Parse(offsets.Find(o => o.Item1 == "LEG1").Item2), -2f), null, "armor_offset"), - new GeometryBone("rightBootArmorOffset", "rightLeg", new Vector3(2f, 12f - float.Parse(offsets.Find(o => o.Item1 == "BOOT0").Item2), 0f), null, "armor_offset"), - new GeometryBone("leftBootArmorOffset", "leftLeg", new Vector3(-2f, 12f - float.Parse(offsets.Find(o => o.Item1 == "BOOT1").Item2), 0f), null, "armor_offset") - ); - - string capepath = file.Properties.Find(o => o.property == "CAPEPATH").value; - - JToken geo = JToken.FromObject(geometry); - if (capepath != null) - geo["cape"] = capepath; - - GJSON.Add(skinObj.GeometryName, geo); - - File.WriteAllBytes(exportPath + "\\" + skinObj.LocalizationName + ".png", file.Data); - return skinObj; - } - - void ExportCape(PckFile.FileData file, string exportPath) - { - if (file.Filetype != PckFile.FileData.FileType.CapeFile) - return; - - string capeId = file.Filename.Replace(".png", string.Empty).Replace("Skins/", string.Empty); - File.WriteAllBytes(exportPath + "\\" + capeId + ".png", file.Data); - } - - Manifest CreateSkinPackManifest(string localizedName) - { - return new Manifest - { - Header = new ManifestHeader() - { - Name = localizedName, - Description = "Exported with " + Application.ProductName, - }, - Modules = new ManifestModule[] - { - new ManifestModule("skin_pack") - } - }; - } - } -} diff --git a/PCK-Studio/Classes/Conversion/Bedrock/BedrockSkinExporter.cs b/PCK-Studio/Classes/Conversion/Bedrock/BedrockSkinExporter.cs new file mode 100644 index 00000000..d5005e95 --- /dev/null +++ b/PCK-Studio/Classes/Conversion/Bedrock/BedrockSkinExporter.cs @@ -0,0 +1,466 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Numerics; +using System.Windows.Forms; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using OMI.Formats.Languages; +using OMI.Formats.Pck; +using OMI.Workers.Language; +using OMI.Workers.Pck; + +using PckStudio.Conversion.Bedrock.Json; +using PckStudio.Classes.Utils; +using PckStudio.Conversion.Bedrock.JsonDefinitions; +using System.Diagnostics; +using System.Linq; + +namespace PckStudio.Conversion.Bedrock +{ + internal class BedrockSkinExporter + { + private IExportContext _exportContext; + + private LOCFile loc; + + /// + /// + /// + static string[] SupportedBedrockLanguages = new string[] + { + "en_US", + "de_DE", + "ru_RU", + "zh_CN", + "fr_FR", + "it_IT", + "pt_BR", + "fr_CA", + "zh_TW", + "es_MX", + "es_ES", + "pt_PT", + "en_GB", + "ko_KR", + "ja_JP", + "nl_NL", + "bg_BG", + "cs_CZ", + "da_DK", + "el_GR", + "fi_FI", + "hu_HU", + "id_ID", + "nb_NO", + "pl_PL", + "sk_SK", + "sv_SE", + "tr_TR", + "uk_UA" + }; + + static List OffsetNames = new List + { + "HEAD", "HELMET", + "BODY", "CHEST", "BELT", + "ARM0", "ARMARMOR0", "SHOULDER0", "TOOL0", + "ARM1", "ARMARMOR1", "SHOULDER1", "TOOL1", + "LEG0", "LEGGING0", "BOOT0", + "LEG1", "LEGGING1", "BOOT1" + }; + + /// + /// (Bedrock)Languge to key value dictionary + /// + private Dictionary> bedrockLanguageFiles = new Dictionary>(SupportedBedrockLanguages.Length); + + public BedrockSkinExporter(IExportContext exportContext) + { + _exportContext = exportContext; + } + + private static List<(string, string)> GetSkinOffsets(PckFile.PCKProperties skinProperties) + { + List<(string, string)> skinOffsets = new List<(string, string)>(); + + string part_offset = "0"; + + foreach (string offsetName in OffsetNames) + { + var v = skinProperties.FirstOrDefault(prop => prop.property == "OFFSET" && prop.value.Equals(offsetName)).value; + if (v != default && v.Length >= 2) + part_offset = v.Split(' ')[2]; + + switch (offsetName) + { + case "HEAD": + skinOffsets.Add(("HEADWEAR", part_offset)); + break; + case "BODY": + skinOffsets.Add(("JACKET", part_offset)); + skinOffsets.Add(("BODYARMOR", part_offset)); + break; + case "CHEST": + skinOffsets.Add(("BELT", part_offset)); + skinOffsets.Add(("WAIST", part_offset)); + break; + case "ARM0": + skinOffsets.Add(("SLEEVE0", part_offset)); + skinOffsets.Add(("SHOULDER0", part_offset)); + break; + case "ARM1": + skinOffsets.Add(("SLEEVE1", part_offset)); + skinOffsets.Add(("SHOULDER1", part_offset)); + break; + case "LEG0": + skinOffsets.Add(("PANTS0", part_offset)); + skinOffsets.Add(("SOCK0", part_offset)); + break; + case "LEG1": + skinOffsets.Add(("PANTS1", part_offset)); + skinOffsets.Add(("SOCK1", part_offset)); + break; + } + + if (skinOffsets.Find(p => p.Item1 == offsetName) != default!) + skinOffsets.Add((offsetName, part_offset)); + } + + return skinOffsets; + } + + private static GeometryCube[] ConvertBoxes(string part, PckFile.FileData file, Vector3 pivot, List<(string, string)> offsets) + { + List cubes = new List(); + var anim = new SkinANIM(); + Debug.WriteLine(part); + float offset = float.Parse(offsets.Find(o => o.Item1 == part).Item2); + + foreach (var (name, value) in file.Properties) + { + string entry = value; + switch (name) + { + case "ANIM": + anim = SkinANIM.FromString(value); + break; + case "BOX": + SkinBOX box = SkinBOX.FromString(entry); + if (box.Parent == part) + { + float y = -1 * (box.Pos.Y + offset + box.Size.Y); + cubes.Add(new GeometryCube(new Vector3(pivot.X + box.Pos.X, pivot.Y + y, pivot.Z + box.Pos.Z), + box.Size, box.UV, box.Mirror, box.Inflation)); + } + break; + default: + break; + } + } + + bool slim = anim.GetFlag(ANIM_EFFECTS.SLIM_MODEL); + bool classic_res = !(slim && anim.GetFlag(ANIM_EFFECTS.RESOLUTION_64x64)); + + switch (part) + { + case "HEAD": + if (!anim.GetFlag(ANIM_EFFECTS.HEAD_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(-4, 24 - offset, -4), new Vector3(8, 8, 8), new Vector2(0, 0))); + break; + case "BODY": + if (!anim.GetFlag(ANIM_EFFECTS.BODY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(-4, 12 - offset, -2), new Vector3(8, 12, 4), new Vector2(16, 16))); + break; + case "ARM0": + if (!anim.GetFlag(ANIM_EFFECTS.RIGHT_ARM_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(slim ? -7 : -8, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), new Vector2(40, 16))); + break; + case "ARM1": + if (!anim.GetFlag(ANIM_EFFECTS.LEFT_ARM_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(4, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), classic_res ? new Vector2(40, 16) : new Vector2(32, 48), classic_res)); + break; + case "LEG0": + if (!anim.GetFlag(ANIM_EFFECTS.RIGHT_LEG_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(-3.9f, 0 - offset, -2), new Vector3(4, 12, 4), new Vector2(0, 16))); + break; + case "LEG1": + if (!anim.GetFlag(ANIM_EFFECTS.LEFT_LEG_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(0.1f, 0 - offset, -2), new Vector3(4, 12, 4), classic_res ? new Vector2(0, 16) : new Vector2(16, 48), classic_res)); + break; + case "HEADWEAR": + if (!anim.GetFlag(ANIM_EFFECTS.HEAD_OVERLAY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(-4, 24 - offset, -4), new Vector3(8, 8, 8), new Vector2(32, 0), false, 0.5f)); + break; + case "JACKET": + if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.BODY_OVERLAY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(0, 24 - offset, 0), new Vector3(8, 12, 4), new Vector2(16, 32), false, 0.25f)); + break; + case "SLEEVE0": + if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.RIGHT_ARM_OVERLAY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(slim ? -7 : -8, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), new Vector2(40, 32), false, 0.25f)); + break; + case "SLEEVE1": + if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.LEFT_ARM_OVERLAY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(4, 12 - offset, -2), new Vector3(slim ? 3 : 4, 12, 4), new Vector2(48, 48), false, 0.25f)); + break; + case "PANTS0": + if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.RIGHT_LEG_OVERLAY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(-3.9f, 0 - offset, -2), new Vector3(4, 12, 4), new Vector2(0, 32), false, 0.25f)); + break; + case "PANTS1": + if (!classic_res && !anim.GetFlag(ANIM_EFFECTS.LEFT_LEG_OVERLAY_DISABLED)) + cubes.Add(new GeometryCube(new Vector3(0.1f, 0 - offset, -2), new Vector3(4, 12, 4), new Vector2(0, 48), false, 0.25f)); + break; + default: + break; + } + + return cubes.ToArray(); + } + + public void ConvertSkinPack(PckFile skinPck) + { + SkinJSON skinJson = new SkinJSON(); // Skins.json + JObject geometryJson = new JObject(); // Geometry.json + + loc = AcquireLocFile(skinPck); + if (loc == null) + { + throw new ArgumentNullException("Loc file acquire fail."); + } + + string bedrockPackName = "DummySkinPack"; + var packNameTranslations = loc.GetLocEntries("IDS_DISPLAY_NAME"); + if (packNameTranslations.ContainsKey("en-EN")) + { + bedrockPackName = packNameTranslations["en-EN"].ToLower().Replace(":", string.Empty).Replace(' ', '_'); + } + foreach (var item in packNameTranslations) + { + string language = item.Key; + string value = item.Value; + string bedrockLanguage = language.Replace('-', '_'); + if (SupportedBedrockLanguages.Contains(bedrockLanguage)) + { + bedrockLanguageFiles[bedrockLanguage][$"skinpack.{bedrockPackName}"] = value; + } + } + + skinJson.Skins = ConvertSkins(skinPck, geometryJson, bedrockPackName); + skinJson.LocalizationName = skinJson.SerializeName = bedrockPackName; + + // msdocs says it's always "pack.name" ? + var skinManifest = CreateSkinPackManifest(skinJson.LocalizationName); + + string skinManifest_JSON = JsonConvert.SerializeObject(skinManifest, Formatting.Indented); + _exportContext.PutNextEntry("skin_pack\\manifest.json"); + byte[] skinManifestData = Encoding.UTF8.GetBytes(skinManifest_JSON); + _exportContext.Write(skinManifestData, 0, skinManifestData.Length); + + string SKINS_JSON = JsonConvert.SerializeObject(skinJson, Formatting.Indented); + _exportContext.PutNextEntry("skin_pack\\skins.json"); + byte[] skinsJsonData = Encoding.UTF8.GetBytes(SKINS_JSON); + _exportContext.Write(skinsJsonData, 0, skinsJsonData.Length); + + string GEO_JSON = JsonConvert.SerializeObject(geometryJson, Formatting.Indented); + _exportContext.PutNextEntry("skin_pack\\geometry.json"); + byte[] geometryJsonData = Encoding.UTF8.GetBytes(GEO_JSON); + _exportContext.Write(geometryJsonData, 0, geometryJsonData.Length); + } + + + public void ConvertCape(PckFile.FileData file, SkinObject skin = default!) + { + if (file.Filetype != PckFile.FileData.FileType.CapeFile) + return; + string capeId = file.Filename.Replace(".png", string.Empty).Replace("Skins/", string.Empty); + _exportContext.PutNextEntry($"{capeId}.png"); + _exportContext.Write(file.Data, 0, file.Size); + } + + private SkinObject ConvertSkin(PckFile.FileData file, string packName, JObject geometryJson) + { + if (file.Filetype != PckFile.FileData.FileType.SkinFile) + return default!; + + SkinObject skinObj = new SkinObject(); + skinObj.LocalizationName = Path.GetFileNameWithoutExtension(file.Filename); + skinObj.TextureName = Path.GetFileName(file.Filename); + skinObj.GeometryName = $"geometry.{packName}.{skinObj.LocalizationName}"; + + Vector3 head_and_body_pivot = new Vector3(0, 24, 0); + Vector3 right_arm_pivot = new Vector3(-5, 22, 0); + Vector3 left_arm_pivot = new Vector3(5, 22, 0); + Vector3 right_leg_pivot = new Vector3(-1.9f, 12, 0); + Vector3 left_leg_pivot = new Vector3(1.9f, 12, 0); + + var offsets = GetSkinOffsets(file.Properties); + + var geometry = new Geometry( + new GeometryBone("head", null, head_and_body_pivot, ConvertBoxes("HEAD", file, head_and_body_pivot, offsets), metaBone: "base"), + new GeometryBone("body", null, head_and_body_pivot, ConvertBoxes("BODY", file, head_and_body_pivot, offsets), metaBone: "base"), + + new GeometryBone("rightArm", null, right_arm_pivot, ConvertBoxes("ARM0", file, right_arm_pivot, offsets), metaBone: "base"), + new GeometryBone("leftArm", null, left_arm_pivot, ConvertBoxes("ARM1", file, left_arm_pivot, offsets), metaBone: "base"), + new GeometryBone("rightLeg", null, right_leg_pivot, ConvertBoxes("LEG0", file, right_leg_pivot, offsets), metaBone: "base"), + new GeometryBone("leftLeg", null, left_leg_pivot, ConvertBoxes("LEG1", file, left_leg_pivot, offsets), metaBone: "base"), + + new GeometryBone("hat", "head", head_and_body_pivot, ConvertBoxes("HEADWEAR", file, head_and_body_pivot, offsets), metaBone: "clothing"), + new GeometryBone("jacket", "body", head_and_body_pivot, ConvertBoxes("JACKET", file, head_and_body_pivot, offsets), metaBone: "clothing"), + + new GeometryBone("rightSleeve", "rightArm", right_arm_pivot, ConvertBoxes("SLEEVE0", file, right_arm_pivot, offsets), metaBone: "clothing"), + new GeometryBone("leftSleeve", "leftArm", left_arm_pivot, ConvertBoxes("SLEEVE1", file, left_arm_pivot, offsets), metaBone: "clothing"), + new GeometryBone("rightPants", "rightLeg", right_leg_pivot, ConvertBoxes("PANTS0", file, right_leg_pivot, offsets), metaBone: "clothing"), + new GeometryBone("leftPants", "leftLeg", left_leg_pivot, ConvertBoxes("PANTS1", file, left_leg_pivot, offsets), metaBone: "clothing"), + new GeometryBone("rightSock", "rightLeg", right_leg_pivot, ConvertBoxes("SOCK0", file, right_leg_pivot, offsets), metaBone: "clothing"), + new GeometryBone("leftSock", "leftLeg", left_leg_pivot, ConvertBoxes("SOCK1", file, left_leg_pivot, offsets), metaBone: "clothing"), + + new GeometryBone("helmet", "head", head_and_body_pivot, ConvertBoxes("HELMET", file, head_and_body_pivot, offsets), "armor"), + new GeometryBone("bodyArmor", "body", head_and_body_pivot, ConvertBoxes("BODYARMOR", file, head_and_body_pivot, offsets), "armor"), + + new GeometryBone("belt", "body", head_and_body_pivot, ConvertBoxes("BELT", file, head_and_body_pivot, offsets), metaBone: "armor"), + new GeometryBone("rightArmArmor", "rightArm", right_arm_pivot, ConvertBoxes("ARMARMOR0", file, right_arm_pivot, offsets), metaBone: "armor"), + new GeometryBone("leftArmArmor", "leftArm", left_arm_pivot, ConvertBoxes("ARMARMOR1", file, left_arm_pivot, offsets), metaBone: "armor"), + new GeometryBone("rightLegArmor", "rightLeg", right_leg_pivot, ConvertBoxes("LEGGING0", file, right_leg_pivot, offsets), metaBone: "armor"), + new GeometryBone("leftLegArmor", "leftLeg", left_leg_pivot, ConvertBoxes("LEGGING1", file, left_leg_pivot, offsets), metaBone: "armor"), + new GeometryBone("rightBoot", "rightLeg", right_leg_pivot, ConvertBoxes("BOOT0", file, right_leg_pivot, offsets), metaBone: "armor"), + new GeometryBone("leftBoot", "leftLeg", left_leg_pivot, ConvertBoxes("BOOT1", file, left_leg_pivot, offsets), metaBone: "armor"), + + // calculates armor and item offsets + new GeometryBone("rightItem", "rightArm", new Vector3(-6.0f, 15.0f - float.Parse(offsets.Find(o => o.Item1 == "TOOL0").Item2), 1.0f), null, "item"), + new GeometryBone("leftItem", "leftArm", new Vector3(6.0f, 15.0f - float.Parse(offsets.Find(o => o.Item1 == "TOOL1").Item2), 1.0f), null, "item"), + + new GeometryBone("helmetArmorOffset", "head", new Vector3(0f, 24f - float.Parse(offsets.Find(o => o.Item1 == "HEAD").Item2), 0f), null, "armor_offset"), + new GeometryBone("bodyArmorOffset", "body", new Vector3(-4f, 12f - float.Parse(offsets.Find(o => o.Item1 == "BODY").Item2), -2f), null, "armor_offset"), + new GeometryBone("rightArmArmorOffset", "rightArm", new Vector3(4f, 12f - float.Parse(offsets.Find(o => o.Item1 == "ARM0").Item2), -2f), null, "armor_offset"), + new GeometryBone("leftArmArmorOffset", "leftArm", new Vector3(-8f, 12f - float.Parse(offsets.Find(o => o.Item1 == "ARM1").Item2), -2f), null, "armor_offset"), + new GeometryBone("rightLegArmorOffset", "rightLeg", new Vector3(-0.1f, float.Parse(offsets.Find(o => o.Item1 == "LEG0").Item2), -2f), null, "armor_offset"), + new GeometryBone("leftLegArmorOffset", "leftLeg", new Vector3(-4.1f, float.Parse(offsets.Find(o => o.Item1 == "LEG1").Item2), -2f), null, "armor_offset"), + new GeometryBone("rightBootArmorOffset", "rightLeg", new Vector3(2f, 12f - float.Parse(offsets.Find(o => o.Item1 == "BOOT0").Item2), 0f), null, "armor_offset"), + new GeometryBone("leftBootArmorOffset", "leftLeg", new Vector3(-2f, 12f - float.Parse(offsets.Find(o => o.Item1 == "BOOT1").Item2), 0f), null, "armor_offset") + ); + + string capepath = file.Properties.Find(o => o.property == "CAPEPATH").value; + + JToken geo = JToken.FromObject(geometry); + if (capepath != null) + geo["cape"] = capepath; + + geometryJson.Add(skinObj.GeometryName, geo); + _exportContext.PutNextEntry(skinObj.TextureName); + _exportContext.Write(file.Data, 0, file.Size); + return skinObj; + } + + private LOCFile AcquireLocFile(PckFile sourcePck) + { + if (sourcePck.TryGetFile("localisation.loc", PckFile.FileData.FileType.LocalisationFile, out PckFile.FileData locFileData) || + sourcePck.TryGetFile("languages.loc", PckFile.FileData.FileType.LocalisationFile, out locFileData)) + { + var reader = new LOCFileReader(); + LOCFile locFile; + using (var ms = new MemoryStream(locFileData.Data)) + { + locFile = reader.FromStream(ms); + } + return locFile; + } + return null; + } + + private SkinObject[] ConvertSkins(PckFile sourcePck, JObject geometryJson, string packName) + { + List skins = new List(); + foreach (PckFile.FileData file in sourcePck.Files) + { + switch (file.Filetype) + { + case PckFile.FileData.FileType.SkinFile: + var skin = ConvertSkin(file, packName, geometryJson); + if (skin != null) + skins.Add(skin); + break; + case PckFile.FileData.FileType.CapeFile: + ConvertCape(file); + break; + case PckFile.FileData.FileType.SkinDataFile: + var reader = new PckFileReader(); + using (var ms = new MemoryStream(file.Data)) + { + PckFile subPack = reader.FromStream(ms); + skins.AddRange(ConvertSkins(subPack, geometryJson, packName)); + } + break; + } + } + return skins.ToArray(); + } + + [Obsolete] + void ExportLOC(LOCFile locFile, string packName) + { + List supportedLanguages = new List(); + + foreach (var language in locFile.Languages) + { + // string bedrockLang = languageMap[language]; + string bedrockLang = language.Replace('-', '_'); + if (!SupportedBedrockLanguages.Contains(bedrockLang)) + continue; + + supportedLanguages.Add(bedrockLang); + _exportContext.PutNextEntry($"texts/{bedrockLang}.lang"); + //var languageKeyToValue = locFile.GetLanguage(language); + //var wrapper = new MemoryStream(); + //using (var stringWriter = new StreamWriter(wrapper, Encoding.UTF8, 1024, leaveOpen: true)) + //{ + // foreach (var kvp in languageKeyToValue) + // { + // if (kvp.Key.EndsWith("_THEMENAME")) + // continue; + // string keyName = string.Empty; + // if (kvp.Key.Equals("IDS_DISPLAY_NAME")) + // { + // keyName = $"skinpack.{packName}"; + // } + + // if (kvp.Key.StartsWith("IDS_DLCSKIN")) + // { + // keyName = $"skin.{packName}.{kvp.Key}"; + // } + + // stringWriter.WriteLine($"{keyName}={kvp.Value}"); + // } + //} + //byte[] data = wrapper.ToArray(); + //_exportContext.Write(data, 0, data.Length); + } + string serializedLanguages = JsonConvert.SerializeObject(supportedLanguages.ToArray(), Formatting.Indented); + var serializedLanguageData = Encoding.UTF8.GetBytes(serializedLanguages); + + _exportContext.PutNextEntry("languages.json"); + _exportContext.Write(serializedLanguageData, 0, serializedLanguageData.Length); + } + + Manifest CreateSkinPackManifest(string name) + { + return new Manifest + { + Header = new ManifestHeader() + { + Name = name, + Description = "Exported with " + Application.ProductName, + }, + Modules = new ManifestModule[1] + { + new ManifestModule("skin_pack") + } + }; + } + } +} diff --git a/PCK-Studio/Classes/Conversion/Bedrock/FileExportContext.cs b/PCK-Studio/Classes/Conversion/Bedrock/FileExportContext.cs new file mode 100644 index 00000000..ca82e6bf --- /dev/null +++ b/PCK-Studio/Classes/Conversion/Bedrock/FileExportContext.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PckStudio.Conversion.Bedrock +{ + internal class FileExportContext : IExportContext + { + Stream _currentEntry; + string _filepath; + + public FileExportContext(string filepath) + { + if (filepath == null) + throw new ArgumentNullException(nameof(filepath)); + + if (!Directory.Exists(filepath)) + throw new DirectoryNotFoundException("Directory does not exist!"); + + _filepath = filepath; + } + + public void Dispose() + { + if (_currentEntry != null) + { + _currentEntry.Flush(); + _currentEntry.Close(); + } + } + + public void PutNextEntry(string name) + { + if (_currentEntry != null) + { + _currentEntry.Flush(); + _currentEntry.Close(); + } + string path = Path.Combine(_filepath, name); + Directory.CreateDirectory(Path.GetDirectoryName(path)); // to avoid subdirectory exceptions + _currentEntry = File.OpenWrite(path); + } + + public void Write(byte[] buffer, int offset, int count) + { + if (_currentEntry == null) + throw new ArgumentNullException(nameof(_currentEntry)); + _currentEntry.Write(buffer, offset, count); + } + } +} diff --git a/PCK-Studio/Classes/Conversion/Bedrock/IExportContext.cs b/PCK-Studio/Classes/Conversion/Bedrock/IExportContext.cs new file mode 100644 index 00000000..fc092c15 --- /dev/null +++ b/PCK-Studio/Classes/Conversion/Bedrock/IExportContext.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PckStudio.Conversion.Bedrock +{ + internal interface IExportContext : IDisposable + { + void PutNextEntry(string name); + void Write(byte[] buffer, int offset, int count); + } +} diff --git a/PCK-Studio/Classes/Conversion/Bedrock/InMemoryExportContext.cs b/PCK-Studio/Classes/Conversion/Bedrock/InMemoryExportContext.cs new file mode 100644 index 00000000..047ad604 --- /dev/null +++ b/PCK-Studio/Classes/Conversion/Bedrock/InMemoryExportContext.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PckStudio.Conversion.Bedrock +{ + internal class InMemoryExportContext : IExportContext + { + private string _currentEntry; + private Dictionary _buffer; + + public void Dispose() + { + _buffer.Clear(); + } + + public void PutNextEntry(string name) + { + _currentEntry = name; + } + + public void Write(byte[] buffer, int offset, int count) + { + _buffer[_currentEntry] = buffer.Skip(offset).Take(count).ToArray(); + } + } +} diff --git a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/Geometry.cs b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/Geometry.cs index eef34e51..327e692d 100644 --- a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/Geometry.cs +++ b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/Geometry.cs @@ -15,18 +15,38 @@ * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ +using System.Numerics; using Newtonsoft.Json; -namespace PckStudio.Classes.Conversion.Bedrock.JsonDefinitions +namespace PckStudio.Conversion.Bedrock.JsonDefinitions { internal class Geometry { - public Geometry((int Width, int Height, int[] Offset) visibleBounds, params GeometryBone[] bones) + public struct VisibleBounds + { + public VisibleBounds(int width, int height, int[] offset) + { + Width = width; + Height = height; + Offset = new Vector(offset,offset.Length-3); + } + + public readonly int Width; + public readonly int Height; + public readonly Vector Offset; + } + + public Geometry(params GeometryBone[] bones) + : this(new VisibleBounds(width: 1, height: 2, offset: new int[3] { 0, 1, 0 }), bones) + { + } + + public Geometry(VisibleBounds visibleBounds, params GeometryBone[] bones) { Bones = bones; VisibleBoundsWidth = visibleBounds.Width; VisibleBoundsHeight = visibleBounds.Height; - VisibleBoundsOffset = visibleBounds.Offset; + visibleBounds.Offset.CopyTo(VisibleBoundsOffset); } [JsonProperty("visible_bounds_width")] @@ -41,4 +61,4 @@ namespace PckStudio.Classes.Conversion.Bedrock.JsonDefinitions [JsonProperty("bones")] public GeometryBone[] Bones { get; set; } } -} +} \ No newline at end of file diff --git a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryBone.cs b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryBone.cs index 12458252..e347d930 100644 --- a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryBone.cs +++ b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryBone.cs @@ -18,7 +18,7 @@ using System.Numerics; using Newtonsoft.Json; -namespace PckStudio.Classes.Conversion.Bedrock.JsonDefinitions +namespace PckStudio.Conversion.Bedrock.JsonDefinitions { internal class GeometryBone { @@ -44,6 +44,6 @@ namespace PckStudio.Classes.Conversion.Bedrock.JsonDefinitions public float[] Pivot = { 0, 0, 0 }; [JsonProperty(PropertyName = "cubes", NullValueHandling = NullValueHandling.Ignore)] - public GeometryCube[] Cubes = null; + public GeometryCube[] Cubes { get; set; } } } diff --git a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryCube.cs b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryCube.cs index 206be188..1d31a906 100644 --- a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryCube.cs +++ b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/GeometryCube.cs @@ -18,7 +18,7 @@ using System.Numerics; using Newtonsoft.Json; -namespace PckStudio.Classes.Conversion.Bedrock.JsonDefinitions +namespace PckStudio.Conversion.Bedrock.JsonDefinitions { internal class GeometryCube { diff --git a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/ManifestHeader.cs b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/ManifestHeader.cs index 83edf357..6466eaf8 100644 --- a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/ManifestHeader.cs +++ b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/ManifestHeader.cs @@ -33,7 +33,7 @@ namespace PckStudio.Conversion.Bedrock.Json } [JsonProperty("version")] - public int[] Version = { 1, 0, 0 }; + public int[] Version { get; set; } [JsonProperty("name")] public string Name { get; set; } = string.Empty; diff --git a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/SkinObject.cs b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/SkinObject.cs index 9959243a..25b24461 100644 --- a/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/SkinObject.cs +++ b/PCK-Studio/Classes/Conversion/Bedrock/JsonDefinitions/SkinObject.cs @@ -22,13 +22,13 @@ namespace PckStudio.Conversion.Bedrock.Json internal class SkinObject { [JsonProperty("localization_name")] - public string LocalizationName = "dlcskin00000000"; + public string LocalizationName { get; set; } [JsonProperty("geometry")] public string GeometryName = "geometry.humanoid.custom"; [JsonProperty("texture")] - public string TextureName = "dlcskin00000000.png"; + public string TextureName { get; set; } [JsonProperty("type")] public string Type = "free"; diff --git a/PCK-Studio/Classes/Conversion/Bedrock/ZipExportContext.cs b/PCK-Studio/Classes/Conversion/Bedrock/ZipExportContext.cs new file mode 100644 index 00000000..7577661c --- /dev/null +++ b/PCK-Studio/Classes/Conversion/Bedrock/ZipExportContext.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Text; +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Zip; +using System.IO; + +namespace PckStudio.Conversion.Bedrock +{ + internal class ZipExportContext : IExportContext + { + private ZipOutputStream _stream; + + public ZipExportContext(string filename, int level = Deflater.NO_COMPRESSION) + { + var fs = File.OpenWrite(filename); + _stream = new ZipOutputStream(fs); + _stream.SetLevel(level); + } + + public void Dispose() + { + _stream?.Close(); + _stream = null; + } + + public void PutNextEntry(string name) + { + _stream.PutNextEntry(new ZipEntry(name)); + } + + public void Write(byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + } + } +} diff --git a/PCK-Studio/MainForm.cs b/PCK-Studio/MainForm.cs index 4165a2bc..c32c86f4 100644 --- a/PCK-Studio/MainForm.cs +++ b/PCK-Studio/MainForm.cs @@ -27,7 +27,7 @@ using PckStudio.Forms.Additional_Popups.Animation; using PckStudio.Forms.Additional_Popups; using PckStudio.Classes.Misc; using PckStudio.Classes.IO.PCK; -using PckStudio.Classes.Bedrock; +using PckStudio.Conversion.Bedrock; namespace PckStudio { @@ -1700,12 +1700,15 @@ namespace PckStudio { SaveFileDialog sfd = new SaveFileDialog { - Filter = "MCPack File|*.mcpack" + Filter = "Minecraft Pack File|*.mcpack" }; if (sfd.ShowDialog() == DialogResult.OK) { - BedrockConverter lb = new BedrockConverter(); - lb.ConvertSkinPack(currentPCK, sfd.FileName); + using (var exportContext = new ZipExportContext(sfd.FileName)) + { + BedrockSkinExporter converter = new BedrockSkinExporter(exportContext); + converter.ConvertSkinPack(currentPCK); + } } } diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj index 193aa787..836eb087 100644 --- a/PCK-Studio/PckStudio.csproj +++ b/PCK-Studio/PckStudio.csproj @@ -170,7 +170,10 @@ - + + + + @@ -179,6 +182,7 @@ +