diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index 9e4a32024..ee9ee299f 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -1013,6 +1013,8 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) case ACTION_MENU_LEFT_SCROLL: kbDown = KMInput.IsKeyDown('Q'); kbPressed = KMInput.IsKeyPressed('Q'); kbReleased = KMInput.IsKeyReleased('Q'); break; case ACTION_MENU_RIGHT_SCROLL: kbDown = KMInput.IsKeyDown('E'); kbPressed = KMInput.IsKeyPressed('E'); kbReleased = KMInput.IsKeyReleased('E'); break; case ACTION_MENU_QUICK_MOVE: kbDown = KMInput.IsKeyDown(VK_SHIFT); kbPressed = KMInput.IsKeyPressed(VK_SHIFT); kbReleased = KMInput.IsKeyReleased(VK_SHIFT); break; + case ACTION_MENU_X: kbDown = KMInput.IsKeyDown('X'); kbPressed = KMInput.IsKeyPressed('X'); kbReleased = KMInput.IsKeyReleased('X'); break; + case ACTION_MENU_Y: kbDown = KMInput.IsKeyDown('Y'); kbPressed = KMInput.IsKeyPressed('Y'); kbReleased = KMInput.IsKeyReleased('Y'); break; } pressed = pressed || kbPressed; released = released || kbReleased; diff --git a/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp b/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp index d4f26ae71..db852294a 100644 --- a/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp @@ -7,6 +7,8 @@ #elif defined __PSVITA__ #include #endif +#include +#include // Necesario para GetOpenFileNameW #define SKIN_SELECT_PACK_DEFAULT 0 #define SKIN_SELECT_PACK_FAVORITES 1 @@ -53,6 +55,7 @@ UIScene_SkinSelectMenu::UIScene_SkinSelectMenu(int iPad, void *initData, UILayer m_bSlidingSkins = false; m_bAnimatingMove = false; m_bSkinIndexChanged = false; + m_bUsingCustomSkin = (!GET_IS_DLC_SKIN_FROM_BITMASK(m_originalSkinId) && GET_UGC_SKIN_ID_FROM_BITMASK(m_originalSkinId) != 0); m_currentNavigation = eSkinNavigation_Skin; @@ -225,6 +228,14 @@ void UIScene_SkinSelectMenu::handleInput(int iPad, int key, bool repeat, bool pr InputActionOK(iPad); } break; + case ACTION_MENU_X: + if (pressed && !repeat) + { + LoadExternalSkin(); + handled = true; + return; + } + break; case ACTION_MENU_UP: case ACTION_MENU_DOWN: if(pressed) @@ -265,6 +276,7 @@ void UIScene_SkinSelectMenu::handleInput(int iPad, int key, bool repeat, bool pr m_skinIndex = getPreviousSkinIndex(m_skinIndex); //handleSkinIndexChanged(); + m_bUsingCustomSkin = false; m_bSlidingSkins = true; m_bAnimatingMove = true; @@ -300,6 +312,7 @@ void UIScene_SkinSelectMenu::handleInput(int iPad, int key, bool repeat, bool pr m_skinIndex = getNextSkinIndex(m_skinIndex); //handleSkinIndexChanged(); + m_bUsingCustomSkin = false; m_bSlidingSkins = true; m_bAnimatingMove = true; @@ -402,6 +415,15 @@ void UIScene_SkinSelectMenu::InputActionOK(unsigned int iPad) { ui.AnimateKeyPress(iPad, ACTION_MENU_OK, false, true, false); + if (m_bUsingCustomSkin) + { + ui.PlayUISFX(eSFX_Press); + m_currentSkinPath = app.GetPlayerSkinName(iPad); + m_originalSkinId = app.GetPlayerSkinId(iPad); + setCharacterSelected(true); + return; + } + // if the profile data has been changed, then force a profile write // It seems we're allowed to break the 5 minute rule if it's the result of a user action switch(m_packIndex) @@ -646,7 +668,19 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged() m_controlSkinNamePlate.setVisible( false ); - if( m_currentPack != NULL ) + if (m_bUsingCustomSkin) + { + m_selectedSkinPath = m_currentSkinPath; + m_selectedCapePath = L""; + m_vAdditionalSkinBoxes = NULL; + skinName = L"Custom Skin"; + skinOrigin = L"External PNG"; + setCharacterSelected(true); + setCharacterLocked(false); + m_characters[eCharacter_Current].setVisible(true); + m_controlSkinNamePlate.setVisible(true); + } + else if( m_currentPack != NULL ) { skinFile = m_currentPack->getSkinFile(m_skinIndex); m_selectedSkinPath = skinFile->getPath(); @@ -752,6 +786,16 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged() m_labelSkinName.setLabel(skinName); m_labelSkinOrigin.setLabel(skinOrigin); + if (m_packIndex == SKIN_SELECT_PACK_DEFAULT && m_bUsingCustomSkin) + { + m_labelSkinOrigin.setLabel(L"X: Custom Skin"); // Muestra la ayuda visual + } + + if (m_selectedSkinPath.compare(m_currentSkinPath) == 0) + { + setCharacterSelected(true); // Activa el icono de seleccionado + } + if(m_vAdditionalSkinBoxes && m_vAdditionalSkinBoxes->size()!=0) { @@ -1734,4 +1778,92 @@ int UIScene_SkinSelectMenu::PSNSignInReturned(void* pParam, bool bContinue, int } return 0; } -#endif // __PSVITA__ \ No newline at end of file +#endif // __PSVITA__ + +void UIScene_SkinSelectMenu::LoadExternalSkin() +{ + OPENFILENAMEW ofn; + wchar_t szFile[MAX_PATH]; + + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = NULL; + ofn.lpstrFile = szFile; + ofn.lpstrFile[0] = L'\0'; + ofn.nMaxFile = MAX_PATH; + ofn.lpstrFilter = L"PNG Files\0*.png\0All Files\0*.*\0"; + ofn.nFilterIndex = 1; + + // IMPORTANTE: OFN_NOCHANGEDIR evita que el Explorador cambie la carpeta de trabajo. + // Si la carpeta cambia, el juego NO podrá guardar mundos (Assertion failed en STO_SaveGame.cpp). + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + + if (GetOpenFileNameW(&ofn) == TRUE) + { + std::ifstream file(ofn.lpstrFile, std::ios::binary | std::ios::ate); + if (file.is_open()) + { + std::streamsize size = file.tellg(); + if (size <= 0 || size > 2 * 1024 * 1024) { // Limite 2MB + file.close(); + ui.PlayUISFX(eSFX_CraftFail); + return; + } + file.seekg(0, std::ios::beg); + + // MEMORIA: El motor AddMemoryTextureFile NO copia los datos, usa el puntero directamente. + // Si usas un vector o buffer local, la skin se corromperá al salir de la función. + PBYTE persistentBuffer = new (std::nothrow) BYTE[(size_t)size]; + if (!persistentBuffer || !file.read((char*)persistentBuffer, size)) { + if(persistentBuffer) delete[] persistentBuffer; + file.close(); + return; + } + + // Validar Cabecera PNG y Dimensiones (64x32 o 64x64) + if (size > 24 && (unsigned char)persistentBuffer[0] == 0x89 && persistentBuffer[1] == 'P') + { + unsigned int w = (unsigned char)persistentBuffer[16] << 24 | (unsigned char)persistentBuffer[17] << 16 | (unsigned char)persistentBuffer[18] << 8 | (unsigned char)persistentBuffer[19]; + unsigned int h = (unsigned char)persistentBuffer[20] << 24 | (unsigned char)persistentBuffer[21] << 16 | (unsigned char)persistentBuffer[22] << 8 | (unsigned char)persistentBuffer[23]; + + if ((w == 64 && h == 32) || (w == 64 && h == 64)) + { + // CACHE BUSTING: Si usas el mismo nombre "custom.png", el cache del motor no se refresca. + // Usamos un contador para generar IDs únicos (ugcskin00000101.png, 102, etc). + static int s_ugcSkinLoadCount = 0; + s_ugcSkinLoadCount++; + + wchar_t textureNameBuf[256]; + // Formato ugcskinXXXXXXXX.png es obligatorio para que getSkinIdFromPath genere un ID válido. + // Ajustamos el ID para que los bits bajos (0-4) sean 0, respetando el bitmask de UGC. + swprintf(textureNameBuf, 256, L"ugcskin%08X.png", 0x100 + (s_ugcSkinLoadCount << 5)); + wstring textureName = textureNameBuf; + + // Limpieza opcional del buffer anterior + if (m_bUsingCustomSkin && !m_selectedSkinPath.empty()) { + app.RemoveMemoryTextureFile(m_selectedSkinPath); + } + + // 1. Cargar datos en el sistema de archivos en memoria + app.AddMemoryTextureFile(textureName, persistentBuffer, (DWORD)size); + + // 2. Aplicar la skin al jugador local + app.SetPlayerSkin(m_iPad, textureName); + + // 3. Forzar refresco de la UI + m_bUsingCustomSkin = true; + m_selectedSkinPath = textureName; + m_currentSkinPath = textureName; + m_selectedCapePath = L""; + handleSkinIndexChanged(); + + file.close(); + return; + } + } + delete[] persistentBuffer; // Borrar si falló la validación + ui.PlayUISFX(eSFX_CraftFail); + file.close(); + } + } +} \ No newline at end of file diff --git a/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.h b/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.h index e8d760967..1db9aa265 100644 --- a/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.h @@ -136,9 +136,12 @@ protected: // TODO: This should be pure virtual in this class virtual wstring getMoviePath(); + bool m_bUsingCustomSkin; // Flag para saber si hay una skin externa activa + public: // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); + void LoadExternalSkin(); // Función principal del explorador virtual void customDraw(IggyCustomDrawCallbackRegion *region);