mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/PCK-Studio.git
synced 2026-06-26 20:46:00 +00:00
Renamed BedrockConverter to BedrockSkinExporter, Added ExportContext and Update Json Definitions
This commit is contained in:
@@ -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<string, string> languageMap = new Dictionary<string, string>()
|
||||
{
|
||||
{"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<string> OffsetNames = new List<string>
|
||||
{
|
||||
"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<string, string> ImgCopyLookup = new Dictionary<string, string>()
|
||||
{
|
||||
{ "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<SkinObject> ConvertSkins(PckFile sourcePck, string exportPath)
|
||||
{
|
||||
List<SkinObject> skins = new List<SkinObject>();
|
||||
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<string> languages = new List<string>();
|
||||
|
||||
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<string, string> 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<ValueTuple<string, string>>();
|
||||
|
||||
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<GeometryCube> cubes = new List<GeometryCube>();
|
||||
|
||||
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")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
466
PCK-Studio/Classes/Conversion/Bedrock/BedrockSkinExporter.cs
Normal file
466
PCK-Studio/Classes/Conversion/Bedrock/BedrockSkinExporter.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="https://learn.microsoft.com/en-us/minecraft/creator/documents/packagingaskinpack#languagesjson"/>
|
||||
/// </summary>
|
||||
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<string> OffsetNames = new List<string>
|
||||
{
|
||||
"HEAD", "HELMET",
|
||||
"BODY", "CHEST", "BELT",
|
||||
"ARM0", "ARMARMOR0", "SHOULDER0", "TOOL0",
|
||||
"ARM1", "ARMARMOR1", "SHOULDER1", "TOOL1",
|
||||
"LEG0", "LEGGING0", "BOOT0",
|
||||
"LEG1", "LEGGING1", "BOOT1"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// (Bedrock)Languge to key value dictionary
|
||||
/// </summary>
|
||||
private Dictionary<string, SortedDictionary<string, string>> bedrockLanguageFiles = new Dictionary<string, SortedDictionary<string, string>>(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<GeometryCube> cubes = new List<GeometryCube>();
|
||||
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<SkinObject> skins = new List<SkinObject>();
|
||||
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<string> supportedLanguages = new List<string>();
|
||||
|
||||
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")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
54
PCK-Studio/Classes/Conversion/Bedrock/FileExportContext.cs
Normal file
54
PCK-Studio/Classes/Conversion/Bedrock/FileExportContext.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
PCK-Studio/Classes/Conversion/Bedrock/IExportContext.cs
Normal file
14
PCK-Studio/Classes/Conversion/Bedrock/IExportContext.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<string, byte[]> _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<int>(offset,offset.Length-3);
|
||||
}
|
||||
|
||||
public readonly int Width;
|
||||
public readonly int Height;
|
||||
public readonly Vector<int> 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
using System.Numerics;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PckStudio.Classes.Conversion.Bedrock.JsonDefinitions
|
||||
namespace PckStudio.Conversion.Bedrock.JsonDefinitions
|
||||
{
|
||||
internal class GeometryCube
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
37
PCK-Studio/Classes/Conversion/Bedrock/ZipExportContext.cs
Normal file
37
PCK-Studio/Classes/Conversion/Bedrock/ZipExportContext.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,10 @@
|
||||
<Compile Include="Classes\API\PCKCenter\PCKCollections.cs" />
|
||||
<Compile Include="Classes\API\PCKCenter\PCKCollectionsLocal.cs" />
|
||||
<Compile Include="Classes\API\PCKCenter\SaveLocalJSON.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\BedrockConverter.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\BedrockSkinExporter.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\FileExportContext.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\IExportContext.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\InMemoryExportContext.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\JsonDefinitions\Geometry.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\JsonDefinitions\GeometryBone.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\JsonDefinitions\GeometryCube.cs" />
|
||||
@@ -179,6 +182,7 @@
|
||||
<Compile Include="Classes\Conversion\Bedrock\JsonDefinitions\Manifest.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\JsonDefinitions\ManifestHeader.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\JsonDefinitions\SkinObject.cs" />
|
||||
<Compile Include="Classes\Conversion\Bedrock\ZipExportContext.cs" />
|
||||
<Compile Include="Classes\Extentions\EnumerableExtentions.cs" />
|
||||
<Compile Include="Classes\FileTypes\CSMBFile.cs" />
|
||||
<Compile Include="Classes\FileTypes\PCKAudioFile.cs" />
|
||||
|
||||
Reference in New Issue
Block a user