Files
GabsPuNs-Project_Zenith_Main/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp
GabsPuNs 7c3e4fb0d5 Some cleanup
Just a bit. i dont want to clean all the ifdef for consoles right now
2026-05-28 18:52:42 -04:00

2217 lines
77 KiB
C++

#include "UI.h"
#include "UIScene_LoadOrJoinMenu.h"
#include "..\..\..\Minecraft.World\StringHelpers.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.item.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.level.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.level.chunk.storage.h"
#include "..\..\..\Minecraft.World\ConsoleSaveFile.h"
#include "..\..\..\Minecraft.World\ConsoleSaveFileOriginal.h"
#include "..\..\ProgressRenderer.h"
#include "..\..\MinecraftServer.h"
#include "..\..\TexturePackRepository.h"
#include "..\..\TexturePack.h"
#include "..\Network\SessionInfo.h"
#include "../../Windows64/KeyboardMouseInput.h"
#ifdef _WINDOWS64
#include "..\..\..\Minecraft.World\NbtIo.h"
#include "..\..\..\Minecraft.World\compression.h"
static wstring ReadLevelNameFromSaveFile(const wstring& filePath)
{
HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (hFile == INVALID_HANDLE_VALUE) return L"";
DWORD fileSize = GetFileSize(hFile, nullptr);
if (fileSize < 12 || fileSize == INVALID_FILE_SIZE)
{
CloseHandle(hFile);
return L"";
}
std::vector<uint8_t> rawData(fileSize);
DWORD bytesRead = 0;
if (!ReadFile(hFile, rawData.data(), fileSize, &bytesRead, nullptr) || bytesRead != fileSize)
{
CloseHandle(hFile);
return L"";
}
CloseHandle(hFile);
std::vector<uint8_t> decompressedData;
uint8_t* saveData = nullptr;
unsigned int saveSize = 0;
if (*reinterpret_cast<unsigned int*>(rawData.data()) == 0)
{
// Compressed format: bytes 0-3=0, bytes 4-7=decompressed size, bytes 8+=compressed data
unsigned int decompSize = *reinterpret_cast<unsigned int*>(rawData.data() + 4);
if (decompSize == 0 || decompSize > 128 * 1024 * 1024)
{
return L"";
}
decompressedData.resize(decompSize);
Compression::getCompression()->Decompress(decompressedData.data(), &decompSize, rawData.data() + 8, fileSize - 8);
saveData = decompressedData.data();
saveSize = decompSize;
}
else
{
saveData = rawData.data();
saveSize = fileSize;
}
wstring result = L"";
if (saveSize >= 12)
{
unsigned int headerOffset = *reinterpret_cast<unsigned int*>(saveData);
unsigned int numEntries = *reinterpret_cast<unsigned int*>(saveData + 4);
const unsigned int entrySize = sizeof(FileEntrySaveData);
if (headerOffset < saveSize && numEntries > 0 && numEntries < 10000 &&
headerOffset + numEntries * entrySize <= saveSize)
{
auto* table = reinterpret_cast<FileEntrySaveData*>(saveData + headerOffset);
for (unsigned int i = 0; i < numEntries; i++)
{
if (wcscmp(table[i].filename, L"level.dat") == 0)
{
unsigned int off = table[i].startOffset;
unsigned int len = table[i].length;
if (off >= 12 && off + len <= saveSize && len > 0 && len < 4 * 1024 * 1024)
{
byteArray ba;
ba.data = saveData + off;
ba.length = len;
std::unique_ptr<CompoundTag> root(NbtIo::decompress(ba));
if (root != nullptr)
{
CompoundTag* dataTag = root->getCompound(L"Data");
if (dataTag != nullptr)
result = dataTag->getString(L"LevelName");
}
}
break;
}
}
}
}
// "world" is the engine default — it means no real name was ever set, so
// return empty to let the caller fall back to the save filename (timestamp).
if (result == L"world") result = L"";
return result;
}
#endif
#define JOIN_LOAD_ONLINE_TIMER_ID 0
#define JOIN_LOAD_ONLINE_TIMER_TIME 100
int UIScene_LoadOrJoinMenu::LoadSaveDataThumbnailReturned(LPVOID lpParam,PBYTE pbThumbnail,DWORD dwThumbnailBytes)
{
UIScene_LoadOrJoinMenu *pClass= static_cast<UIScene_LoadOrJoinMenu *>(lpParam);
app.DebugPrintf("Received data for save thumbnail\n");
if(pbThumbnail && dwThumbnailBytes)
{
pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].pbThumbnailData = new BYTE[dwThumbnailBytes];
memcpy(pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].pbThumbnailData, pbThumbnail, dwThumbnailBytes);
pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].dwThumbnailSize = dwThumbnailBytes;
}
else
{
app.DebugPrintf("Save thumbnail data is nullptr, or has size 0\n");
wstring wsName = L"Graphics\\MinecraftIcon.png";
byteArray baIcon = app.getArchiveFile(wsName);
pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].pbThumbnailData = baIcon.data;
pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].dwThumbnailSize = baIcon.length;
}
pClass->m_bSaveThumbnailReady = true;
return 0;
}
int UIScene_LoadOrJoinMenu::LoadSaveCallback(LPVOID lpParam,bool bRes)
{
//UIScene_LoadOrJoinMenu *pClass= (UIScene_LoadOrJoinMenu *)lpParam;
// Get the save data now
if(bRes)
{
app.DebugPrintf("Loaded save OK\n");
}
return 0;
}
UIScene_LoadOrJoinMenu::UIScene_LoadOrJoinMenu(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
{
constexpr uint64_t MAXIMUM_SAVE_STORAGE = 4LL * 1024LL * 1024LL * 1024LL;
// Setup all the Iggy references we need for this scene
initialiseMovie();
app.SetLiveLinkRequired( true );
m_iRequestingThumbnailId = 0;
m_iSaveInfoC=0;
m_bIgnoreInput = false;
m_bShowingPartyGamesOnly = false;
m_bInParty = false;
m_currentSessions = nullptr;
m_iState=e_SavesIdle;
//m_bRetrievingSaveInfo=false;
m_buttonListSaves.init(eControl_SavesList);
m_buttonListGames.init(eControl_GamesList);
m_labelSavesListTitle.init( IDS_START_GAME );
m_labelJoinListTitle.init( IDS_JOIN_GAME );
m_labelNoGames.init( IDS_NO_GAMES_FOUND );
m_labelNoGames.setVisible( false );
m_controlSavesTimer.setVisible( true );
m_controlJoinTimer.setVisible( true );
#if defined(_XBOX_ONE) || defined(__ORBIS__) || defined(_WINDOWS64)
m_spaceIndicatorSaves.init(L"",eControl_SpaceIndicator,0, MAXIMUM_SAVE_STORAGE);
#endif
m_bUpdateSaveSize = false;
m_bAllLoaded = false;
m_bRetrievingSaveThumbnails = false;
m_bSaveThumbnailReady = false;
m_bExitScene=false;
m_pSaveDetails=nullptr;
m_bSavesDisplayed=false;
m_saveDetails = nullptr;
m_iSaveDetailsCount = 0;
m_iTexturePacksNotInstalled = 0;
m_bCopying = false;
m_bCopyingCancelled = false;
#ifndef _XBOX_ONE
m_bSaveTransferCancelled=false;
m_bSaveTransferInProgress=false;
#endif
m_eAction = eAction_None;
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
int iLB = -1;
#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) || defined(_DURANGO) || defined(_WINDOWS64)
// Always clear the saves when we enter this menu
StorageManager.ClearSavesInfo();
#endif
// block input if we're waiting for DLC to install, and wipe the saves list. The end of dlc mounting custom message will fill the list again
if(app.StartInstallDLCProcess(m_iPad)==true || app.DLCInstallPending())
{
// if we're waiting for DLC to mount, don't fill the save list. The custom message on end of dlc mounting will do that
m_bIgnoreInput = true;
}
else
{
Initialise();
}
UpdateGamesList();
g_NetworkManager.SetSessionsUpdatedCallback( &UpdateGamesListCallback, this );
m_initData= new JoinMenuInitData();
// 4J Stu - Fix for #12530 -TCR 001 BAS Game Stability: Title will crash if the player disconnects while starting a new world and then opts to play the tutorial once they have been returned to the Main Menu.
MinecraftServer::resetFlags();
// If we're not ignoring input, then we aren't still waiting for the DLC to mount, and can now check for corrupt dlc. Otherwise this will happen when the dlc has finished mounting.
if( !m_bIgnoreInput)
{
app.m_dlcManager.checkForCorruptDLCAndAlert();
}
}
UIScene_LoadOrJoinMenu::~UIScene_LoadOrJoinMenu()
{
g_NetworkManager.SetSessionsUpdatedCallback( nullptr, nullptr );
app.SetLiveLinkRequired( false );
if (m_currentSessions)
{
for (const auto& it : *m_currentSessions)
delete it;
delete m_currentSessions;
m_currentSessions = nullptr;
}
#if TO_BE_IMPLEMENTED
// Reset the background downloading, in case we changed it by attempting to download a texture pack
XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_AUTO);
#endif
if(m_saveDetails)
{
for(int i = 0; i < m_iSaveDetailsCount; ++i)
{
delete m_saveDetails[i].pbThumbnailData;
}
delete [] m_saveDetails;
}
}
void UIScene_LoadOrJoinMenu::updateTooltips()
{
#if defined __PS3__ || defined __ORBIS__ || defined __PSVITA__
if(m_eSaveTransferState!=eSaveTransfer_Idle)
{
// we're in a full screen progress for the save download here, so don't change the tooltips
return;
}
#endif
// update the tooltips
// if the saves list has focus, then we should show the Delete Save tooltip
// if the games list has focus, then we should the the View Gamercard tooltip
int iRB=-1;
int iY = -1;
int iLB = -1;
int iX=-1;
if (DoesGamesListHaveFocus() && m_buttonListGames.getItemCount() > 0)
{
iY = IDS_TOOLTIPS_VIEW_GAMERCARD;
}
else if (DoesSavesListHaveFocus())
{
if((m_iDefaultButtonsC > 0) && (m_iSaveListIndex >= m_iDefaultButtonsC))
{
if(StorageManager.GetSaveDisabled())
{
iRB=IDS_TOOLTIPS_DELETESAVE;
}
else
{
if(StorageManager.EnoughSpaceForAMinSaveGame())
{
iRB=IDS_TOOLTIPS_SAVEOPTIONS;
}
else
{
iRB=IDS_TOOLTIPS_DELETESAVE;
}
}
}
}
else if(DoesMashUpWorldHaveFocus())
{
// If it's a mash-up pack world, give the Hide option
iRB=IDS_TOOLTIPS_HIDE;
}
if(m_bInParty)
{
if( m_bShowingPartyGamesOnly ) iLB = IDS_TOOLTIPS_ALL_GAMES;
else iLB = IDS_TOOLTIPS_PARTY_GAMES;
}
#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__)
if(m_iPad == ProfileManager.GetPrimaryPad() ) iY = IDS_TOOLTIPS_GAME_INVITES;
#endif
if(ProfileManager.IsFullVersion()==false )
{
iRB = -1;
}
else if(StorageManager.GetSaveDisabled())
{
#ifdef _XBOX
iX = IDS_TOOLTIPS_SELECTDEVICE;
#endif
}
else
{
#if defined _XBOX_ONE
if(ProfileManager.IsSignedInLive( m_iPad ))
{
// Is there a save from 360 on TMS?
iX=IDS_TOOLTIPS_SAVETRANSFER_DOWNLOAD;
}
#elif defined SONY_REMOTE_STORAGE_DOWNLOAD
// Is there a save from PS3 or PSVita available?
// Sony asked that this be displayed at all times so users are aware of the functionality. We'll display some text when there's no save available
//if(app.getRemoteStorage()->saveIsAvailable())
{
bool bSignedInLive = ProfileManager.IsSignedInLive(m_iPad);
if(bSignedInLive)
{
iX=IDS_TOOLTIPS_SAVETRANSFER_DOWNLOAD;
}
}
#else
iX = IDS_TOOLTIPS_CHANGEDEVICE;
#endif
}
ui.SetTooltips( DEFAULT_XUI_MENU_USER, IDS_TOOLTIPS_SELECT, IDS_TOOLTIPS_BACK, iX, iY,-1,-1,iLB,iRB);
}
//
void UIScene_LoadOrJoinMenu::Initialise()
{
m_iSaveListIndex = 0;
m_iGameListIndex = 0;
#ifdef _WINDOWS64
m_addServerPhase = eAddServer_Idle;
#endif
m_iDefaultButtonsC = 0;
m_iMashUpButtonsC=0;
// Check if we're in the trial version
if(ProfileManager.IsFullVersion()==false)
{
AddDefaultButtons();
#if TO_BE_IMPLEMENTED
m_pSavesList->SetCurSelVisible(0);
#endif
}
else if(StorageManager.GetSaveDisabled())
{
#if defined(__PS3__) || defined(__ORBIS__) || defined (__PSVITA__)
GetSaveInfo();
#else
#if TO_BE_IMPLEMENTED
if(StorageManager.GetSaveDeviceSelected(m_iPad))
#endif
{
// saving is disabled, but we should still be able to load from a selected save device
GetSaveInfo();
}
#if TO_BE_IMPLEMENTED
else
{
AddDefaultButtons();
m_controlSavesTimer.setVisible( false );
}
#endif
#endif // __PS3__ || __ORBIS
}
else
{
// 4J-PB - we need to check that there is enough space left to create a copy of the save (for a rename)
bool bCanRename = StorageManager.EnoughSpaceForAMinSaveGame();
GetSaveInfo();
}
m_bIgnoreInput=false;
app.m_dlcManager.checkForCorruptDLCAndAlert();
}
void UIScene_LoadOrJoinMenu::updateComponents()
{
m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,true);
m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true);
}
void UIScene_LoadOrJoinMenu::handleDestroy()
{
// shut down the keyboard if it is displayed
#if ( defined __PS3__ || defined __ORBIS__ || defined _DURANGO)
InputManager.DestroyKeyboard();
#endif
}
void UIScene_LoadOrJoinMenu::handleGainFocus(bool navBack)
{
UIScene::handleGainFocus(navBack);
updateTooltips();
// Add load online timer
addTimer(JOIN_LOAD_ONLINE_TIMER_ID,JOIN_LOAD_ONLINE_TIMER_TIME);
if(navBack)
{
app.SetLiveLinkRequired( true );
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
// re-enable button presses
m_bIgnoreInput=false;
// block input if we're waiting for DLC to install, and wipe the saves list. The end of dlc mounting custom message will fill the list again
if(app.StartInstallDLCProcess(m_iPad)==false)
{
// not doing a mount, so re-enable input
m_bIgnoreInput=false;
}
else
{
m_bIgnoreInput=true;
m_buttonListSaves.clearList();
m_controlSavesTimer.setVisible(true);
}
if( m_bMultiplayerAllowed )
{
#if TO_BE_IMPLEMENTED
HXUICLASS hClassFullscreenProgress = XuiFindClass( L"CScene_FullscreenProgress" );
HXUICLASS hClassConnectingProgress = XuiFindClass( L"CScene_ConnectingProgress" );
// If we are navigating back from a full screen progress scene, then that means a connection attempt failed
if( XuiIsInstanceOf( hSceneFrom, hClassFullscreenProgress ) || XuiIsInstanceOf( hSceneFrom, hClassConnectingProgress ) )
{
UpdateGamesList();
}
#endif
}
else
{
m_buttonListGames.clearList();
m_controlJoinTimer.setVisible(true);
m_labelNoGames.setVisible(false);
#if TO_BE_IMPLEMENTED
m_SavesList.InitFocus(m_iPad);
#endif
}
// are we back here because of a delete of a corrupt save?
if(app.GetCorruptSaveDeleted())
{
// wipe the list and repopulate it
m_iState=e_SavesRepopulateAfterDelete;
app.SetCorruptSaveDeleted(false);
}
}
}
void UIScene_LoadOrJoinMenu::handleLoseFocus()
{
// Kill load online timer
killTimer(JOIN_LOAD_ONLINE_TIMER_ID);
}
wstring UIScene_LoadOrJoinMenu::getMoviePath()
{
return L"LoadOrJoinMenu";
}
void UIScene_LoadOrJoinMenu::tick()
{
UIScene::tick();
#if (defined __PS3__ || defined __ORBIS__ || defined _DURANGO || defined _WINDOWS64 || defined __PSVITA__)
if(m_bExitScene) // navigate forward or back
{
if(!m_bRetrievingSaveThumbnails)
{
// need to wait for any callback retrieving thumbnail to complete
navigateBack();
}
}
// Stop loading thumbnails if we navigate forwards
if(hasFocus(m_iPad))
{
#ifdef SONY_REMOTE_STORAGE_DOWNLOAD
// if the loadCreateJoin menu has focus again, we can clear the saveTransfer flag now. Added so we can delay the ehternet disconnect till it's cleaned up
if(m_eSaveTransferState == eSaveTransfer_Idle)
m_bSaveTransferRunning = false;
#endif
#if defined(_XBOX_ONE) || defined(__ORBIS__) || defined(_WINDOWS64)
if(m_bUpdateSaveSize)
{
if((m_iDefaultButtonsC > 0) && (m_iSaveListIndex >= m_iDefaultButtonsC))
{
m_spaceIndicatorSaves.selectSave(m_iSaveListIndex-m_iDefaultButtonsC);
}
else
{
m_spaceIndicatorSaves.selectSave(-1);
}
m_bUpdateSaveSize = false;
}
#endif
// Display the saves if we have them
if(!m_bSavesDisplayed)
{
m_pSaveDetails=StorageManager.ReturnSavesInfo();
if(m_pSaveDetails!=nullptr)
{
//CD - Fix - Adding define for ORBIS/XBOXONE
#if defined(_XBOX_ONE) || defined(__ORBIS__) || defined(_WINDOWS64)
m_spaceIndicatorSaves.reset();
#endif
AddDefaultButtons();
m_bSavesDisplayed=true;
UpdateGamesList();
if(m_saveDetails!=nullptr)
{
for(unsigned int i = 0; i < m_iSaveDetailsCount; ++i)
{
if(m_saveDetails[i].pbThumbnailData!=nullptr)
{
delete m_saveDetails[i].pbThumbnailData;
}
}
delete m_saveDetails;
}
m_saveDetails = new SaveListDetails[m_pSaveDetails->iSaveC];
m_iSaveDetailsCount = m_pSaveDetails->iSaveC;
#ifdef _WINDOWS64
// Sort index array by lastWriteTime
int *sortedIdx = new int[m_pSaveDetails->iSaveC];
for (int si = 0; si < (int)m_pSaveDetails->iSaveC; ++si) sortedIdx[si] = si;
for (int si = 1; si < (int)m_pSaveDetails->iSaveC; ++si)
{
int key = sortedIdx[si];
int sj = si - 1;
while (sj >= 0 && CompareFileTime(&m_pSaveDetails->SaveInfoA[sortedIdx[sj]].lastWriteTime, &m_pSaveDetails->SaveInfoA[key].lastWriteTime) < 0)
{
sortedIdx[sj + 1] = sortedIdx[sj];
--sj;
}
sortedIdx[sj + 1] = key;
}
#endif
for(unsigned int i = 0; i < m_pSaveDetails->iSaveC; ++i)
{
#if defined(_XBOX_ONE)
m_spaceIndicatorSaves.addSave(m_pSaveDetails->SaveInfoA[i].totalSize);
#elif defined(_WINDOWS64)
int origIdx = sortedIdx[i];
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
ZeroMemory(wFilename, sizeof(wFilename));
mbstowcs_s(nullptr, wFilename, MAX_SAVEFILENAME_LENGTH, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, _TRUNCATE);
wchar_t wSaveTitle[MAX_DISPLAYNAME_LENGTH];
ZeroMemory(wSaveTitle, sizeof(wSaveTitle));
mbstowcs_s(nullptr, wSaveTitle, MAX_DISPLAYNAME_LENGTH, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, _TRUNCATE);
wstring filePath = wstring(L"Data/Saves") + wstring(wFilename) + std::wstring(wSaveTitle) + L".ms";
HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
DWORD fileSize = 0;
if (hFile != INVALID_HANDLE_VALUE) {
fileSize = GetFileSize(hFile, nullptr);
if (fileSize < 12 || fileSize == INVALID_FILE_SIZE) fileSize = 0;
CloseHandle(hFile);
}
m_spaceIndicatorSaves.addSave(fileSize);
#elif defined(__ORBIS__)
m_spaceIndicatorSaves.addSave(m_pSaveDetails->SaveInfoA[i].blocksUsed * (32 * 1024) );
#endif
#ifdef _DURANGO
m_buttonListSaves.addItem(m_pSaveDetails->SaveInfoA[i].UTF16SaveTitle, L"");
m_saveDetails[i].saveId = i;
memcpy(m_saveDetails[i].UTF16SaveName, m_pSaveDetails->SaveInfoA[i].UTF16SaveTitle, 128);
memcpy(m_saveDetails[i].UTF16SaveFilename, m_pSaveDetails->SaveInfoA[i].UTF16SaveFilename, MAX_SAVEFILENAME_LENGTH);
#else
#ifdef _WINDOWS64
{
wstring levelName = ReadLevelNameFromSaveFile(filePath);
if (!levelName.empty())
{
m_buttonListSaves.addItem(levelName, wstring(L""));
wcstombs_s(nullptr, m_saveDetails[i].UTF8SaveName, 127, levelName.c_str(), _TRUNCATE);
m_saveDetails[i].UTF8SaveName[127] = '\0';
}
else
{
m_buttonListSaves.addItem(m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, L"");
memcpy(m_saveDetails[i].UTF8SaveName, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, 128);
}
m_saveDetails[i].saveId = origIdx;
memcpy(m_saveDetails[i].UTF8SaveFilename, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH);
}
#else
m_buttonListSaves.addItem(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, L"");
memcpy(m_saveDetails[i].UTF8SaveName, m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, 128);
m_saveDetails[i].saveId = i;
memcpy(m_saveDetails[i].UTF8SaveFilename, m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH);
#endif
#endif
}
#ifdef _WINDOWS64
delete[] sortedIdx;
#endif
m_controlSavesTimer.setVisible( false );
// set focus on the first button
}
}
if(!m_bExitScene && m_bSavesDisplayed && !m_bRetrievingSaveThumbnails && !m_bAllLoaded)
{
if( m_iRequestingThumbnailId < (m_buttonListSaves.getItemCount() - m_iDefaultButtonsC ))
{
m_bRetrievingSaveThumbnails = true;
app.DebugPrintf("Requesting the first thumbnail\n");
// set the save to load
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
#ifdef _WINDOWS64
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
#else
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
#endif
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
{
// something went wrong
m_bRetrievingSaveThumbnails=false;
m_bAllLoaded = true;
}
}
}
else if (m_bSavesDisplayed && m_bSaveThumbnailReady)
{
m_bSaveThumbnailReady = false;
// check we're not waiting to exit the scene
if(!m_bExitScene)
{
// convert to utf16
uint16_t u16Message[MAX_SAVEFILENAME_LENGTH];
#ifdef _DURANGO
// Already utf16 on durango
memcpy(u16Message, m_saveDetails[m_iRequestingThumbnailId].UTF16SaveFilename, MAX_SAVEFILENAME_LENGTH);
#elif defined(_WINDOWS64)
int result = ::MultiByteToWideChar(
CP_UTF8, // convert from UTF-8
MB_ERR_INVALID_CHARS, // error on invalid chars
m_saveDetails[m_iRequestingThumbnailId].UTF8SaveFilename, // source UTF-8 string
MAX_SAVEFILENAME_LENGTH, // total length of source UTF-8 string,
// in CHAR's (= bytes), including end-of-string \0
(wchar_t *)u16Message, // destination buffer
MAX_SAVEFILENAME_LENGTH // size of destination buffer, in WCHAR's
);
#endif
if( m_saveDetails[m_iRequestingThumbnailId].pbThumbnailData )
{
registerSubstitutionTexture((wchar_t *)u16Message,m_saveDetails[m_iRequestingThumbnailId].pbThumbnailData,m_saveDetails[m_iRequestingThumbnailId].dwThumbnailSize);
}
m_buttonListSaves.setTextureName(m_iRequestingThumbnailId + m_iDefaultButtonsC, (wchar_t *)u16Message);
++m_iRequestingThumbnailId;
if( m_iRequestingThumbnailId < (m_buttonListSaves.getItemCount() - m_iDefaultButtonsC ))
{
app.DebugPrintf("Requesting another thumbnail\n");
// set the save to load
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
#ifdef _WINDOWS64
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
#else
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
#endif
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
{
// something went wrong
m_bRetrievingSaveThumbnails=false;
m_bAllLoaded = true;
}
}
else
{
m_bRetrievingSaveThumbnails = false;
m_bAllLoaded = true;
}
}
else
{
// stop retrieving thumbnails, and exit
m_bRetrievingSaveThumbnails = false;
}
}
}
switch(m_iState)
{
case e_SavesIdle:
break;
case e_SavesRepopulate:
m_bIgnoreInput = false;
m_iState=e_SavesIdle;
m_bAllLoaded=false;
m_bRetrievingSaveThumbnails=false;
m_iRequestingThumbnailId = 0;
GetSaveInfo();
break;
case e_SavesRepopulateAfterMashupHide:
m_bIgnoreInput = false;
m_iRequestingThumbnailId = 0;
m_bAllLoaded=false;
m_bRetrievingSaveThumbnails=false;
m_bSavesDisplayed=false;
m_iSaveInfoC=0;
m_buttonListSaves.clearList();
GetSaveInfo();
m_iState=e_SavesIdle;
break;
case e_SavesRepopulateAfterDelete:
case e_SavesRepopulateAfterTransferDownload:
m_bIgnoreInput = false;
m_iRequestingThumbnailId = 0;
m_bAllLoaded=false;
m_bRetrievingSaveThumbnails=false;
m_bSavesDisplayed=false;
m_iSaveInfoC=0;
m_buttonListSaves.clearList();
StorageManager.ClearSavesInfo();
GetSaveInfo();
m_iState=e_SavesIdle;
break;
}
#else
if(!m_bSavesDisplayed)
{
AddDefaultButtons();
m_bSavesDisplayed=true;
m_controlSavesTimer.setVisible( false );
}
#endif
}
void UIScene_LoadOrJoinMenu::GetSaveInfo()
{
unsigned int uiSaveC=0;
// This will return with the number retrieved in uiSaveC
if(app.DebugSettingsOn() && app.GetLoadSavesFromFolderEnabled())
{
#ifdef __ORBIS__
// We need to make sure this is non-null so that we have an idea of free space
m_pSaveDetails=StorageManager.ReturnSavesInfo();
if(m_pSaveDetails==nullptr)
{
char savename[] = "save";
C4JStorage::ESaveGameState eSGIStatus= StorageManager.GetSavesInfo(m_iPad,nullptr,this,savename);
}
#endif
uiSaveC = 0;
#ifdef _XBOX
File savesDir(L"GAME:\\Saves");
#else
File savesDir(L"Saves");
#endif
if( savesDir.exists() )
{
m_saves = savesDir.listFiles();
uiSaveC = static_cast<unsigned int>(m_saves->size());
}
// add the New Game and Tutorial after the saves list is retrieved, if there are any saves
// Add two for New Game and Tutorial
unsigned int listItems = uiSaveC;
AddDefaultButtons();
for(unsigned int i=0;i<listItems;i++)
{
wstring wName = m_saves->at(i)->getName();
wchar_t *name = new wchar_t[wName.size()+1];
for(unsigned int j = 0; j < wName.size(); ++j)
{
name[j] = wName[j];
}
name[wName.size()] = 0;
m_buttonListSaves.addItem(name,L"");
}
m_bSavesDisplayed = true;
m_bAllLoaded = true;
m_bIgnoreInput = false;
}
else
{
// clear the saves list
m_bSavesDisplayed = false; // we're blocking the exit from this scene until complete
m_buttonListSaves.clearList();
m_iSaveInfoC=0;
m_controlSavesTimer.setVisible(true);
m_pSaveDetails=StorageManager.ReturnSavesInfo();
if(m_pSaveDetails==nullptr)
{
char savename[] = "save";
C4JStorage::ESaveGameState eSGIStatus= StorageManager.GetSavesInfo(m_iPad, nullptr,this,savename);
}
#if TO_BE_IMPLEMENTED
if(eSGIStatus==C4JStorage::ESGIStatus_NoSaves)
{
uiSaveC=0;
m_controlSavesTimer.setVisible( false );
m_SavesList.SetEnable(TRUE);
}
#endif
}
return;
}
void UIScene_LoadOrJoinMenu::AddDefaultButtons()
{
m_iDefaultButtonsC = 0;
m_iMashUpButtonsC=0;
m_generators.clear();
m_buttonListSaves.addItem(app.GetString(IDS_CREATE_NEW_WORLD));
m_iDefaultButtonsC++;
int i = 0;
for ( LevelGenerationOptions *levelGen : *app.getLevelGenerators() )
{
// retrieve the save icon from the texture pack, if there is one
unsigned int uiTexturePackID=levelGen->getRequiredTexturePackId();
if(uiTexturePackID!=0)
{
unsigned int uiMashUpWorldsBitmask=app.GetMashupPackWorlds(m_iPad);
if((uiMashUpWorldsBitmask & (1<<(uiTexturePackID-1024)))==0)
{
// this world is hidden, so skip
continue;
}
}
// 4J-JEV: For debug. Ignore worlds with no name.
LPCWSTR wstr = levelGen->getWorldName();
m_buttonListSaves.addItem( wstr );
m_generators.push_back(levelGen);
if(uiTexturePackID!=0)
{
// increment the count of the mash-up pack worlds in the save list
m_iMashUpButtonsC++;
TexturePack *tp = Minecraft::GetInstance()->skins->getTexturePackById(levelGen->getRequiredTexturePackId());
DWORD dwImageBytes;
PBYTE pbImageData = tp->getPackIcon(dwImageBytes);
if(dwImageBytes > 0 && pbImageData)
{
wchar_t imageName[64];
swprintf(imageName,64,L"tpack%08x",tp->getId());
registerSubstitutionTexture(imageName, pbImageData, dwImageBytes);
m_buttonListSaves.setTextureName( m_buttonListSaves.getItemCount() - 1, imageName );
}
}
++i;
}
m_iDefaultButtonsC += i;
}
void UIScene_LoadOrJoinMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled)
{
if(m_bIgnoreInput) return;
// if we're retrieving save info, ignore key presses
if(!m_bSavesDisplayed) return;
ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released);
switch(key)
{
case ACTION_MENU_CANCEL:
if(pressed)
{
#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__)
m_bExitScene=true;
#else
navigateBack();
#endif
handled = true;
}
break;
case ACTION_MENU_X:
#if TO_BE_IMPLEMENTED
// Change device
// Fix for #12531 - TCR 001: BAS Game Stability: When a player selects to change a storage
// device, and repeatedly backs out of the SD screen, disconnects from LIVE, and then selects a SD, the title crashes.
m_bIgnoreInput=true;
StorageManager.SetSaveDevice(&CScene_MultiGameJoinLoad::DeviceSelectReturned,this,true);
ui.PlayUISFX(eSFX_Press);
#endif
#ifdef _WINDOWS64
// Right click on a save opens save options (same as RB / ACTION_MENU_RIGHT_SCROLL)
if(pressed && !repeat && ProfileManager.IsFullVersion() && !StorageManager.GetSaveDisabled())
{
if(DoesSavesListHaveFocus() && (m_iDefaultButtonsC > 0) && (m_iSaveListIndex >= m_iDefaultButtonsC))
{
m_bIgnoreInput = true;
if(StorageManager.EnoughSpaceForAMinSaveGame())
{
UINT uiIDA[3];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_TITLE_RENAMESAVE;
uiIDA[2]=IDS_TOOLTIPS_DELETESAVE;
ui.RequestAlertMessage(IDS_TOOLTIPS_SAVEOPTIONS, IDS_TEXT_SAVEOPTIONS, uiIDA, 3, iPad,&UIScene_LoadOrJoinMenu::SaveOptionsDialogReturned,this);
}
else
{
UINT uiIDA[2];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETESAVE, IDS_TEXT_DELETE_SAVE, uiIDA, 2, iPad,&UIScene_LoadOrJoinMenu::DeleteSaveDialogReturned,this);
}
ui.PlayUISFX(eSFX_Press);
}
else if(DoesMashUpWorldHaveFocus() && (m_iSaveListIndex != JOIN_LOAD_CREATE_BUTTON_INDEX))
{
LevelGenerationOptions *levelGen = m_generators.at(m_iSaveListIndex - 1);
if(!levelGen->isTutorial() && levelGen->requiresTexturePack())
{
m_bIgnoreInput = true;
app.HideMashupPackWorld(m_iPad, levelGen->getRequiredTexturePackId());
m_iState = e_SavesRepopulateAfterMashupHide;
}
ui.PlayUISFX(eSFX_Press);
}
}
#endif
break;
case ACTION_MENU_Y:
break;
case ACTION_MENU_RIGHT_SCROLL:
if(DoesSavesListHaveFocus())
{
// 4J-PB - check we are on a valid save
if((m_iDefaultButtonsC != 0) && (m_iSaveListIndex >= m_iDefaultButtonsC))
{
m_bIgnoreInput = true;
// Could be delete save or Save Options
if(StorageManager.GetSaveDisabled())
{
// delete the save game
// Have to ask the player if they are sure they want to delete this game
UINT uiIDA[2];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETESAVE, IDS_TEXT_DELETE_SAVE, uiIDA, 2, iPad,&UIScene_LoadOrJoinMenu::DeleteSaveDialogReturned,this);
}
else
{
if(StorageManager.EnoughSpaceForAMinSaveGame())
{
UINT uiIDA[4];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_TITLE_RENAMESAVE;
uiIDA[2]=IDS_TOOLTIPS_DELETESAVE;
int numOptions = 3;
#ifdef SONY_REMOTE_STORAGE_UPLOAD
if(ProfileManager.IsSignedInLive(ProfileManager.GetPrimaryPad()))
{
numOptions = 4;
uiIDA[3]=IDS_TOOLTIPS_SAVETRANSFER_UPLOAD;
}
#endif
#if defined _XBOX_ONE || defined __ORBIS__
numOptions = 4;
uiIDA[3]=IDS_COPYSAVE;
#endif
ui.RequestAlertMessage(IDS_TOOLTIPS_SAVEOPTIONS, IDS_TEXT_SAVEOPTIONS, uiIDA, numOptions, iPad,&UIScene_LoadOrJoinMenu::SaveOptionsDialogReturned,this);
}
else
{
// delete the save game
// Have to ask the player if they are sure they want to delete this game
UINT uiIDA[2];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETESAVE, IDS_TEXT_DELETE_SAVE, uiIDA, 2,iPad,&UIScene_LoadOrJoinMenu::DeleteSaveDialogReturned,this);
}
}
ui.PlayUISFX(eSFX_Press);
}
}
else if(DoesMashUpWorldHaveFocus())
{
// hiding a mash-up world
if((m_iSaveListIndex != JOIN_LOAD_CREATE_BUTTON_INDEX))
{
LevelGenerationOptions *levelGen = m_generators.at(m_iSaveListIndex - 1);
if(!levelGen->isTutorial())
{
if(levelGen->requiresTexturePack())
{
unsigned int uiPackID=levelGen->getRequiredTexturePackId();
m_bIgnoreInput = true;
app.HideMashupPackWorld(m_iPad,uiPackID);
// update the saves list
m_iState = e_SavesRepopulateAfterMashupHide;
}
}
}
ui.PlayUISFX(eSFX_Press);
}
break;
case ACTION_MENU_LEFT_SCROLL:
#ifdef _XBOX
if( m_bInParty )
{
m_bShowingPartyGamesOnly = !m_bShowingPartyGamesOnly;
UpdateGamesList();
CXuiSceneBase::PlayUISFX(eSFX_Press);
}
#endif
break;
case ACTION_MENU_LEFT:
case ACTION_MENU_RIGHT:
{
// if we are on the saves menu, check there are games in the games list to move to
if(DoesSavesListHaveFocus())
{
if( m_buttonListGames.getItemCount() > 0)
{
sendInputToMovie(key, repeat, pressed, released);
}
}
else
{
sendInputToMovie(key, repeat, pressed, released);
}
}
break;
case ACTION_MENU_OK:
#ifdef __ORBIS__
case ACTION_MENU_TOUCHPAD_PRESS:
#endif
case ACTION_MENU_UP:
case ACTION_MENU_DOWN:
case ACTION_MENU_PAGEUP:
case ACTION_MENU_PAGEDOWN:
sendInputToMovie(key, repeat, pressed, released);
handled = true;
break;
case ACTION_MENU_OTHER_STICK_UP:
sendInputToMovie(ACTION_MENU_UP, repeat, pressed, released);
handled = true;
break;
case ACTION_MENU_OTHER_STICK_DOWN:
sendInputToMovie(ACTION_MENU_DOWN, repeat, pressed, released);
handled = true;
break;
}
}
int UIScene_LoadOrJoinMenu::KeyboardCompleteWorldNameCallback(LPVOID lpParam,bool bRes)
{
// 4J HEG - No reason to set value if keyboard was cancelled
UIScene_LoadOrJoinMenu *pClass=static_cast<UIScene_LoadOrJoinMenu *>(lpParam);
pClass->m_bIgnoreInput=false;
if (bRes)
{
uint16_t ui16Text[128] = {};
#ifdef _WINDOWS64
Win64_GetKeyboardText(ui16Text, 128);
#else
InputManager.GetText(ui16Text);
#endif
// check the name is valid
if(ui16Text[0]!=0)
{
int displayIdx = pClass->m_iSaveListIndex - pClass->m_iDefaultButtonsC;
auto pSaveInfo = &pClass->m_pSaveDetails->SaveInfoA[pClass->m_saveDetails[displayIdx].saveId];
StorageManager.RenameSaveData(pSaveInfo, ui16Text, &UIScene_LoadOrJoinMenu::RenameSaveDataReturned, pClass);
}
else
{
pClass->m_bIgnoreInput=false;
pClass->updateTooltips();
}
}
else
{
pClass->m_bIgnoreInput=false;
pClass->updateTooltips();
}
return 0;
}
void UIScene_LoadOrJoinMenu::handleInitFocus(F64 controlId, F64 childId)
{
app.DebugPrintf(app.USER_SR, "UIScene_LoadOrJoinMenu::handleInitFocus - %d , %d\n", static_cast<int>(controlId), static_cast<int>(childId));
}
void UIScene_LoadOrJoinMenu::handleFocusChange(F64 controlId, F64 childId)
{
app.DebugPrintf(app.USER_SR, "UIScene_LoadOrJoinMenu::handleFocusChange - %d , %d\n", static_cast<int>(controlId), static_cast<int>(childId));
switch(static_cast<int>(controlId))
{
case eControl_GamesList:
m_iGameListIndex = childId;
#ifdef _WINDOWS64
// Offset past the "Add Server" button so m_iGameListIndex is a session index
m_iGameListIndex -= 1;
#endif
m_buttonListGames.updateChildFocus( static_cast<int>(childId) );
break;
case eControl_SavesList:
m_iSaveListIndex = childId;
m_bUpdateSaveSize = true;
break;
};
updateTooltips();
}
#ifdef SONY_REMOTE_STORAGE_DOWNLOAD
void UIScene_LoadOrJoinMenu::remoteStorageGetSaveCallback(LPVOID lpParam, SonyRemoteStorage::Status s, int error_code)
{
app.DebugPrintf("remoteStorageGetCallback err : 0x%08x\n", error_code);
assert(error_code == 0);
((UIScene_LoadOrJoinMenu*)lpParam)->LoadSaveFromCloud();
}
#endif
void UIScene_LoadOrJoinMenu::handlePress(F64 controlId, F64 childId)
{
switch(static_cast<int>(controlId))
{
case eControl_SavesList:
{
m_bIgnoreInput=true;
int lGenID = static_cast<int>(childId) - 1;
//CD - Added for audio
ui.PlayUISFX(eSFX_Press);
if(static_cast<int>(childId) == JOIN_LOAD_CREATE_BUTTON_INDEX)
{
app.SetTutorialMode( false );
m_controlJoinTimer.setVisible( false );
app.SetCorruptSaveDeleted(false);
CreateWorldMenuInitData *params = new CreateWorldMenuInitData();
params->iPad = m_iPad;
ui.NavigateToScene(m_iPad,eUIScene_CreateWorldMenu,(void *)params);
}
else if (lGenID < m_generators.size())
{
LevelGenerationOptions *levelGen = m_generators.at(lGenID);
app.SetTutorialMode( levelGen->isTutorial() );
// Reset the autosave time
app.SetAutosaveTimerTime();
if(levelGen->isTutorial())
{
LoadLevelGen(levelGen);
}
else
{
LoadMenuInitData *params = new LoadMenuInitData();
params->iPad = m_iPad;
// need to get the iIndex from the list item, since the position in the list doesn't correspond to the GetSaveGameInfo list because of sorting
params->iSaveGameInfoIndex=-1;
//params->pbSaveRenamed=&m_bSaveRenamed;
params->levelGen = levelGen;
params->saveDetails = nullptr;
// navigate to the settings scene
ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_LoadMenu, params);
}
}
else
{
#ifdef __ORBIS__
// check if this is a damaged save
PSAVE_INFO pSaveInfo = &m_pSaveDetails->SaveInfoA[((int)childId)-m_iDefaultButtonsC];
if(pSaveInfo->thumbnailData == nullptr && pSaveInfo->modifiedTime == 0) // no thumbnail data and time of zero and zero blocks useset for corrupt files
{
// give the option to delete the save
UINT uiIDA[2];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_CORRUPT_OR_DAMAGED_SAVE_TITLE, IDS_CORRUPT_OR_DAMAGED_SAVE_TEXT, uiIDA, 2, ProfileManager.GetPrimaryPad(),&UIScene_LoadOrJoinMenu::DeleteSaveDialogReturned,this);
}
else
#endif
{
app.SetTutorialMode( false );
if(app.DebugSettingsOn() && app.GetLoadSavesFromFolderEnabled())
{
LoadSaveFromDisk(m_saves->at(static_cast<int>(childId)-m_iDefaultButtonsC));
}
else
{
LoadMenuInitData *params = new LoadMenuInitData();
params->iPad = m_iPad;
// need to get the iIndex from the list item, since the position in the list doesn't correspond to the GetSaveGameInfo list because of sorting
params->iSaveGameInfoIndex=m_saveDetails[static_cast<int>(childId)-m_iDefaultButtonsC].saveId;
//params->pbSaveRenamed=&m_bSaveRenamed;
params->levelGen = nullptr;
params->saveDetails = &m_saveDetails[ static_cast<int>(childId)-m_iDefaultButtonsC ];
#ifdef _XBOX_ONE
// On XB1, saves might need syncing, in which case inform the user so they can decide whether they want to wait for this to happen
if( m_pSaveDetails->SaveInfoA[params->iSaveGameInfoIndex].needsSync )
{
unsigned int uiIDA[2];
uiIDA[0]=IDS_CONFIRM_SYNC;
uiIDA[1]=IDS_CONFIRM_CANCEL;
m_loadMenuInitData = params;
ui.RequestAlertMessage(IDS_LOAD_SAVED_WORLD, IDS_CONFIRM_SYNC_REQUIRED, uiIDA, 2, ProfileManager.GetPrimaryPad(),&NeedSyncMessageReturned,this);
}
else
#endif
{
// navigate to the settings scene
ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_LoadMenu, params);
}
}
}
}
}
break;
case eControl_GamesList:
{
#ifdef _WINDOWS64
if (static_cast<int>(childId) == ADD_SERVER_BUTTON_INDEX)
{
ui.PlayUISFX(eSFX_Press);
BeginAddServer();
break;
}
#endif
m_bIgnoreInput=true;
m_eAction = eAction_JoinGame;
//CD - Added for audio
ui.PlayUISFX(eSFX_Press);
{
int nIndex = static_cast<int>(childId);
#ifdef _WINDOWS64
// Offset by 1 because the "Add Server" button is at index 0
nIndex -= 1;
#endif
m_iGameListIndex = nIndex;
CheckAndJoinGame(nIndex);
}
break;
}
}
}
void UIScene_LoadOrJoinMenu::CheckAndJoinGame(int gameIndex)
{
if( m_buttonListGames.getItemCount() > 0 && gameIndex < m_currentSessions->size() )
{
#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__)
// 4J-PB - is the player allowed to join games?
bool noUGC=false;
bool bContentRestricted=false;
// we're online, since we are joining a game
ProfileManager.GetChatAndContentRestrictions(m_iPad,true,&noUGC,&bContentRestricted,nullptr);
#ifdef __ORBIS__
// 4J Stu - On PS4 we don't restrict playing multiplayer based on chat restriction, so remove this check
noUGC = false;
bool bPlayStationPlus=true;
int iPadWithNoPlaystationPlus=0;
bool isSignedInLive = true;
int iPadNotSignedInLive = -1;
for(unsigned int i = 0; i < XUSER_MAX_COUNT; ++i)
{
if( InputManager.IsPadConnected(i) || ProfileManager.IsSignedIn(i) )
{
if (isSignedInLive && !ProfileManager.IsSignedInLive(i))
{
// Record the first non signed in live pad
iPadNotSignedInLive = i;
}
isSignedInLive = isSignedInLive && ProfileManager.IsSignedInLive(i);
if(ProfileManager.HasPlayStationPlus(i)==false)
{
bPlayStationPlus=false;
break;
}
}
}
#endif
if(noUGC)
{
// not allowed to join
#ifndef __PSVITA__
UINT uiIDA[1];
uiIDA[0]=IDS_CONFIRM_OK;
// Not allowed to play online
ui.RequestAlertMessage(IDS_ONLINE_GAME, IDS_CHAT_RESTRICTION_UGC, uiIDA, 1, m_iPad,nullptr,this);
#else
// Not allowed to play online
ProfileManager.ShowSystemMessage( SCE_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION, 0 );
#endif
m_bIgnoreInput=false;
return;
}
else if(bContentRestricted)
{
ui.RequestContentRestrictedMessageBox();
m_bIgnoreInput=false;
return;
}
#ifdef __ORBIS__
// If this is an online game but not all players are signed in to Live, stop!
else if (!isSignedInLive)
{
UINT uiIDA[1];
uiIDA[0]=IDS_CONFIRM_OK;
// Check if PSN is unavailable because of age restriction
int npAvailability = ProfileManager.getNPAvailability(iPadNotSignedInLive);
if (npAvailability == SCE_NP_ERROR_AGE_RESTRICTION)
{
m_bIgnoreInput = false;
// 4J Stu - This is a bit messy and is due to the library incorrectly returning false for IsSignedInLive if the npAvailability isn't SCE_OK
ui.RequestErrorMessage(IDS_ONLINE_SERVICE_TITLE, IDS_CONTENT_RESTRICTION, uiIDA, 1, iPadNotSignedInLive);
}
else
{
ui.RequestErrorMessage( IDS_PRO_NOTONLINE_TITLE, IDS_PRO_NOTONLINE_TEXT, uiIDA,1,iPadNotSignedInLive, &UIScene_LoadOrJoinMenu::MustSignInReturnedPSN, this);
}
return;
}
else if(bPlayStationPlus==false)
{
if(ProfileManager.RequestingPlaystationPlus(iPadWithNoPlaystationPlus))
{
// MGH - added this so we don't try and upsell when we don't know if the player has PS Plus yet (if it can't connect to the PS Plus server).
UINT uiIDA[1];
uiIDA[0]=IDS_OK;
ui.RequestAlertMessage(IDS_ERROR_NETWORK_TITLE, IDS_ERROR_NETWORK, uiIDA, 1, ProfileManager.GetPrimaryPad(), nullptr, nullptr);
return;
}
// PS Plus upsell
// 4J-PB - we're not allowed to show the text Playstation Plus - have to call the upsell all the time!
// upsell psplus
int32_t iResult=sceNpCommerceDialogInitialize();
SceNpCommerceDialogParam param;
sceNpCommerceDialogParamInitialize(&param);
param.mode=SCE_NP_COMMERCE_DIALOG_MODE_PLUS;
param.features = SCE_NP_PLUS_FEATURE_REALTIME_MULTIPLAY;
param.userId = ProfileManager.getUserID(iPadWithNoPlaystationPlus);
iResult=sceNpCommerceDialogOpen(&param);
// UINT uiIDA[2];
// uiIDA[0]=IDS_CONFIRM_OK;
// uiIDA[1]=IDS_PLAYSTATIONPLUS_SIGNUP;
// ui.RequestMessageBox( IDS_FAILED_TO_CREATE_GAME_TITLE, IDS_NO_PLAYSTATIONPLUS, uiIDA,2,ProfileManager.GetPrimaryPad(),&UIScene_LoadOrJoinMenu::PSPlusReturned,this, app.GetStringTable(),nullptr,0,false);
m_bIgnoreInput=false;
return;
}
#endif
#endif
m_initData->iPad = 0;;
m_initData->selectedSession = m_currentSessions->at( gameIndex );
#ifdef _WINDOWS64
{
int serverDbCount = 0;
FILE* dbFile = fopen("servers.db", "rb");
if (dbFile)
{
char magic[4] = {};
if (fread(magic, 1, 4, dbFile) == 4 && memcmp(magic, "MCSV", 4) == 0)
{
uint32_t version = 0, count = 0;
fread(&version, sizeof(uint32_t), 1, dbFile);
fread(&count, sizeof(uint32_t), 1, dbFile);
if (version == 1)
serverDbCount = static_cast<int>(count);
}
fclose(dbFile);
}
int lanCount = static_cast<int>(m_currentSessions->size()) - serverDbCount;
if (gameIndex >= lanCount && lanCount >= 0)
m_initData->serverIndex = gameIndex - lanCount;
else
m_initData->serverIndex = -1;
}
#endif
if(m_initData->selectedSession->data.texturePackParentId!=0)
{
int texturePacksCount = Minecraft::GetInstance()->skins->getTexturePackCount();
bool bHasTexturePackInstalled=false;
for(int i=0;i<texturePacksCount;i++)
{
TexturePack *tp = Minecraft::GetInstance()->skins->getTexturePackByIndex(i);
if(tp->getDLCParentPackId()==m_initData->selectedSession->data.texturePackParentId)
{
bHasTexturePackInstalled=true;
break;
}
}
if(bHasTexturePackInstalled==false)
{
return;
}
}
m_controlJoinTimer.setVisible( false );
#ifdef _XBOX
XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_AUTO);
#endif
m_bIgnoreInput=true;
ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_JoinMenu,m_initData);
}
}
void UIScene_LoadOrJoinMenu::LoadLevelGen(LevelGenerationOptions *levelGen)
{
// Load data from disc
//File saveFile( L"Tutorial\\Tutorial" );
//LoadSaveFromDisk(&saveFile);
// clear out the app's terrain features list
app.ClearTerrainFeaturePosition();
StorageManager.ResetSaveData();
// Make our next save default to the name of the level
StorageManager.SetSaveTitle(levelGen->getDefaultSaveName().c_str());
bool isClientSide = false;
bool isPrivate = false;
// TODO int maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
int maxPlayers = 8;
if( app.GetTutorialMode() )
{
isClientSide = false;
maxPlayers = 4;
}
g_NetworkManager.HostGame(0,isClientSide,isPrivate,maxPlayers,0);
NetworkGameInitData *param = new NetworkGameInitData();
param->seed = 0;
param->saveData = nullptr;
param->settings = app.GetGameHostOption( eGameHostOption_Tutorial );
param->levelGen = levelGen;
if(levelGen->requiresTexturePack())
{
param->texturePackId = levelGen->getRequiredTexturePackId();
Minecraft *pMinecraft = Minecraft::GetInstance();
pMinecraft->skins->selectTexturePackById(param->texturePackId);
//pMinecraft->skins->updateUI();
}
#ifndef _XBOX
g_NetworkManager.FakeLocalPlayerJoined();
#endif
LoadingInputParams *loadingParams = new LoadingInputParams();
loadingParams->func = &CGameNetworkManager::RunNetworkGameThreadProc;
loadingParams->lpParam = static_cast<LPVOID>(param);
UIFullscreenProgressCompletionData *completionData = new UIFullscreenProgressCompletionData();
completionData->bShowBackground=TRUE;
completionData->bShowLogo=TRUE;
completionData->type = e_ProgressCompletion_CloseAllPlayersUIScenes;
completionData->iPad = DEFAULT_XUI_MENU_USER;
loadingParams->completionData = completionData;
ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_FullscreenProgress, loadingParams);
}
void UIScene_LoadOrJoinMenu::UpdateGamesListCallback(LPVOID pParam)
{
if(pParam != nullptr)
{
UIScene_LoadOrJoinMenu *pScene = static_cast<UIScene_LoadOrJoinMenu *>(pParam);
pScene->UpdateGamesList();
}
}
void UIScene_LoadOrJoinMenu::UpdateGamesList()
{
// If we're ignoring input scene isn't active so do nothing
if (m_bIgnoreInput) return;
// If a texture pack is loading, or will be loading, then ignore this ( we are going to be destroyed anyway)
if( Minecraft::GetInstance()->skins->getSelected()->isLoadingData() || (Minecraft::GetInstance()->skins->needsUIUpdate() || ui.IsReloadingSkin()) ) return;
// if we're retrieving save info, don't show the list yet as we will be ignoring press events
if(!m_bSavesDisplayed)
{
return;
}
FriendSessionInfo *pSelectedSession = nullptr;
if(DoesGamesListHaveFocus() && m_buttonListGames.getItemCount() > 0)
{
unsigned int nIndex = m_buttonListGames.getCurrentSelection();
#ifdef _WINDOWS64
// Offset past the "Add Server" button
if (nIndex > 0)
pSelectedSession = m_currentSessions->at( nIndex - 1 );
#else
pSelectedSession = m_currentSessions->at( nIndex );
#endif
}
SessionID selectedSessionId;
ZeroMemory(&selectedSessionId,sizeof(SessionID));
if( pSelectedSession != nullptr )selectedSessionId = pSelectedSession->sessionId;
pSelectedSession = nullptr;
m_controlJoinTimer.setVisible( false );
// if the saves list has focus, then we should show the Delete Save tooltip
// if the games list has focus, then we should show the View Gamercard tooltip
int iRB=-1;
int iY = -1;
int iX=-1;
vector<FriendSessionInfo*>* newSessions = g_NetworkManager.GetSessionList( m_iPad, 1, m_bShowingPartyGamesOnly );
if (m_currentSessions != nullptr && m_currentSessions->size() == newSessions->size())
{
bool same = true;
for (size_t i = 0; i < newSessions->size(); i++)
{
if (memcmp(&(*m_currentSessions)[i]->sessionId, &(*newSessions)[i]->sessionId, sizeof(SessionID)) != 0 ||
wcscmp((*m_currentSessions)[i]->displayLabel ? (*m_currentSessions)[i]->displayLabel : L"",
(*newSessions)[i]->displayLabel ? (*newSessions)[i]->displayLabel : L"") != 0)
{
same = false;
break;
}
}
if (same)
{
for (auto& it : *newSessions)
delete it;
delete newSessions;
return;
}
}
if (m_currentSessions)
{
for (auto& it : *m_currentSessions)
delete it;
delete m_currentSessions;
}
m_currentSessions = newSessions;
// Update the xui list displayed
unsigned int xuiListSize = m_buttonListGames.getItemCount();
unsigned int filteredListSize = static_cast<unsigned int>(m_currentSessions->size());
BOOL gamesListHasFocus = DoesGamesListHaveFocus();
if(filteredListSize > 0)
{
#if TO_BE_IMPLEMENTED
if( !m_pGamesList->IsEnabled() )
{
m_pGamesList->SetEnable(TRUE);
m_pGamesList->SetCurSel( 0 );
}
#endif
m_labelNoGames.setVisible( false );
m_controlJoinTimer.setVisible( false );
}
else
{
#if TO_BE_IMPLEMENTED
m_pGamesList->SetEnable(FALSE);
#endif
m_controlJoinTimer.setVisible( false );
m_labelNoGames.setVisible( true );
#if TO_BE_IMPLEMENTED
if( gamesListHasFocus ) m_pGamesList->InitFocus(m_iPad);
#endif
}
// clear out the games list and re-fill
m_buttonListGames.clearList();
#ifdef _WINDOWS64
// Always add the "Add Server" button as the first entry in the games list
m_buttonListGames.addItem(app.GetString(IDS_SERVER_ADD));
#endif
if( filteredListSize > 0 )
{
// Reset the focus to the selected session if it still exists
unsigned int sessionIndex = 0;
m_buttonListGames.setCurrentSelection(0);
for( FriendSessionInfo *sessionInfo : *m_currentSessions )
{
wchar_t textureName[64] = L"\0";
// Is this a default game or a texture pack game?
if(sessionInfo->data.texturePackParentId!=0)
{
// Do we have the texture pack
Minecraft *pMinecraft = Minecraft::GetInstance();
TexturePack *tp = pMinecraft->skins->getTexturePackById(sessionInfo->data.texturePackParentId);
HRESULT hr;
DWORD dwImageBytes=0;
PBYTE pbImageData=nullptr;
if(tp==nullptr)
{
DWORD dwBytes=0;
PBYTE pbData=nullptr;
app.GetTPD(sessionInfo->data.texturePackParentId,&pbData,&dwBytes);
// is it in the tpd data ?
app.GetFileFromTPD(eTPDFileType_Icon,pbData,dwBytes,&pbImageData,&dwImageBytes );
if(dwImageBytes > 0 && pbImageData)
{
swprintf(textureName,64,L"%ls",sessionInfo->displayLabel);
registerSubstitutionTexture(textureName,pbImageData,dwImageBytes);
}
}
else
{
pbImageData = tp->getPackIcon(dwImageBytes);
if(dwImageBytes > 0 && pbImageData)
{
swprintf(textureName,64,L"%ls",sessionInfo->displayLabel);
registerSubstitutionTexture(textureName,pbImageData,dwImageBytes);
}
}
}
else
{
// default texture pack
Minecraft *pMinecraft = Minecraft::GetInstance();
TexturePack *tp = pMinecraft->skins->getTexturePackByIndex(0);
DWORD dwImageBytes;
PBYTE pbImageData = tp->getPackIcon(dwImageBytes);
if(dwImageBytes > 0 && pbImageData)
{
swprintf(textureName,64,L"%ls",sessionInfo->displayLabel);
registerSubstitutionTexture(textureName,pbImageData,dwImageBytes);
}
}
m_buttonListGames.addItem( sessionInfo->displayLabel, textureName );
if(memcmp( &selectedSessionId, &sessionInfo->sessionId, sizeof(SessionID) ) == 0)
{
#ifdef _WINDOWS64
// Offset past the "Add Server" button
m_buttonListGames.setCurrentSelection(sessionIndex + 1);
#else
m_buttonListGames.setCurrentSelection(sessionIndex);
#endif
break;
}
++sessionIndex;
}
}
updateTooltips();
}
void UIScene_LoadOrJoinMenu::HandleDLCMountingComplete()
{
Initialise();
}
bool UIScene_LoadOrJoinMenu::DoesSavesListHaveFocus()
{
if( m_buttonListSaves.hasFocus() )
{
// check it's not the first or second element (new world or tutorial)
if(m_iSaveListIndex > (m_iDefaultButtonsC-1))
{
return true;
}
}
return false;
}
bool UIScene_LoadOrJoinMenu::DoesMashUpWorldHaveFocus()
{
if(m_buttonListSaves.hasFocus())
{
// check it's not the first or second element (new world or tutorial)
if(m_iSaveListIndex > (m_iDefaultButtonsC - 1))
{
return false;
}
if(m_iSaveListIndex > (m_iDefaultButtonsC - 1 - m_iMashUpButtonsC))
{
return true;
}
else return false;
}
else return false;
}
bool UIScene_LoadOrJoinMenu::DoesGamesListHaveFocus()
{
return m_buttonListGames.hasFocus();
}
void UIScene_LoadOrJoinMenu::handleTimerComplete(int id)
{
switch(id)
{
case JOIN_LOAD_ONLINE_TIMER_ID:
{
bool bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
if(bMultiplayerAllowed != m_bMultiplayerAllowed)
{
if( bMultiplayerAllowed )
{
// m_CheckboxOnline.SetEnable(TRUE);
// m_CheckboxPrivate.SetEnable(TRUE);
}
else
{
m_bInParty = false;
m_buttonListGames.clearList();
m_controlJoinTimer.setVisible( true );
m_labelNoGames.setVisible( false );
}
m_bMultiplayerAllowed = bMultiplayerAllowed;
}
}
break;
}
}
void UIScene_LoadOrJoinMenu::LoadSaveFromDisk(File *saveFile, ESavePlatform savePlatform /*= SAVE_FILE_PLATFORM_LOCAL*/)
{
// we'll only be coming in here when the tutorial is loaded now
StorageManager.ResetSaveData();
// Make our next save default to the name of the level
StorageManager.SetSaveTitle(saveFile->getName().c_str());
int64_t fileSize = saveFile->length();
FileInputStream fis(*saveFile);
byteArray ba(static_cast<unsigned int>(fileSize));
fis.read(ba);
fis.close();
bool isClientSide = false;
bool isPrivate = false;
int maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
if( app.GetTutorialMode() )
{
isClientSide = false;
maxPlayers = 4;
}
app.SetGameHostOption(eGameHostOption_GameType,GameType::CREATIVE->getId() );
g_NetworkManager.HostGame(0,isClientSide,isPrivate,maxPlayers,0);
LoadSaveDataThreadParam *saveData = new LoadSaveDataThreadParam(ba.data, ba.length, saveFile->getName());
NetworkGameInitData *param = new NetworkGameInitData();
param->seed = 0;
param->saveData = saveData;
param->settings = app.GetGameHostOption( eGameHostOption_All );
param->savePlatform = savePlatform;
#ifndef _XBOX
g_NetworkManager.FakeLocalPlayerJoined();
#endif
LoadingInputParams *loadingParams = new LoadingInputParams();
loadingParams->func = &CGameNetworkManager::RunNetworkGameThreadProc;
loadingParams->lpParam = static_cast<LPVOID>(param);
UIFullscreenProgressCompletionData *completionData = new UIFullscreenProgressCompletionData();
completionData->bShowBackground=TRUE;
completionData->bShowLogo=TRUE;
completionData->type = e_ProgressCompletion_CloseAllPlayersUIScenes;
completionData->iPad = DEFAULT_XUI_MENU_USER;
loadingParams->completionData = completionData;
ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_FullscreenProgress, loadingParams);
}
int UIScene_LoadOrJoinMenu::DeleteSaveDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result)
{
auto* pClass = static_cast<UIScene_LoadOrJoinMenu*>(pParam);
// results switched for this dialog
// Check that we have a valid save selected (can get a bad index if the save list has been refreshed)
bool validSelection= pClass->m_iDefaultButtonsC != 0 && pClass->m_iSaveListIndex >= pClass->m_iDefaultButtonsC;
if(result==C4JStorage::EMessage_ResultDecline && validSelection)
{
if(app.DebugSettingsOn() && app.GetLoadSavesFromFolderEnabled())
{
pClass->m_bIgnoreInput=false;
}
else
{
int displayIdx = pClass->m_iSaveListIndex - pClass->m_iDefaultButtonsC;
if (pClass->m_saveDetails && displayIdx >= 0 && pClass->m_saveDetails[displayIdx].UTF8SaveFilename[0])
{
auto pSaveInfo = &pClass->m_pSaveDetails->SaveInfoA[pClass->m_saveDetails[displayIdx].saveId];
StorageManager.DeleteSaveData(pSaveInfo, &UIScene_LoadOrJoinMenu::DeleteSaveDataReturned, (LPVOID)pClass->GetCallbackUniqueId());
}
pClass->m_controlSavesTimer.setVisible( true );
}
}
else
{
pClass->m_bIgnoreInput=false;
}
return 0;
}
int UIScene_LoadOrJoinMenu::DeleteSaveDataReturned(LPVOID lpParam,bool bRes)
{
ui.EnterCallbackIdCriticalSection();
UIScene_LoadOrJoinMenu* pClass = static_cast<UIScene_LoadOrJoinMenu *>(ui.GetSceneFromCallbackId((size_t)lpParam));
if(pClass)
{
if(bRes)
{
// wipe the list and repopulate it
pClass->m_iState=e_SavesRepopulateAfterDelete;
}
else pClass->m_bIgnoreInput=false;
pClass->updateTooltips();
}
ui.LeaveCallbackIdCriticalSection();
return 0;
}
int UIScene_LoadOrJoinMenu::RenameSaveDataReturned(LPVOID lpParam,bool bRes)
{
UIScene_LoadOrJoinMenu* pClass = static_cast<UIScene_LoadOrJoinMenu *>(lpParam);
if(bRes)
{
pClass->m_iState=e_SavesRepopulate;
}
else pClass->m_bIgnoreInput=false;
pClass->updateTooltips();
return 0;
}
int UIScene_LoadOrJoinMenu::SaveOptionsDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result)
{
UIScene_LoadOrJoinMenu* pClass = static_cast<UIScene_LoadOrJoinMenu *>(pParam);
// results switched for this dialog
// EMessage_ResultAccept means cancel
switch(result)
{
case C4JStorage::EMessage_ResultDecline: // rename
{
pClass->m_bIgnoreInput=true;
#ifdef _WINDOWS64
{
wchar_t wSaveName[128];
ZeroMemory(wSaveName, sizeof(wSaveName));
mbstowcs_s(nullptr, wSaveName, 128, pClass->m_saveDetails[pClass->m_iSaveListIndex - pClass->m_iDefaultButtonsC].UTF8SaveName, _TRUNCATE);
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_RENAME_WORLD_TITLE);
kbData.defaultText = wSaveName;
kbData.maxChars = 25;
kbData.callback = &UIScene_LoadOrJoinMenu::KeyboardCompleteWorldNameCallback;
kbData.lpParam = pClass;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
}
#elif defined _DURANGO
// bring up a keyboard
InputManager.RequestKeyboard(app.GetString(IDS_RENAME_WORLD_TITLE), (pClass->m_saveDetails[pClass->m_iSaveListIndex-pClass->m_iDefaultButtonsC]).UTF16SaveName,(DWORD)0,25,&UIScene_LoadOrJoinMenu::KeyboardCompleteWorldNameCallback,pClass,C_4JInput::EKeyboardMode_Default);
#else
// bring up a keyboard
wchar_t wSaveName[128];
//CD - Fix - We must memset the SaveName
ZeroMemory(wSaveName, 128 * sizeof(wchar_t) );
mbstowcs(wSaveName, pClass->m_saveDetails[pClass->m_iSaveListIndex - pClass->m_iDefaultButtonsC].UTF8SaveName, strlen(pClass->m_saveDetails->UTF8SaveName)+1); // plus null
LPWSTR ptr = wSaveName;
InputManager.RequestKeyboard(app.GetString(IDS_RENAME_WORLD_TITLE),wSaveName,(DWORD)0,25,&UIScene_LoadOrJoinMenu::KeyboardCompleteWorldNameCallback,pClass,C_4JInput::EKeyboardMode_Default);
#endif
}
break;
case C4JStorage::EMessage_ResultThirdOption: // delete -
{
// delete the save game
// Have to ask the player if they are sure they want to delete this game
UINT uiIDA[2];
uiIDA[0]=IDS_CONFIRM_CANCEL;
uiIDA[1]=IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETESAVE, IDS_TEXT_DELETE_SAVE, uiIDA, 2, iPad,&UIScene_LoadOrJoinMenu::DeleteSaveDialogReturned,pClass);
}
break;
case C4JStorage::EMessage_Cancelled:
default:
{
// reset the tooltips
pClass->updateTooltips();
pClass->m_bIgnoreInput=false;
}
break;
}
return 0;
}
int UIScene_LoadOrJoinMenu::TexturePackDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result)
{
UIScene_LoadOrJoinMenu *pClass = static_cast<UIScene_LoadOrJoinMenu *>(pParam);
// Exit with or without saving
if(result==C4JStorage::EMessage_ResultAccept)
{
// we need to enable background downloading for the DLC
XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_ALWAYS_ALLOW);
}
pClass->m_bIgnoreInput=false;
return 0;
}
#ifdef _WINDOWS64
// adding servers bellow
void UIScene_LoadOrJoinMenu::BeginAddServer()
{
m_addServerPhase = eAddServer_IP;
m_addServerIP.clear();
m_addServerPort.clear();
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_SERVER_ADDRESS);
kbData.defaultText = L"";
kbData.maxChars = 128;
kbData.callback = &UIScene_LoadOrJoinMenu::AddServerKeyboardCallback;
kbData.lpParam = this;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(m_iPad, eUIScene_Keyboard, &kbData);
}
int UIScene_LoadOrJoinMenu::AddServerKeyboardCallback(LPVOID lpParam, bool bRes)
{
UIScene_LoadOrJoinMenu *pClass = static_cast<UIScene_LoadOrJoinMenu*>(lpParam);
if (!bRes)
{
pClass->m_addServerPhase = eAddServer_Idle;
pClass->m_bIgnoreInput = false;
return 0;
}
uint16_t ui16Text[128] = {};
Win64_GetKeyboardText(ui16Text, 128);
wchar_t wBuf[128] = {};
for (int k = 0; k < 127 && ui16Text[k]; k++)
wBuf[k] = static_cast<wchar_t>(ui16Text[k]);
if (wBuf[0] == 0)
{
pClass->m_addServerPhase = eAddServer_Idle;
pClass->m_bIgnoreInput = false;
return 0;
}
switch (pClass->m_addServerPhase)
{
case eAddServer_IP:
{
pClass->m_addServerIP = wBuf;
pClass->m_addServerPhase = eAddServer_Port;
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_SERVER_PORT);
kbData.defaultText = L"25565";
kbData.maxChars = 6;
kbData.callback = &UIScene_LoadOrJoinMenu::AddServerKeyboardCallback;
kbData.lpParam = pClass;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
break;
}
case eAddServer_Port:
{
pClass->m_addServerPort = wBuf;
pClass->m_addServerPhase = eAddServer_Name;
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_SERVER_NAME);
kbData.defaultText = app.GetString(IDS_SERVER_MINECRAFT);
kbData.maxChars = 64;
kbData.callback = &UIScene_LoadOrJoinMenu::AddServerKeyboardCallback;
kbData.lpParam = pClass;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
break;
}
case eAddServer_Name:
{
wstring name = wBuf;
pClass->AppendServerToFile(pClass->m_addServerIP, pClass->m_addServerPort, name);
pClass->m_addServerPhase = eAddServer_Idle;
pClass->m_bIgnoreInput = false;
g_NetworkManager.ForceFriendsSessionRefresh();
break;
}
default:
pClass->m_addServerPhase = eAddServer_Idle;
pClass->m_bIgnoreInput = false;
break;
}
return 0;
}
void UIScene_LoadOrJoinMenu::AppendServerToFile(const wstring& ip, const wstring& port, const wstring& name)
{
char narrowIP[256] = {};
char narrowPort[16] = {};
char narrowName[256] = {};
wcstombs_s(nullptr, narrowIP, sizeof(narrowIP), ip.c_str(), _TRUNCATE);
wcstombs_s(nullptr, narrowPort, sizeof(narrowPort), port.c_str(), _TRUNCATE);
wcstombs_s(nullptr, narrowName, sizeof(narrowName), name.c_str(), _TRUNCATE);
uint16_t portNum = static_cast<uint16_t>(atoi(narrowPort));
struct ServerEntry { std::string ip; uint16_t port; std::string name; };
std::vector<ServerEntry> entries;
FILE* file = fopen("servers.db", "rb");
if (file)
{
char magic[4] = {};
if (fread(magic, 1, 4, file) == 4 && memcmp(magic, "MCSV", 4) == 0)
{
uint32_t version = 0, count = 0;
fread(&version, sizeof(uint32_t), 1, file);
fread(&count, sizeof(uint32_t), 1, file);
if (version == 1)
{
for (uint32_t s = 0; s < count; s++)
{
uint16_t ipLen = 0, p = 0, nameLen = 0;
if (fread(&ipLen, sizeof(uint16_t), 1, file) != 1) break;
if (ipLen == 0 || ipLen > 256) break;
char ipBuf[257] = {};
if (fread(ipBuf, 1, ipLen, file) != ipLen) break;
if (fread(&p, sizeof(uint16_t), 1, file) != 1) break;
if (fread(&nameLen, sizeof(uint16_t), 1, file) != 1) break;
if (nameLen > 256) break;
char nameBuf[257] = {};
if (nameLen > 0 && fread(nameBuf, 1, nameLen, file) != nameLen) break;
entries.push_back({std::string(ipBuf), p, std::string(nameBuf)});
}
}
}
fclose(file);
}
entries.push_back({std::string(narrowIP), portNum, std::string(narrowName)});
file = fopen("servers.db", "wb");
if (file)
{
fwrite("MCSV", 1, 4, file);
uint32_t version = 1;
uint32_t count = static_cast<uint32_t>(entries.size());
fwrite(&version, sizeof(uint32_t), 1, file);
fwrite(&count, sizeof(uint32_t), 1, file);
for (size_t i = 0; i < entries.size(); i++)
{
uint16_t ipLen = static_cast<uint16_t>(entries[i].ip.length());
fwrite(&ipLen, sizeof(uint16_t), 1, file);
fwrite(entries[i].ip.c_str(), 1, ipLen, file);
fwrite(&entries[i].port, sizeof(uint16_t), 1, file);
uint16_t nameLen = static_cast<uint16_t>(entries[i].name.length());
fwrite(&nameLen, sizeof(uint16_t), 1, file);
fwrite(entries[i].name.c_str(), 1, nameLen, file);
}
fclose(file);
}
}
#endif // _WINDOWS64