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 000000000..b8c7fb588 Binary files /dev/null and b/x64/Debug/CustomSkins/Doge.png differ diff --git a/x64/Debug/CustomSkins/Herobrine.png b/x64/Debug/CustomSkins/Herobrine.png new file mode 100644 index 000000000..dd3d633df Binary files /dev/null and b/x64/Debug/CustomSkins/Herobrine.png differ diff --git a/x64/Debug/CustomSkins/README.txt b/x64/Debug/CustomSkins/README.txt new file mode 100644 index 000000000..7f85856bb --- /dev/null +++ b/x64/Debug/CustomSkins/README.txt @@ -0,0 +1,7 @@ +To import a Minecraft skin, you need to convert it using a site that transforms the image from 64x64 to 64x32 +You can use sites like: +https://mcskins.top/x64-to-x32 + +Then paste the image into this directory and restart the game + +In this directory you will find testing skins that you can use \ No newline at end of file