Some changes were reversed and a warning was fixed.

This commit is contained in:
IsraelDXPP 2026-03-05 10:09:49 -07:00
parent f73679c8c1
commit 672df27123
3 changed files with 175 additions and 170 deletions

View file

@ -9296,6 +9296,13 @@ void CMinecraftApp::SetAnimOverrideBitmask(DWORD dwSkinID,unsigned int uiAnimOve
DWORD CMinecraftApp::getSkinIdFromPath(const wstring &skin)
{
if (skin.find(L"custom_skin_") != wstring::npos)
{
// If the texture name is our new custom_skin_ format, return a fixed UGC ID (0x100).
// This prevents the engine from parsing non-hex characters and defaulting to 0.
return MAKE_SKIN_BITMASK(false, 0x100);
}
bool dlcSkin = false;
unsigned int skinId = 0;
@ -9322,62 +9329,71 @@ DWORD CMinecraftApp::getSkinIdFromPath(const wstring &skin)
wstring CMinecraftApp::getSkinPathFromId(DWORD skinId)
{
// 4J Stu - This function maps the encoded DWORD we store in the player profile
// to a filename that is stored as a memory texture and shared between systems in game
// Maps the encoded DWORD stored in the player profile to a texture filename
// that the engine can look up in the in-memory texture filesystem.
wchar_t chars[256];
if( GET_IS_DLC_SKIN_FROM_BITMASK(skinId) )
if (GET_IS_DLC_SKIN_FROM_BITMASK(skinId))
{
// 4J Stu - DLC skins are numbered using decimal rather than hex to make it easier to number manually
// DLC skins use decimal numbering to make manual numbering easier.
swprintf(chars, 256, L"dlcskin%08d.png", GET_DLC_SKIN_ID_FROM_BITMASK(skinId));
}
else
{
DWORD ugcSkinIndex = GET_UGC_SKIN_ID_FROM_BITMASK(skinId);
DWORD ugcSkinIndex = GET_UGC_SKIN_ID_FROM_BITMASK(skinId);
DWORD defaultSkinIndex = GET_DEFAULT_SKIN_ID_FROM_BITMASK(skinId);
if( ugcSkinIndex == 0 )
if (ugcSkinIndex == 0)
{
swprintf(chars, 256, L"defskin%08X.png",defaultSkinIndex);
swprintf(chars, 256, L"defskin%08X.png", defaultSkinIndex);
}
else
{
swprintf(chars, 256, L"ugcskin%08X.png",ugcSkinIndex);
// --- PERSISTENCE: Check if we need to load this skin from disk ---
// Iterate through all possible active players (max 4) to find if this skin belongs to one of them.
// For custom (UGC) skins we use a player-readable name: custom_skin_{gamertag}.png.
// Check every signed-in slot and load the skin from disk if it isn't in memory yet.
for (int i = 0; i < 4; ++i)
{
if (ProfileManager.IsSignedIn(i)) // Check if player is signed in
if (!ProfileManager.IsSignedIn(i))
continue;
char* gamertag = ProfileManager.GetGamertag(i);
const char* tag = (gamertag && gamertag[0]) ? gamertag : "Player";
// The in-memory texture name for this player's custom skin.
wchar_t texName[128];
swprintf(texName, 128, L"custom_skin_%hs.png", tag);
if (!app.IsFileInMemoryTextures(texName))
{
wstring username = ProfileManager.GetDisplayName(i);
if (username.empty()) username = L"default";
// Disk path: CustomSkin/custom_skin_{gamertag}.png
wchar_t diskPath[MAX_PATH];
swprintf(diskPath, MAX_PATH, L"CustomSkin/custom_skin_%hs.png", tag);
WCHAR expectedSkinPath[MAX_PATH];
swprintf(expectedSkinPath, 256, L"CustomSkin\\custom_skin_%ls.png", username.c_str());
// If the file is not already in memory textures, load it.
// Note: app.IsFileInMemoryTextures check is internal to app.AddMemoryTextureFile normally,
// but we check here to avoid redundant file I/O.
if (!app.IsFileInMemoryTextures(chars))
File skinFile(diskPath);
if (skinFile.exists())
{
std::ifstream file(expectedSkinPath, std::ios::binary | std::ios::ate);
if (file.is_open())
DWORD size = (DWORD)skinFile.length();
if (size > 0 && size <= 2 * 1024 * 1024)
{
std::streamsize size = file.tellg();
if (size > 0 && size <= 2 * 1024 * 1024)
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
if (buffer)
{
file.seekg(0, std::ios::beg);
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
if (buffer && file.read((char*)buffer, size))
{
app.AddMemoryTextureFile(chars, buffer, (DWORD)size);
}
else if (buffer) delete[] buffer;
FileInputStream fis(skinFile);
byteArray b;
b.data = buffer;
b.length = size;
if (fis.read(b, 0, size) == size)
app.AddMemoryTextureFile(texName, buffer, size);
else
delete[] buffer;
fis.close();
}
file.close();
}
}
}
// Return the texture name for the first signed-in player slot —
// this matches what LoadExternalSkin registers on behalf of m_iPad.
wcscpy_s(chars, 256, texName);
break;
}
}
}

View file

@ -9,6 +9,7 @@
#endif
#include <fstream>
#include <commdlg.h> // Required for GetOpenFileNameW
#include <array>
#define SKIN_SELECT_PACK_DEFAULT 0
#define SKIN_SELECT_PACK_FAVORITES 1
@ -56,42 +57,53 @@ UIScene_SkinSelectMenu::UIScene_SkinSelectMenu(int iPad, void *initData, UILayer
m_bAnimatingMove = false;
m_bSkinIndexChanged = false;
m_customSkinTextureName = L"";
m_customSkinPath = L"";
m_bUsingCustomSkin = false;
// --- PERSISTENCE: Check for existing custom skin on disk ---
wstring username = ProfileManager.GetDisplayName(iPad);
if (username.empty()) username = L"default";
WCHAR skinPath[MAX_PATH];
swprintf(skinPath, 256, L"CustomSkin\\custom_skin_%ls.png", username.c_str());
// Try to load a previously saved custom skin from disk.
// The file is named after the player's gamertag so each player has their own skin.
char* gamertag = ProfileManager.GetGamertag(iPad);
const char* tag = (gamertag && gamertag[0]) ? gamertag : "Player";
std::ifstream file(skinPath, std::ios::binary | std::ios::ate);
if (file.is_open())
std::array<wchar_t, MAX_PATH> diskPathBuf = {};
swprintf(diskPathBuf.data(), diskPathBuf.size(), L"CustomSkin/custom_skin_%hs.png", tag);
File skinFile(diskPathBuf.data());
if (skinFile.exists())
{
std::streamsize size = file.tellg();
DWORD size = (DWORD)skinFile.length();
if (size > 0 && size <= 2 * 1024 * 1024)
{
file.seekg(0, std::ios::beg);
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
if (buffer && file.read((char*)buffer, size))
if (buffer)
{
static int s_persistentLoadCount = 0;
s_persistentLoadCount++;
wchar_t nameBuf[256];
swprintf(nameBuf, 256, L"ugcskin%08X.png", 0x300 + (s_persistentLoadCount << 5));
m_customSkinTextureName = nameBuf;
app.AddMemoryTextureFile(m_customSkinTextureName, buffer, (DWORD)size);
m_bUsingCustomSkin = true;
FileInputStream fis(skinFile);
byteArray b;
b.data = buffer;
b.length = size;
if (fis.read(b, 0, size) == size)
{
std::array<wchar_t, 128> texNameBuf = {};
swprintf(texNameBuf.data(), texNameBuf.size(), L"custom_skin_%hs.png", tag);
wstring texName = texNameBuf.data();
app.AddMemoryTextureFile(texName, buffer, size);
m_customSkinPath = texName;
m_bUsingCustomSkin = true;
}
else
{
delete[] buffer;
}
fis.close();
}
else if (buffer) delete[] buffer;
}
file.close();
}
// If the player is already wearing a custom skin, override with the current one
// If the profile already tracks a custom skin name, prefer that over the disk load.
if (!GET_IS_DLC_SKIN_FROM_BITMASK(m_originalSkinId) && GET_UGC_SKIN_ID_FROM_BITMASK(m_originalSkinId) != 0)
{
m_customSkinTextureName = m_currentSkinPath;
m_customSkinPath = m_currentSkinPath;
m_bUsingCustomSkin = true;
}
@ -454,10 +466,10 @@ void UIScene_SkinSelectMenu::InputActionOK(unsigned int iPad)
// If we are specifically on the custom skin slot (0) and we have a custom skin loaded,
// apply it and set the active flag to true.
if (!m_customSkinTextureName.empty() && m_packIndex == SKIN_SELECT_PACK_DEFAULT && m_skinIndex == 0)
if (!m_customSkinPath.empty() && m_packIndex == SKIN_SELECT_PACK_DEFAULT && m_skinIndex == 0)
{
ui.PlayUISFX(eSFX_Press);
app.SetPlayerSkin(iPad, m_customSkinTextureName);
app.SetPlayerSkin(iPad, m_customSkinPath);
m_currentSkinPath = app.GetPlayerSkinName(iPad);
m_originalSkinId = app.GetPlayerSkinId(iPad);
m_bUsingCustomSkin = true;
@ -465,8 +477,8 @@ void UIScene_SkinSelectMenu::InputActionOK(unsigned int iPad)
return;
}
// If the user selects a DIFFERENT skin (DLC, Default, etc.), clear the active flag
// but KEEP m_customSkinTextureName so it stays in the menu.
// If the user selects a different skin (DLC, Default, etc.), clear the active flag
// but KEEP m_customSkinPath so the custom skin stays available in the menu.
m_bUsingCustomSkin = false;
// if the profile data has been changed, then force a profile write
@ -666,7 +678,10 @@ void UIScene_SkinSelectMenu::InputActionOK(unsigned int iPad)
void UIScene_SkinSelectMenu::customDraw(IggyCustomDrawCallbackRegion *region)
{
int characterId = -1;
swscanf((wchar_t*)region->name,L"Character%d",&characterId);
if (swscanf((wchar_t*)region->name,L"Character%d",&characterId) != 1)
{
characterId = -1;
}
if (characterId == -1)
{
app.DebugPrintf("Invalid character to render found\n");
@ -713,19 +728,18 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
m_controlSkinNamePlate.setVisible( false );
// --- PERSISTENCE: Custom Skin UI Display ---
// Show the "Custom Skin" info if we have a custom skin loaded and are on slot 0.
if (!m_customSkinTextureName.empty() && m_packIndex == SKIN_SELECT_PACK_DEFAULT && m_skinIndex == 0)
if (!m_customSkinPath.empty() && m_packIndex == SKIN_SELECT_PACK_DEFAULT && m_skinIndex == 0)
{
m_selectedSkinPath = m_customSkinTextureName;
m_selectedSkinPath = m_customSkinPath;
m_selectedCapePath = L"";
m_vAdditionalSkinBoxes = NULL;
skinName = L"Custom Skin";
skinOrigin = L"External PNG";
// If the player is currently wearing THE custom skin, show the checkmark.
// We use m_currentSkinPath (what the player is actually wearing) to decide.
if (m_selectedSkinPath.compare(app.GetPlayerSkinName(m_iPad)) == 0)
// If the player is wearing this custom skin, show the "Selected" checkmark
// We also check m_bUsingCustomSkin because the session sequence number in the memory path might differ.
if (m_bUsingCustomSkin || m_selectedSkinPath.compare(app.GetPlayerSkinName(m_iPad)) == 0)
{
setCharacterSelected(true);
}
@ -840,7 +854,7 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
m_labelSkinName.setLabel(skinName);
m_labelSkinOrigin.setLabel(skinOrigin);
if (m_selectedSkinPath.compare(m_currentSkinPath) == 0)
if (m_selectedSkinPath.compare(m_currentSkinPath) == 0 || (m_bUsingCustomSkin && m_packIndex == SKIN_SELECT_PACK_DEFAULT && m_skinIndex == 0))
{
setCharacterSelected(true); // Enable selection checkmark
}
@ -937,9 +951,9 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
switch(m_packIndex)
{
case SKIN_SELECT_PACK_DEFAULT:
if (nextIndex == 0 && !m_customSkinTextureName.empty())
if (nextIndex == 0 && !m_customSkinPath.empty())
{
otherSkinPath = m_customSkinTextureName;
otherSkinPath = m_customSkinPath;
backupTexture = TN_MOB_CHAR;
}
else
@ -1016,9 +1030,9 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
switch(m_packIndex)
{
case SKIN_SELECT_PACK_DEFAULT:
if (previousIndex == 0 && !m_customSkinTextureName.empty())
if (previousIndex == 0 && !m_customSkinPath.empty())
{
otherSkinPath = m_customSkinTextureName;
otherSkinPath = m_customSkinPath;
backupTexture = TN_MOB_CHAR;
}
else
@ -1845,106 +1859,81 @@ int UIScene_SkinSelectMenu::PSNSignInReturned(void* pParam, bool bContinue, int
}
#endif // __PSVITA__
// We include array at the top if not present, but using std::array here:
void UIScene_SkinSelectMenu::LoadExternalSkin()
{
OPENFILENAMEW ofn;
wchar_t szFile[MAX_PATH];
std::array<wchar_t, MAX_PATH> szFile = {};
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;
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFile = szFile.data();
ofn.nMaxFile = (DWORD)szFile.size();
ofn.lpstrFilter = L"PNG Files\0*.png\0All Files\0*.*\0";
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
// IMPORTANT: OFN_NOCHANGEDIR prevents the File Explorer from changing the current working directory.
// If the directory changes, the game will FAIL TO SAVE worlds (Assertion failed in STO_SaveGame.cpp).
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
if (!GetOpenFileNameW(&ofn))
return;
if (GetOpenFileNameW(&ofn) == TRUE)
File srcFile(ofn.lpstrFile);
DWORD size = (DWORD)srcFile.length();
if (size == 0 || size > 2 * 1024 * 1024) // invalid size
{
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);
// MEMORY MANAGEMENT: AddMemoryTextureFile does NOT copy the buffer; it uses the pointer directly.
// If you use a local vector or stack buffer, the skin will corrupt/crash as soon as the function returns.
PBYTE persistentBuffer = new (std::nothrow) BYTE[(size_t)size];
if (!persistentBuffer || !file.read((char*)persistentBuffer, size)) {
if(persistentBuffer) delete[] persistentBuffer;
file.close();
return;
}
// Validate PNG Header and Dimensions (64x32 or 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: If we use the same filename (e.g., "custom.png"), the engine's texture cache won't refresh.
// We use a counter to generate unique texture names (ugcskin00000100.png, 120, etc).
static int s_ugcSkinLoadCount = 0;
s_ugcSkinLoadCount++;
wchar_t textureNameBuf[256];
// Standard ugcskinXXXXXXXX.png naming is required for correct internal ID generation via getSkinIdFromPath.
// We shift the counter by 5 bits to align with the UGC bitmask (lower 5 bits are reserved for default skins).
swprintf(textureNameBuf, 256, L"ugcskin%08X.png", 0x100 + (s_ugcSkinLoadCount << 5));
wstring textureName = textureNameBuf;
// Optional: Remove previous custom texture from memory to avoid leaks
if (m_bUsingCustomSkin && !m_selectedSkinPath.empty()) {
app.RemoveMemoryTextureFile(m_selectedSkinPath);
}
// 1. Add texture data to the in-memory filesystem
app.AddMemoryTextureFile(textureName, persistentBuffer, (DWORD)size);
// --- PERSISTENCE: Save the custom skin to disk ---
// Create the 'CustomSkin' directory if it doesn't exist, and save as 'custom_skin_%username%.png'
CreateDirectoryW(L"CustomSkin", NULL);
wstring username = ProfileManager.GetDisplayName(m_iPad);
if (username.empty()) username = L"default";
WCHAR skinPath[MAX_PATH];
swprintf(skinPath, 256, L"CustomSkin\\custom_skin_%ls.png", username.c_str());
std::ofstream outFile(skinPath, std::ios::binary);
if (outFile.is_open())
{
outFile.write((char*)persistentBuffer, size);
outFile.close();
}
// 2. Apply the skin path to the local player character
app.SetPlayerSkin(m_iPad, textureName);
// 3. Force UI refresh and update internal state
m_bUsingCustomSkin = true;
m_customSkinTextureName = textureName;
m_selectedSkinPath = textureName;
m_currentSkinPath = textureName;
m_selectedCapePath = L"";
handleSkinIndexChanged();
file.close();
return;
}
}
delete[] persistentBuffer; // Cleanup if validation or loading failed
ui.PlayUISFX(eSFX_CraftFail);
file.close();
}
ui.PlayUISFX(eSFX_CraftFail);
return;
}
}
FileInputStream fis(srcFile);
PBYTE buffer = new (std::nothrow) BYTE[size];
if (!buffer || fis.read({ buffer, size }, 0, size) != size) // read error
{
delete[] buffer;
ui.PlayUISFX(eSFX_CraftFail);
return;
}
fis.close();
// validate PNG header
if (size <= 24 || buffer[0] != 0x89 || buffer[1] != 'P')
{
delete[] buffer;
ui.PlayUISFX(eSFX_CraftFail);
return;
}
unsigned int w = (buffer[16] << 24) | (buffer[17] << 16) | (buffer[18] << 8) | buffer[19];
unsigned int h = (buffer[20] << 24) | (buffer[21] << 16) | (buffer[22] << 8) | buffer[23];
if (!((w == 64 && h == 32) || (w == 64 && h == 64))) // invalid dimensions
{
delete[] buffer;
ui.PlayUISFX(eSFX_CraftFail);
return;
}
char* gamertag = ProfileManager.GetGamertag(m_iPad);
std::array<wchar_t, 128> texNameBuf = {};
swprintf(texNameBuf.data(), texNameBuf.size(), L"custom_skin_%hs.png", gamertag ? gamertag : "Player");
wstring textureName = texNameBuf.data();
if (m_bUsingCustomSkin && !m_customSkinPath.empty())
app.RemoveMemoryTextureFile(m_customSkinPath);
app.AddMemoryTextureFile(textureName, buffer, size); // register in memory
// save to disk
std::array<wchar_t, MAX_PATH> diskPathBuf = {};
swprintf(diskPathBuf.data(), diskPathBuf.size(), L"CustomSkin/custom_skin_%hs.png", gamertag ? gamertag : "Player");
File(L"CustomSkin").mkdirs();
FileOutputStream(File(diskPathBuf.data())).write({ buffer, size }, 0, size);
app.SetPlayerSkin(m_iPad, textureName); // apply to player
// update state
m_bUsingCustomSkin = true;
m_customSkinPath = textureName;
m_selectedSkinPath = textureName;
m_currentSkinPath = textureName;
m_selectedCapePath = L"";
handleSkinIndexChanged();
}

View file

@ -102,10 +102,11 @@ private:
DLCPack *m_currentPack;
DWORD m_packIndex, m_skinIndex;
DWORD m_originalSkinId;
wstring m_currentSkinPath, m_selectedSkinPath, m_selectedCapePath, m_customSkinTextureName;
wstring m_currentSkinPath, m_selectedSkinPath, m_selectedCapePath;
wstring m_customSkinPath; // Persistent path for the loaded custom skin — not overwritten on scroll
vector<SKIN_BOX *> *m_vAdditionalSkinBoxes;
bool m_bSlidingSkins, m_bAnimatingMove;
bool m_bSlidingSkins, m_bAnimatingMove, m_bUsingCustomSkin;
ESkinSelectNavigation m_currentNavigation;
bool m_bNoSkinsToShow;
@ -136,7 +137,6 @@ protected:
// TODO: This should be pure virtual in this class
virtual wstring getMoviePath();
bool m_bUsingCustomSkin; // Flag indicating if a custom external skin is active
public:
// INPUT