mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
Some changes were reversed and a warning was fixed.
This commit is contained in:
parent
f73679c8c1
commit
672df27123
|
|
@ -9296,6 +9296,13 @@ void CMinecraftApp::SetAnimOverrideBitmask(DWORD dwSkinID,unsigned int uiAnimOve
|
||||||
|
|
||||||
DWORD CMinecraftApp::getSkinIdFromPath(const wstring &skin)
|
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;
|
bool dlcSkin = false;
|
||||||
unsigned int skinId = 0;
|
unsigned int skinId = 0;
|
||||||
|
|
||||||
|
|
@ -9322,62 +9329,71 @@ DWORD CMinecraftApp::getSkinIdFromPath(const wstring &skin)
|
||||||
|
|
||||||
wstring CMinecraftApp::getSkinPathFromId(DWORD skinId)
|
wstring CMinecraftApp::getSkinPathFromId(DWORD skinId)
|
||||||
{
|
{
|
||||||
// 4J Stu - This function maps the encoded DWORD we store in the player profile
|
// Maps the encoded DWORD stored in the player profile to a texture filename
|
||||||
// to a filename that is stored as a memory texture and shared between systems in game
|
// that the engine can look up in the in-memory texture filesystem.
|
||||||
wchar_t chars[256];
|
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));
|
swprintf(chars, 256, L"dlcskin%08d.png", GET_DLC_SKIN_ID_FROM_BITMASK(skinId));
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
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);
|
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
|
else
|
||||||
{
|
{
|
||||||
swprintf(chars, 256, L"ugcskin%08X.png",ugcSkinIndex);
|
// 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.
|
||||||
// --- 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 (int i = 0; i < 4; ++i)
|
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);
|
// Disk path: CustomSkin/custom_skin_{gamertag}.png
|
||||||
if (username.empty()) username = L"default";
|
wchar_t diskPath[MAX_PATH];
|
||||||
|
swprintf(diskPath, MAX_PATH, L"CustomSkin/custom_skin_%hs.png", tag);
|
||||||
|
|
||||||
WCHAR expectedSkinPath[MAX_PATH];
|
File skinFile(diskPath);
|
||||||
swprintf(expectedSkinPath, 256, L"CustomSkin\\custom_skin_%ls.png", username.c_str());
|
if (skinFile.exists())
|
||||||
|
|
||||||
// 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))
|
|
||||||
{
|
{
|
||||||
std::ifstream file(expectedSkinPath, std::ios::binary | std::ios::ate);
|
DWORD size = (DWORD)skinFile.length();
|
||||||
if (file.is_open())
|
if (size > 0 && size <= 2 * 1024 * 1024)
|
||||||
{
|
{
|
||||||
std::streamsize size = file.tellg();
|
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
|
||||||
if (size > 0 && size <= 2 * 1024 * 1024)
|
if (buffer)
|
||||||
{
|
{
|
||||||
file.seekg(0, std::ios::beg);
|
FileInputStream fis(skinFile);
|
||||||
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
|
byteArray b;
|
||||||
if (buffer && file.read((char*)buffer, size))
|
b.data = buffer;
|
||||||
{
|
b.length = size;
|
||||||
app.AddMemoryTextureFile(chars, buffer, (DWORD)size);
|
if (fis.read(b, 0, size) == size)
|
||||||
}
|
app.AddMemoryTextureFile(texName, buffer, size);
|
||||||
else if (buffer) delete[] buffer;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#endif
|
#endif
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <commdlg.h> // Required for GetOpenFileNameW
|
#include <commdlg.h> // Required for GetOpenFileNameW
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#define SKIN_SELECT_PACK_DEFAULT 0
|
#define SKIN_SELECT_PACK_DEFAULT 0
|
||||||
#define SKIN_SELECT_PACK_FAVORITES 1
|
#define SKIN_SELECT_PACK_FAVORITES 1
|
||||||
|
|
@ -56,42 +57,53 @@ UIScene_SkinSelectMenu::UIScene_SkinSelectMenu(int iPad, void *initData, UILayer
|
||||||
m_bAnimatingMove = false;
|
m_bAnimatingMove = false;
|
||||||
m_bSkinIndexChanged = false;
|
m_bSkinIndexChanged = false;
|
||||||
|
|
||||||
m_customSkinTextureName = L"";
|
m_customSkinPath = L"";
|
||||||
m_bUsingCustomSkin = false;
|
m_bUsingCustomSkin = false;
|
||||||
|
|
||||||
// --- PERSISTENCE: Check for existing custom skin on disk ---
|
// Try to load a previously saved custom skin from disk.
|
||||||
wstring username = ProfileManager.GetDisplayName(iPad);
|
// The file is named after the player's gamertag so each player has their own skin.
|
||||||
if (username.empty()) username = L"default";
|
char* gamertag = ProfileManager.GetGamertag(iPad);
|
||||||
WCHAR skinPath[MAX_PATH];
|
const char* tag = (gamertag && gamertag[0]) ? gamertag : "Player";
|
||||||
swprintf(skinPath, 256, L"CustomSkin\\custom_skin_%ls.png", username.c_str());
|
|
||||||
|
|
||||||
std::ifstream file(skinPath, std::ios::binary | std::ios::ate);
|
std::array<wchar_t, MAX_PATH> diskPathBuf = {};
|
||||||
if (file.is_open())
|
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)
|
if (size > 0 && size <= 2 * 1024 * 1024)
|
||||||
{
|
{
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
|
PBYTE buffer = new (std::nothrow) BYTE[(size_t)size];
|
||||||
if (buffer && file.read((char*)buffer, size))
|
if (buffer)
|
||||||
{
|
{
|
||||||
static int s_persistentLoadCount = 0;
|
FileInputStream fis(skinFile);
|
||||||
s_persistentLoadCount++;
|
byteArray b;
|
||||||
wchar_t nameBuf[256];
|
b.data = buffer;
|
||||||
swprintf(nameBuf, 256, L"ugcskin%08X.png", 0x300 + (s_persistentLoadCount << 5));
|
b.length = size;
|
||||||
m_customSkinTextureName = nameBuf;
|
if (fis.read(b, 0, size) == size)
|
||||||
app.AddMemoryTextureFile(m_customSkinTextureName, buffer, (DWORD)size);
|
{
|
||||||
m_bUsingCustomSkin = true;
|
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)
|
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;
|
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,
|
// 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.
|
// 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);
|
ui.PlayUISFX(eSFX_Press);
|
||||||
app.SetPlayerSkin(iPad, m_customSkinTextureName);
|
app.SetPlayerSkin(iPad, m_customSkinPath);
|
||||||
m_currentSkinPath = app.GetPlayerSkinName(iPad);
|
m_currentSkinPath = app.GetPlayerSkinName(iPad);
|
||||||
m_originalSkinId = app.GetPlayerSkinId(iPad);
|
m_originalSkinId = app.GetPlayerSkinId(iPad);
|
||||||
m_bUsingCustomSkin = true;
|
m_bUsingCustomSkin = true;
|
||||||
|
|
@ -465,8 +477,8 @@ void UIScene_SkinSelectMenu::InputActionOK(unsigned int iPad)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user selects a DIFFERENT skin (DLC, Default, etc.), clear the active flag
|
// If the user selects a different skin (DLC, Default, etc.), clear the active flag
|
||||||
// but KEEP m_customSkinTextureName so it stays in the menu.
|
// but KEEP m_customSkinPath so the custom skin stays available in the menu.
|
||||||
m_bUsingCustomSkin = false;
|
m_bUsingCustomSkin = false;
|
||||||
|
|
||||||
// if the profile data has been changed, then force a profile write
|
// 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)
|
void UIScene_SkinSelectMenu::customDraw(IggyCustomDrawCallbackRegion *region)
|
||||||
{
|
{
|
||||||
int characterId = -1;
|
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)
|
if (characterId == -1)
|
||||||
{
|
{
|
||||||
app.DebugPrintf("Invalid character to render found\n");
|
app.DebugPrintf("Invalid character to render found\n");
|
||||||
|
|
@ -713,19 +728,18 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
|
||||||
|
|
||||||
m_controlSkinNamePlate.setVisible( false );
|
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.
|
// 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_selectedCapePath = L"";
|
||||||
m_vAdditionalSkinBoxes = NULL;
|
m_vAdditionalSkinBoxes = NULL;
|
||||||
skinName = L"Custom Skin";
|
skinName = L"Custom Skin";
|
||||||
skinOrigin = L"External PNG";
|
skinOrigin = L"External PNG";
|
||||||
|
|
||||||
// If the player is currently wearing THE custom skin, show the checkmark.
|
// If the player is wearing this custom skin, show the "Selected" checkmark
|
||||||
// We use m_currentSkinPath (what the player is actually wearing) to decide.
|
// We also check m_bUsingCustomSkin because the session sequence number in the memory path might differ.
|
||||||
if (m_selectedSkinPath.compare(app.GetPlayerSkinName(m_iPad)) == 0)
|
if (m_bUsingCustomSkin || m_selectedSkinPath.compare(app.GetPlayerSkinName(m_iPad)) == 0)
|
||||||
{
|
{
|
||||||
setCharacterSelected(true);
|
setCharacterSelected(true);
|
||||||
}
|
}
|
||||||
|
|
@ -840,7 +854,7 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
|
||||||
m_labelSkinName.setLabel(skinName);
|
m_labelSkinName.setLabel(skinName);
|
||||||
m_labelSkinOrigin.setLabel(skinOrigin);
|
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
|
setCharacterSelected(true); // Enable selection checkmark
|
||||||
}
|
}
|
||||||
|
|
@ -937,9 +951,9 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
|
||||||
switch(m_packIndex)
|
switch(m_packIndex)
|
||||||
{
|
{
|
||||||
case SKIN_SELECT_PACK_DEFAULT:
|
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;
|
backupTexture = TN_MOB_CHAR;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1016,9 +1030,9 @@ void UIScene_SkinSelectMenu::handleSkinIndexChanged()
|
||||||
switch(m_packIndex)
|
switch(m_packIndex)
|
||||||
{
|
{
|
||||||
case SKIN_SELECT_PACK_DEFAULT:
|
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;
|
backupTexture = TN_MOB_CHAR;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1845,106 +1859,81 @@ int UIScene_SkinSelectMenu::PSNSignInReturned(void* pParam, bool bContinue, int
|
||||||
}
|
}
|
||||||
#endif // __PSVITA__
|
#endif // __PSVITA__
|
||||||
|
|
||||||
|
// We include array at the top if not present, but using std::array here:
|
||||||
void UIScene_SkinSelectMenu::LoadExternalSkin()
|
void UIScene_SkinSelectMenu::LoadExternalSkin()
|
||||||
{
|
{
|
||||||
OPENFILENAMEW ofn;
|
OPENFILENAMEW ofn;
|
||||||
wchar_t szFile[MAX_PATH];
|
std::array<wchar_t, MAX_PATH> szFile = {};
|
||||||
|
|
||||||
ZeroMemory(&ofn, sizeof(ofn));
|
ZeroMemory(&ofn, sizeof(ofn));
|
||||||
ofn.lStructSize = sizeof(ofn);
|
ofn.lStructSize = sizeof(ofn);
|
||||||
ofn.hwndOwner = NULL;
|
ofn.lpstrFile = szFile.data();
|
||||||
ofn.lpstrFile = szFile;
|
ofn.nMaxFile = (DWORD)szFile.size();
|
||||||
ofn.lpstrFile[0] = L'\0';
|
ofn.lpstrFilter = L"PNG Files\0*.png\0All Files\0*.*\0";
|
||||||
ofn.nMaxFile = MAX_PATH;
|
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
|
||||||
ofn.lpstrFilter = L"PNG Files\0*.png\0All Files\0*.*\0";
|
|
||||||
ofn.nFilterIndex = 1;
|
|
||||||
|
|
||||||
// IMPORTANT: OFN_NOCHANGEDIR prevents the File Explorer from changing the current working directory.
|
if (!GetOpenFileNameW(&ofn))
|
||||||
// If the directory changes, the game will FAIL TO SAVE worlds (Assertion failed in STO_SaveGame.cpp).
|
return;
|
||||||
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
|
|
||||||
|
|
||||||
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);
|
ui.PlayUISFX(eSFX_CraftFail);
|
||||||
if (file.is_open())
|
return;
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +102,11 @@ private:
|
||||||
DLCPack *m_currentPack;
|
DLCPack *m_currentPack;
|
||||||
DWORD m_packIndex, m_skinIndex;
|
DWORD m_packIndex, m_skinIndex;
|
||||||
DWORD m_originalSkinId;
|
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;
|
vector<SKIN_BOX *> *m_vAdditionalSkinBoxes;
|
||||||
|
|
||||||
bool m_bSlidingSkins, m_bAnimatingMove;
|
bool m_bSlidingSkins, m_bAnimatingMove, m_bUsingCustomSkin;
|
||||||
ESkinSelectNavigation m_currentNavigation;
|
ESkinSelectNavigation m_currentNavigation;
|
||||||
|
|
||||||
bool m_bNoSkinsToShow;
|
bool m_bNoSkinsToShow;
|
||||||
|
|
@ -136,7 +137,6 @@ protected:
|
||||||
// TODO: This should be pure virtual in this class
|
// TODO: This should be pure virtual in this class
|
||||||
virtual wstring getMoviePath();
|
virtual wstring getMoviePath();
|
||||||
|
|
||||||
bool m_bUsingCustomSkin; // Flag indicating if a custom external skin is active
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// INPUT
|
// INPUT
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue