From 3f55d59e6d24f48a915fd73f834477aa44da83e4 Mon Sep 17 00:00:00 2001 From: Loki Rautio Date: Thu, 5 Mar 2026 08:52:25 -0600 Subject: [PATCH] Custom skins feature (from: https://github.com/LCEMP/LCEMP/pull/22) --- .gitignore | 3 + Minecraft.Client/Common/Consoles_App.cpp | 104 ++++++++++++++++-- Minecraft.Client/Common/Consoles_App.h | 2 + .../Common/UI/UIScene_SkinSelectMenu.cpp | 81 +++++++++++++- .../Common/XUI/XUI_SkinSelect.cpp | 87 +++++++++++++-- x64/Debug/CustomSkins/Doge.png | Bin 0 -> 1636 bytes x64/Debug/CustomSkins/Herobrine.png | Bin 0 -> 1673 bytes x64/Debug/CustomSkins/README.txt | 7 ++ 8 files changed, 265 insertions(+), 19 deletions(-) create mode 100644 x64/Debug/CustomSkins/Doge.png create mode 100644 x64/Debug/CustomSkins/Herobrine.png create mode 100644 x64/Debug/CustomSkins/README.txt diff --git a/.gitignore b/.gitignore index 9caff84a1..6eb76cef7 100644 --- a/.gitignore +++ b/.gitignore @@ -431,5 +431,8 @@ build/* !x64/**/mss64.dll !x64/**/redist64/ +# Custom data +!x64/**/CustomSkins/* + # Local saves Minecraft.Client/Saves/ diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index b476ca909..325ba4f81 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -158,6 +158,17 @@ CMinecraftApp::CMinecraftApp() // m_bRead_TMS_XUIDS_XML=false; // m_bRead_TMS_DLCINFO_XML=false; + InitializeCriticalSection(&csDLCDownloadQueue); + InitializeCriticalSection(&csTMSPPDownloadQueue); + InitializeCriticalSection(&csAdditionalModelParts); + InitializeCriticalSection(&csAdditionalSkinBoxes); + InitializeCriticalSection(&csAnimOverrideBitmask); + InitializeCriticalSection(&csMemFilesLock); + InitializeCriticalSection(&csMemTPDLock); + + ScanAndLoadCustomSkins(); + ZeroMemory(&m_InviteData, sizeof(JoinFromInviteData)); + m_pDLCFileBuffer=NULL; m_dwDLCFileSize=0; m_pBannedListFileBuffer=NULL; @@ -188,15 +199,9 @@ CMinecraftApp::CMinecraftApp() m_iDLCOfferC=0; m_bAllDLCContentRetrieved=true; - InitializeCriticalSection(&csDLCDownloadQueue); + m_bAllTMSContentRetrieved=true; m_bTickTMSDLCFiles=true; - InitializeCriticalSection(&csTMSPPDownloadQueue); - InitializeCriticalSection(&csAdditionalModelParts); - InitializeCriticalSection(&csAdditionalSkinBoxes); - InitializeCriticalSection(&csAnimOverrideBitmask); - InitializeCriticalSection(&csMemFilesLock); - InitializeCriticalSection(&csMemTPDLock); InitializeCriticalSection(&m_saveNotificationCriticalSection); m_saveNotificationDepth = 0; @@ -5577,6 +5582,67 @@ void CMinecraftApp::HandleDLC(DLCPack *pack) if( dwFilesProcessed == 0 ) m_dlcManager.removePack(pack); } +void CMinecraftApp::ScanAndLoadCustomSkins() // By 0xlibless ;) +{ + DebugPrintf("CustomSkins scanning inited\n"); + + // Try multiple relative paths just in case (game dir or project dir) + const wchar_t *paths[] = {L"CustomSkins\\*.png", L"..\\CustomSkins\\*.png"}; + const wchar_t *folderPaths[] = {L"CustomSkins\\", L"..\\CustomSkins\\"}; + + for (int i = 0; i < 2; ++i) + { + WIN32_FIND_DATAW fd; + HANDLE hFind = FindFirstFileW(paths[i], &fd); + if (hFind != INVALID_HANDLE_VALUE) + { + DebugPrintf("Found CustomSkins folder at: %ls\n", folderPaths[i]); + do + { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + wstring filename = fd.cFileName; + wstring fullPath = wstring(folderPaths[i]) + filename; + FILE *f = _wfopen(fullPath.c_str(), L"rb"); + if (f) + { + DebugPrintf("Loading skin file: %ls\n", fullPath.c_str()); + fseek(f, 0, SEEK_END); + DWORD dwSize = (DWORD)ftell(f); + rewind(f); + if (dwSize > 0 && dwSize < 2 * 1024 * 1024) // 2MB limit + { + BYTE *pData = new BYTE[dwSize]; + if (fread(pData, 1, dwSize, f) == dwSize) + { + AddMemoryTextureFile(filename, pData, dwSize); + app.m_customSkinNames.push_back(filename); + DebugPrintf("Successfully loaded custom skin: %ls\n", filename.c_str()); + } + else + { + delete[] pData; + } + } + else + { + DebugPrintf("Skin file too large or empty: %ls (%d bytes)\n", fullPath.c_str(), dwSize); + } + fclose(f); + } + else + { + DebugPrintf("Failed to open file: %ls\n", fullPath.c_str()); + } + } + } while (FindNextFileW(hFind, &fd)); + FindClose(hFind); + return; // Found and scanned + } + } + DebugPrintf("Error: CustomSkins folder not found in common locations.\n"); +} + // int CMinecraftApp::DLCReadCallback(LPVOID pParam,C4JStorage::DLC_FILE_DETAILS *pDLCData) // { // @@ -5758,7 +5824,12 @@ void CMinecraftApp::GetMemFileDetails(const wstring &wName,PBYTE *ppbData,DWORD PMEMDATA pData = (*it).second; *ppbData=pData->pbData; *pdwBytes=pData->dwBytes; - } + } + else + { + *ppbData = NULL; + *pdwBytes = 0; + } LeaveCriticalSection(&csMemFilesLock); } @@ -9298,6 +9369,14 @@ void CMinecraftApp::SetAnimOverrideBitmask(DWORD dwSkinID,unsigned int uiAnimOve DWORD CMinecraftApp::getSkinIdFromPath(const wstring &skin) { + for (size_t i = 0; i < app.m_customSkinNames.size(); ++i) + { + if (app.m_customSkinNames[i] == skin) + { + return 0x20000000 | (DWORD)i; + } + } + bool dlcSkin = false; unsigned int skinId = 0; @@ -9333,6 +9412,15 @@ wstring CMinecraftApp::getSkinPathFromId(DWORD skinId) swprintf(chars, 256, L"dlcskin%08d.png", GET_DLC_SKIN_ID_FROM_BITMASK(skinId)); } + else if (skinId & 0x20000000) + { + DWORD index = skinId & 0x1FFFFFFF; + if (index < app.m_customSkinNames.size()) + { + return app.m_customSkinNames[index]; + } + swprintf(chars, 256, L"defskin00000000.png"); + } else { DWORD ugcSkinIndex = GET_UGC_SKIN_ID_FROM_BITMASK(skinId); diff --git a/Minecraft.Client/Common/Consoles_App.h b/Minecraft.Client/Common/Consoles_App.h index ec36b7652..e4191a09b 100644 --- a/Minecraft.Client/Common/Consoles_App.h +++ b/Minecraft.Client/Common/Consoles_App.h @@ -76,6 +76,7 @@ public: // storing skin files std::vector vSkinNames; + std::vector m_customSkinNames; DLCManager m_dlcManager; // storing credits text from the DLC @@ -356,6 +357,7 @@ public: void RemoveMemoryTextureFile(const wstring &wName); void GetMemFileDetails(const wstring &wName,PBYTE *ppbData,DWORD *pdwBytes); bool IsFileInMemoryTextures(const wstring &wName); + void ScanAndLoadCustomSkins(); // Texture Pack Data files (icon, banner, comparison shot & text) void AddMemoryTPDFile(int iConfig,PBYTE pbData,DWORD dwBytes); diff --git a/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp b/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp index d4f26ae71..233bb047f 100644 --- a/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_SkinSelectMenu.cpp @@ -10,8 +10,8 @@ #define SKIN_SELECT_PACK_DEFAULT 0 #define SKIN_SELECT_PACK_FAVORITES 1 -//#define SKIN_SELECT_PACK_PLAYER_CUSTOM 1 -#define SKIN_SELECT_MAX_DEFAULTS 2 +#define SKIN_SELECT_PACK_PLAYER_CUSTOM 2 +#define SKIN_SELECT_MAX_DEFAULTS 3 WCHAR *UIScene_SkinSelectMenu::wchDefaultNamesA[]= { @@ -435,6 +435,18 @@ void UIScene_SkinSelectMenu::InputActionOK(unsigned int iPad) } } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (app.m_customSkinNames.size() > 0) + { + wstring selectedSkin = app.m_customSkinNames[m_skinIndex]; + app.SetPlayerSkin(iPad, selectedSkin); + app.SetPlayerCape(iPad, L""); + setCharacterSelected(true); + m_currentSkinPath = app.GetPlayerSkinName(iPad); + m_originalSkinId = app.GetPlayerSkinId(iPad); + ui.PlayUISFX(eSFX_Press); + } + break; default: if( m_currentPack != NULL ) { @@ -746,6 +758,27 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged() m_bNoSkinsToShow=true; } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (app.m_customSkinNames.size() > 0) + { + m_selectedSkinPath = app.m_customSkinNames[m_skinIndex]; + skinName = m_selectedSkinPath; + skinOrigin = L"Custom Skins"; + + if (m_selectedSkinPath.compare(m_currentSkinPath) == 0) + { + setCharacterSelected(true); + } + setCharacterLocked(false); + m_characters[eCharacter_Current].setVisible(true); + m_controlSkinNamePlate.setVisible(true); + } + else + { + m_characters[eCharacter_Current].setVisible(false); + m_bNoSkinsToShow = true; + } + break; } } @@ -864,6 +897,15 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged() } } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (app.m_customSkinNames.size() > 0) + { + otherSkinPath = app.m_customSkinNames[nextIndex]; + otherCapePath = L""; + othervAdditionalSkinBoxes = NULL; + backupTexture = TN_MOB_CHAR; + } + break; default: break; } @@ -936,6 +978,15 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged() } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (app.m_customSkinNames.size() > 0) + { + otherSkinPath = app.m_customSkinNames[previousIndex]; + otherCapePath = L""; + othervAdditionalSkinBoxes = NULL; + backupTexture = TN_MOB_CHAR; + } + break; default: break; } @@ -1014,6 +1065,13 @@ int UIScene_SkinSelectMenu::getNextSkinIndex(DWORD sourceIndex) } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + ++nextSkin; + if (nextSkin >= (int)app.m_customSkinNames.size()) + { + nextSkin = 0; + } + break; default: ++nextSkin; @@ -1048,6 +1106,16 @@ int UIScene_SkinSelectMenu::getPreviousSkinIndex(DWORD sourceIndex) --previousSkin; } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (previousSkin == 0) + { + previousSkin = (int)app.m_customSkinNames.size() - 1; + } + else + { + --previousSkin; + } + break; default: if(previousSkin==0) { @@ -1157,6 +1225,9 @@ void UIScene_SkinSelectMenu::updatePackDisplay() case SKIN_SELECT_PACK_FAVORITES: setCentreLabel(app.GetString(IDS_FAVORITES_SKIN_PACK)); break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + setCentreLabel(L"Custom Skins"); + break; } } @@ -1178,6 +1249,9 @@ void UIScene_SkinSelectMenu::updatePackDisplay() case SKIN_SELECT_PACK_FAVORITES: setRightLabel(app.GetString(IDS_FAVORITES_SKIN_PACK)); break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + setRightLabel(L"Custom Skins"); + break; } } @@ -1199,6 +1273,9 @@ void UIScene_SkinSelectMenu::updatePackDisplay() case SKIN_SELECT_PACK_FAVORITES: setLeftLabel(app.GetString(IDS_FAVORITES_SKIN_PACK)); break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + setLeftLabel(L"Custom Skins"); + break; } } diff --git a/Minecraft.Client/Common/XUI/XUI_SkinSelect.cpp b/Minecraft.Client/Common/XUI/XUI_SkinSelect.cpp index ee7066108..b1e2a3c6c 100644 --- a/Minecraft.Client/Common/XUI/XUI_SkinSelect.cpp +++ b/Minecraft.Client/Common/XUI/XUI_SkinSelect.cpp @@ -4,8 +4,8 @@ #define SKIN_SELECT_PACK_DEFAULT 0 #define SKIN_SELECT_PACK_FAVORITES 1 -//#define SKIN_SELECT_PACK_PLAYER_CUSTOM 1 -#define SKIN_SELECT_MAX_DEFAULTS 2 +#define SKIN_SELECT_PACK_PLAYER_CUSTOM 2 +#define SKIN_SELECT_MAX_DEFAULTS 3 WCHAR *CScene_SkinSelect::wchDefaultNamesA[]= { @@ -287,14 +287,29 @@ HRESULT CScene_SkinSelect::OnKeyDown(XUIMessageInput* pInputData, BOOL& rfHandle } else { - app.SetPlayerSkin(pInputData->UserIndex, skinFile->getPath()); - app.SetPlayerCape(pInputData->UserIndex, skinFile->getParameterAsString(DLCManager::e_DLCParamType_Cape)); - m_selectedGroup.SetShow( TRUE ); - m_currentSkinPath = app.GetPlayerSkinName(m_iPad); - m_originalSkinId = app.GetPlayerSkinId(m_iPad); + if (m_packIndex == SKIN_SELECT_PACK_PLAYER_CUSTOM) + { + if (app.m_customSkinNames.size() > 0) + { + wstring selectedSkin = app.m_customSkinNames[m_skinIndex]; + app.SetPlayerSkin(pInputData->UserIndex, selectedSkin); + app.SetPlayerCape(pInputData->UserIndex, L""); + m_selectedGroup.SetShow(TRUE); + m_currentSkinPath = selectedSkin; + m_originalSkinId = 0; + } + } + else + { + app.SetPlayerSkin(pInputData->UserIndex, skinFile->getPath()); + app.SetPlayerCape(pInputData->UserIndex, skinFile->getParameterAsString(DLCManager::e_DLCParamType_Cape)); + m_selectedGroup.SetShow(TRUE); + m_currentSkinPath = app.GetPlayerSkinName(m_iPad); + m_originalSkinId = app.GetPlayerSkinId(m_iPad); - // push this onto the favorite list - AddFavoriteSkin(m_iPad,GET_DLC_SKIN_ID_FROM_BITMASK(m_originalSkinId)); + // push this onto the favorite list + AddFavoriteSkin(m_iPad, GET_DLC_SKIN_ID_FROM_BITMASK(m_originalSkinId)); + } } } else @@ -743,6 +758,31 @@ void CScene_SkinSelect::handleSkinIndexChanged() bNoSkinsToShow=true; } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (app.m_customSkinNames.size() > 0) + { + m_selectedSkinPath = app.m_customSkinNames[m_skinIndex]; + skinName = m_selectedSkinPath; + skinOrigin = L"Custom Skins"; + + if (m_selectedSkinPath.compare(m_currentSkinPath) == 0) + { + m_selectedGroup.SetShow(TRUE); + } + else + { + m_selectedGroup.SetShow(FALSE); + } + m_imagePadlock.SetShow(FALSE); + m_previewControl->SetShow(TRUE); + m_skinDetails.SetShow(TRUE); + } + else + { + m_previewControl->SetShow(FALSE); + bNoSkinsToShow = true; + } + break; } } @@ -1041,6 +1081,9 @@ void CScene_SkinSelect::handlePackIndexChanged() } } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + // No extra logic needed for custom pack index change for now + break; default: break; } @@ -1071,6 +1114,9 @@ void CScene_SkinSelect::updatePackDisplay() case SKIN_SELECT_PACK_FAVORITES: m_packCenter.SetText(app.GetString(IDS_FAVORITES_SKIN_PACK)); break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + m_packCenter.SetText(L"Custom Skins"); + break; } } @@ -1090,6 +1136,9 @@ void CScene_SkinSelect::updatePackDisplay() case SKIN_SELECT_PACK_FAVORITES: m_packRight.SetText(app.GetString(IDS_FAVORITES_SKIN_PACK)); break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + m_packRight.SetText(L"Custom Skins"); + break; } } @@ -1109,6 +1158,9 @@ void CScene_SkinSelect::updatePackDisplay() case SKIN_SELECT_PACK_FAVORITES: m_packLeft.SetText(app.GetString(IDS_FAVORITES_SKIN_PACK)); break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + m_packLeft.SetText(L"Custom Skins"); + break; } } @@ -1192,6 +1244,13 @@ int CScene_SkinSelect::getNextSkinIndex(DWORD sourceIndex) } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + ++nextSkin; + if (nextSkin >= (int)app.m_customSkinNames.size()) + { + nextSkin = 0; + } + break; default: ++nextSkin; @@ -1226,6 +1285,16 @@ int CScene_SkinSelect::getPreviousSkinIndex(DWORD sourceIndex) --previousSkin; } break; + case SKIN_SELECT_PACK_PLAYER_CUSTOM: + if (previousSkin == 0) + { + previousSkin = (int)app.m_customSkinNames.size() - 1; + } + else + { + --previousSkin; + } + break; default: if(previousSkin==0) { diff --git a/x64/Debug/CustomSkins/Doge.png b/x64/Debug/CustomSkins/Doge.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c7fb588188c11f355a4b911e214b148d9d8099 GIT binary patch literal 1636 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