Merge branch 'main' into feat/project-modernization

This commit is contained in:
Chase Cooper 2026-03-08 19:00:22 -04:00
commit e12adc1a22
65 changed files with 2665 additions and 552 deletions

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: MinecraftConsoles Community Discord
url: https://discord.gg/jrum7HhegA
about: If you need help, please ask for it in our Discord! You will get assistance much faster there, including help getting the project to compile.

View file

@ -142,6 +142,13 @@ ClientConnection::ClientConnection(Minecraft *minecraft, Socket *socket, int iUs
deferredEntityLinkPackets = vector<DeferredEntityLinkPacket>();
}
bool ClientConnection::isPrimaryConnection() const
{
// On host, all connections are primary (server is authoritative).
// On non-host, only the primary pad processes shared entity state.
return g_NetworkManager.IsHost() || m_userIndex == ProfileManager.GetPrimaryPad();
}
ClientConnection::~ClientConnection()
{
delete connection;
@ -304,6 +311,10 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
level->isClientSide = true;
minecraft->setLevel(level);
}
else
{
level = (MultiPlayerLevel *)dimensionLevel;
}
minecraft->player->setPlayerIndex( packet->m_playerIndex );
minecraft->player->setCustomSkin( app.GetPlayerSkinId(m_userIndex) );
@ -427,6 +438,7 @@ void ClientConnection::handleAddEntity(shared_ptr<AddEntityPacket> packet)
{
case AddEntityPacket::MINECART:
e = Minecart::createMinecart(level, x, y, z, packet->data);
break;
case AddEntityPacket::FISH_HOOK:
{
// 4J Stu - Brought forward from 1.4 to be able to drop XP from fishing
@ -704,6 +716,7 @@ void ClientConnection::handleAddExperienceOrb(shared_ptr<AddExperienceOrbPacket>
void ClientConnection::handleAddGlobalEntity(shared_ptr<AddGlobalEntityPacket> packet)
{
if (!isPrimaryConnection()) return;
double x = packet->x / 32.0;
double y = packet->y / 32.0;
double z = packet->z / 32.0;
@ -729,6 +742,13 @@ void ClientConnection::handleAddPainting(shared_ptr<AddPaintingPacket> packet)
void ClientConnection::handleSetEntityMotion(shared_ptr<SetEntityMotionPacket> packet)
{
if (!isPrimaryConnection())
{
// Secondary connection: only accept motion for our own local player (knockback)
if (minecraft->localplayers[m_userIndex] == NULL ||
packet->id != minecraft->localplayers[m_userIndex]->entityId)
return;
}
shared_ptr<Entity> e = getEntity(packet->id);
if (e == nullptr) return;
e->lerpMotion(packet->xa / 8000.0, packet->ya / 8000.0, packet->za / 8000.0);
@ -959,6 +979,7 @@ void ClientConnection::handleSetCarriedItem(shared_ptr<SetCarriedItemPacket> pac
void ClientConnection::handleMoveEntity(shared_ptr<MoveEntityPacket> packet)
{
if (!isPrimaryConnection()) return;
shared_ptr<Entity> e = getEntity(packet->id);
if (e == nullptr) return;
e->xp += packet->xa;
@ -988,6 +1009,7 @@ void ClientConnection::handleRotateMob(shared_ptr<RotateHeadPacket> packet)
void ClientConnection::handleMoveEntitySmall(shared_ptr<MoveEntityPacketSmall> packet)
{
if (!isPrimaryConnection()) return;
shared_ptr<Entity> e = getEntity(packet->id);
if (e == nullptr) return;
e->xp += packet->xa;
@ -1112,6 +1134,7 @@ void ClientConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
// 4J Added
void ClientConnection::handleChunkVisibilityArea(shared_ptr<ChunkVisibilityAreaPacket> packet)
{
if (level == NULL) return;
for(int z = packet->m_minZ; z <= packet->m_maxZ; ++z)
for(int x = packet->m_minX; x <= packet->m_maxX; ++x)
level->setChunkVisible(x, z, true);
@ -1119,11 +1142,13 @@ void ClientConnection::handleChunkVisibilityArea(shared_ptr<ChunkVisibilityAreaP
void ClientConnection::handleChunkVisibility(shared_ptr<ChunkVisibilityPacket> packet)
{
if (level == NULL) return;
level->setChunkVisible(packet->x, packet->z, packet->visible);
}
void ClientConnection::handleChunkTilesUpdate(shared_ptr<ChunkTilesUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
// 4J - changed to encode level in packet
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->levels[packet->levelIdx];
if( dimensionLevel )
@ -1193,16 +1218,29 @@ void ClientConnection::handleChunkTilesUpdate(shared_ptr<ChunkTilesUpdatePacket>
void ClientConnection::handleBlockRegionUpdate(shared_ptr<BlockRegionUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
// 4J - changed to encode level in packet
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->levels[packet->levelIdx];
if( dimensionLevel )
{
PIXBeginNamedEvent(0,"Handle block region update");
if(packet->bIsFullChunk && packet->ys == 0)
{
app.DebugPrintf("[BRUP-CLIENT] *** EMPTY FULL CHUNK received at (%d,%d)! Buffer length=%d\n",
packet->x>>4, packet->z>>4, packet->buffer.length);
}
int y1 = packet->y + packet->ys;
if(packet->bIsFullChunk)
{
y1 = Level::maxBuildHeight;
// Ensure the chunk exists in the cache before writing data.
// The ChunkVisibilityAreaPacket that creates chunks can arrive AFTER the first BRUP,
// causing getChunk() to return EmptyLevelChunk (whose setBlocksAndData is a no-op).
dimensionLevel->setChunkVisible(packet->x >> 4, packet->z >> 4, true);
if(packet->buffer.length > 0)
{
PIXBeginNamedEvent(0, "Reordering to XZY");
@ -1241,6 +1279,7 @@ void ClientConnection::handleBlockRegionUpdate(shared_ptr<BlockRegionUpdatePacke
void ClientConnection::handleTileUpdate(shared_ptr<TileUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
// 4J added - using a block of 255 to signify that this is a packet for destroying a tile, where we need to inform the level renderer that we are about to do so.
// This is used in creative mode as the point where a tile is first destroyed at the client end of things. Packets formed like this are potentially sent from
// ServerPlayerGameMode::destroyBlock
@ -1355,6 +1394,7 @@ void ClientConnection::send(shared_ptr<Packet> packet)
void ClientConnection::handleTakeItemEntity(shared_ptr<TakeItemEntityPacket> packet)
{
if (!isPrimaryConnection()) return;
shared_ptr<Entity> from = getEntity(packet->itemId);
shared_ptr<LivingEntity> to = dynamic_pointer_cast<LivingEntity>(getEntity(packet->playerId));
@ -2859,31 +2899,34 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
void ClientConnection::handleExplosion(shared_ptr<ExplodePacket> packet)
{
if(!packet->m_bKnockbackOnly)
// World modification (block destruction) must only happen once
if (isPrimaryConnection())
{
//app.DebugPrintf("Received ExplodePacket with explosion data\n");
PIXBeginNamedEvent(0,"Handling explosion");
Explosion *e = new Explosion(minecraft->level, nullptr, packet->x, packet->y, packet->z, packet->r);
PIXBeginNamedEvent(0,"Finalizing");
if(!packet->m_bKnockbackOnly)
{
//app.DebugPrintf("Received ExplodePacket with explosion data\n");
PIXBeginNamedEvent(0,"Handling explosion");
Explosion *e = new Explosion(minecraft->level, nullptr, packet->x, packet->y, packet->z, packet->r);
PIXBeginNamedEvent(0,"Finalizing");
// Fix for #81758 - TCR 006 BAS Non-Interactive Pause: TU9: Performance: Gameplay: After detonating bunch of TNT, game enters unresponsive state for couple of seconds.
// The changes we are making here have been decided by the server, so we don't need to add them to the vector that resets tiles changes made
// on the client as we KNOW that the server is matching these changes
MultiPlayerLevel *mpLevel = (MultiPlayerLevel *)minecraft->level;
mpLevel->enableResetChanges(false);
// 4J - now directly pass a pointer to the toBlow array in the packet rather than copying around
e->finalizeExplosion(true, &packet->toBlow);
mpLevel->enableResetChanges(true);
PIXEndNamedEvent();
PIXEndNamedEvent();
delete e;
}
else
{
//app.DebugPrintf("Received ExplodePacket with knockback only data\n");
// Fix for #81758 - TCR 006 BAS Non-Interactive Pause: TU9: Performance: Gameplay: After detonating bunch of TNT, game enters unresponsive state for couple of seconds.
// The changes we are making here have been decided by the server, so we don't need to add them to the vector that resets tiles changes made
// on the client as we KNOW that the server is matching these changes
MultiPlayerLevel *mpLevel = (MultiPlayerLevel *)minecraft->level;
mpLevel->enableResetChanges(false);
// 4J - now directly pass a pointer to the toBlow array in the packet rather than copying around
e->finalizeExplosion(true, &packet->toBlow);
mpLevel->enableResetChanges(true);
PIXEndNamedEvent();
PIXEndNamedEvent();
delete e;
}
}
// Per-player knockback — each connection applies to its own local player
//app.DebugPrintf("Adding knockback (%f,%f,%f) for player %d\n", packet->getKnockbackX(), packet->getKnockbackY(), packet->getKnockbackZ(), m_userIndex);
if (minecraft->localplayers[m_userIndex] == NULL)
return;
minecraft->localplayers[m_userIndex]->xd += packet->getKnockbackX();
minecraft->localplayers[m_userIndex]->yd += packet->getKnockbackY();
minecraft->localplayers[m_userIndex]->zd += packet->getKnockbackZ();
@ -2893,6 +2936,8 @@ void ClientConnection::handleContainerOpen(shared_ptr<ContainerOpenPacket> packe
{
bool failed = false;
shared_ptr<MultiplayerLocalPlayer> player = minecraft->localplayers[m_userIndex];
if (player == NULL)
return;
switch(packet->type)
{
case ContainerOpenPacket::BONUS_CHEST:
@ -3199,6 +3244,7 @@ void ClientConnection::handleTileEditorOpen(shared_ptr<TileEditorOpenPacket> pac
void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
{
if (!isPrimaryConnection()) return;
app.DebugPrintf("ClientConnection::handleSignUpdate - ");
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
@ -3232,6 +3278,7 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
void ClientConnection::handleTileEntityData(shared_ptr<TileEntityDataPacket> packet)
{
if (!isPrimaryConnection()) return;
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
shared_ptr<TileEntity> te = minecraft->level->getTileEntity(packet->x, packet->y, packet->z);
@ -3284,6 +3331,7 @@ void ClientConnection::handleContainerClose(shared_ptr<ContainerClosePacket> pac
void ClientConnection::handleTileEvent(shared_ptr<TileEventPacket> packet)
{
if (!isPrimaryConnection()) return;
PIXBeginNamedEvent(0,"Handle tile event\n");
minecraft->level->tileEvent(packet->x, packet->y, packet->z, packet->tile, packet->b0, packet->b1);
PIXEndNamedEvent();
@ -3291,6 +3339,7 @@ void ClientConnection::handleTileEvent(shared_ptr<TileEventPacket> packet)
void ClientConnection::handleTileDestruction(shared_ptr<TileDestructionPacket> packet)
{
if (!isPrimaryConnection()) return;
minecraft->level->destroyTileProgress(packet->getEntityId(), packet->getX(), packet->getY(), packet->getZ(), packet->getState());
}
@ -3372,6 +3421,7 @@ void ClientConnection::handleGameEvent(shared_ptr<GameEventPacket> gameEventPack
void ClientConnection::handleComplexItemData(shared_ptr<ComplexItemDataPacket> packet)
{
if (!isPrimaryConnection()) return;
if (packet->itemType == Item::map->id)
{
MapItem::getSavedData(packet->itemId, minecraft->level)->handleComplexItemData(packet->data);
@ -3386,6 +3436,7 @@ void ClientConnection::handleComplexItemData(shared_ptr<ComplexItemDataPacket> p
void ClientConnection::handleLevelEvent(shared_ptr<LevelEventPacket> packet)
{
if (!isPrimaryConnection()) return;
if (packet->type == LevelEvent::SOUND_DRAGON_DEATH)
{
for(unsigned int i = 0; i < XUSER_MAX_COUNT; ++i)
@ -3609,6 +3660,7 @@ void ClientConnection::handlePlayerAbilities(shared_ptr<PlayerAbilitiesPacket> p
void ClientConnection::handleSoundEvent(shared_ptr<LevelSoundPacket> packet)
{
if (!isPrimaryConnection()) return;
minecraft->level->playLocalSound(packet->getX(), packet->getY(), packet->getZ(), packet->getSound(), packet->getVolume(), packet->getPitch(), false);
}
@ -3921,6 +3973,7 @@ void ClientConnection::handleSetPlayerTeamPacket(shared_ptr<SetPlayerTeamPacket>
void ClientConnection::handleParticleEvent(shared_ptr<LevelParticlesPacket> packet)
{
if (!isPrimaryConnection()) return;
for (int i = 0; i < packet->getCount(); i++)
{
double xVarience = random->nextGaussian() * packet->getXDist();

View file

@ -43,6 +43,7 @@ public:
private:
DWORD m_userIndex; // 4J Added
bool isPrimaryConnection() const;
public:
SavedDataStorage *savedDataStorage;
ClientConnection(Minecraft *minecraft, const wstring& ip, int port);

View file

@ -41,6 +41,11 @@
#include "..\Minecraft.World\DurangoStats.h"
#endif
#ifdef _WINDOWS64
#include "..\..\Windows64\Network\WinsockNetLayer.h"
#include "..\..\Windows64\Windows64_Xuid.h"
#endif
// Global instance
CGameNetworkManager g_NetworkManager;
CPlatformNetworkManager *CGameNetworkManager::s_pPlatformNetworkManager;
@ -1501,6 +1506,45 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc
}
else
{
#ifdef _WINDOWS64
// Non-host split-screen: open a dedicated TCP connection for this pad
if (localPlayer && !g_NetworkManager.IsHost() && g_NetworkManager.IsInGameplay())
{
int padIdx = pNetworkPlayer->GetUserIndex();
BYTE assignedSmallId = 0;
if (!WinsockNetLayer::JoinSplitScreen(padIdx, &assignedSmallId))
{
app.DebugPrintf("Split-screen pad %d: failed to open TCP to host\n", padIdx);
pMinecraft->connectionDisconnected(padIdx, DisconnectPacket::eDisconnect_ConnectionCreationFailed);
return;
}
// Update the local IQNetPlayer (at pad index) with the host-assigned smallId.
// The NetworkPlayerXbox created by NotifyPlayerJoined already points to
// m_player[padIdx], so we just set the smallId for network routing.
IQNet::m_player[padIdx].m_smallId = assignedSmallId;
IQNet::m_player[padIdx].m_resolvedXuid = Win64Xuid::DeriveXuidForPad(Win64Xuid::ResolvePersistentXuid(), padIdx);
// Network socket (not hostLocal) — data goes through TCP via GetLocalSocket
socket = new Socket(pNetworkPlayer, false, false);
pNetworkPlayer->SetSocket(socket);
ClientConnection* connection = new ClientConnection(pMinecraft, socket, padIdx);
if (connection->createdOk)
{
connection->send(shared_ptr<PreLoginPacket>(new PreLoginPacket(pNetworkPlayer->GetOnlineName())));
pMinecraft->addPendingLocalConnection(padIdx, connection);
}
else
{
pMinecraft->connectionDisconnected(padIdx, DisconnectPacket::eDisconnect_ConnectionCreationFailed);
delete connection;
}
return;
}
#endif
socket = new Socket( pNetworkPlayer, g_NetworkManager.IsHost(), g_NetworkManager.IsHost() && localPlayer );
pNetworkPlayer->SetSocket( socket );

View file

@ -90,8 +90,8 @@ void CPlatformNetworkManagerStub::NotifyPlayerJoined(IQNetPlayer *pQNetPlayer )
pQNetPlayer,
pQNetPlayer->GetGamertag(),
pszDescription,
(int) pQNetPlayer->HasVoice(),
(int) pQNetPlayer->HasCamera() );
pQNetPlayer->HasVoice(),
pQNetPlayer->HasCamera() );
if( m_pIQNet->IsHost() )
@ -243,12 +243,18 @@ void CPlatformNetworkManagerStub::DoWork()
if (IQNet::s_playerCount > 1)
IQNet::s_playerCount--;
}
// Always return smallId to the free pool so it can be reused (game may have already cleared the slot).
WinsockNetLayer::PushFreeSmallId(disconnectedSmallId);
// Clear O(1) socket lookup so GetSocketForSmallId stays fast (s_connections never shrinks).
WinsockNetLayer::ClearSocketForSmallId(disconnectedSmallId);
// NOTE: Do NOT call PushFreeSmallId here. The old PlayerConnection's
// write thread may still be alive (it dies in PlayerList::tick when
// m_smallIdsToClose is processed). If we recycle the smallId now,
// AcceptThread can reuse it for a new connection, and the old write
// thread's getPlayer() lookup will resolve to the NEW player, sending
// stale game packets to the new client's TCP socket — corrupting its
// login handshake (bad packet id crash). PushFreeSmallId and
// ClearSocketForSmallId are called from PlayerList::tick after the
// old Connection threads are dead.
//
// Clear chunk visibility flags for this system so rejoin gets fresh chunk state.
SystemFlagRemoveBySmallId((int)disconnectedSmallId);
SystemFlagRemoveBySmallId(disconnectedSmallId);
}
}
#endif
@ -261,7 +267,7 @@ bool CPlatformNetworkManagerStub::CanAcceptMoreConnections()
if (server == NULL) return true;
PlayerList* list = server->getPlayerList();
if (list == NULL) return true;
return (unsigned int)list->players.size() < (unsigned int)list->getMaxPlayers();
return static_cast<unsigned int>(list->players.size()) < static_cast<unsigned int>(list->getMaxPlayers());
#else
return true;
#endif
@ -289,12 +295,40 @@ int CPlatformNetworkManagerStub::GetLocalPlayerMask(int playerIndex)
bool CPlatformNetworkManagerStub::AddLocalPlayerByUserIndex( int userIndex )
{
NotifyPlayerJoined(m_pIQNet->GetLocalPlayerByUserIndex(userIndex));
return ( m_pIQNet->AddLocalPlayerByUserIndex(userIndex) == S_OK );
if ( m_pIQNet->AddLocalPlayerByUserIndex(userIndex) != S_OK )
return false;
// Player is now registered in IQNet — get a pointer and notify the network layer.
// Use the static array directly: GetLocalPlayerByUserIndex checks customData which
// isn't set until addNetworkPlayer runs inside NotifyPlayerJoined.
NotifyPlayerJoined(&IQNet::m_player[userIndex]);
return true;
}
bool CPlatformNetworkManagerStub::RemoveLocalPlayerByUserIndex( int userIndex )
{
#ifdef _WINDOWS64
if (userIndex > 0 && userIndex < XUSER_MAX_COUNT && !m_pIQNet->IsHost())
{
IQNetPlayer* qp = &IQNet::m_player[userIndex];
// Notify the network layer before clearing the slot
if (qp->GetCustomDataValue() != 0)
{
NotifyPlayerLeaving(qp);
}
// Close the split-screen TCP connection and reset WinsockNetLayer state
WinsockNetLayer::CloseSplitScreenConnection(userIndex);
// Clear the IQNet slot so it can be reused on rejoin
qp->m_smallId = 0;
qp->m_isRemote = false;
qp->m_isHostPlayer = false;
qp->m_resolvedXuid = INVALID_XUID;
qp->m_gamertag[0] = 0;
qp->SetCustomDataValue(0);
}
#endif
return true;
}
@ -601,7 +635,7 @@ CPlatformNetworkManagerStub::PlayerFlags::PlayerFlags(INetworkPlayer *pNetworkPl
this->flags = new unsigned char [ count / 8 ];
memset( this->flags, 0, count / 8 );
this->count = count;
this->m_smallId = (pNetworkPlayer && pNetworkPlayer->IsLocal()) ? 256 : (pNetworkPlayer ? (int)pNetworkPlayer->GetSmallId() : -1);
this->m_smallId = (pNetworkPlayer && pNetworkPlayer->IsLocal()) ? 256 : (pNetworkPlayer ? static_cast<int>(pNetworkPlayer->GetSmallId()) : -1);
}
CPlatformNetworkManagerStub::PlayerFlags::~PlayerFlags()
{
@ -778,52 +812,52 @@ void CPlatformNetworkManagerStub::SearchForGames()
friendsSessions[0].push_back(info);
}
std::FILE* file = std::fopen("servers.txt", "r");
if (std::FILE* file = std::fopen("servers.db", "rb")) {
char magic[4] = {};
if (std::fread(magic, 1, 4, file) == 4 && memcmp(magic, "MCSV", 4) == 0)
{
uint32_t version = 0, count = 0;
std::fread(&version, sizeof(uint32_t), 1, file);
std::fread(&count, sizeof(uint32_t), 1, file);
if (file) {
wstring wline;
int phase = 0;
if (version == 1)
{
for (uint32_t s = 0; s < count; s++)
{
uint16_t ipLen = 0, port = 0, nameLen = 0;
if (std::fread(&ipLen, sizeof(uint16_t), 1, file) != 1) break;
if (ipLen == 0 || ipLen > 256) break;
string ip;
wstring port;
wstring name;
char ipBuf[257] = {};
if (std::fread(ipBuf, 1, ipLen, file) != ipLen) break;
if (std::fread(&port, sizeof(uint16_t), 1, file) != 1) break;
char buffer[512];
while (std::fgets(buffer, sizeof(buffer), file)) {
if (phase == 0) {
ip = buffer;
if (!ip.empty() && (ip.back() == '\n' || ip.back() == '\r'))
ip.pop_back();
phase = 1;
}
else if (phase == 1) {
wline = convStringToWstring(buffer);
port = wline;
phase = 2;
}
else if (phase == 2) {
wline = convStringToWstring(buffer);
name = wline;
phase = 0;
if (std::fread(&nameLen, sizeof(uint16_t), 1, file) != 1) break;
if (nameLen > 256) break;
//THEY GET DELETED AFTER USE LIKE 30 LINES UP!!
FriendSessionInfo* info = new FriendSessionInfo();
wchar_t label[128];
wcsncpy_s(label, sizeof(label)/sizeof(wchar_t), name.c_str(), _TRUNCATE);
size_t nameLen = wcslen(label);
info->displayLabel = new wchar_t[nameLen+1];
wcscpy_s(info->displayLabel, nameLen + 1, label);
info->displayLabelLength = static_cast<unsigned char>(nameLen);
info->displayLabelViewableStartIndex = 0;
info->data.isReadyToJoin = true;
info->data.isJoinable = true;
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), ip.c_str(), _TRUNCATE);
info->data.hostPort = stoi(port);
info->sessionId = (SessionID)(static_cast<uint64_t>(inet_addr(ip.c_str())) | (static_cast<uint64_t>(stoi(port)) << 32));
friendsSessions[0].push_back(info);
char nameBuf[257] = {};
if (nameLen > 0)
{
if (std::fread(nameBuf, 1, nameLen, file) != nameLen) break;
}
wstring wName = convStringToWstring(nameBuf);
FriendSessionInfo* info = new FriendSessionInfo();
size_t nLen = wName.length();
info->displayLabel = new wchar_t[nLen + 1];
wcscpy_s(info->displayLabel, nLen + 1, wName.c_str());
info->displayLabelLength = static_cast<unsigned char>(nLen);
info->displayLabelViewableStartIndex = 0;
info->data.isReadyToJoin = true;
info->data.isJoinable = true;
strncpy_s(info->data.hostIP, sizeof(info->data.hostIP), ipBuf, _TRUNCATE);
info->data.hostPort = port;
info->sessionId = static_cast<uint64_t>(inet_addr(ipBuf)) | static_cast<uint64_t>(port) << 32;
friendsSessions[0].push_back(info);
}
}
}
std::fclose(file);
}
@ -849,7 +883,7 @@ vector<FriendSessionInfo *> *CPlatformNetworkManagerStub::GetSessionList(int iPa
{
vector<FriendSessionInfo*>* filteredList = new vector<FriendSessionInfo*>();
for (size_t i = 0; i < friendsSessions[0].size(); i++)
filteredList->push_back(friendsSessions[0][i]);
filteredList->push_back(new FriendSessionInfo(*friendsSessions[0][i]));
return filteredList;
}

View file

@ -119,9 +119,36 @@ public:
hasPartyMember = false;
}
FriendSessionInfo(const FriendSessionInfo& other)
{
sessionId = other.sessionId;
#ifdef _XBOX
searchResult = other.searchResult;
#elif defined(__PS3__) || defined(__ORBIS__) || defined (__PSVITA__)
searchResult = other.searchResult;
#elif defined(_DURANGO)
searchResult = other.searchResult;
#endif
displayLabelLength = other.displayLabelLength;
displayLabelViewableStartIndex = other.displayLabelViewableStartIndex;
data = other.data;
hasPartyMember = other.hasPartyMember;
if (other.displayLabel != NULL)
{
displayLabel = new wchar_t[displayLabelLength + 1];
wcscpy_s(displayLabel, displayLabelLength + 1, other.displayLabel);
}
else
{
displayLabel = NULL;
}
}
FriendSessionInfo& operator=(const FriendSessionInfo&) = delete;
~FriendSessionInfo()
{
if (displayLabel != nullptr)
delete displayLabel;
delete[] displayLabel;
}
};

View file

@ -482,25 +482,14 @@ void IUIScene_AbstractContainerMenu::onMouseTick()
#endif
#ifdef _WINDOWS64
if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive())
if (iPad == 0 && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsKBMActive())
{
int deltaX = g_KBMInput.GetMouseDeltaX();
int deltaY = g_KBMInput.GetMouseDeltaY();
extern HWND g_hWnd;
RECT rc;
GetClientRect(g_hWnd, &rc);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
if (winW > 0 && winH > 0)
{
float scaleX = static_cast<float>(getMovieWidth()) / static_cast<float>(winW);
float scaleY = static_cast<float>(getMovieHeight()) / static_cast<float>(winH);
vPointerPos.x += static_cast<float>(deltaX) * scaleX;
vPointerPos.y += static_cast<float>(deltaY) * scaleY;
}
float scaleX, scaleY;
getMouseToSWFScale(scaleX, scaleY);
vPointerPos.x += static_cast<float>(deltaX) * scaleX;
vPointerPos.y += static_cast<float>(deltaY) * scaleY;
if (deltaX != 0 || deltaY != 0)
{

View file

@ -277,4 +277,5 @@ public:
virtual int getPad() = 0;
virtual int getMovieWidth() = 0;
virtual int getMovieHeight() = 0;
virtual void getMouseToSWFScale(float &scaleX, float &scaleY) = 0;
};

View file

@ -250,15 +250,22 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
// Choose a reasonable glyph scale.
float glyphScale = 1.0f, truePixelScale = 1.0f / m_cFontData->getFontData()->m_fAdvPerPixel;
F32 targetPixelScale = pixel_scale;
//if(!RenderManager.IsWidescreen())
//{
// // Fix for different scales in 480
// targetPixelScale = pixel_scale*2/3;
//}
while ( (0.5f + glyphScale) * truePixelScale < targetPixelScale)
while ( (0.5f + glyphScale) * truePixelScale < pixel_scale)
glyphScale++;
// Debug: log each unique (font, pixel_scale) pair
{
static std::unordered_set<int> s_loggedScaleKeys;
// Encode font pointer + quantized scale into a key to log each combo once
int scaleKey = (int)(pixel_scale * 100.0f) ^ (int)(uintptr_t)m_cFontData;
if (s_loggedScaleKeys.find(scaleKey) == s_loggedScaleKeys.end() && s_loggedScaleKeys.size() < 50) {
s_loggedScaleKeys.insert(scaleKey);
float tps = truePixelScale;
app.DebugPrintf("[FONT-DBG] GetGlyphBitmap: font=%s glyph=%d pixel_scale=%.3f truePixelScale=%.1f glyphScale=%.0f\n",
m_cFontData->getFontName().c_str(), glyph, pixel_scale, tps, glyphScale);
}
}
// 4J-JEV: Debug code to check which font sizes are being used.
#if (!defined _CONTENT_PACKAGE) && (VERBOSE_FONT_OUTPUT > 0)
@ -303,9 +310,6 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
}
#endif
//app.DebugPrintf("Request glyph_%d (U+%.4X) at %f, converted to %f (%f)\n",
// glyph, GetUnicode(glyph), pixel_scale, targetPixelScale, glyphScale);
// It is not necessary to shrink the glyph width here
// as its already been done in 'GetGlyphMetrics' by:
// > metrics->x1 = m_kerningTable[glyph] * ratio;
@ -324,27 +328,57 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
bitmap->top_left_y = -static_cast<S32>(m_cFontData->getFontData()->m_uiGlyphHeight) * m_cFontData->getFontData()->m_fAscent;
bitmap->oversample = 0;
bitmap->point_sample = true;
// 4J-JEV:
// pixel_scale == font size chosen in flash.
// bitmap->pixel_scale_correct = (float) m_glyphHeight; // Scales the glyph to desired size.
// bitmap->pixel_scale_correct = pixel_scale; // Always the same size (not desired size).
// bitmap->pixel_scale_correct = pixel_scale * 0.5; // Doubles original size.
// bitmap->pixel_scale_correct = pixel_scale * 2; // Halves original size.
// Actual scale, and possible range of scales.
bitmap->pixel_scale_correct = pixel_scale / glyphScale;
bitmap->pixel_scale_max = 99.0f;
bitmap->pixel_scale_min = 0.0f;
/* 4J-JEV: Some of Sean's code.
int glyphScaleMin = 1;
int glyphScaleMax = 3;
float actualScale = pixel_scale / glyphScale;
bitmap->pixel_scale_correct = actualScale;
bitmap->pixel_scale_min = actualScale * glyphScaleMin * 0.999f;
bitmap->pixel_scale_max = actualScale * glyphScaleMax * 1.001f; */
#ifdef _WINDOWS64
// On Windows64 the window can be any size, producing fractional
// pixel_scale values that don't align to integer multiples of
// truePixelScale. The original console code cached glyphs with a
// broad [truePixelScale, 99] range in the "normal" branch, which
// works on consoles (fixed 1080p — font sizes are exact multiples)
// but causes cache pollution on Windows: the first glyph cached in
// that range sets pixel_scale_correct for ALL subsequent requests,
// so different font sizes get scaled by wrong ratios, producing
// mixed letter sizes on screen.
//
// Fix: always use pixel_scale_correct = truePixelScale so every
// cache entry is consistent. Two ranges: downscale (bilinear for
// smooth reduction) and upscale (point_sample for crisp pixel-art).
bitmap->pixel_scale_correct = truePixelScale;
if (pixel_scale < truePixelScale)
{
bitmap->pixel_scale_min = 0.0f;
bitmap->pixel_scale_max = truePixelScale;
bitmap->point_sample = false;
}
else
{
bitmap->pixel_scale_min = truePixelScale;
bitmap->pixel_scale_max = 99.0f;
bitmap->point_sample = true;
}
#else
if (glyphScale <= 1 && pixel_scale < truePixelScale)
{
// Small display: pixel_scale is less than the native glyph size.
// Report the bitmap at its true native scale so Iggy downscales it
// to match the layout metrics (bilinear for smooth downscaling).
bitmap->pixel_scale_correct = truePixelScale;
bitmap->pixel_scale_min = 0.0f;
bitmap->pixel_scale_max = truePixelScale * 1.001f;
bitmap->point_sample = false;
}
else
{
// Normal/upscale case: integer-multiple scaling for pixel-art look.
// Console-only — fixed resolution means pixel_scale values are exact
// integer multiples of truePixelScale, so cache sharing is safe.
float actualScale = pixel_scale / glyphScale;
bitmap->pixel_scale_correct = actualScale;
bitmap->pixel_scale_min = truePixelScale;
bitmap->pixel_scale_max = 99.0f;
bitmap->point_sample = true;
}
#endif
// 4J-JEV: Nothing to do with glyph placement,
// entirely to do with cropping your glyph out of an archive.

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIComponent_Chat.h"
#include "UISplitScreenHelpers.h"
#include "..\..\Minecraft.h"
#include "..\..\Gui.h"
@ -120,6 +121,7 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
S32 tileWidth = width;
S32 tileHeight = height;
bool needsYTile = false;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -127,25 +129,30 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
tileHeight = static_cast<S32>(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
S32 contentOffX, contentOffY;
ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY);
xPos += contentOffX;
yPos += contentOffY;
ui.setupRenderPosition(xPos, yPos);
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
@ -153,7 +160,7 @@ void UIComponent_Chat::render(S32 width, S32 height, C4JRender::eViewportType vi
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else

View file

@ -2,6 +2,8 @@
#include "UI.h"
#include "UIComponent_MenuBackground.h"
#include <algorithm>
UIComponent_MenuBackground::UIComponent_MenuBackground(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
{
m_bSplitscreen = false;
@ -68,24 +70,29 @@ void UIComponent_MenuBackground::render(S32 width, S32 height, C4JRender::eViewp
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
tileYStart = static_cast<S32>(ui.getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
tileYStart = static_cast<S32>(ui.getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
tileYStart = static_cast<S32>(ui.getScreenHeight() / 2);
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scaleW = static_cast<F32>(tileXStart + tileWidth) / static_cast<F32>(m_movieWidth);
F32 scaleH = static_cast<F32>(tileYStart + tileHeight) / static_cast<F32>(m_movieHeight);
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
scale = max(scale, 1.0f);
IggyPlayerSetDisplaySize( getMovie(), static_cast<S32>(m_movieWidth * scale), static_cast<S32>(m_movieHeight * scale) );
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
@ -93,11 +100,15 @@ void UIComponent_MenuBackground::render(S32 width, S32 height, C4JRender::eViewp
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else
{
UIScene::render(width, height, viewport);
if(m_bIsReloading) return;
if(!m_hasTickedOnce || !getMovie()) return;
ui.setupRenderPosition(0, 0);
IggyPlayerSetDisplaySize( getMovie(), static_cast<S32>(ui.getScreenWidth()), static_cast<S32>(ui.getScreenHeight()) );
IggyPlayerDraw( getMovie() );
}
}

View file

@ -93,38 +93,47 @@ void UIComponent_Panorama::render(S32 width, S32 height, C4JRender::eViewportTyp
}
ui.setupRenderPosition(xPos, yPos);
if((viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT) || (viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT))
S32 tileXStart = 0;
S32 tileYStart = 0;
S32 tileWidth = width;
S32 tileHeight = height;
if((viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT) || (viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT))
{
// Need to render at full height, but only the left side of the scene
S32 tileXStart = 0;
S32 tileYStart = 0;
S32 tileWidth = width;
S32 tileHeight = static_cast<S32>(ui.getScreenHeight());
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
tileXStart ,
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
tileHeight = static_cast<S32>(ui.getScreenHeight());
}
else
{
// Need to render at full height, and full width. But compressed into the viewport
IggyPlayerSetDisplaySize( getMovie(), ui.getScreenWidth(), ui.getScreenHeight()/2 );
IggyPlayerDraw( getMovie() );
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = static_cast<S32>(ui.getScreenHeight() / 2);
}
F32 scaleW = static_cast<F32>(tileXStart + tileWidth) / static_cast<F32>(m_movieWidth);
F32 scaleH = static_cast<F32>(tileYStart + tileHeight) / static_cast<F32>(m_movieHeight);
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
IggyPlayerSetDisplaySize( getMovie(), static_cast<S32>(m_movieWidth * scale), static_cast<S32>(m_movieHeight * scale) );
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
tileXStart ,
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else
{
UIScene::render(width, height, viewport);
if(m_bIsReloading) return;
if(!m_hasTickedOnce || !getMovie()) return;
ui.setupRenderPosition(0, 0);
IggyPlayerSetDisplaySize( getMovie(), static_cast<S32>(ui.getScreenWidth()), static_cast<S32>(ui.getScreenHeight()) );
IggyPlayerDraw( getMovie() );
}
}

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIComponent_Tooltips.h"
#include "UISplitScreenHelpers.h"
UIComponent_Tooltips::UIComponent_Tooltips(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
{
@ -224,6 +225,7 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
S32 tileWidth = width;
S32 tileHeight = height;
bool needsYTile = false;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
@ -231,25 +233,30 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
tileHeight = static_cast<S32>(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
S32 contentOffX, contentOffY;
ComputeSplitContentOffset(viewport, m_movieWidth, m_movieHeight, scale, tileWidth, tileHeight, tileYStart, contentOffX, contentOffY);
xPos += contentOffX;
yPos += contentOffY;
ui.setupRenderPosition(xPos, yPos);
IggyPlayerDrawTilesStart ( getMovie() );
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
IggyPlayerDrawTile ( getMovie() ,
@ -257,7 +264,7 @@ void UIComponent_Tooltips::render(S32 width, S32 height, C4JRender::eViewportTyp
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else

View file

@ -13,6 +13,7 @@
#include "..\..\EnderDragonRenderer.h"
#include "..\..\MultiPlayerLocalPlayer.h"
#include "UIFontData.h"
#include "UISplitScreenHelpers.h"
#ifdef _WINDOWS64
#include "..\..\Windows64\KeyboardMouseInput.h"
#endif
@ -57,6 +58,8 @@ bool UIController::ms_bReloadSkinCSInitialised = false;
DWORD UIController::m_dwTrialTimerLimitSecs=DYNAMIC_CONFIG_DEFAULT_TRIAL_TIME;
// GetViewportRect and Fit16x9 are now in UISplitScreenHelpers.h
#ifdef _WINDOWS64
static UIControl_Slider *FindSliderById(UIScene *pScene, int sliderId)
{
@ -801,20 +804,22 @@ void UIController::tickInput()
#ifndef _CONTENT_PACKAGE
eUILayer_Debug,
#endif
eUILayer_Error,
eUILayer_Alert,
eUILayer_Popup,
eUILayer_Fullscreen,
eUILayer_Scene,
};
for (int l = 0; l < _countof(mouseLayers) && !pScene; ++l)
eUILayer_Error,
eUILayer_Alert,
eUILayer_Popup,
eUILayer_Fullscreen,
eUILayer_Scene,
};
// Only check the fullscreen group and the primary (KBM) player's group.
// Other splitscreen players use controllers — mouse must not affect them.
const int mouseGroups[] = { static_cast<int>(eUIGroup_Fullscreen), ProfileManager.GetPrimaryPad() + 1 };
for (int l = 0; l < _countof(mouseLayers) && !pScene; ++l)
{
for (int g = 0; g < _countof(mouseGroups) && !pScene; ++g)
{
for (int grp = 0; grp < eUIGroup_COUNT && !pScene; ++grp)
{
pScene = m_groups[grp]->GetTopScene(mouseLayers[l]);
}
pScene = m_groups[mouseGroups[g]]->GetTopScene(mouseLayers[l]);
}
}
if (pScene && pScene->getMovie())
{
int rawMouseX = g_KBMInput.GetMouseX();
@ -843,9 +848,14 @@ void UIController::tickInput()
m_lastHoverMouseX = rawMouseX;
m_lastHoverMouseY = rawMouseY;
// Convert mouse to scene/movie coordinates
F32 sceneMouseX = (F32)rawMouseX;
F32 sceneMouseY = (F32)rawMouseY;
// Convert mouse window-pixel coords to Flash/SWF authoring coords.
// In split-screen the scene is rendered at a tile-origin offset
// and at a smaller display size, so we must:
// 1. Map window pixels -> UIController screen space
// 2. Subtract the viewport tile origin
// 3. Scale from display dimensions to SWF authoring dimensions
F32 sceneMouseX = static_cast<F32>(rawMouseX);
F32 sceneMouseY = static_cast<F32>(rawMouseY);
{
extern HWND g_hWnd;
RECT rc;
@ -855,8 +865,30 @@ void UIController::tickInput()
int winH = rc.bottom - rc.top;
if (winW > 0 && winH > 0)
{
sceneMouseX = sceneMouseX * ((F32)pScene->getRenderWidth() / (F32)winW);
sceneMouseY = sceneMouseY * ((F32)pScene->getRenderHeight() / (F32)winH);
// Step 1: window pixels -> screen space
F32 screenX = sceneMouseX * (getScreenWidth() / static_cast<F32>(winW));
F32 screenY = sceneMouseY * (getScreenHeight() / static_cast<F32>(winH));
// Step 2 & 3: account for split-screen viewport
C4JRender::eViewportType vp = pScene->GetParentLayer()->getViewport();
S32 displayW = 0, displayH = 0;
getRenderDimensions(vp, displayW, displayH);
F32 vpOriginX, vpOriginY, vpW, vpH;
GetViewportRect(getScreenWidth(), getScreenHeight(), vp, vpOriginX, vpOriginY, vpW, vpH);
// All viewports use Fit16x9 for menu scenes
S32 fitW, fitH, fitOffsetX, fitOffsetY;
Fit16x9(vpW, vpH, fitW, fitH, fitOffsetX, fitOffsetY);
S32 originX = static_cast<S32>(vpOriginX) + fitOffsetX;
S32 originY = static_cast<S32>(vpOriginY) + fitOffsetY;
displayW = fitW;
displayH = fitH;
if (displayW > 0 && displayH > 0)
{
sceneMouseX = (screenX - originX) * (static_cast<F32>(pScene->getRenderWidth()) / static_cast<F32>(displayW));
sceneMouseY = (screenY - originY) * (static_cast<F32>(pScene->getRenderHeight()) / static_cast<F32>(displayH));
}
}
}
}
@ -908,7 +940,7 @@ void UIController::tickInput()
// TexturePackList origin is where the slot area starts,
// not the top-left of the whole control — use GetRealHeight.
if (type == UIControl::eTexturePackList)
ch = ((UIControl_TexturePackList *)ctrl)->GetRealHeight();
ch = static_cast<UIControl_TexturePackList*>(ctrl)->GetRealHeight();
if (cw <= 0 || ch <= 0)
continue;
@ -919,8 +951,8 @@ void UIController::tickInput()
{
// ButtonList manages focus internally via Flash —
// pass mouse coords so it can highlight the right item.
((UIControl_ButtonList *)ctrl)->SetTouchFocus(
(S32)sceneMouseX, (S32)sceneMouseY, false);
static_cast<UIControl_ButtonList*>(ctrl)->SetTouchFocus(
static_cast<S32>(sceneMouseX), static_cast<S32>(sceneMouseY), false);
hitControlId = -1;
hitArea = INT_MAX;
hitCtrl = NULL;
@ -929,10 +961,10 @@ void UIController::tickInput()
if (type == UIControl::eTexturePackList)
{
// TexturePackList expects coords relative to its origin.
UIControl_TexturePackList *pList = (UIControl_TexturePackList *)ctrl;
UIControl_TexturePackList *pList = static_cast<UIControl_TexturePackList*>(ctrl);
pScene->SetFocusToElement(ctrl->getId());
pList->SetTouchFocus(
(S32)(sceneMouseX - cx), (S32)(sceneMouseY - cy), false);
static_cast<S32>(sceneMouseX - cx), static_cast<S32>(sceneMouseY - cy), false);
m_bMouseHoverHorizontalList = true;
hitControlId = -1;
hitArea = INT_MAX;
@ -968,7 +1000,7 @@ void UIController::tickInput()
// happens after both tickInput and scene tick, so no flicker.
if (hitCtrl && hitCtrl->getControlType() == UIControl::eTextInput)
{
((UIControl_TextInput *)hitCtrl)->setCaretVisible(false);
static_cast<UIControl_TextInput*>(hitCtrl)->setCaretVisible(false);
}
}
}
@ -1584,73 +1616,48 @@ void UIController::renderScenes()
void UIController::getRenderDimensions(C4JRender::eViewportType viewport, S32 &width, S32 &height)
{
switch( viewport )
F32 originX, originY, viewW, viewH;
GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, originX, originY, viewW, viewH);
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
case C4JRender::VIEWPORT_TYPE_FULLSCREEN:
width = static_cast<S32>(getScreenWidth());
height = static_cast<S32>(getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
width = static_cast<S32>(getScreenWidth() / 2);
height = static_cast<S32>(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
width = static_cast<S32>(getScreenWidth() / 2);
height = static_cast<S32>(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
width = static_cast<S32>(getScreenWidth() / 2);
height = static_cast<S32>(getScreenHeight() / 2);
break;
S32 offsetX, offsetY;
Fit16x9(viewW, viewH, width, height, offsetX, offsetY);
}
else
{
// Split-screen: use raw viewport dims — the SWF tiling code handles non-16:9
width = static_cast<S32>(viewW);
height = static_cast<S32>(viewH);
}
}
void UIController::setupRenderPosition(C4JRender::eViewportType viewport)
{
if(m_bCustomRenderPosition || m_currentRenderViewport != viewport)
m_currentRenderViewport = viewport;
m_bCustomRenderPosition = false;
F32 vpOriginX, vpOriginY, vpW, vpH;
GetViewportRect(getScreenWidth(), getScreenHeight(), viewport, vpOriginX, vpOriginY, vpW, vpH);
S32 xPos, yPos;
if(viewport == C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
m_currentRenderViewport = viewport;
m_bCustomRenderPosition = false;
S32 xPos = 0;
S32 yPos = 0;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
xPos = static_cast<S32>(getScreenWidth() / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
xPos = static_cast<S32>(getScreenWidth() / 4);
yPos = static_cast<S32>(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
yPos = static_cast<S32>(getScreenHeight() / 4);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
xPos = static_cast<S32>(getScreenWidth() / 2);
yPos = static_cast<S32>(getScreenHeight() / 4);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
xPos = static_cast<S32>(getScreenWidth() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
yPos = static_cast<S32>(getScreenHeight() / 2);
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
xPos = static_cast<S32>(getScreenWidth() / 2);
yPos = static_cast<S32>(getScreenHeight() / 2);
break;
}
m_tileOriginX = xPos;
m_tileOriginY = yPos;
setTileOrigin(xPos, yPos);
S32 fitW, fitH, fitOffsetX, fitOffsetY;
Fit16x9(vpW, vpH, fitW, fitH, fitOffsetX, fitOffsetY);
xPos = static_cast<S32>(vpOriginX) + fitOffsetX;
yPos = static_cast<S32>(vpOriginY) + fitOffsetY;
}
else
{
// Split-screen: position at viewport origin, no 16:9 fitting
xPos = static_cast<S32>(vpOriginX);
yPos = static_cast<S32>(vpOriginY);
}
m_tileOriginX = xPos;
m_tileOriginY = yPos;
setTileOrigin(xPos, yPos);
}
void UIController::setupRenderPosition(S32 xOrigin, S32 yOrigin)
@ -1858,8 +1865,11 @@ void RADLINK UIController::TextureSubstitutionDestroyCallback ( void * user_call
ui.destroySubstitutionTexture(user_callback_data, handle);
Textures *t = Minecraft::GetInstance()->textures;
t->releaseTexture( id );
Minecraft* mc = Minecraft::GetInstance();
if (mc && mc->textures)
{
mc->textures->releaseTexture( id );
}
}
void UIController::registerSubstitutionTexture(const wstring &textureName, PBYTE pbData, DWORD dwLength)

View file

@ -257,6 +257,7 @@ public:
// RENDERING
float getScreenWidth() { return m_fScreenWidth; }
float getScreenHeight() { return m_fScreenHeight; }
void updateScreenSize(S32 w, S32 h) { m_fScreenWidth = (float)w; m_fScreenHeight = (float)h; app.DebugPrintf("[UI-INIT] updateScreenSize: %d x %d\n", w, h); }
virtual void render() = 0;
void getRenderDimensions(C4JRender::eViewportType viewport, S32 &width, S32 &height);

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIScene.h"
#include "UISplitScreenHelpers.h"
#include "..\..\Lighting.h"
#include "..\..\LocalPlayer.h"
@ -285,26 +286,8 @@ void UIScene::loadMovie()
moviePath.append(L"Vita.swf");
m_loadedResolution = eSceneResolution_Vita;
#elif defined _WINDOWS64
if(ui.getScreenHeight() == 720)
{
moviePath.append(L"720.swf");
m_loadedResolution = eSceneResolution_720;
}
else if(ui.getScreenHeight() == 480)
{
moviePath.append(L"480.swf");
m_loadedResolution = eSceneResolution_480;
}
else if(ui.getScreenHeight() < 720)
{
moviePath.append(L"Vita.swf");
m_loadedResolution = eSceneResolution_Vita;
}
else
{
moviePath.append(L"1080.swf");
m_loadedResolution = eSceneResolution_1080;
}
moviePath.append(L"1080.swf");
m_loadedResolution = eSceneResolution_1080;
#else
moviePath.append(L"1080.swf");
m_loadedResolution = eSceneResolution_1080;
@ -332,8 +315,6 @@ void UIScene::loadMovie()
int64_t beforeLoad = ui.iggyAllocCount;
swf = IggyPlayerCreateFromMemory ( baFile.data , baFile.length, nullptr);
int64_t afterLoad = ui.iggyAllocCount;
IggyPlayerInitializeAndTickRS ( swf );
int64_t afterTick = ui.iggyAllocCount;
if(!swf)
{
@ -343,17 +324,44 @@ void UIScene::loadMovie()
#endif
app.FatalLoadError();
}
app.DebugPrintf( app.USER_SR, "Loaded iggy movie %ls\n", moviePath.c_str() );
// Read movie dimensions from the SWF header (available immediately after
// CreateFromMemory, no init tick needed).
IggyProperties *properties = IggyPlayerProperties ( swf );
m_movieHeight = properties->movie_height_in_pixels;
m_movieWidth = properties->movie_width_in_pixels;
m_renderWidth = m_movieWidth;
m_renderHeight = m_movieHeight;
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
IggyPlayerSetDisplaySize( swf, width, height );
// Set display size BEFORE the init tick to match what render() will use.
// InitializeAndTickRS runs ActionScript that creates text fields. If the
// display size here differs from what render() passes to SetDisplaySize,
// Iggy can cache glyph rasterizations at one scale during init and then
// reuse them at a different scale during draw, producing mixed glyph sizes.
#ifdef _WINDOWS64
{
S32 fitW, fitH, fitOffX, fitOffY;
Fit16x9(ui.getScreenWidth(), ui.getScreenHeight(), fitW, fitH, fitOffX, fitOffY);
IggyPlayerSetDisplaySize( swf, fitW, fitH );
}
#else
IggyPlayerSetDisplaySize( swf, m_movieWidth, m_movieHeight );
#endif
IggyPlayerInitializeAndTickRS ( swf );
int64_t afterTick = ui.iggyAllocCount;
#ifdef _WINDOWS64
// Flush Iggy's internal font caches so all glyphs get rasterized fresh
// at the current display scale on the first Draw. Without this, stale
// cache entries from a previous scene (loaded at a different display size)
// cause mixed glyph sizes. ResizeD3D already calls this, which is why
// fonts look correct after a resize but break when a scene reloads
// without one.
IggyFlushInstalledFonts();
#endif
app.DebugPrintf( app.USER_SR, "Loaded iggy movie %ls\n", moviePath.c_str() );
IggyPlayerSetUserdata(swf,this);
@ -561,7 +569,7 @@ bool UIScene::handleMouseClick(F32 x, F32 y)
{
if (bestCtrl->getControlType() == UIControl::eCheckBox)
{
UIControl_CheckBox *cb = (UIControl_CheckBox *)bestCtrl;
UIControl_CheckBox *cb = static_cast<UIControl_CheckBox*>(bestCtrl);
bool newState = !cb->IsChecked();
cb->setChecked(newState);
handleCheckboxToggled((F64)bestId, newState);
@ -686,9 +694,23 @@ void UIScene::render(S32 width, S32 height, C4JRender::eViewportType viewport)
{
if(m_bIsReloading) return;
if(!m_hasTickedOnce || !swf) return;
ui.setupRenderPosition(viewport);
IggyPlayerSetDisplaySize( swf, width, height );
IggyPlayerDraw( swf );
if(viewport != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
F32 originX, originY, viewW, viewH;
GetViewportRect(ui.getScreenWidth(), ui.getScreenHeight(), viewport, originX, originY, viewW, viewH);
S32 fitW, fitH, offsetX, offsetY;
Fit16x9(viewW, viewH, fitW, fitH, offsetX, offsetY);
ui.setupRenderPosition(static_cast<S32>(originX) + offsetX, static_cast<S32>(originY) + offsetY);
IggyPlayerSetDisplaySize( swf, fitW, fitH );
IggyPlayerDraw( swf );
}
else
{
ui.setupRenderPosition(viewport);
IggyPlayerSetDisplaySize( swf, width, height );
IggyPlayerDraw( swf );
}
}
void UIScene::setOpacity(float percent)
@ -1332,11 +1354,16 @@ bool UIScene::hasRegisteredSubstitutionTexture(const wstring &textureName)
void UIScene::_handleFocusChange(F64 controlId, F64 childId)
{
m_iFocusControl = static_cast<int>(controlId);
m_iFocusChild = static_cast<int>(childId);
int newControl = static_cast<int>(controlId);
int newChild = static_cast<int>(childId);
if (newControl != m_iFocusControl || newChild != m_iFocusChild)
{
m_iFocusControl = newControl;
m_iFocusChild = newChild;
handleFocusChange(controlId, childId);
ui.PlayUISFX(eSFX_Focus);
handleFocusChange(controlId, childId);
ui.PlayUISFX(eSFX_Focus);
}
}
void UIScene::_handleInitFocus(F64 controlId, F64 childId)

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIScene_AbstractContainerMenu.h"
#include "UISplitScreenHelpers.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h"
#include "..\..\..\Minecraft.World\net.minecraft.world.item.h"
@ -187,14 +188,19 @@ void UIScene_AbstractContainerMenu::PlatformInitialize(int iPad, int startIndex)
IggyEvent mouseEvent;
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
C4JRender::eViewportType vp = m_parentLayer->getViewport();
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(width, height);
S32 x = m_pointerPos.x*(static_cast<float>(width)/m_movieWidth);
S32 y = m_pointerPos.y*(static_cast<float>(height)/m_movieHeight);
S32 y = m_pointerPos.y*(static_cast<float>(height)/m_movieHeight);
IggyMakeEventMouseMove( &mouseEvent, x, y);
IggyEventResult result;
IggyPlayerDispatchEventRS ( getMovie() , &mouseEvent , &result );
#ifdef USE_POINTER_ACCEL
#ifdef USE_POINTER_ACCEL
m_fPointerVelX = 0.0f;
m_fPointerVelY = 0.0f;
m_fPointerAccelX = 0.0f;
@ -212,8 +218,12 @@ void UIScene_AbstractContainerMenu::tick()
S32 width, height;
m_parentLayer->getRenderDimensions(width, height);
S32 x = static_cast<S32>(m_pointerPos.x * ((float)width / m_movieWidth));
S32 y = static_cast<S32>(m_pointerPos.y * ((float)height / m_movieHeight));
C4JRender::eViewportType vp = m_parentLayer->getViewport();
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(width, height);
S32 x = static_cast<S32>(m_pointerPos.x * (static_cast<float>(width) / m_movieWidth));
S32 y = static_cast<S32>(m_pointerPos.y * (static_cast<float>(height) / m_movieHeight));
IggyMakeEventMouseMove( &mouseEvent, x, y);
@ -251,6 +261,27 @@ void UIScene_AbstractContainerMenu::render(S32 width, S32 height, C4JRender::eVi
m_needsCacheRendered = false;
}
void UIScene_AbstractContainerMenu::getMouseToSWFScale(float &scaleX, float &scaleY)
{
extern HWND g_hWnd;
RECT rc;
GetClientRect(g_hWnd, &rc);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
if(winW <= 0 || winH <= 0) { scaleX = 1.0f; scaleY = 1.0f; return; }
S32 renderW, renderH;
C4JRender::eViewportType vp = GetParentLayer()->getViewport();
ui.getRenderDimensions(vp, renderW, renderH);
if(vp != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
Fit16x9(renderW, renderH);
float screenW = (float)ui.getScreenWidth();
float screenH = (float)ui.getScreenHeight();
scaleX = static_cast<float>(m_movieWidth) * screenW / (static_cast<float>(renderW) * static_cast<float>(winW));
scaleY = static_cast<float>(m_movieHeight) * screenH / (static_cast<float>(renderH) * static_cast<float>(winH));
}
void UIScene_AbstractContainerMenu::customDraw(IggyCustomDrawCallbackRegion *region)
{
Minecraft *pMinecraft = Minecraft::GetInstance();

View file

@ -38,6 +38,7 @@ public:
int getPad() { return m_iPad; }
int getMovieWidth() { return m_movieWidth; }
int getMovieHeight() { return m_movieHeight; }
void getMouseToSWFScale(float &scaleX, float &scaleY);
bool getIgnoreInput() { return m_bIgnoreInput; }
void setIgnoreInput(bool bVal) { m_bIgnoreInput=bVal; }

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIScene_HUD.h"
#include "UISplitScreenHelpers.h"
#include "BossMobGuiInfo.h"
#include "..\..\Minecraft.h"
#include "..\..\MultiplayerLocalPlayer.h"
@ -266,8 +267,6 @@ void UIScene_HUD::handleReload()
SetDisplayName(ProfileManager.GetDisplayName(m_iPad));
repositionHud();
SetTooltipsEnabled(((ui.GetMenuDisplayed(ProfileManager.GetPrimaryPad())) || (app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Tooltips) != 0)));
}
@ -697,30 +696,32 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
S32 tileWidth = width;
S32 tileHeight = height;
bool needsYTile = false;
switch( viewport )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
tileHeight = static_cast<S32>(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
tileWidth = static_cast<S32>(ui.getScreenWidth());
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
tileYStart = (S32)(m_movieHeight / 2);
needsYTile = true;
break;
}
IggyPlayerSetDisplaySize( getMovie(), m_movieWidth, m_movieHeight );
F32 scale;
ComputeTileScale(tileWidth, tileHeight, m_movieWidth, m_movieHeight, needsYTile, scale, tileYStart);
IggyPlayerSetDisplaySize( getMovie(), static_cast<S32>(m_movieWidth * scale), static_cast<S32>(m_movieHeight * scale) );
repositionHud(tileWidth, tileHeight, scale);
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
@ -730,7 +731,7 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
tileYStart ,
tileXStart + tileWidth ,
tileYStart + tileHeight ,
0 );
0 );
IggyPlayerDrawTilesEnd ( getMovie() );
}
else
@ -790,34 +791,24 @@ void UIScene_HUD::handleTimerComplete(int id)
//setVisible(anyVisible);
}
void UIScene_HUD::repositionHud()
void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale)
{
if(!m_bSplitscreen) return;
S32 width = 0;
S32 height = 0;
m_parentLayer->getRenderDimensions( width, height );
// Pass the visible tile area in SWF coordinates so ActionScript
// positions elements (crosshair, hotbar, etc.) centered in the
// actually visible region, not the raw viewport.
S32 visibleW = static_cast<S32>(tileWidth / scale);
S32 visibleH = static_cast<S32>(tileHeight / scale);
switch( m_parentLayer->getViewport() )
{
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
height = static_cast<S32>(ui.getScreenHeight());
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
width = static_cast<S32>(ui.getScreenWidth());
break;
}
app.DebugPrintf(app.USER_SR, "Reposition HUD with dims %d, %d\n", width, height );
app.DebugPrintf(app.USER_SR, "Reposition HUD: tile %dx%d, scale %.3f, visible SWF %dx%d\n", tileWidth, tileHeight, scale, visibleW, visibleH );
IggyDataValue result;
IggyDataValue value[2];
value[0].type = IGGY_DATATYPE_number;
value[0].number = width;
value[0].number = visibleW;
value[1].type = IGGY_DATATYPE_number;
value[1].number = height;
value[1].number = visibleH;
IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcRepositionHud , 2 , value );
}

View file

@ -176,5 +176,5 @@ protected:
#endif
private:
void repositionHud();
void repositionHud(S32 tileWidth, S32 tileHeight, F32 scale);
};

View file

@ -21,12 +21,19 @@ UIScene_JoinMenu::UIScene_JoinMenu(int iPad, void *_initData, UILayer *parentLay
m_friendInfoUpdatedOK = false;
m_friendInfoUpdatedERROR = false;
m_friendInfoRequestIssued = false;
#ifdef _WINDOWS64
m_serverIndex = initData->serverIndex;
m_editServerPhase = eEditServer_Idle;
m_editServerButtonIndex = -1;
m_deleteServerButtonIndex = -1;
#endif
}
void UIScene_JoinMenu::updateTooltips()
{
int iA = -1;
int iY = -1;
int iX = -1;
if (getControlFocus() == eControl_GamePlayers)
{
#ifdef _DURANGO
@ -38,7 +45,15 @@ void UIScene_JoinMenu::updateTooltips()
iA = IDS_TOOLTIPS_SELECT;
}
ui.SetTooltips( DEFAULT_XUI_MENU_USER, iA, IDS_TOOLTIPS_BACK, -1, iY );
#ifdef _WINDOWS64
if (m_serverIndex >= 0)
{
iX = IDS_TOOLTIPS_DELETE;
iY = IDS_TITLE_RENAME;
}
#endif
ui.SetTooltips( DEFAULT_XUI_MENU_USER, iA, IDS_TOOLTIPS_BACK, iX, iY );
}
@ -107,6 +122,16 @@ void UIScene_JoinMenu::tick()
}
#endif
#ifdef _WINDOWS64
if (m_serverIndex >= 0)
{
m_editServerButtonIndex = m_buttonListPlayers.getItemCount();
m_buttonListPlayers.addItem(L"Edit Server");
m_deleteServerButtonIndex = m_buttonListPlayers.getItemCount();
m_buttonListPlayers.addItem(L"Delete Server");
}
#endif
m_labelLabels[eLabel_Difficulty].init(app.GetString(IDS_LABEL_DIFFICULTY));
m_labelLabels[eLabel_GameType].init(app.GetString(IDS_LABEL_GAME_TYPE));
m_labelLabels[eLabel_GamertagsOn].init(app.GetString(IDS_LABEL_GAMERTAGS));
@ -278,12 +303,38 @@ void UIScene_JoinMenu::handleInput(int iPad, int key, bool repeat, bool pressed,
if( uid != INVALID_XUID ) ProfileManager.ShowProfileCard(ProfileManager.GetLockedProfile(),uid);
}
break;
#endif
#ifdef _WINDOWS64
case ACTION_MENU_X:
if(pressed && m_serverIndex >= 0)
{
BeginDeleteServer();
handled = true;
}
break;
case ACTION_MENU_Y:
if(pressed && m_serverIndex >= 0)
{
BeginEditServer();
handled = true;
}
break;
#endif
case ACTION_MENU_OK:
if (getControlFocus() != eControl_GamePlayers)
{
sendInputToMovie(key, repeat, pressed, released);
}
#ifdef _WINDOWS64
else if (pressed && m_serverIndex >= 0)
{
int sel = m_buttonListPlayers.getCurrentSelection();
if (sel == m_editServerButtonIndex)
BeginEditServer();
else if (sel == m_deleteServerButtonIndex)
BeginDeleteServer();
}
#endif
handled = true;
break;
#ifdef __ORBIS__
@ -318,6 +369,16 @@ void UIScene_JoinMenu::handlePress(F64 controlId, F64 childId)
}
break;
case eControl_GamePlayers:
#ifdef _WINDOWS64
if (m_serverIndex >= 0)
{
int sel = (int)childId;
if (sel == m_editServerButtonIndex)
BeginEditServer();
else if (sel == m_deleteServerButtonIndex)
BeginDeleteServer();
}
#endif
break;
};
}
@ -635,4 +696,279 @@ void UIScene_JoinMenu::handleTimerComplete(int id)
}
break;
};
}
}
#ifdef _WINDOWS64
void UIScene_JoinMenu::BeginDeleteServer()
{
m_bIgnoreInput = true;
UINT uiIDA[2];
uiIDA[0] = IDS_CONFIRM_CANCEL;
uiIDA[1] = IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETE, IDS_TEXT_DELETE_SAVE, uiIDA, 2, m_iPad, &UIScene_JoinMenu::DeleteServerDialogReturned, this);
}
int UIScene_JoinMenu::DeleteServerDialogReturned(void *pParam, int iPad, C4JStorage::EMessageResult result)
{
UIScene_JoinMenu* pClass = (UIScene_JoinMenu*)pParam;
if (result == C4JStorage::EMessage_ResultDecline)
{
pClass->RemoveServerFromFile();
g_NetworkManager.ForceFriendsSessionRefresh();
pClass->navigateBack();
}
else
{
pClass->m_bIgnoreInput = false;
}
return 0;
}
void UIScene_JoinMenu::BeginEditServer()
{
m_bIgnoreInput = true;
m_editServerPhase = eEditServer_IP;
m_editServerIP.clear();
m_editServerPort.clear();
wchar_t wDefaultIP[64] = {};
mbstowcs(wDefaultIP, m_selectedSession->data.hostIP, 63);
UIKeyboardInitData kbData;
kbData.title = L"Server Address";
kbData.defaultText = wDefaultIP;
kbData.maxChars = 128;
kbData.callback = &UIScene_JoinMenu::EditServerKeyboardCallback;
kbData.lpParam = this;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(m_iPad, eUIScene_Keyboard, &kbData);
}
int UIScene_JoinMenu::EditServerKeyboardCallback(LPVOID lpParam, bool bRes)
{
UIScene_JoinMenu *pClass = (UIScene_JoinMenu *)lpParam;
if (!bRes)
{
pClass->m_editServerPhase = eEditServer_Idle;
pClass->m_bIgnoreInput = false;
return 0;
}
uint16_t ui16Text[256];
ZeroMemory(ui16Text, sizeof(ui16Text));
Win64_GetKeyboardText(ui16Text, 256);
wchar_t wBuf[256] = {};
for (int k = 0; k < 255 && ui16Text[k]; k++)
wBuf[k] = (wchar_t)ui16Text[k];
if (wBuf[0] == 0)
{
pClass->m_editServerPhase = eEditServer_Idle;
pClass->m_bIgnoreInput = false;
return 0;
}
switch (pClass->m_editServerPhase)
{
case eEditServer_IP:
{
pClass->m_editServerIP = wBuf;
pClass->m_editServerPhase = eEditServer_Port;
wchar_t wDefaultPort[16] = {};
swprintf(wDefaultPort, 16, L"%d", pClass->m_selectedSession->data.hostPort);
UIKeyboardInitData kbData;
kbData.title = L"Server Port";
kbData.defaultText = wDefaultPort;
kbData.maxChars = 6;
kbData.callback = &UIScene_JoinMenu::EditServerKeyboardCallback;
kbData.lpParam = pClass;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
break;
}
case eEditServer_Port:
{
pClass->m_editServerPort = wBuf;
pClass->m_editServerPhase = eEditServer_Name;
wchar_t wDefaultName[64] = {};
if (pClass->m_selectedSession->displayLabel)
wcsncpy(wDefaultName, pClass->m_selectedSession->displayLabel, 63);
UIKeyboardInitData kbData;
kbData.title = L"Server Name";
kbData.defaultText = wDefaultName;
kbData.maxChars = 64;
kbData.callback = &UIScene_JoinMenu::EditServerKeyboardCallback;
kbData.lpParam = pClass;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
break;
}
case eEditServer_Name:
{
wstring newName = wBuf;
pClass->UpdateServerInFile(pClass->m_editServerIP, pClass->m_editServerPort, newName);
pClass->m_editServerPhase = eEditServer_Idle;
pClass->m_bIgnoreInput = false;
g_NetworkManager.ForceFriendsSessionRefresh();
pClass->navigateBack();
break;
}
default:
pClass->m_editServerPhase = eEditServer_Idle;
pClass->m_bIgnoreInput = false;
break;
}
return 0;
}
void UIScene_JoinMenu::UpdateServerInFile(const wstring& newIP, const wstring& newPort, const wstring& newName)
{
char narrowNewIP[256] = {};
char narrowNewPort[16] = {};
char narrowNewName[256] = {};
wcstombs(narrowNewIP, newIP.c_str(), sizeof(narrowNewIP) - 1);
wcstombs(narrowNewPort, newPort.c_str(), sizeof(narrowNewPort) - 1);
wcstombs(narrowNewName, newName.c_str(), sizeof(narrowNewName) - 1);
uint16_t newPortNum = (uint16_t)atoi(narrowNewPort);
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);
}
// Find and update the matching entry by original IP and port
int idx = m_serverIndex;
if (idx >= 0 && idx < (int)entries.size())
{
entries[idx].ip = std::string(narrowNewIP);
entries[idx].port = newPortNum;
entries[idx].name = std::string(narrowNewName);
}
file = fopen("servers.db", "wb");
if (file)
{
fwrite("MCSV", 1, 4, file);
uint32_t version = 1;
uint32_t count = (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 = (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 = (uint16_t)entries[i].name.length();
fwrite(&nameLen, sizeof(uint16_t), 1, file);
fwrite(entries[i].name.c_str(), 1, nameLen, file);
}
fclose(file);
}
}
void UIScene_JoinMenu::RemoveServerFromFile()
{
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);
}
// Remove the entry at m_serverIndex
int idx = m_serverIndex;
if (idx >= 0 && idx < (int)entries.size())
{
entries.erase(entries.begin() + idx);
}
file = fopen("servers.db", "wb");
if (file)
{
fwrite("MCSV", 1, 4, file);
uint32_t version = 1;
uint32_t count = (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 = (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 = (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

View file

@ -62,6 +62,16 @@ private:
bool m_friendInfoUpdatedOK;
bool m_friendInfoUpdatedERROR;
#ifdef _WINDOWS64
int m_serverIndex; // Index in servers.db, -1 if not a saved server
enum eEditServerPhase { eEditServer_Idle, eEditServer_IP, eEditServer_Port, eEditServer_Name };
eEditServerPhase m_editServerPhase;
wstring m_editServerIP;
wstring m_editServerPort;
int m_editServerButtonIndex;
int m_deleteServerButtonIndex;
#endif
public:
UIScene_JoinMenu(int iPad, void *initData, UILayer *parentLayer);
void tick();
@ -95,4 +105,13 @@ protected:
static int StartGame_SignInReturned(void *pParam, bool, int);
static void JoinGame(UIScene_JoinMenu* pClass);
#ifdef _WINDOWS64
void BeginEditServer();
void BeginDeleteServer();
static int EditServerKeyboardCallback(LPVOID lpParam, bool bRes);
static int DeleteServerDialogReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
void UpdateServerInFile(const wstring& newIP, const wstring& newPort, const wstring& newName);
void RemoveServerFromFile();
#endif
};

View file

@ -38,6 +38,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
}
m_win64TextBuffer = defaultText;
m_iCursorPos = (int)m_win64TextBuffer.length();
m_EnterTextLabel.init(titleText);
m_KeyboardTextInput.init(defaultText, -1);
@ -111,6 +112,9 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
if (IggyValuePathMakeNameRef(&keyPath, root, s_keyNames[i]))
IggyValueSetBooleanRS(&keyPath, nameVisible, nullptr, false);
}
m_KeyboardTextInput.setCaretVisible(true);
m_KeyboardTextInput.setCaretIndex(m_iCursorPos);
}
#endif
@ -165,9 +169,13 @@ void UIScene_Keyboard::tick()
// Sync our buffer from Flash so we pick up changes made via controller/on-screen buttons.
// Without this, switching between controller and keyboard would use stale text.
const wchar_t* flashText = m_KeyboardTextInput.getLabel();
if (flashText)
m_win64TextBuffer = flashText;
// In PC mode we own the buffer — skip sync to preserve cursor position.
if (!m_bPCMode)
{
const wchar_t* flashText = m_KeyboardTextInput.getLabel();
if (flashText)
m_win64TextBuffer = flashText;
}
// Accumulate physical keyboard chars into our own buffer, then push to Flash via setLabel.
// This bypasses Iggy's focus system (char events only route to the focused element).
@ -178,7 +186,16 @@ void UIScene_Keyboard::tick()
{
if (ch == 0x08) // backspace
{
if (!m_win64TextBuffer.empty())
if (m_bPCMode)
{
if (m_iCursorPos > 0)
{
m_win64TextBuffer.erase(m_iCursorPos - 1, 1);
m_iCursorPos--;
changed = true;
}
}
else if (!m_win64TextBuffer.empty())
{
m_win64TextBuffer.pop_back();
changed = true;
@ -194,13 +211,45 @@ void UIScene_Keyboard::tick()
}
else if (static_cast<int>(m_win64TextBuffer.length()) < m_win64MaxChars)
{
m_win64TextBuffer += ch;
if (m_bPCMode)
{
m_win64TextBuffer.insert(m_iCursorPos, 1, ch);
m_iCursorPos++;
}
else
{
m_win64TextBuffer += ch;
}
changed = true;
}
}
if (m_bPCMode)
{
// Arrow keys, Home, End, Delete for cursor movement
if (g_KBMInput.IsKeyPressed(VK_LEFT) && m_iCursorPos > 0)
m_iCursorPos--;
if (g_KBMInput.IsKeyPressed(VK_RIGHT) && m_iCursorPos < (int)m_win64TextBuffer.length())
m_iCursorPos++;
if (g_KBMInput.IsKeyPressed(VK_HOME))
m_iCursorPos = 0;
if (g_KBMInput.IsKeyPressed(VK_END))
m_iCursorPos = (int)m_win64TextBuffer.length();
if (g_KBMInput.IsKeyPressed(VK_DELETE) && m_iCursorPos < (int)m_win64TextBuffer.length())
{
m_win64TextBuffer.erase(m_iCursorPos, 1);
changed = true;
}
}
if (changed)
m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/);
if (m_bPCMode)
{
m_KeyboardTextInput.setCaretVisible(true);
m_KeyboardTextInput.setCaretIndex(m_iCursorPos);
}
}
#endif
@ -229,27 +278,31 @@ void UIScene_Keyboard::handleInput(int iPad, int key, bool repeat, bool pressed,
handled = true;
break;
case ACTION_MENU_X: // X
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcBackspaceButtonPressed, 0 , nullptr );
handled = true;
break;
case ACTION_MENU_PAGEUP: // LT
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSymbolButtonPressed, 0 , nullptr );
handled = true;
break;
case ACTION_MENU_Y: // Y
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSpaceButtonPressed, 0 , nullptr );
handled = true;
break;
case ACTION_MENU_STICK_PRESS: // LS
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcCapsButtonPressed, 0 , nullptr );
handled = true;
break;
case ACTION_MENU_LEFT_SCROLL: // LB
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcCursorLeftButtonPressed, 0 , nullptr );
handled = true;
break;
case ACTION_MENU_RIGHT_SCROLL: // RB
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcCursorRightButtonPressed, 0 , nullptr );
#ifdef _WINDOWS64
if (m_bPCMode)
{
handled = true;
break;
}
#endif
if (key == ACTION_MENU_X)
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcBackspaceButtonPressed, 0 , nullptr);
else if (key == ACTION_MENU_PAGEUP)
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSymbolButtonPressed, 0 , nullptr);
else if (key == ACTION_MENU_Y)
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSpaceButtonPressed, 0 , nullptr);
else if (key == ACTION_MENU_STICK_PRESS)
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcCapsButtonPressed, 0 , nullptr);
else if (key == ACTION_MENU_LEFT_SCROLL)
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcCursorLeftButtonPressed, 0 , nullptr);
else if (key == ACTION_MENU_RIGHT_SCROLL)
out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcCursorRightButtonPressed, 0 , nullptr);
handled = true;
break;
case ACTION_MENU_PAUSEMENU: // Start
@ -269,11 +322,23 @@ void UIScene_Keyboard::handleInput(int iPad, int key, bool repeat, bool pressed,
switch(key)
{
case ACTION_MENU_OK:
#ifdef _WINDOWS64
if (m_bPCMode)
{
// pressing enter sometimes causes a "y" to be entered.
handled = true;
break;
}
#endif
// fall through for controller mode
case ACTION_MENU_LEFT:
case ACTION_MENU_RIGHT:
case ACTION_MENU_UP:
case ACTION_MENU_DOWN:
sendInputToMovie(key, repeat, pressed, released);
#ifdef _WINDOWS64
if (!m_bPCMode)
#endif
sendInputToMovie(key, repeat, pressed, released);
handled = true;
break;
}

View file

@ -13,6 +13,7 @@ private:
wstring m_win64TextBuffer;
int m_win64MaxChars;
bool m_bPCMode; // Hides on-screen keyboard buttons; physical keyboard only
int m_iCursorPos;
#endif
protected:

View file

@ -402,8 +402,13 @@ UIScene_LoadOrJoinMenu::~UIScene_LoadOrJoinMenu()
g_NetworkManager.SetSessionsUpdatedCallback( nullptr, nullptr );
app.SetLiveLinkRequired( false );
delete m_currentSessions;
m_currentSessions = nullptr;
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
@ -520,6 +525,9 @@ void UIScene_LoadOrJoinMenu::Initialise()
{
m_iSaveListIndex = 0;
m_iGameListIndex = 0;
#ifdef _WINDOWS64
m_addServerPhase = eAddServer_Idle;
#endif
m_iDefaultButtonsC = 0;
m_iMashUpButtonsC=0;
@ -1051,8 +1059,7 @@ void UIScene_LoadOrJoinMenu::GetSaveInfo()
m_pSaveDetails=StorageManager.ReturnSavesInfo();
if(m_pSaveDetails==nullptr)
{
char savename[] = "save";
C4JStorage::ESaveGameState eSGIStatus = StorageManager.GetSavesInfo(m_iPad, nullptr, this, savename);
C4JStorage::ESaveGameState eSGIStatus= StorageManager.GetSavesInfo(m_iPad, nullptr,this,"save");
}
#if TO_BE_IMPLEMENTED
@ -1471,6 +1478,10 @@ void UIScene_LoadOrJoinMenu::handleFocusChange(F64 controlId, F64 childId)
{
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:
@ -1598,6 +1609,14 @@ void UIScene_LoadOrJoinMenu::handlePress(F64 controlId, F64 childId)
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;
@ -1607,6 +1626,10 @@ void UIScene_LoadOrJoinMenu::handlePress(F64 controlId, F64 childId)
{
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);
}
@ -1744,12 +1767,34 @@ void UIScene_LoadOrJoinMenu::CheckAndJoinGame(int gameIndex)
#endif
#endif
//CScene_MultiGameInfo::JoinMenuInitData *initData = new CScene_MultiGameInfo::JoinMenuInitData();
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
// check that we have the texture pack available
// If it's not the default texture pack
if(m_initData->selectedSession->data.texturePackParentId!=0)
{
int texturePacksCount = Minecraft::GetInstance()->skins->getTexturePackCount();
@ -1767,8 +1812,7 @@ void UIScene_LoadOrJoinMenu::CheckAndJoinGame(int gameIndex)
if(bHasTexturePackInstalled==false)
{
// upsell the texture pack
// tell sentient about the upsell of the full version of the skin pack
#ifdef _XBOX
ULONGLONG ullOfferID_Full;
app.GetDLCFullOfferIDForPackID(m_initData->selectedSession->data.texturePackParentId,&ullOfferID_Full);
@ -1781,8 +1825,6 @@ void UIScene_LoadOrJoinMenu::CheckAndJoinGame(int gameIndex)
//uiIDA[1]=IDS_TEXTURE_PACK_TRIALVERSION;
uiIDA[1]=IDS_CONFIRM_CANCEL;
// Give the player a warning about the texture pack missing
ui.RequestAlertMessage(IDS_DLC_TEXTUREPACK_NOT_PRESENT_TITLE, IDS_DLC_TEXTUREPACK_NOT_PRESENT, uiIDA, 2, m_iPad,&UIScene_LoadOrJoinMenu::TexturePackDialogReturned,this);
return;
@ -1800,7 +1842,6 @@ void UIScene_LoadOrJoinMenu::CheckAndJoinGame(int gameIndex)
m_controlJoinTimer.setVisible( false );
#ifdef _XBOX
// Reset the background downloading, in case we changed it by attempting to download a texture pack
XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_AUTO);
#endif
@ -1896,7 +1937,13 @@ void UIScene_LoadOrJoinMenu::UpdateGamesList()
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;
@ -1912,8 +1959,37 @@ void UIScene_LoadOrJoinMenu::UpdateGamesList()
int iY = -1;
int iX=-1;
delete m_currentSessions;
m_currentSessions = g_NetworkManager.GetSessionList( m_iPad, 1, m_bShowingPartyGamesOnly );
vector<FriendSessionInfo*>* newSessions = g_NetworkManager.GetSessionList( m_iPad, 1, m_bShowingPartyGamesOnly );
if (m_currentSessions != NULL && 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();
@ -1949,6 +2025,11 @@ void UIScene_LoadOrJoinMenu::UpdateGamesList()
// 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(wstring(L"Add Server"));
#endif
if( filteredListSize > 0 )
{
// Reset the focus to the selected session if it still exists
@ -2014,7 +2095,12 @@ void UIScene_LoadOrJoinMenu::UpdateGamesList()
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;
@ -4051,3 +4137,168 @@ int UIScene_LoadOrJoinMenu::CopySaveErrorDialogFinishedCallback(void *pParam,int
}
#endif // _XBOX_ONE
#ifdef _WINDOWS64
// adding servers bellow
void UIScene_LoadOrJoinMenu::BeginAddServer()
{
m_addServerPhase = eAddServer_IP;
m_addServerIP.clear();
m_addServerPort.clear();
UIKeyboardInitData kbData;
kbData.title = L"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[256];
ZeroMemory(ui16Text, sizeof(ui16Text));
Win64_GetKeyboardText(ui16Text, 256);
wchar_t wBuf[256] = {};
for (int k = 0; k < 255 && 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 = L"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 = L"Server Name";
kbData.defaultText = L"Minecraft Server";
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(narrowIP, ip.c_str(), sizeof(narrowIP) - 1);
wcstombs(narrowPort, port.c_str(), sizeof(narrowPort) - 1);
wcstombs(narrowName, name.c_str(), sizeof(narrowName) - 1);
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

View file

@ -175,6 +175,18 @@ public:
private:
void CheckAndJoinGame(int gameIndex);
#ifdef _WINDOWS64
static const int ADD_SERVER_BUTTON_INDEX = 0;
enum eAddServerPhase { eAddServer_Idle, eAddServer_IP, eAddServer_Port, eAddServer_Name };
eAddServerPhase m_addServerPhase;
wstring m_addServerIP;
wstring m_addServerPort;
void BeginAddServer();
void AppendServerToFile(const wstring& ip, const wstring& port, const wstring& name);
static int AddServerKeyboardCallback(LPVOID lpParam, bool bRes);
#endif
#if defined(__PS3__) || defined(__PSVITA__) || defined(__ORBIS__)
static int MustSignInReturnedPSN(void *pParam,int iPad,C4JStorage::EMessageResult result);
static int PSN_SignInReturned(void *pParam,bool bContinue, int iPad);

View file

@ -0,0 +1,114 @@
#pragma once
// Shared split-screen UI helpers to avoid duplicating viewport math
// across HUD, Chat, Tooltips, and container menus.
// Compute the raw viewport rectangle for a given viewport type.
inline void GetViewportRect(F32 screenW, F32 screenH, C4JRender::eViewportType viewport,
F32 &originX, F32 &originY, F32 &viewW, F32 &viewH)
{
originX = originY = 0;
viewW = screenW;
viewH = screenH;
switch(viewport)
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
originY = screenH * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
viewW = screenW * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
originX = screenW * 0.5f; viewW = screenW * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
originX = screenW * 0.5f; viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
originY = screenH * 0.5f; viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
originX = screenW * 0.5f; originY = screenH * 0.5f;
viewW = screenW * 0.5f; viewH = screenH * 0.5f; break;
default: break;
}
}
// Fit a 16:9 rectangle inside the given dimensions.
inline void Fit16x9(F32 viewW, F32 viewH, S32 &fitW, S32 &fitH, S32 &offsetX, S32 &offsetY)
{
const F32 kAspect = 16.0f / 9.0f;
if(viewW / viewH > kAspect)
{
fitH = (S32)viewH;
fitW = (S32)(viewH * kAspect);
}
else
{
fitW = (S32)viewW;
fitH = (S32)(viewW / kAspect);
}
offsetX = (S32)((viewW - fitW) * 0.5f);
offsetY = (S32)((viewH - fitH) * 0.5f);
}
// Convenience: just fit 16:9 dimensions, ignore offsets.
inline void Fit16x9(S32 &width, S32 &height)
{
S32 offX, offY;
Fit16x9((F32)width, (F32)height, width, height, offX, offY);
}
// Compute the uniform scale and tileYStart for split-screen tile rendering.
// Used by HUD, Chat, and Tooltips to scale the SWF movie to cover the viewport tile.
inline void ComputeTileScale(S32 tileWidth, S32 tileHeight, S32 movieWidth, S32 movieHeight,
bool needsYTile, F32 &outScale, S32 &outTileYStart)
{
F32 scaleW = (F32)tileWidth / (F32)movieWidth;
F32 scaleH = (F32)tileHeight / (F32)movieHeight;
F32 scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
outTileYStart = 0;
if(needsYTile)
{
S32 dispH = (S32)(movieHeight * scale);
outTileYStart = dispH - tileHeight;
if(outTileYStart < 0) outTileYStart = 0;
scaleH = (F32)(outTileYStart + tileHeight) / (F32)movieHeight;
scale = (scaleW > scaleH) ? scaleW : scaleH;
if(scale < 1.0f) scale = 1.0f;
}
outScale = scale;
}
// Compute the render offset to center split-screen SWF content in the viewport.
// Used by Chat and Tooltips (HUD uses repositionHud instead).
inline void ComputeSplitContentOffset(C4JRender::eViewportType viewport, S32 movieWidth, S32 movieHeight,
F32 scale, S32 tileWidth, S32 tileHeight, S32 tileYStart,
S32 &outXOffset, S32 &outYOffset)
{
S32 contentCenterX, contentCenterY;
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT || viewport == C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT)
{
contentCenterX = (S32)(movieWidth * scale / 4);
contentCenterY = (S32)(movieHeight * scale / 2);
}
else if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_TOP || viewport == C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM)
{
contentCenterX = (S32)(movieWidth * scale / 2);
contentCenterY = (S32)(movieHeight * scale * 3 / 4);
}
else
{
contentCenterX = (S32)(movieWidth * scale / 4);
contentCenterY = (S32)(movieHeight * scale * 3 / 4);
}
outXOffset = 0;
outYOffset = 0;
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT)
outXOffset = -(tileWidth / 2 - contentCenterX);
if(viewport == C4JRender::VIEWPORT_TYPE_SPLIT_TOP || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT || viewport == C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT)
outYOffset = -(tileHeight / 2 - (contentCenterY - tileYStart));
}

View file

@ -280,6 +280,9 @@ typedef struct _JoinMenuInitData
{
FriendSessionInfo *selectedSession;
int iPad;
#ifdef _WINDOWS64
int serverIndex; // Index of the server in servers.db, -1 if not a saved server
#endif
} JoinMenuInitData;
// Native keyboard (Windows64 replacement for InputManager.RequestKeyboard WinAPI dialog)

View file

@ -234,6 +234,7 @@ void EntityRenderDispatcher::prepare(Level *level, Textures *textures, Font *fon
if (pl->ThirdPersonView() == 2)
{
playerRotY += 180;
playerRotX = -playerRotX;
}
xPlayer = player->xOld + (player->x - player->xOld) * a;

View file

@ -194,7 +194,16 @@ void IQNetPlayer::SendData(IQNetPlayer * player, const void* pvData, DWORD dwDat
{
if (WinsockNetLayer::IsActive())
{
WinsockNetLayer::SendToSmallId(player->m_smallId, pvData, dwDataSize);
if (!WinsockNetLayer::IsHosting() && !m_isRemote)
{
SOCKET sock = WinsockNetLayer::GetLocalSocket(m_smallId);
if (sock != INVALID_SOCKET)
WinsockNetLayer::SendOnSocket(sock, pvData, dwDataSize);
}
else
{
WinsockNetLayer::SendToSmallId(player->m_smallId, pvData, dwDataSize);
}
}
}
bool IQNetPlayer::IsSameSystem(IQNetPlayer * player) { return (this == player) || (!m_isRemote && !player->m_isRemote); }
@ -245,7 +254,20 @@ void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost
static bool Win64_IsActivePlayer(IQNetPlayer* p, DWORD index);
HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) { return S_OK; }
HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) {
if (dwUserIndex >= MINECRAFT_NET_MAX_PLAYERS) return E_FAIL;
m_player[dwUserIndex].m_isRemote = false;
m_player[dwUserIndex].m_isHostPlayer = false;
// Give the joining player a distinct gamertag
extern wchar_t g_Win64UsernameW[17];
if (dwUserIndex == 0)
wcscpy_s(m_player[0].m_gamertag, 32, g_Win64UsernameW);
else
swprintf_s(m_player[dwUserIndex].m_gamertag, 32, L"%s(%d)", g_Win64UsernameW, dwUserIndex + 1);
if (dwUserIndex >= s_playerCount)
s_playerCount = dwUserIndex + 1;
return S_OK;
}
IQNetPlayer* IQNet::GetHostPlayer() { return &m_player[0]; }
IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex)
{
@ -257,13 +279,31 @@ IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex)
return &m_player[dwUserIndex];
return nullptr;
}
if (dwUserIndex != 0)
return nullptr;
for (DWORD i = 0; i < s_playerCount; i++)
if (dwUserIndex == 0)
{
if (!m_player[i].m_isRemote && Win64_IsActivePlayer(&m_player[i], i))
return &m_player[i];
// Primary pad: use direct index when networking is active (smallId may not be 0)
if (WinsockNetLayer::IsActive())
{
DWORD idx = WinsockNetLayer::GetLocalSmallId();
if (idx < MINECRAFT_NET_MAX_PLAYERS &&
!m_player[idx].m_isRemote &&
Win64_IsActivePlayer(&m_player[idx], idx))
return &m_player[idx];
return nullptr;
}
// Offline: scan for first local player
for (DWORD i = 0; i < s_playerCount; i++)
{
if (!m_player[i].m_isRemote && Win64_IsActivePlayer(&m_player[i], i))
return &m_player[i];
}
return nullptr;
}
// Split-screen pads 1-3: the player is at m_player[dwUserIndex] with isRemote=false
if (dwUserIndex < MINECRAFT_NET_MAX_PLAYERS &&
!m_player[dwUserIndex].m_isRemote &&
Win64_IsActivePlayer(&m_player[dwUserIndex], dwUserIndex))
return &m_player[dwUserIndex];
return nullptr;
}
static bool Win64_IsActivePlayer(IQNetPlayer * p, DWORD index)
@ -584,7 +624,7 @@ void C_4JProfile::SetTrialTextStringTable(CXuiStringTable * pStringTable, int
void C_4JProfile::SetTrialAwardText(eAwardType AwardType, int iTitle, int iText) {}
int C_4JProfile::GetLockedProfile() { return 0; }
void C_4JProfile::SetLockedProfile(int iProf) {}
bool C_4JProfile::IsSignedIn(int iQuadrant) { return (iQuadrant == 0); }
bool C_4JProfile::IsSignedIn(int iQuadrant) { return (iQuadrant == 0) || InputManager.IsPadConnected(iQuadrant); }
bool C_4JProfile::IsSignedInLive(int iProf) { return true; }
bool C_4JProfile::IsGuest(int iQuadrant) { return false; }
UINT C_4JProfile::RequestSignInUI(bool bFromInvite, bool bLocalGame, bool bNoGuestsAllowed, bool bMultiplayerSignIn, bool bAddUser, int(*Func)(LPVOID, const bool, const int iPad), LPVOID lpParam, int iQuadrant) { return 0; }
@ -595,18 +635,10 @@ bool C_4JProfile::QuerySigninStatus(void) { return true; }
void C_4JProfile::GetXUID(int iPad, PlayerUID * pXuid, bool bOnlineXuid)
{
#ifdef _WINDOWS64
if (iPad != 0)
{
*pXuid = INVALID_XUID;
return;
}
// LoginPacket reads this value as client identity:
// - host keeps legacy host XUID for world compatibility
// - non-host uses persistent uid.dat-backed XUID
if (IQNet::s_isHosting)
*pXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
else
*pXuid = Win64Xuid::ResolvePersistentXuid();
// Each pad gets a unique XUID derived from the persistent uid.dat value.
// Pad 0 uses the base XUID directly. Pads 1-3 get a deterministic hash
// of (base + pad) to produce fully independent IDs with no overlap risk.
*pXuid = Win64Xuid::DeriveXuidForPad(Win64Xuid::ResolvePersistentXuid(), iPad);
#else
* pXuid = 0xe000d45248242f2e + iPad;
#endif
@ -636,8 +668,24 @@ void C_4JProfile::SetPrimaryPad(int iPad) {}
char fakeGamerTag[32] = "PlayerName";
void SetFakeGamertag(char* name) { strcpy_s(fakeGamerTag, name); }
#else
char* C_4JProfile::GetGamertag(int iPad) { extern char g_Win64Username[17]; return g_Win64Username; }
wstring C_4JProfile::GetDisplayName(int iPad) { extern wchar_t g_Win64UsernameW[17]; return g_Win64UsernameW; }
char* C_4JProfile::GetGamertag(int iPad) {
extern char g_Win64Username[17];
if (iPad > 0 && iPad < XUSER_MAX_COUNT && IQNet::m_player[iPad].m_gamertag[0] != 0 &&
!IQNet::m_player[iPad].m_isRemote)
{
static char s_padGamertag[XUSER_MAX_COUNT][17];
WideCharToMultiByte(CP_ACP, 0, IQNet::m_player[iPad].m_gamertag, -1, s_padGamertag[iPad], 17, nullptr, nullptr);
return s_padGamertag[iPad];
}
return g_Win64Username;
}
wstring C_4JProfile::GetDisplayName(int iPad) {
extern wchar_t g_Win64UsernameW[17];
if (iPad > 0 && iPad < XUSER_MAX_COUNT && IQNet::m_player[iPad].m_gamertag[0] != 0 &&
!IQNet::m_player[iPad].m_isRemote)
return IQNet::m_player[iPad].m_gamertag;
return g_Win64UsernameW;
}
#endif
bool C_4JProfile::IsFullVersion() { return s_bProfileIsFullVersion; }
void C_4JProfile::SetSignInChangeCallback(void (*Func)(LPVOID, bool, unsigned int), LPVOID lpParam) {}

View file

@ -149,7 +149,7 @@ void Font::renderStyleLine(float x0, float y0, float x1, float y1)
void Font::addCharacterQuad(wchar_t c)
{
float xOff = c % m_cols * m_charWidth;
float yOff = c / m_cols * m_charWidth;
float yOff = c / m_cols * m_charHeight; // was m_charWidth — wrong when glyphs aren't square
float width = charWidths[c] - .01f;
float height = m_charHeight - .01f;
float fontWidth = m_cols * m_charWidth;
@ -187,7 +187,7 @@ void Font::addCharacterQuad(wchar_t c)
void Font::renderCharacter(wchar_t c)
{
float xOff = c % m_cols * m_charWidth;
float yOff = c / m_cols * m_charWidth;
float yOff = c / m_cols * m_charHeight; // was m_charWidth — wrong when glyphs aren't square
float width = charWidths[c] - .01f;
float height = m_charHeight - .01f;

View file

@ -594,9 +594,10 @@ void GameRenderer::unZoomRegion()
// 4J added as we have more complex adjustments to make for fov & aspect on account of viewports
void GameRenderer::getFovAndAspect(float& fov, float& aspect, float a, bool applyEffects)
{
// 4J - split out aspect ratio and fov here so we can adjust for viewports - we might need to revisit these as
// they are maybe be too generous for performance.
aspect = mc->width / static_cast<float>(mc->height);
// Use the real window dimensions so the perspective updates on resize.
extern int g_rScreenWidth;
extern int g_rScreenHeight;
aspect = g_rScreenWidth / static_cast<float>(g_rScreenHeight);
fov = getFov(a, applyEffects);
if( ( mc->player->m_iScreenSection == C4JRender::VIEWPORT_TYPE_SPLIT_TOP ) ||
@ -946,9 +947,9 @@ float GameRenderer::ComputeGammaFromSlider(float slider0to100)
slider = min(slider, 100.0f);
if (slider > 50.0f)
return 1.0f + (slider - 50.0f) / 50.0f * 0.5f; // 1.0 -> 1.5
return 1.0f + (slider - 50.0f) / 50.0f * 1.2f; // 1.0 -> 1.5
else
return 1.0f - (50.0f - slider) / 50.0f * 0.5f; // 1.0 -> 0.5
return 1.0f - (50.0f - slider) / 50.0f * 0.4f; // 1.0 -> 0.5
}
void GameRenderer::CachePlayerGammas()
@ -969,6 +970,10 @@ void GameRenderer::CachePlayerGammas()
bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport) const
{
// Use the actual backbuffer dimensions so viewports adapt to window resize.
extern int g_rScreenWidth;
extern int g_rScreenHeight;
int active = 0;
int indexMap[NUM_LIGHT_TEXTURES] = {-1, -1, -1, -1};
for (int i = 0; i < XUSER_MAX_COUNT && i < NUM_LIGHT_TEXTURES; ++i)
@ -981,8 +986,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
{
outViewport.TopLeftX = 0.0f;
outViewport.TopLeftY = 0.0f;
outViewport.Width = static_cast<FLOAT>(mc->width);
outViewport.Height = static_cast<FLOAT>(mc->height);
outViewport.Width = static_cast<FLOAT>(g_rScreenWidth);
outViewport.Height = static_cast<FLOAT>(g_rScreenHeight);
outViewport.MinDepth = 0.0f;
outViewport.MaxDepth = 1.0f;
return true;
@ -998,8 +1003,8 @@ bool GameRenderer::ComputeViewportForPlayer(int j, D3D11_VIEWPORT &outViewport)
if (k < 0)
return false;
const float width = static_cast<float>(mc->width);
const float height = static_cast<float>(mc->height);
const float width = static_cast<float>(g_rScreenWidth);
const float height = static_cast<float>(g_rScreenHeight);
if (active == 2)
{
@ -1176,16 +1181,16 @@ void GameRenderer::render(float a, bool bFirst)
if (mc->noRender)
return;
GameRenderer::anaglyph3d = mc->options->anaglyph3d;
anaglyph3d = mc->options->anaglyph3d;
glViewport(0, 0, mc->width, mc->height); // 4J - added
ScreenSizeCalculator ssc(mc->options, mc->width, mc->height);
int screenWidth = ssc.getWidth();
int screenHeight = ssc.getHeight();
int xMouse = Mouse::getX() * screenWidth / mc->width;
int yMouse = screenHeight - Mouse::getY() * screenHeight / mc->height - 1;
glViewport(0, 0, mc->width, mc->height); // 4J - added (no-op on Win64, viewport set by StateSetViewport)
ScreenSizeCalculator ssc(mc->options, mc->width, mc->height);
const int screenWidth = ssc.getWidth();
const int screenHeight = ssc.getHeight();
const int xMouse = Mouse::getX() * screenWidth / mc->width;
const int yMouse = screenHeight - Mouse::getY() * screenHeight / mc->height - 1;
int maxFps = getFpsCap(mc->options->framerateLimit);
const int maxFps = getFpsCap(mc->options->framerateLimit);
if (mc->level != nullptr)
{

View file

@ -902,14 +902,14 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
int zBlockPos = Mth::floor(minecraft->player->z);
// Chunk player is in
int xChunkPos = minecraft->player->xChunk;
int yChunkPos = minecraft->player->yChunk;
int zChunkPos = minecraft->player->zChunk;
int xChunkPos = xBlockPos >> 4;
int yChunkPos = yBlockPos >> 4;
int zChunkPos = zBlockPos >> 4;
// Players offset within the chunk
int xChunkOffset = xBlockPos - xChunkPos * 16;
int yChunkOffset = yBlockPos - yChunkPos * 16;
int zChunkOffset = zBlockPos - zChunkPos * 16;
int xChunkOffset = xBlockPos & 15;
int yChunkOffset = yBlockPos & 15;
int zChunkOffset = zBlockPos & 15;
// Format the position like java with limited decumal places
WCHAR posString[44]; // Allows upto 7 digit positions (+-9_999_999)
@ -958,18 +958,20 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
if (minecraft->level != NULL && minecraft->level->hasChunkAt(xBlockPos, fmod(yBlockPos, 256), zBlockPos))
{
LevelChunk *chunkAt = minecraft->level->getChunkAt(xBlockPos, zBlockPos);
if (chunkAt != NULL)
{
int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset);
int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset);
int maxLight = fmax(skyLight, blockLight);
lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)");
int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset);
int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset);
int maxLight = fmax(skyLight, blockLight);
lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)");
lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset)));
lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset)));
Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource());
lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")");
Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource());
lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")");
lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")");
lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")");
}
}

View file

@ -55,8 +55,15 @@ void HorseRenderer::renderModel(shared_ptr<LivingEntity> mob, float wp, float ws
void HorseRenderer::bindTexture(ResourceLocation *location)
{
// Set up (potentially) multiple texture layers for the horse
entityRenderDispatcher->textures->bindTextureLayers(location);
if (location->getTextureCount() > 1)
{
// Set up multiple texture layers for the horse
entityRenderDispatcher->textures->bindTextureLayers(location);
}
else
{
EntityRenderer::bindTexture(location);
}
}
ResourceLocation *HorseRenderer::getTextureLocation(shared_ptr<Entity> entity)

View file

@ -935,6 +935,14 @@ void ItemInHandRenderer::tick()
}
void ItemInHandRenderer::reset()
{
selectedItem = nullptr;
lastSlot = -1;
height = 0.0f;
oHeight = 0.0f;
}
void ItemInHandRenderer::itemPlaced()
{
height = 0;

View file

@ -41,6 +41,7 @@ private:
int lastSlot;
public:
void tick();
void reset();
void itemPlaced();
void itemUsed();
};

View file

@ -554,7 +554,8 @@ void LevelRenderer::renderEntities(Vec3 *cam, Culler *culler, float a)
for (auto& entity : entities)
{
bool shouldRender = (entity->shouldRender(cam) && (entity->noCulling || culler->isVisible(entity->bb)));
bool isPlayerVehicle = (entity == mc->cameraTargetPlayer->riding);
bool shouldRender = (entity->shouldRender(cam) && (entity->noCulling || isPlayerVehicle || culler->isVisible(entity->bb)));
// Render the mob if the mob's leash holder is within the culler
if ( !shouldRender && entity->instanceof(eTYPE_MOB) )

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "Minecraft.h"
#include "Common/UI/UIScene.h"
#include "GameMode.h"
#include "Timer.h"
#include "ProgressRenderer.h"
@ -9,6 +10,7 @@
#include "User.h"
#include "Textures.h"
#include "GameRenderer.h"
#include "ItemInHandRenderer.h"
#include "HumanoidModel.h"
#include "Options.h"
#include "TexturePackRepository.h"
@ -217,7 +219,8 @@ Minecraft::Minecraft(Component *mouseComponent, Canvas *parent, MinecraftApplet
{
m_pendingLocalConnections[i] = nullptr;
m_connectionFailed[i] = false;
localgameModes[i]=nullptr;
localgameModes[i]= nullptr;
localitemInHandRenderers[i] = nullptr;
}
animateTickLevel = nullptr; // 4J added
@ -1481,9 +1484,22 @@ void Minecraft::run_middle()
if(g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_RIGHT))
localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_USE;
bool isClosableByEitherKey = ui.IsSceneInStack(i, eUIScene_FurnaceMenu) ||
ui.IsSceneInStack(i, eUIScene_ContainerMenu) ||
ui.IsSceneInStack(i, eUIScene_DispenserMenu) ||
ui.IsSceneInStack(i, eUIScene_EnchantingMenu) ||
ui.IsSceneInStack(i, eUIScene_BrewingStandMenu) ||
ui.IsSceneInStack(i, eUIScene_TradingMenu) ||
ui.IsSceneInStack(i, eUIScene_AnvilMenu) ||
ui.IsSceneInStack(i, eUIScene_HopperMenu) ||
ui.IsSceneInStack(i, eUIScene_BeaconMenu) ||
ui.IsSceneInStack(i, eUIScene_InventoryMenu) ||
ui.IsSceneInStack(i, eUIScene_HorseMenu);
bool isEditing = ui.GetTopScene(i) && ui.GetTopScene(i)->isDirectEditBlocking();
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_INVENTORY))
{
if(ui.IsSceneInStack(i, eUIScene_InventoryMenu))
if(isClosableByEitherKey && !isEditing)
{
ui.CloseUIScenes(i);
}
@ -1498,7 +1514,7 @@ void Minecraft::run_middle()
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_CRAFTING) || g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_CRAFTING_ALT))
{
if(ui.IsSceneInStack(i, eUIScene_Crafting2x2Menu) || ui.IsSceneInStack(i, eUIScene_Crafting3x3Menu) || ui.IsSceneInStack(i, eUIScene_CreativeMenu))
if((ui.IsSceneInStack(i, eUIScene_Crafting2x2Menu) || ui.IsSceneInStack(i, eUIScene_Crafting3x3Menu) || ui.IsSceneInStack(i, eUIScene_CreativeMenu) || isClosableByEitherKey) && !isEditing)
{
ui.CloseUIScenes(i);
}
@ -2359,16 +2375,21 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures)
}
#ifdef _WINDOWS64
if ((screen != nullptr || ui.GetMenuDisplayed(iPad)) && g_KBMInput.IsMouseGrabbed())
// Mouse grab/release only for the primary (KBM) player — splitscreen
// players use controllers and must never fight over the cursor state.
if (iPad == ProfileManager.GetPrimaryPad())
{
g_KBMInput.SetMouseGrabbed(false);
if ((screen != nullptr || ui.GetMenuDisplayed(iPad)) && g_KBMInput.IsMouseGrabbed())
{
g_KBMInput.SetMouseGrabbed(false);
}
}
#endif
if (screen == nullptr && !ui.GetMenuDisplayed(iPad) )
{
#ifdef _WINDOWS64
if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsWindowFocused())
if (iPad == ProfileManager.GetPrimaryPad() && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsWindowFocused())
{
g_KBMInput.SetMouseGrabbed(true);
}
@ -4228,6 +4249,17 @@ void Minecraft::setLevel(MultiPlayerLevel *level, int message /*=-1*/, shared_pt
// 4J - stop update thread from processing this level, which blocks until it is safe to move on - will be re-enabled if we set the level to be non-nullptr
gameRenderer->DisableUpdateThread();
if (level == nullptr || player == nullptr)
{
for (int i = 0; i < XUSER_MAX_COUNT; ++i)
{
if (localitemInHandRenderers[i] != nullptr)
{
localitemInHandRenderers[i]->reset();
}
}
}
for(unsigned int i = 0; i < levels.length; ++i)
{
// 4J We only need to save out in multiplayer is we are setting the level to nullptr
@ -4623,8 +4655,8 @@ void Minecraft::start(const wstring& name, const wstring& sid)
void Minecraft::startAndConnectTo(const wstring& name, const wstring& sid, const wstring& url)
{
bool fullScreen = false;
wstring userName = name;
const bool fullScreen = false;
const wstring userName = name;
/* 4J - removed window handling things here
final Frame frame = new Frame("Minecraft");
@ -4643,10 +4675,15 @@ void Minecraft::startAndConnectTo(const wstring& name, const wstring& sid, const
*/
Minecraft *minecraft;
// 4J - was new Minecraft(frame, canvas, nullptr, 854, 480, fullScreen);
minecraft = new Minecraft(nullptr, nullptr, nullptr, 1280, 720, fullScreen);
// 4J - was new Minecraft(frame, canvas, NULL, 854, 480, fullScreen);
// Logical width is proportional to the real screen aspect ratio so that
// the ortho projection and HUD layout match the viewport without stretching.
extern int g_iScreenWidth;
extern int g_iScreenHeight;
constexpr int logicalH = 720;
const int logicalW = logicalH * g_iScreenWidth / g_iScreenHeight;
minecraft = new Minecraft(nullptr, nullptr, nullptr, logicalW, logicalH, fullScreen);
/* - 4J - removed
{
@Override

View file

@ -2169,12 +2169,16 @@ void MinecraftServer::tick()
}
Entity::tickExtraWandering(); // 4J added
PIXBeginNamedEvent(0,"Connection tick");
connection->tick();
PIXEndNamedEvent();
// Process player disconnect/kick queue BEFORE ticking connections.
// PendingConnection::handleLogin rejects duplicate XUIDs, so the old
// player must be removed from PlayerList before a reconnecting client's
// LoginPacket is processed.
PIXBeginNamedEvent(0,"Players tick");
players->tick();
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Connection tick");
connection->tick();
PIXEndNamedEvent();
// 4J - removed
#if 0

View file

@ -43,7 +43,7 @@ Dungeon!
Exclusive!
The bee's knees!
Down with O.P.P.!
Closed source!
Closed source xD!
Classy!
Wow!
Not on steam!

View file

@ -190,9 +190,34 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
}
else if (duplicateXuid)
{
// if same XUID already in use by another player so disconnect this one.
app.DebugPrintf("Rejecting duplicate xuid for name: %ls\n", name.c_str());
disconnect(DisconnectPacket::eDisconnect_Banned);
// The old player is still in PlayerList (disconnect hasn't been
// processed yet). Force-close the stale connection so the
// reconnecting client isn't rejected.
app.DebugPrintf("RECONNECT: Duplicate xuid for name: %ls, forcing old connection closed\n", name.c_str());
shared_ptr<ServerPlayer> stalePlayer = server->getPlayers()->getPlayer(loginXuid);
if (stalePlayer == nullptr && packet->m_onlineXuid != INVALID_XUID)
stalePlayer = server->getPlayers()->getPlayer(packet->m_onlineXuid);
if (stalePlayer != nullptr && stalePlayer->connection != nullptr)
{
BYTE oldSmallId = 0;
if (stalePlayer->connection->connection != nullptr && stalePlayer->connection->connection->getSocket() != nullptr)
oldSmallId = stalePlayer->connection->connection->getSocket()->getSmallId();
app.DebugPrintf("RECONNECT: Force-disconnecting old player smallId=%d\n", oldSmallId);
stalePlayer->connection->disconnect(DisconnectPacket::eDisconnect_Closed);
// Queue the old SmallId for recycling so it's not permanently leaked.
// PlayerList::tick() will call PushFreeSmallId/ClearSocketForSmallId.
if (oldSmallId != 0)
server->getPlayers()->queueSmallIdForRecycle(oldSmallId);
app.DebugPrintf("RECONNECT: Old player force-disconnect complete\n");
}
// Accept the login now that the old entry is removed.
app.DebugPrintf("RECONNECT: Calling handleAcceptedLogin for new connection\n");
handleAcceptedLogin(packet);
app.DebugPrintf("RECONNECT: handleAcceptedLogin complete\n");
}
#ifdef _WINDOWS64
else if (g_bRejectDuplicateNames)

View file

@ -19,6 +19,9 @@
#include "..\Minecraft.World\net.minecraft.network.packet.h"
#include "..\Minecraft.World\net.minecraft.network.h"
#include "Windows64\Windows64_Xuid.h"
#ifdef _WINDOWS64
#include "Windows64\Network\WinsockNetLayer.h"
#endif
#include "..\Minecraft.World\Pos.h"
#include "..\Minecraft.World\ProgressListener.h"
#include "..\Minecraft.World\HellRandomLevelSource.h"
@ -245,10 +248,25 @@ bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
addPlayerToReceiving( player );
int maxPlayersForPacket = getMaxPlayers() > 255 ? 255 : getMaxPlayers();
playerConnection->send(std::make_shared<LoginPacket>(L"", player->entityId, level->getLevelData()->getGenerator(), level->getSeed(), player->gameMode->getGameModeForPlayer()->getId(),
static_cast<byte>(level->dimension->id), static_cast<byte>(level->getMaxBuildHeight()), static_cast<byte>(maxPlayersForPacket),
level->difficulty, TelemetryManager->GetMultiplayerInstanceID(), static_cast<BYTE>(playerIndex), level->useNewSeaLevel(), player->getAllPlayerGamePrivileges(),
level->getLevelData()->getXZSize(), level->getLevelData()->getHellScale()));
BYTE newSmallId = 0;
Socket *sock = connection->getSocket();
INetworkPlayer *np = sock ? sock->getPlayer() : nullptr;
if (np) newSmallId = np->GetSmallId();
app.DebugPrintf("RECONNECT: placeNewPlayer smallId=%d entityId=%d dim=%d\n",
newSmallId, player->entityId, level->dimension->id);
playerConnection->send(std::make_shared<LoginPacket>(L"", player->entityId, level->getLevelData()->getGenerator(),
level->getSeed(),
player->gameMode->getGameModeForPlayer()->getId(),
static_cast<byte>(level->dimension->id), static_cast<byte>(level->getMaxBuildHeight()),
static_cast<byte>(maxPlayersForPacket),
level->difficulty,
TelemetryManager->GetMultiplayerInstanceID(),
static_cast<BYTE>(playerIndex), level->useNewSeaLevel(),
player->getAllPlayerGamePrivileges(),
level->getLevelData()->getXZSize(),
level->getLevelData()->getHellScale()));
playerConnection->send(std::make_shared<SetSpawnPositionPacket>(spawnPos->x, spawnPos->y, spawnPos->z));
playerConnection->send(std::make_shared<PlayerAbilitiesPacket>(&player->abilities));
playerConnection->send(std::make_shared<SetCarriedItemPacket>(player->inventory->selected));
@ -930,8 +948,8 @@ void PlayerList::repositionAcrossDimension(shared_ptr<Entity> entity, int lastDi
if (lastDimension != 1)
{
xt = static_cast<double>(Mth::clamp((int)xt, -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
zt = static_cast<double>(Mth::clamp((int)zt, -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
xt = static_cast<double>(Mth::clamp(static_cast<int>(xt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
zt = static_cast<double>(Mth::clamp(static_cast<int>(zt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
if (entity->isAlive())
{
newLevel->addEntity(entity);
@ -987,6 +1005,14 @@ void PlayerList::tick()
{
player->connection->disconnect( DisconnectPacket::eDisconnect_Closed );
}
#ifdef _WINDOWS64
// The old Connection's read/write threads are now dead (disconnect waits
// for them). Safe to recycle the smallId — no stale write thread can
// resolve getPlayer() to a new connection that reuses this slot.
WinsockNetLayer::PushFreeSmallId(smallId);
WinsockNetLayer::ClearSocketForSmallId(smallId);
#endif
}
LeaveCriticalSection(&m_closePlayersCS);
@ -1626,6 +1652,13 @@ void PlayerList::closePlayerConnectionBySmallId(BYTE networkSmallId)
LeaveCriticalSection(&m_closePlayersCS);
}
void PlayerList::queueSmallIdForRecycle(BYTE smallId)
{
EnterCriticalSection(&m_closePlayersCS);
m_smallIdsToClose.push_back(smallId);
LeaveCriticalSection(&m_closePlayersCS);
}
bool PlayerList::isXuidBanned(PlayerUID xuid)
{
if( xuid == INVALID_XUID ) return false;

View file

@ -133,6 +133,7 @@ public:
// 4J Added
void kickPlayerByShortId(BYTE networkSmallId);
void closePlayerConnectionBySmallId(BYTE networkSmallId);
void queueSmallIdForRecycle(BYTE smallId);
bool isXuidBanned(PlayerUID xuid);
// AP added for Vita so the range can be increased once the level starts
void setViewDistance(int newViewDistance);

View file

@ -46,19 +46,30 @@ void ServerConnection::handleConnection(shared_ptr<PendingConnection> uc)
void ServerConnection::stop()
{
std::vector<shared_ptr<PendingConnection> > pendingSnapshot;
EnterCriticalSection(&pending_cs);
for (unsigned int i = 0; i < pending.size(); i++)
{
shared_ptr<PendingConnection> uc = pending[i];
uc->connection->close(DisconnectPacket::eDisconnect_Closed);
}
pendingSnapshot = pending;
LeaveCriticalSection(&pending_cs);
for (unsigned int i = 0; i < players.size(); i++)
for (unsigned int i = 0; i < pendingSnapshot.size(); i++)
{
shared_ptr<PlayerConnection> player = players[i];
player->connection->close(DisconnectPacket::eDisconnect_Closed);
}
shared_ptr<PendingConnection> uc = pendingSnapshot[i];
if (uc != NULL && !uc->done)
{
uc->disconnect(DisconnectPacket::eDisconnect_Closed);
}
}
// Snapshot to avoid iterator invalidation if disconnect modifies the vector.
std::vector<shared_ptr<PlayerConnection> > playerSnapshot = players;
for (unsigned int i = 0; i < playerSnapshot.size(); i++)
{
shared_ptr<PlayerConnection> player = playerSnapshot[i];
if (player != NULL && !player->done)
{
player->disconnect(DisconnectPacket::eDisconnect_Quitting);
}
}
}
void ServerConnection::tick()
@ -107,7 +118,10 @@ void ServerConnection::tick()
players.erase(players.begin()+i);
i--;
}
player->connection->flush();
else
{
player->connection->flush();
}
}
}

View file

@ -256,7 +256,6 @@ void ServerLevel::tick()
if (time % (saveInterval) == (dimension->id * dimension->id * (saveInterval/2)))
#endif
{
//app.DebugPrintf("Incremental save\n");
PIXBeginNamedEvent(0,"Incremental save");
save(false, nullptr);
PIXEndNamedEvent();

View file

@ -426,7 +426,6 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
// unloaded on the client and so just gradually build up more and more of the finite set of chunks as the player moves
if( !g_NetworkManager.SystemFlagGet(connection->getNetworkPlayer(),flagIndex) )
{
// app.DebugPrintf("Creating BRUP for %d %d\n",nearest.x, nearest.z);
PIXBeginNamedEvent(0,"Creation BRUP for sending\n");
int64_t before = System::currentTimeMillis();
const auto packet = std::make_shared<BlockRegionUpdatePacket>(nearest.x * 16, 0, nearest.z * 16, 16, Level::maxBuildHeight, 16, level);

View file

@ -118,9 +118,7 @@ void StringTable::ProcessStringTableData(void)
else
{
app.DebugPrintf("Failed to get language\n");
#ifdef _DEBUG
__debugbreak();
#endif
isStatic = false;
}

View file

@ -997,8 +997,15 @@ void Textures::replaceTextureDirect(shortArray rawPixels, int w, int h, int id)
void Textures::releaseTexture(int id)
{
if (id <= 0) return;
loadedImages.erase(id);
glDeleteTextures(id);
// TextureFree() has no bounds check and crashes on stale IDs (e.g. after
// RenderManager.Initialise() which memsets the texture table to zero).
// TextureGetTexture() IS safe — returns NULL for invalid/unallocated IDs.
if (RenderManager.TextureGetTexture(id) != NULL)
{
glDeleteTextures(id);
}
}
int Textures::loadHttpTexture(const wstring& url, const wstring& backup)

View file

@ -996,6 +996,13 @@ void gdraw_D3D1X_(SetTileOrigin)(ID3D1X(RenderTargetView) *main_rt, ID3D1X(Depth
static void RADLINK gdraw_SetViewSizeAndWorldScale(S32 w, S32 h, F32 scalex, F32 scaley)
{
static S32 s_lastW = 0, s_lastH = 0;
static F32 s_lastSx = 0, s_lastSy = 0;
if (w != s_lastW || h != s_lastH || scalex != s_lastSx || scaley != s_lastSy) {
app.DebugPrintf("[GDRAW] SetViewSize: fw=%d fh=%d scale=%.6f,%.6f frametex=%dx%d vx=%d vy=%d\n",
w, h, scalex, scaley, gdraw->frametex_width, gdraw->frametex_height, gdraw->vx, gdraw->vy);
s_lastW = w; s_lastH = h; s_lastSx = scalex; s_lastSy = scaley;
}
memset(gdraw->frame, 0, sizeof(gdraw->frame));
gdraw->cur = gdraw->frame;
gdraw->fw = w;

View file

@ -26,7 +26,7 @@ bool WinsockNetLayer::s_initialized = false;
BYTE WinsockNetLayer::s_localSmallId = 0;
BYTE WinsockNetLayer::s_hostSmallId = 0;
unsigned int WinsockNetLayer::s_nextSmallId = 1;
unsigned int WinsockNetLayer::s_nextSmallId = XUSER_MAX_COUNT;
CRITICAL_SECTION WinsockNetLayer::s_sendLock;
CRITICAL_SECTION WinsockNetLayer::s_connectionsLock;
@ -54,6 +54,10 @@ std::vector<BYTE> WinsockNetLayer::s_freeSmallIds;
SOCKET WinsockNetLayer::s_smallIdToSocket[256];
CRITICAL_SECTION WinsockNetLayer::s_smallIdToSocketLock;
SOCKET WinsockNetLayer::s_splitScreenSocket[XUSER_MAX_COUNT] = { INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET };
BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF };
HANDLE WinsockNetLayer::s_splitScreenRecvThread[XUSER_MAX_COUNT] = {nullptr, nullptr, nullptr, nullptr};
bool g_Win64MultiplayerHost = false;
bool g_Win64MultiplayerJoin = false;
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
@ -111,6 +115,15 @@ void WinsockNetLayer::Shutdown()
s_hostConnectionSocket = INVALID_SOCKET;
}
// Stop accept loop first so no new RecvThread can be created while shutting down.
if (s_acceptThread != nullptr)
{
WaitForSingleObject(s_acceptThread, 2000);
CloseHandle(s_acceptThread);
s_acceptThread = nullptr;
}
std::vector<HANDLE> recvThreads;
EnterCriticalSection(&s_connectionsLock);
for (size_t i = 0; i < s_connections.size(); i++)
{
@ -118,18 +131,27 @@ void WinsockNetLayer::Shutdown()
if (s_connections[i].tcpSocket != INVALID_SOCKET)
{
closesocket(s_connections[i].tcpSocket);
s_connections[i].tcpSocket = INVALID_SOCKET;
}
if (s_connections[i].recvThread != nullptr)
{
recvThreads.push_back(s_connections[i].recvThread);
s_connections[i].recvThread = nullptr;
}
}
s_connections.clear();
LeaveCriticalSection(&s_connectionsLock);
if (s_acceptThread != nullptr)
// Wait for all host-side receive threads to exit before destroying state.
for (size_t i = 0; i < recvThreads.size(); i++)
{
WaitForSingleObject(s_acceptThread, 2000);
CloseHandle(s_acceptThread);
s_acceptThread = nullptr;
WaitForSingleObject(recvThreads[i], 2000);
CloseHandle(recvThreads[i]);
}
EnterCriticalSection(&s_connectionsLock);
s_connections.clear();
LeaveCriticalSection(&s_connectionsLock);
if (s_clientRecvThread != nullptr)
{
WaitForSingleObject(s_clientRecvThread, 2000);
@ -137,16 +159,38 @@ void WinsockNetLayer::Shutdown()
s_clientRecvThread = nullptr;
}
for (int i = 0; i < XUSER_MAX_COUNT; i++)
{
if (s_splitScreenSocket[i] != INVALID_SOCKET)
{
closesocket(s_splitScreenSocket[i]);
s_splitScreenSocket[i] = INVALID_SOCKET;
}
if (s_splitScreenRecvThread[i] != nullptr)
{
WaitForSingleObject(s_splitScreenRecvThread[i], 2000);
CloseHandle(s_splitScreenRecvThread[i]);
s_splitScreenRecvThread[i] = nullptr;
}
s_splitScreenSmallId[i] = 0xFF;
}
if (s_initialized)
{
EnterCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.clear();
LeaveCriticalSection(&s_disconnectLock);
EnterCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
LeaveCriticalSection(&s_freeSmallIdLock);
DeleteCriticalSection(&s_sendLock);
DeleteCriticalSection(&s_connectionsLock);
DeleteCriticalSection(&s_advertiseLock);
DeleteCriticalSection(&s_discoveryLock);
DeleteCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.clear();
DeleteCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
DeleteCriticalSection(&s_smallIdToSocketLock);
WSACleanup();
s_initialized = false;
@ -160,7 +204,7 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp)
s_isHost = true;
s_localSmallId = 0;
s_hostSmallId = 0;
s_nextSmallId = 1;
s_nextSmallId = XUSER_MAX_COUNT;
s_hostGamePort = port;
EnterCriticalSection(&s_freeSmallIdLock);
@ -249,6 +293,17 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
s_hostConnectionSocket = INVALID_SOCKET;
}
// Wait for old client recv thread to fully exit before starting a new connection.
// Without this, the old thread can read from the new socket (s_hostConnectionSocket
// is a global) and steal bytes from the new connection's TCP stream, causing
// packet stream misalignment on reconnect.
if (s_clientRecvThread != nullptr)
{
WaitForSingleObject(s_clientRecvThread, 5000);
CloseHandle(s_clientRecvThread);
s_clientRecvThread = nullptr;
}
struct addrinfo hints = {};
struct addrinfo* result = nullptr;
@ -351,6 +406,13 @@ bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize)
{
if (sock == INVALID_SOCKET || dataSize <= 0) return false;
// TODO: s_sendLock is a single global lock for ALL sockets. If one client's
// send() blocks (TCP window full, slow WiFi), every other write thread stalls
// waiting for this lock — no data flows to any player until the slow send
// completes. This scales badly with player count (8+ players = noticeable).
// Fix: replace with per-socket locks indexed by smallId (s_perSocketSendLock[256]).
// The lock only needs to prevent interleaving of header+payload on the SAME socket;
// sends to different sockets are independent and should never block each other.
EnterCriticalSection(&s_sendLock);
BYTE header[4];
@ -450,19 +512,28 @@ void WinsockNetLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsig
INetworkPlayer* pPlayerFrom = g_NetworkManager.GetPlayerBySmallId(fromSmallId);
INetworkPlayer* pPlayerTo = g_NetworkManager.GetPlayerBySmallId(toSmallId);
if (pPlayerFrom == nullptr || pPlayerTo == nullptr) return;
if (pPlayerFrom == nullptr || pPlayerTo == nullptr)
{
app.DebugPrintf("NET RECV: DROPPED %u bytes from=%d to=%d (player NULL: from=%p to=%p)\n",
dataSize, fromSmallId, toSmallId, pPlayerFrom, pPlayerTo);
return;
}
if (s_isHost)
{
::Socket* pSocket = pPlayerFrom->GetSocket();
if (pSocket != nullptr)
pSocket->pushDataToQueue(data, dataSize, false);
else
app.DebugPrintf("NET RECV: DROPPED %u bytes, host pSocket NULL for from=%d\n", dataSize, fromSmallId);
}
else
{
::Socket* pSocket = pPlayerTo->GetSocket();
if (pSocket != nullptr)
pSocket->pushDataToQueue(data, dataSize, true);
else
app.DebugPrintf("NET RECV: DROPPED %u bytes, client pSocket NULL for to=%d\n", dataSize, toSmallId);
}
}
@ -490,7 +561,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param)
}
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
if (g_pPlatformNetworkManager != NULL && !g_pPlatformNetworkManager->CanAcceptMoreConnections())
if (g_pPlatformNetworkManager != nullptr && !g_pPlatformNetworkManager->CanAcceptMoreConnections())
{
app.DebugPrintf("Win64 LAN: Rejecting connection, server at max players\n");
SendRejectWithReason(clientSocket, DisconnectPacket::eDisconnect_ServerFull);
@ -525,6 +596,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param)
{
app.DebugPrintf("Failed to send small ID to client\n");
closesocket(clientSocket);
PushFreeSmallId(assignedSmallId);
continue;
}
@ -662,7 +734,16 @@ bool WinsockNetLayer::PopDisconnectedSmallId(BYTE* outSmallId)
void WinsockNetLayer::PushFreeSmallId(BYTE smallId)
{
EnterCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.push_back(smallId);
// Guard against double-recycle: the reconnect path (queueSmallIdForRecycle) and
// the DoWork disconnect path can both push the same smallId. If we allow duplicates,
// AcceptThread will hand out the same smallId to two different connections.
bool alreadyFree = false;
for (size_t i = 0; i < s_freeSmallIds.size(); i++)
{
if (s_freeSmallIds[i] == smallId) { alreadyFree = true; break; }
}
if (!alreadyFree)
s_freeSmallIds.push_back(smallId);
LeaveCriticalSection(&s_freeSmallIdLock);
}
@ -682,6 +763,171 @@ void WinsockNetLayer::CloseConnectionBySmallId(BYTE smallId)
LeaveCriticalSection(&s_connectionsLock);
}
BYTE WinsockNetLayer::GetSplitScreenSmallId(int padIndex)
{
if (padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) return 0xFF;
return s_splitScreenSmallId[padIndex];
}
SOCKET WinsockNetLayer::GetLocalSocket(BYTE senderSmallId)
{
if (senderSmallId == s_localSmallId)
return s_hostConnectionSocket;
for (int i = 1; i < XUSER_MAX_COUNT; i++)
{
if (s_splitScreenSmallId[i] == senderSmallId && s_splitScreenSocket[i] != INVALID_SOCKET)
return s_splitScreenSocket[i];
}
return INVALID_SOCKET;
}
bool WinsockNetLayer::JoinSplitScreen(int padIndex, BYTE* outSmallId)
{
if (!s_active || s_isHost || padIndex <= 0 || padIndex >= XUSER_MAX_COUNT)
return false;
if (s_splitScreenSocket[padIndex] != INVALID_SOCKET)
{
return false;
}
struct addrinfo hints = {};
struct addrinfo* result = nullptr;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
char portStr[16];
sprintf_s(portStr, "%d", g_Win64MultiplayerPort);
if (getaddrinfo(g_Win64MultiplayerIP, portStr, &hints, &result) != 0 || result == nullptr)
{
app.DebugPrintf("Win64 LAN: Split-screen getaddrinfo failed for %s:%d\n", g_Win64MultiplayerIP, g_Win64MultiplayerPort);
return false;
}
SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (sock == INVALID_SOCKET)
{
freeaddrinfo(result);
return false;
}
int noDelay = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay));
if (connect(sock, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR)
{
app.DebugPrintf("Win64 LAN: Split-screen connect() failed: %d\n", WSAGetLastError());
closesocket(sock);
freeaddrinfo(result);
return false;
}
freeaddrinfo(result);
BYTE assignBuf[1];
if (!RecvExact(sock, assignBuf, 1))
{
app.DebugPrintf("Win64 LAN: Split-screen failed to receive smallId\n");
closesocket(sock);
return false;
}
if (assignBuf[0] == WIN64_SMALLID_REJECT)
{
BYTE rejectBuf[5];
RecvExact(sock, rejectBuf, 5);
app.DebugPrintf("Win64 LAN: Split-screen connection rejected\n");
closesocket(sock);
return false;
}
BYTE assignedSmallId = assignBuf[0];
s_splitScreenSocket[padIndex] = sock;
s_splitScreenSmallId[padIndex] = assignedSmallId;
*outSmallId = assignedSmallId;
app.DebugPrintf("Win64 LAN: Split-screen pad %d connected, assigned smallId=%d\n", padIndex, assignedSmallId);
int* threadParam = new int;
*threadParam = padIndex;
s_splitScreenRecvThread[padIndex] = CreateThread(nullptr, 0, SplitScreenRecvThreadProc, threadParam, 0, nullptr);
if (s_splitScreenRecvThread[padIndex] == nullptr)
{
delete threadParam;
closesocket(sock);
s_splitScreenSocket[padIndex] = INVALID_SOCKET;
s_splitScreenSmallId[padIndex] = 0xFF;
app.DebugPrintf("Win64 LAN: CreateThread failed for split-screen pad %d\n", padIndex);
return false;
}
return true;
}
void WinsockNetLayer::CloseSplitScreenConnection(int padIndex)
{
if (padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) return;
if (s_splitScreenSocket[padIndex] != INVALID_SOCKET)
{
closesocket(s_splitScreenSocket[padIndex]);
s_splitScreenSocket[padIndex] = INVALID_SOCKET;
}
s_splitScreenSmallId[padIndex] = 0xFF;
if (s_splitScreenRecvThread[padIndex] != nullptr)
{
WaitForSingleObject(s_splitScreenRecvThread[padIndex], 2000);
CloseHandle(s_splitScreenRecvThread[padIndex]);
s_splitScreenRecvThread[padIndex] = nullptr;
}
}
DWORD WINAPI WinsockNetLayer::SplitScreenRecvThreadProc(LPVOID param)
{
int padIndex = *(int*)param;
delete (int*)param;
SOCKET sock = s_splitScreenSocket[padIndex];
BYTE localSmallId = s_splitScreenSmallId[padIndex];
std::vector<BYTE> recvBuf;
recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE);
while (s_active && s_splitScreenSocket[padIndex] != INVALID_SOCKET)
{
BYTE header[4];
if (!RecvExact(sock, header, 4))
{
app.DebugPrintf("Win64 LAN: Split-screen pad %d disconnected from host\n", padIndex);
break;
}
int packetSize = ((uint32_t)header[0] << 24) | ((uint32_t)header[1] << 16) |
((uint32_t)header[2] << 8) | ((uint32_t)header[3]);
if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE)
{
app.DebugPrintf("Win64 LAN: Split-screen pad %d invalid packet size %d\n", padIndex, packetSize);
break;
}
if ((int)recvBuf.size() < packetSize)
recvBuf.resize(packetSize);
if (!RecvExact(sock, &recvBuf[0], packetSize))
{
app.DebugPrintf("Win64 LAN: Split-screen pad %d disconnected from host (body)\n", padIndex);
break;
}
HandleDataReceived(s_hostSmallId, localSmallId, &recvBuf[0], packetSize);
}
EnterCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.push_back(localSmallId);
LeaveCriticalSection(&s_disconnectLock);
return 0;
}
DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param)
{
std::vector<BYTE> recvBuf;

View file

@ -72,6 +72,12 @@ public:
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
static bool SendOnSocket(SOCKET sock, const void* data, int dataSize);
// Non-host split-screen: additional TCP connections to host, one per pad
static bool JoinSplitScreen(int padIndex, BYTE* outSmallId);
static void CloseSplitScreenConnection(int padIndex);
static SOCKET GetLocalSocket(BYTE senderSmallId);
static BYTE GetSplitScreenSmallId(int padIndex);
static bool IsHosting() { return s_isHost; }
static bool IsConnected() { return s_connected; }
static bool IsActive() { return s_active; }
@ -103,6 +109,7 @@ private:
static DWORD WINAPI AcceptThreadProc(LPVOID param);
static DWORD WINAPI RecvThreadProc(LPVOID param);
static DWORD WINAPI ClientRecvThreadProc(LPVOID param);
static DWORD WINAPI SplitScreenRecvThreadProc(LPVOID param);
static DWORD WINAPI AdvertiseThreadProc(LPVOID param);
static DWORD WINAPI DiscoveryThreadProc(LPVOID param);
@ -147,6 +154,11 @@ private:
static SOCKET s_smallIdToSocket[256];
static CRITICAL_SECTION s_smallIdToSocketLock;
// Per-pad split-screen TCP connections (client-side, non-host only)
static SOCKET s_splitScreenSocket[XUSER_MAX_COUNT];
static BYTE s_splitScreenSmallId[XUSER_MAX_COUNT];
static HANDLE s_splitScreenRecvThread[XUSER_MAX_COUNT];
public:
static void ClearSocketForSmallId(BYTE smallId);
};

View file

@ -49,6 +49,11 @@
#include "Windows64_Xuid.h"
#include "Common/UI/UI.h"
// Forward-declare the internal Renderer class and its global instance from 4J_Render_PC_d.lib.
// C4JRender (RenderManager) is a stateless wrapper — all D3D state lives in InternalRenderManager.
class Renderer;
extern Renderer InternalRenderManager;
#include "Xbox/resource.h"
#ifdef _MSC_VER
@ -91,10 +96,20 @@ DWORD dwProfileSettingsA[NUM_PROFILE_VALUES]=
BOOL g_bWidescreen = TRUE;
// Screen resolution — auto-detected from the monitor at startup.
// The 3D world renders at native resolution; Flash UI is 16:9-fitted and centered
// within each viewport (pillarboxed on ultrawide, letterboxed on tall displays).
// ApplyScreenMode() can still override these for debug/test resolutions via launch args.
int g_iScreenWidth = 1920;
int g_iScreenHeight = 1080;
// Real window dimensions — updated on every WM_SIZE so the 3D perspective
// always matches the current window, even after a resize.
int g_rScreenWidth = 1920;
int g_rScreenHeight = 1080;
float g_iAspectRatio = static_cast<float>(g_iScreenWidth) / g_iScreenHeight;
static bool g_bResizeReady = false;
char g_Win64Username[17] = { 0 };
wchar_t g_Win64UsernameW[17] = { 0 };
@ -103,14 +118,6 @@ wchar_t g_Win64UsernameW[17] = { 0 };
static bool g_isFullscreen = false;
static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) };
//--------------------------------------------------------------------------------------
// Update the Aspect Ratio to support Any Aspect Ratio
//--------------------------------------------------------------------------------------
void UpdateAspectRatio(int width, int height)
{
g_iAspectRatio = static_cast<float>(width) / height;
}
struct Win64LaunchOptions
{
int screenMode;
@ -531,6 +538,11 @@ ID3D11Texture2D* g_pDepthStencilBuffer = nullptr;
// WM_SIZE - handle resizing logic to support Any Aspect Ratio
//
//
static bool ResizeD3D(int newW, int newH); // forward declaration
static bool g_bInSizeMove = false; // true while the user is dragging the window border
static int g_pendingResizeW = 0; // deferred resize dimensions
static int g_pendingResizeH = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
@ -668,9 +680,40 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
}
}
break;
case WM_ENTERSIZEMOVE:
g_bInSizeMove = true;
break;
case WM_EXITSIZEMOVE:
g_bInSizeMove = false;
if (g_pendingResizeW > 0 && g_pendingResizeH > 0)
{
// g_rScreenWidth/Height updated inside ResizeD3D to backbuffer dims
ResizeD3D(g_pendingResizeW, g_pendingResizeH);
g_pendingResizeW = 0;
g_pendingResizeH = 0;
}
break;
case WM_SIZE:
{
UpdateAspectRatio(LOWORD(lParam), HIWORD(lParam));
int newW = LOWORD(lParam);
int newH = HIWORD(lParam);
if (newW > 0 && newH > 0)
{
if (g_bInSizeMove)
{
// Just store the latest size, resize when dragging ends
g_pendingResizeW = newW;
g_pendingResizeH = newH;
}
else
{
// Immediate resize (maximize, programmatic resize, etc.)
// g_rScreenWidth/Height updated inside ResizeD3D to backbuffer dims
ResizeD3D(newW, newH);
}
}
}
break;
default:
@ -719,7 +762,7 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
g_hInst = hInstance; // Store instance handle in our global variable
RECT wr = {0, 0, g_iScreenWidth, g_iScreenHeight}; // set the size, but not the position
RECT wr = {0, 0, g_rScreenWidth, g_rScreenHeight}; // set the size, but not the position
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); // adjust the size
g_hWnd = CreateWindow( "MinecraftClass",
@ -805,8 +848,8 @@ HRESULT InitDevice()
UINT width = rc.right - rc.left;
UINT height = rc.bottom - rc.top;
//app.DebugPrintf("width: %d, height: %d\n", width, height);
width = g_iScreenWidth;
height = g_iScreenHeight;
width = g_rScreenWidth;
height = g_rScreenHeight;
//app.DebugPrintf("width: %d, height: %d\n", width, height);
UINT createDeviceFlags = 0;
@ -838,7 +881,7 @@ HRESULT InitDevice()
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
@ -922,6 +965,281 @@ void Render()
g_pSwapChain->Present( 0, 0 );
}
//--------------------------------------------------------------------------------------
// Rebuild D3D11 resources after a window resize
//--------------------------------------------------------------------------------------
static bool ResizeD3D(int newW, int newH)
{
if (newW <= 0 || newH <= 0) return false;
if (!g_pSwapChain) return false;
if (!g_bResizeReady) return false;
int bbW = newW;
int bbH = newH;
// InternalRenderManager member offsets (from decompiled Renderer.h):
// 0x10 m_pDevice (ID3D11Device*)
// 0x18 m_pDeviceContext (ID3D11DeviceContext*)
// 0x20 m_pSwapChain (IDXGISwapChain*)
// 0x28 renderTargetView (ID3D11RenderTargetView*) — backbuffer RTV
// 0x50 renderTargetShaderResourceView (ID3D11ShaderResourceView*)
// 0x98 depthStencilView (ID3D11DepthStencilView*)
// 0x5138 backBufferWidth (DWORD) — used by StartFrame() for viewport
// 0x513C backBufferHeight (DWORD) — used by StartFrame() for viewport
//
// Strategy: destroy old swap chain, create new one, patch Renderer's internal
// pointers directly. This avoids both ResizeBuffers (outstanding ref issues)
// and Initialise() (which wipes the texture table via memset).
// The Renderer's old RTV/SRV/DSV are intentionally NOT released — they become
// orphaned with the old swap chain. Tiny leak, but avoids fighting unknown refs.
char* pRM = (char*)&InternalRenderManager;
ID3D11RenderTargetView** ppRM_RTV = (ID3D11RenderTargetView**)(pRM + 0x28);
ID3D11ShaderResourceView** ppRM_SRV = (ID3D11ShaderResourceView**)(pRM + 0x50);
ID3D11DepthStencilView** ppRM_DSV = (ID3D11DepthStencilView**)(pRM + 0x98);
IDXGISwapChain** ppRM_SC = (IDXGISwapChain**)(pRM + 0x20);
DWORD* pRM_BBWidth = (DWORD*)(pRM + 0x5138);
DWORD* pRM_BBHeight = (DWORD*)(pRM + 0x513C);
// Verify offsets by checking device and swap chain pointers
ID3D11Device** ppRM_Device = (ID3D11Device**)(pRM + 0x10);
if (*ppRM_Device != g_pd3dDevice || *ppRM_SC != g_pSwapChain)
{
app.DebugPrintf("[RESIZE] ERROR: RenderManager offset verification failed! "
"device=%p (expected %p) swapchain=%p (expected %p)\n",
*ppRM_Device, g_pd3dDevice, *ppRM_SC, g_pSwapChain);
return false;
}
// Cross-check backbuffer dimension offsets against swap chain desc
DXGI_SWAP_CHAIN_DESC oldScDesc;
g_pSwapChain->GetDesc(&oldScDesc);
bool bbDimsValid = (*pRM_BBWidth == oldScDesc.BufferDesc.Width &&
*pRM_BBHeight == oldScDesc.BufferDesc.Height);
if (!bbDimsValid)
{
app.DebugPrintf("[RESIZE] WARNING: backBuffer dim offsets wrong: "
"stored=%ux%u, swapchain=%ux%u\n",
*pRM_BBWidth, *pRM_BBHeight, oldScDesc.BufferDesc.Width, oldScDesc.BufferDesc.Height);
}
RenderManager.Suspend();
while (!RenderManager.Suspended()) { Sleep(1); }
PostProcesser::GetInstance().Cleanup();
g_pImmediateContext->ClearState();
g_pImmediateContext->Flush();
// Release OUR views and depth buffer
if (g_pRenderTargetView) { g_pRenderTargetView->Release(); g_pRenderTargetView = NULL; }
if (g_pDepthStencilView) { g_pDepthStencilView->Release(); g_pDepthStencilView = NULL; }
if (g_pDepthStencilBuffer) { g_pDepthStencilBuffer->Release(); g_pDepthStencilBuffer = NULL; }
gdraw_D3D11_PreReset();
// Get IDXGIFactory from the existing device BEFORE destroying the old swap chain.
// If anything fails before we have a new swap chain, we abort without destroying
// the old one — leaving the Renderer in a valid (old-size) state.
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
bool success = false;
HRESULT hr;
IDXGIDevice* dxgiDevice = NULL;
IDXGIAdapter* dxgiAdapter = NULL;
IDXGIFactory* dxgiFactory = NULL;
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
if (FAILED(hr)) goto postReset;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; }
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
dxgiAdapter->Release();
dxgiDevice->Release();
if (FAILED(hr)) goto postReset;
// Create new swap chain at backbuffer size
{
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferCount = 1;
sd.BufferDesc.Width = bbW;
sd.BufferDesc.Height = bbH;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
IDXGISwapChain* pNewSwapChain = NULL;
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
dxgiFactory->Release();
if (FAILED(hr) || pNewSwapChain == NULL)
{
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr);
goto postReset;
}
// New swap chain created successfully — NOW destroy the old one.
// The Renderer's internal RTV/SRV still reference the old backbuffer —
// those COM objects become orphaned (tiny leak, harmless). We DON'T
// release them because unknown code may also hold refs.
pOldSwapChain->Release();
g_pSwapChain = pNewSwapChain;
}
// Patch Renderer's swap chain pointer
*ppRM_SC = g_pSwapChain;
// Create render target views from new backbuffer
{
ID3D11Texture2D* pBackBuffer = NULL;
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
if (FAILED(hr)) goto postReset;
// Our RTV
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
if (FAILED(hr)) { pBackBuffer->Release(); goto postReset; }
// Renderer's internal RTV (offset 0x28)
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, ppRM_RTV);
if (FAILED(hr)) { pBackBuffer->Release(); goto postReset; }
// Renderer's SRV: separate texture matching backbuffer dims (used by CaptureThumbnail)
D3D11_TEXTURE2D_DESC backDesc = {};
pBackBuffer->GetDesc(&backDesc);
pBackBuffer->Release();
D3D11_TEXTURE2D_DESC srvDesc = backDesc;
srvDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
ID3D11Texture2D* srvTexture = NULL;
hr = g_pd3dDevice->CreateTexture2D(&srvDesc, NULL, &srvTexture);
if (SUCCEEDED(hr))
{
hr = g_pd3dDevice->CreateShaderResourceView(srvTexture, NULL, ppRM_SRV);
srvTexture->Release();
}
if (FAILED(hr)) goto postReset;
}
// Recreate depth stencil at backbuffer size
{
D3D11_TEXTURE2D_DESC descDepth = {};
descDepth.Width = bbW;
descDepth.Height = bbH;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
hr = g_pd3dDevice->CreateTexture2D(&descDepth, NULL, &g_pDepthStencilBuffer);
if (FAILED(hr)) goto postReset;
D3D11_DEPTH_STENCIL_VIEW_DESC descDSView = {};
descDSView.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDSView.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
hr = g_pd3dDevice->CreateDepthStencilView(g_pDepthStencilBuffer, &descDSView, &g_pDepthStencilView);
if (FAILED(hr)) goto postReset;
}
// Patch Renderer's DSV (AddRef because both we and the Renderer reference it)
g_pDepthStencilView->AddRef();
*ppRM_DSV = g_pDepthStencilView;
// Update Renderer's cached backbuffer dimensions (StartFrame uses these for viewport)
if (bbDimsValid)
{
*pRM_BBWidth = (DWORD)bbW;
*pRM_BBHeight = (DWORD)bbH;
}
// Rebind render targets and viewport
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
{
D3D11_VIEWPORT vp = {};
vp.Width = (FLOAT)bbW;
vp.Height = (FLOAT)bbH;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
g_pImmediateContext->RSSetViewports(1, &vp);
}
ui.updateRenderTargets(g_pRenderTargetView, g_pDepthStencilView);
ui.updateScreenSize(bbW, bbH);
// Track actual backbuffer dimensions for the rest of the engine
g_rScreenWidth = bbW;
g_rScreenHeight = bbH;
success = true;
postReset:
if (!success && g_pSwapChain != NULL)
{
// Failure recovery: recreate our views from whatever swap chain survived
// so ui.m_pRenderTargetView / m_pDepthStencilView don't dangle.
DXGI_SWAP_CHAIN_DESC recoveryDesc;
g_pSwapChain->GetDesc(&recoveryDesc);
int recW = (int)recoveryDesc.BufferDesc.Width;
int recH = (int)recoveryDesc.BufferDesc.Height;
if (g_pRenderTargetView == NULL)
{
ID3D11Texture2D* pBB = NULL;
if (SUCCEEDED(g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBB)))
{
g_pd3dDevice->CreateRenderTargetView(pBB, NULL, &g_pRenderTargetView);
pBB->Release();
}
}
if (g_pDepthStencilView == NULL)
{
D3D11_TEXTURE2D_DESC dd = {};
dd.Width = recW; dd.Height = recH; dd.MipLevels = 1; dd.ArraySize = 1;
dd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dd.SampleDesc.Count = 1; dd.Usage = D3D11_USAGE_DEFAULT;
dd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
if (g_pDepthStencilBuffer == NULL)
g_pd3dDevice->CreateTexture2D(&dd, NULL, &g_pDepthStencilBuffer);
if (g_pDepthStencilBuffer != NULL)
{
D3D11_DEPTH_STENCIL_VIEW_DESC dsvd = {};
dsvd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
g_pd3dDevice->CreateDepthStencilView(g_pDepthStencilBuffer, &dsvd, &g_pDepthStencilView);
}
}
if (g_pRenderTargetView != NULL)
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
ui.updateRenderTargets(g_pRenderTargetView, g_pDepthStencilView);
// If the surviving swap chain is the OLD one, dims are unchanged.
// If it's the NEW one (partial failure after swap), update to new dims.
if (g_pSwapChain != pOldSwapChain)
{
g_rScreenWidth = recW;
g_rScreenHeight = recH;
ui.updateScreenSize(recW, recH);
}
app.DebugPrintf("[RESIZE] FAILED but recovered views at %dx%d\n", g_rScreenWidth, g_rScreenHeight);
}
gdraw_D3D11_PostReset();
gdraw_D3D11_SetRendertargetSize(g_rScreenWidth, g_rScreenHeight);
if (success)
IggyFlushInstalledFonts();
RenderManager.Resume();
if (success)
PostProcesser::GetInstance().Init();
return success;
}
//--------------------------------------------------------------------------------------
// Toggle borderless fullscreen
//--------------------------------------------------------------------------------------
@ -963,6 +1281,8 @@ void CleanupDevice()
{
if( g_pImmediateContext ) g_pImmediateContext->ClearState();
if( g_pDepthStencilView ) g_pDepthStencilView->Release();
if( g_pDepthStencilBuffer ) g_pDepthStencilBuffer->Release();
if( g_pRenderTargetView ) g_pRenderTargetView->Release();
if( g_pSwapChain ) g_pSwapChain->Release();
if( g_pImmediateContext ) g_pImmediateContext->Release();
@ -976,7 +1296,7 @@ static Minecraft* InitialiseMinecraftRuntime()
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
app.loadStringTable();
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_rScreenWidth, g_rScreenHeight);
InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
g_KBMInput.Init();
@ -1203,8 +1523,12 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
// Declare DPI awareness so GetSystemMetrics returns physical pixels
SetProcessDPIAware();
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// Use the native monitor resolution for the window and swap chain,
// but keep g_iScreenWidth/Height at 1920x1080 for logical resolution
// (SWF selection, ortho projection, game logic). The real window
// dimensions are tracked by g_rScreenWidth/g_rScreenHeight.
g_rScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_rScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// Load username from username.txt
char exePath[MAX_PATH] = {};
@ -1257,6 +1581,72 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17);
// convert servers.txt to servers.db
if (GetFileAttributesA("servers.txt") != INVALID_FILE_ATTRIBUTES &&
GetFileAttributesA("servers.db") == INVALID_FILE_ATTRIBUTES)
{
FILE* txtFile = nullptr;
if (fopen_s(&txtFile, "servers.txt", "r") == 0 && txtFile)
{
struct MigEntry { std::string ip; uint16_t port; std::string name; };
std::vector<MigEntry> migEntries;
char line[512];
while (fgets(line, sizeof(line), txtFile))
{
int l = (int)strlen(line);
while (l > 0 && (line[l - 1] == '\n' || line[l - 1] == '\r' || line[l - 1] == ' '))
line[--l] = '\0';
if (l == 0) continue;
std::string srvIP = line;
if (!fgets(line, sizeof(line), txtFile)) break;
l = (int)strlen(line);
while (l > 0 && (line[l - 1] == '\n' || line[l - 1] == '\r' || line[l - 1] == ' '))
line[--l] = '\0';
uint16_t srvPort = (uint16_t)atoi(line);
std::string srvName;
if (fgets(line, sizeof(line), txtFile))
{
l = (int)strlen(line);
while (l > 0 && (line[l - 1] == '\n' || line[l - 1] == '\r' || line[l - 1] == ' '))
line[--l] = '\0';
srvName = line;
}
if (!srvIP.empty() && srvPort > 0)
migEntries.push_back({srvIP, srvPort, srvName});
}
fclose(txtFile);
if (!migEntries.empty())
{
FILE* dbFile = nullptr;
if (fopen_s(&dbFile, "servers.db", "wb") == 0 && dbFile)
{
fwrite("MCSV", 1, 4, dbFile);
uint32_t ver = 1;
uint32_t cnt = (uint32_t)migEntries.size();
fwrite(&ver, sizeof(uint32_t), 1, dbFile);
fwrite(&cnt, sizeof(uint32_t), 1, dbFile);
for (size_t i = 0; i < migEntries.size(); i++)
{
uint16_t ipLen = (uint16_t)migEntries[i].ip.length();
fwrite(&ipLen, sizeof(uint16_t), 1, dbFile);
fwrite(migEntries[i].ip.c_str(), 1, ipLen, dbFile);
fwrite(&migEntries[i].port, sizeof(uint16_t), 1, dbFile);
uint16_t nameLen = (uint16_t)migEntries[i].name.length();
fwrite(&nameLen, sizeof(uint16_t), 1, dbFile);
fwrite(migEntries[i].name.c_str(), 1, nameLen, dbFile);
}
fclose(dbFile);
}
}
}
}
// Initialize global strings
MyRegisterClass(hInstance);
@ -1345,6 +1735,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
CleanupDevice();
return 1;
}
g_bResizeReady = true;
//app.TemporaryCreateGameStart();
@ -1384,6 +1775,14 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
}
if (msg.message == WM_QUIT) break;
// When the window is minimized (e.g. "Show Desktop"), skip rendering entirely
// to avoid pegging the GPU at 100% presenting to a non-visible swap chain.
if (IsIconic(g_hWnd))
{
Sleep(100);
continue;
}
RenderManager.StartFrame();
#if 0
if(pMinecraft->soundEngine->isStreamingWavebankReady() &&

View file

@ -143,6 +143,12 @@ void ConsoleUIController::endCustomDraw(IggyCustomDrawCallbackRegion *region)
PIXEndNamedEvent();
}
void ConsoleUIController::updateRenderTargets(ID3D11RenderTargetView* rtv, ID3D11DepthStencilView* dsv)
{
m_pRenderTargetView = rtv;
m_pDepthStencilView = dsv;
}
void ConsoleUIController::setTileOrigin(S32 xPos, S32 yPos)
{
gdraw_D3D11_SetTileOrigin( m_pRenderTargetView,

View file

@ -16,10 +16,12 @@ public:
virtual CustomDrawData *calculateCustomDraw(IggyCustomDrawCallbackRegion *region);
virtual void endCustomDraw(IggyCustomDrawCallbackRegion *region);
void updateRenderTargets(ID3D11RenderTargetView* rtv, ID3D11DepthStencilView* dsv);
protected:
virtual void setTileOrigin(S32 xPos, S32 yPos);
public:
public:
GDrawTexture *getSubstitutionTexture(int textureId);
void destroySubstitutionTexture(void *destroyCallBackData, GDrawTexture *handle);

View file

@ -186,6 +186,29 @@ namespace Win64Xuid
return xuid;
}
inline PlayerUID DeriveXuidForPad(PlayerUID baseXuid, int iPad)
{
if (iPad == 0)
return baseXuid;
// Deterministic per-pad XUID: hash the base XUID with the pad number.
// Produces a fully unique 64-bit value with no risk of overlap.
// Suggested by rtm516 to avoid adjacent-integer collisions from the old "+ iPad" approach.
uint64_t raw = Mix64((uint64_t)baseXuid + (uint64_t)iPad);
raw |= 0x8000000000000000ULL; // keep high bit set like all our XUIDs
PlayerUID xuid = (PlayerUID)raw;
if (!IsPersistedUidValid(xuid))
{
raw ^= 0x0100000000000001ULL;
xuid = (PlayerUID)raw;
}
if (!IsPersistedUidValid(xuid))
xuid = (PlayerUID)(0xD15EA5E000000001ULL + iPad);
return xuid;
}
inline PlayerUID ResolvePersistentXuid()
{
// Process-local cache: uid.dat is immutable during runtime and this path is hot.

View file

@ -48,11 +48,12 @@ void glLoadIdentity()
RenderManager.MatrixSetIdentity();
}
// AAR - Use calculated aspect ratio to support dynamic resizing
extern float g_iAspectRatio;
// AAR - Use the aspect ratio passed by the caller. For single-player this
// equals g_iAspectRatio (screen width / height), but for split-screen
// getFovAndAspect adjusts it to match the viewport dimensions.
void gluPerspective(float fovy, float aspect, float zNear, float zFar)
{
RenderManager.MatrixPerspective(fovy, g_iAspectRatio, zNear, zFar);
RenderManager.MatrixPerspective(fovy, aspect, zNear, zFar);
}
void glOrtho(float left,float right,float bottom,float top,float zNear,float zFar)

View file

@ -303,16 +303,20 @@ void Arrow::tick()
damageSource = DamageSource::arrow(dynamic_pointer_cast<Arrow>(shared_from_this()), owner);
}
if(!res->entity->isInvulnerable())
{
if (isOnFire() && res->entity->GetType() != eTYPE_ENDERMAN)
{
res->entity->setOnFire(5);
}
}
if(res->entity->hurt(damageSource, dmg))
{
// Firx for #67839 - Customer Encountered: Bows enchanted with "Flame" still set things on fire if pvp/attack animals is turned off
// 4J Stu - We should not set the entity on fire unless we can cause some damage (this doesn't necessarily mean that the arrow hit lowered their health)
// set targets on fire first because we want cooked
// pork/chicken/steak
if (isOnFire() && res->entity->GetType() != eTYPE_ENDERMAN)
{
res->entity->setOnFire(5);
}
if (res->entity->instanceof(eTYPE_LIVINGENTITY))
{

View file

@ -48,7 +48,7 @@ BlockRegionUpdatePacket::BlockRegionUpdatePacket(int x, int y, int z, int xs, in
// TODO - we should be using compressed data directly here rather than decompressing first and then recompressing...
byteArray rawBuffer;
if( xs == 16 && ys == Level::maxBuildHeight && zs == 16 && ( ( x & 15 ) == 0 ) && ( y == 0 ) && ( ( z & 15 ) == 0 ) )
if( xs == 16 && ys == Level::maxBuildHeight && zs == 16 && ( ( x & 15 ) == 0 ) && ( y == 0 ) && ( ( z & 15 ) == 0 ) )
{
bIsFullChunk = true;
@ -66,6 +66,8 @@ BlockRegionUpdatePacket::BlockRegionUpdatePacket(int x, int y, int z, int xs, in
{
size = 0;
buffer = byteArray();
app.DebugPrintf("[BRUP-SERVER] *** EMPTY BUFFER for chunk(%d,%d) ys=%d bIsFullChunk=%d\n",
x>>4, z>>4, this->ys, bIsFullChunk ? 1 : 0);
}
else
{
@ -96,7 +98,7 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
zs = dis->read() + 1;
bIsFullChunk = (chunkFlags & BLOCK_REGION_UPDATE_FULLCHUNK) ? true : false;
if(chunkFlags & BLOCK_REGION_UPDATE_ZEROHEIGHT)
if(chunkFlags & BLOCK_REGION_UPDATE_ZEROHEIGHT)
ys = 0;
size = dis->readInt();
@ -106,6 +108,10 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
if(size == 0)
{
buffer = byteArray();
if(bIsFullChunk)
{
app.DebugPrintf("[BRUP-READ] *** ZERO-SIZE full chunk packet at (%d,%d)!\n", x>>4, z>>4);
}
}
else
{
@ -131,6 +137,11 @@ void BlockRegionUpdatePacket::read(DataInputStream *dis) //throws IOException
delete [] compressedBuffer.data;
if(buffer.length != outputSize)
{
app.DebugPrintf("*** BlockRegionUpdatePacket DECOMPRESS MISMATCH: expected=%d got=%d xs=%d ys=%d zs=%d fullChunk=%d compressedSize=%d levelIdx=%d\n",
buffer.length, outputSize, xs, ys, zs, bIsFullChunk ? 1 : 0, size, levelIdx);
}
assert(buffer.length == outputSize);
}
}

View file

@ -146,7 +146,9 @@ CompressedTileStorage::CompressedTileStorage(bool isEmpty)
bool CompressedTileStorage::isRenderChunkEmpty(int y) // y == 0, 16, 32... 112 (representing a 16 byte range)
{
int block;
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return true;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
for( int x = 0; x < 16; x += 4 )
for( int z = 0; z < 16; z += 4 )
@ -533,8 +535,12 @@ void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
// Gets all tile values into an array of length 32768.
void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
{
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *data = indicesAndData + 1024;
// Snapshot pointer to avoid race with compress() swapping indicesAndData
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
unsigned char *data = localIndicesAndData + 1024;
for( int i = 0; i < 512; i++ )
{
@ -588,10 +594,13 @@ void CompressedTileStorage::getData(byteArray retArray, unsigned int retOffset)
// Get an individual tile value
int CompressedTileStorage::get(int x, int y, int z)
{
if(!indicesAndData) return 0;
// Take a local snapshot of indicesAndData to avoid a race with compress() which swaps the pointer.
// Both blockIndices and data must reference the same buffer, otherwise index offsets won't match.
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return 0;
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *data = indicesAndData + 1024;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
unsigned char *data = localIndicesAndData + 1024;
int block, tile;
getBlockAndTile( &block, &tile, x, y, z );
@ -1223,7 +1232,10 @@ int CompressedTileStorage::getAllocatedSize(int *count0, int *count1, int *count
*count4 = 0;
*count8 = 0;
unsigned short *blockIndices = (unsigned short *)indicesAndData;
// Volatile read: compress() can swap indicesAndData concurrently outside cs_write
unsigned char *localIndicesAndData = *(unsigned char *volatile *)&indicesAndData;
if(!localIndicesAndData) return 0;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
for(int i = 0; i < 512; i++ )
{
unsigned short idxType = blockIndices[i] & INDEX_TYPE_MASK;
@ -1256,7 +1268,9 @@ int CompressedTileStorage::getAllocatedSize(int *count0, int *count1, int *count
int CompressedTileStorage::getHighestNonEmptyY()
{
unsigned short *blockIndices = (unsigned short *)indicesAndData;
unsigned char *localIndicesAndData = indicesAndData;
if(!localIndicesAndData) return -1;
unsigned short *blockIndices = (unsigned short *)localIndicesAndData;
unsigned int highestYBlock = 0;
bool found = false;
@ -1297,19 +1311,26 @@ int CompressedTileStorage::getHighestNonEmptyY()
// Multiply by the number of vertical tiles in a block, and then add that again to be at the top of the block
highestNonEmptyY = (highestYBlock * 4) + 4;
}
else
{
app.DebugPrintf("[CTS-WARN] getHighestNonEmptyY() returned -1! allocatedSize=%d indicesAndData=%p\n",
allocatedSize, indicesAndData);
}
return highestNonEmptyY;
}
void CompressedTileStorage::write(DataOutputStream *dos)
{
dos->writeInt(allocatedSize);
if(indicesAndData)
// Volatile read: compress() can swap indicesAndData concurrently outside cs_write
unsigned char *localIndicesAndData = *(unsigned char *volatile *)&indicesAndData;
if(localIndicesAndData)
{
if(LOCALSYTEM_ENDIAN == BIGENDIAN)
{
// The first 1024 bytes are an array of shorts, so we need to reverse the endianness
byteArray indicesCopy(1024);
memcpy(indicesCopy.data, indicesAndData, 1024);
memcpy(indicesCopy.data, localIndicesAndData, 1024);
reverseIndices(indicesCopy.data);
dos->write(indicesCopy);
delete [] indicesCopy.data;
@ -1317,13 +1338,13 @@ void CompressedTileStorage::write(DataOutputStream *dos)
// Write the rest of the data
if(allocatedSize > 1024)
{
byteArray dataWrapper(indicesAndData + 1024, allocatedSize - 1024);
byteArray dataWrapper(localIndicesAndData + 1024, allocatedSize - 1024);
dos->write(dataWrapper);
}
}
else
{
byteArray wrapper(indicesAndData, allocatedSize);
byteArray wrapper(localIndicesAndData, allocatedSize);
dos->write(wrapper);
}
}

View file

@ -250,6 +250,14 @@ bool Connection::writeTick()
// Otherwise just buffer the packet with other outgoing packets as the java game did
if(packet->shouldDelay)
{
// Flush any buffered data BEFORE writing directly to the socket.
// bufferedDos and sos->writeWithFlags both write to the same underlying
// socket stream. If bufferedDos has unflushed bytes (from packets written
// via the outgoing queue above), writing directly to sos here would send
// the delayed packet's bytes BEFORE the buffered bytes, desynchronizing
// the TCP stream on the receiving end.
bufferedDos->flush();
Packet::writePacket(packet, byteArrayDos);
// 4J Stu - Changed this so that rather than writing to the network stream through a buffered stream we want to:

View file

@ -4239,6 +4239,10 @@ void Level::setBlocksAndData(int x, int y, int z, int xs, int ys, int zs, byteAr
if (z0 < 0) z0 = 0;
if (z1 > 16) z1 = 16;
LevelChunk *lc = getChunk(xc, zc);
if(lc->isEmpty())
{
app.DebugPrintf("[SETBLOCKS-BUG] getChunk(%d,%d) returned EmptyLevelChunk! Data will be LOST\n", xc, zc);
}
// 4J Stu - Unshare before we make any changes incase the server is already another step ahead of us
// Fix for #7904 - Gameplay: Players can dupe torches by throwing them repeatedly into water.
// This is quite expensive so only actually do it if we are hosting, online, and the update will actually

View file

@ -80,9 +80,12 @@ void MinecartTNT::explode(double speedSqr)
if (!level->isClientSide)
{
double speed = sqrt(speedSqr);
if (speed > 5) speed = 5;
level->explode(shared_from_this(), x, y, z, static_cast<float>(4 + random->nextDouble() * 1.5f * speed), true);
remove();
if (speed > 5.0) speed = 5.0;
if (app.GetGameHostOption(eGameHostOption_TNT))
{
level->explode(shared_from_this(), x, y, z, static_cast<float>(4 + random->nextDouble() * 1.5f * speed), true);
remove();
}
}
}

View file

@ -327,9 +327,30 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
id = dis->read();
if (id == -1) return nullptr;
// Track last few good packets for diagnosing TCP desync
static thread_local int s_lastIds[8] = {};
static thread_local int s_lastIdPos = 0;
static thread_local int s_packetCount = 0;
if ((isServer && serverReceivedPackets.find(id) == serverReceivedPackets.end()) || (!isServer && clientReceivedPackets.find(id) == clientReceivedPackets.end()))
{
//app.DebugPrintf("Bad packet id %d\n", id);
app.DebugPrintf("*** BAD PACKET ID %d (0x%02X) isServer=%d totalPacketsRead=%d\n", id, id, isServer ? 1 : 0, s_packetCount);
app.DebugPrintf("*** Last %d good packet IDs (oldest first): ", 8);
for (int dbg = 0; dbg < 8; dbg++)
{
int idx = (s_lastIdPos + dbg) % 8;
app.DebugPrintf("%d ", s_lastIds[idx]);
}
app.DebugPrintf("\n");
// Dump the next 32 bytes from the stream to see what follows
app.DebugPrintf("*** Next bytes in stream: ");
for (int dbg = 0; dbg < 32; dbg++)
{
int b = dis->read();
if (b == -1) { app.DebugPrintf("[EOS] "); break; }
app.DebugPrintf("%02X ", b);
}
app.DebugPrintf("\n");
__debugbreak();
assert(false);
// throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id));
@ -338,7 +359,10 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
packet = getPacket(id);
if (packet == nullptr) assert(false);//throw new IOException(wstring(L"Bad packet id ") + std::to_wstring(id));
//app.DebugPrintf("%s reading packet %d\n", isServer ? "Server" : "Client", packet->getId());
s_lastIds[s_lastIdPos] = id;
s_lastIdPos = (s_lastIdPos + 1) % 8;
s_packetCount++;
packet->read(dis);
// }
// catch (EOFException e)
@ -372,7 +396,7 @@ shared_ptr<Packet> Packet::readPacket(DataInputStream *dis, bool isServer) // th
void Packet::writePacket(shared_ptr<Packet> packet, DataOutputStream *dos) // throws IOException TODO 4J JEV, should this declare a throws?
{
//app.DebugPrintf("Writing packet %d\n", packet->getId());
//app.DebugPrintf("NET WRITE: packet id=%d (0x%02X) estSize=%d\n", packet->getId(), packet->getId(), packet->getEstimatedSize());
dos->write(packet->getId());
packet->write(dos);
}

View file

@ -137,6 +137,9 @@ void Socket::pushDataToQueue(const BYTE * pbData, DWORD dwDataSize, bool fromHos
return;
}
//app.DebugPrintf("SOCKET PUSH: %u bytes to queue[%d] firstByte=0x%02X smallId=%d\n",
// dwDataSize, queueIdx, dwDataSize > 0 ? pbData[0] : 0, networkPlayerSmallId);
EnterCriticalSection(&m_queueLockNetwork[queueIdx]);
for( unsigned int i = 0; i < dwDataSize; i++ )
{
@ -311,7 +314,8 @@ void Socket::SocketInputStreamLocal::close()
{
m_streamOpen = false;
EnterCriticalSection(&s_hostQueueLock[m_queueIdx]);
s_hostQueue[m_queueIdx].empty();
std::queue<byte> empty;
std::swap(s_hostQueue[m_queueIdx], empty);
LeaveCriticalSection(&s_hostQueueLock[m_queueIdx]);
}
@ -359,7 +363,8 @@ void Socket::SocketOutputStreamLocal::close()
{
m_streamOpen = false;
EnterCriticalSection(&s_hostQueueLock[m_queueIdx]);
s_hostQueue[m_queueIdx].empty();
std::queue<byte> empty;
std::swap(s_hostQueue[m_queueIdx], empty);
LeaveCriticalSection(&s_hostQueueLock[m_queueIdx]);
}

View file

@ -42,7 +42,7 @@ void Compression::UseDefaultThreadStorage()
void Compression::ReleaseThreadStorage()
{
ThreadStorage *tls = static_cast<ThreadStorage *>(TlsGetValue(tlsIdx));
const ThreadStorage *tls = static_cast<ThreadStorage *>(TlsGetValue(tlsIdx));
if( tls == tlsDefault ) return;
delete tls;
@ -50,7 +50,7 @@ void Compression::ReleaseThreadStorage()
Compression *Compression::getCompression()
{
ThreadStorage *tls = static_cast<ThreadStorage *>(TlsGetValue(tlsIdx));
const ThreadStorage *tls = static_cast<ThreadStorage *>(TlsGetValue(tlsIdx));
return tls->compression;
}
@ -59,9 +59,27 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
EnterCriticalSection(&rleCompressLock);
//static unsigned char rleBuf[1024*100];
unsigned char *pucIn = static_cast<unsigned char *>(pSource);
unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = (unsigned char *)rleCompressBuf;
// RLE can expand data (each 0xFF byte becomes 2 bytes), so worst case is 2*SrcSize.
// Use the static buffer when it fits, dynamically allocate otherwise.
static const unsigned int staticCompressSize = 1024*100;
unsigned int rleBufSize = SrcSize * 2;
unsigned char *dynamicRleBuf = nullptr;
unsigned char *rleBuf;
if(rleBufSize <= staticCompressSize)
{
rleBuf = rleCompressBuf;
rleBufSize = staticCompressSize;
}
else
{
dynamicRleBuf = new unsigned char[rleBufSize];
rleBuf = dynamicRleBuf;
}
const unsigned char *pucIn = static_cast<unsigned char*>(pSource);
const unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = rleBuf;
// Compress with RLE first:
// 0 - 254 - encodes a single byte
@ -70,7 +88,7 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
PIXBeginNamedEvent(0,"RLE compression");
do
{
unsigned char thisOne = *pucIn++;
const unsigned char thisOne = *pucIn++;
unsigned int count = 1;
while( ( pucIn != pucEnd ) && ( *pucIn == thisOne ) && ( count < 256 ) )
@ -101,12 +119,15 @@ HRESULT Compression::CompressLZXRLE(void *pDestination, unsigned int *pDestSize,
*pucOut++ = thisOne;
}
} while (pucIn != pucEnd);
unsigned int rleSize = static_cast<unsigned int>(pucOut - rleCompressBuf);
const unsigned int rleSize = static_cast<unsigned int>(pucOut - rleBuf);
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Secondary compression");
Compress(pDestination, pDestSize, rleCompressBuf, rleSize);
Compress(pDestination, pDestSize, rleBuf, rleSize);
PIXEndNamedEvent();
if(dynamicRleBuf != nullptr) delete [] dynamicRleBuf;
LeaveCriticalSection(&rleCompressLock);
// printf("Compressed from %d to %d to %d\n",SrcSize,rleSize,*pDestSize);
@ -118,9 +139,26 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
EnterCriticalSection(&rleCompressLock);
//static unsigned char rleBuf[1024*100];
unsigned char *pucIn = static_cast<unsigned char *>(pSource);
unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = (unsigned char *)rleCompressBuf;
// RLE can expand data (each 0xFF byte becomes 2 bytes), so worst case is 2*SrcSize.
static const unsigned int staticCompressSize = 1024*100;
unsigned int rleBufSize = SrcSize * 2;
unsigned char *dynamicRleBuf = nullptr;
unsigned char *rleBuf;
if(rleBufSize <= staticCompressSize)
{
rleBuf = rleCompressBuf;
rleBufSize = staticCompressSize;
}
else
{
dynamicRleBuf = new unsigned char[rleBufSize];
rleBuf = dynamicRleBuf;
}
const unsigned char *pucIn = static_cast<unsigned char*>(pSource);
const unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = rleBuf;
// Compress with RLE first:
// 0 - 254 - encodes a single byte
@ -129,7 +167,7 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
PIXBeginNamedEvent(0,"RLE compression");
do
{
unsigned char thisOne = *pucIn++;
const unsigned char thisOne = *pucIn++;
unsigned int count = 1;
while( ( pucIn != pucEnd ) && ( *pucIn == thisOne ) && ( count < 256 ) )
@ -160,15 +198,14 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
*pucOut++ = thisOne;
}
} while (pucIn != pucEnd);
unsigned int rleSize = static_cast<unsigned int>(pucOut - rleCompressBuf);
const unsigned int rleSize = static_cast<unsigned int>(pucOut - rleBuf);
PIXEndNamedEvent();
LeaveCriticalSection(&rleCompressLock);
// Return
if (rleSize <= *pDestSize)
{
*pDestSize = rleSize;
memcpy(pDestination, rleCompressBuf, *pDestSize);
memcpy(pDestination, rleBuf, *pDestSize);
}
else
{
@ -177,6 +214,9 @@ HRESULT Compression::CompressRLE(void *pDestination, unsigned int *pDestSize, vo
#endif
}
if(dynamicRleBuf != nullptr) delete [] dynamicRleBuf;
LeaveCriticalSection(&rleCompressLock);
return S_OK;
}
@ -195,33 +235,47 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
//static unsigned char rleBuf[staticRleSize];
unsigned int rleSize = staticRleSize;
unsigned char *dynamicRleBuf = nullptr;
HRESULT decompressResult;
if(*pDestSize > rleSize)
{
rleSize = *pDestSize;
dynamicRleBuf = new unsigned char[rleSize];
Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize);
decompressResult = Decompress(dynamicRleBuf, &rleSize, pSource, SrcSize);
pucIn = (unsigned char *)dynamicRleBuf;
}
else
{
Decompress(rleDecompressBuf, &rleSize, pSource, SrcSize);
pucIn = static_cast<unsigned char *>(rleDecompressBuf);
decompressResult = Decompress(rleDecompressBuf, &rleSize, pSource, SrcSize);
pucIn = static_cast<unsigned char*>(rleDecompressBuf);
}
if(decompressResult != S_OK)
{
app.DebugPrintf("*** DecompressLZXRLE: zlib Decompress FAILED hr=0x%08X srcSize=%u expectedDest=%u rleSize=%u\n",
decompressResult, SrcSize, *pDestSize, rleSize);
if(dynamicRleBuf != nullptr) delete [] dynamicRleBuf;
*pDestSize = 0;
LeaveCriticalSection(&rleDecompressLock);
return decompressResult;
}
//unsigned char *pucIn = (unsigned char *)rleDecompressBuf;
unsigned char *pucEnd = pucIn + rleSize;
unsigned char *pucOut = static_cast<unsigned char *>(pDestination);
const unsigned char *pucEnd = pucIn + rleSize;
unsigned char *pucOut = static_cast<unsigned char*>(pDestination);
const unsigned char *pucOutEnd = pucOut + *pDestSize;
while( pucIn != pucEnd )
{
unsigned char thisOne = *pucIn++;
const unsigned char thisOne = *pucIn++;
if( thisOne == 255 )
{
if( pucIn >= pucEnd ) break;
unsigned int count = *pucIn++;
if( count < 3 )
{
count++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = 255;
@ -230,7 +284,9 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
else
{
count++;
unsigned char data = *pucIn++;
if( pucIn >= pucEnd ) break;
const unsigned char data = *pucIn++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = data;
@ -239,10 +295,11 @@ HRESULT Compression::DecompressLZXRLE(void *pDestination, unsigned int *pDestSiz
}
else
{
if( pucOut >= pucOutEnd ) break;
*pucOut++ = thisOne;
}
}
*pDestSize = static_cast<unsigned int>(pucOut - (unsigned char *)pDestination);
*pDestSize = static_cast<unsigned int>(pucOut - static_cast<unsigned char*>(pDestination));
// printf("Decompressed from %d to %d to %d\n",SrcSize,rleSize,*pDestSize);
@ -258,18 +315,21 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
//unsigned char *pucIn = (unsigned char *)rleDecompressBuf;
unsigned char *pucIn = static_cast<unsigned char *>(pSource);
unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = static_cast<unsigned char *>(pDestination);
const unsigned char *pucEnd = pucIn + SrcSize;
unsigned char *pucOut = static_cast<unsigned char*>(pDestination);
const unsigned char *pucOutEnd = pucOut + *pDestSize;
while( pucIn != pucEnd )
{
unsigned char thisOne = *pucIn++;
const unsigned char thisOne = *pucIn++;
if( thisOne == 255 )
{
if( pucIn >= pucEnd ) break;
unsigned int count = *pucIn++;
if( count < 3 )
{
count++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = 255;
@ -278,7 +338,9 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
else
{
count++;
unsigned char data = *pucIn++;
if( pucIn >= pucEnd ) break;
const unsigned char data = *pucIn++;
if( pucOut + count > pucOutEnd ) break;
for( unsigned int i = 0; i < count; i++ )
{
*pucOut++ = data;
@ -287,10 +349,11 @@ HRESULT Compression::DecompressRLE(void *pDestination, unsigned int *pDestSize,
}
else
{
if( pucOut >= pucOutEnd ) break;
*pucOut++ = thisOne;
}
}
*pDestSize = static_cast<unsigned int>(pucOut - (unsigned char *)pDestination);
*pDestSize = static_cast<unsigned int>(pucOut - static_cast<unsigned char*>(pDestination));
LeaveCriticalSection(&rleDecompressLock);
return S_OK;
@ -302,7 +365,7 @@ HRESULT Compression::Compress(void *pDestination, unsigned int *pDestSize, void
// Using zlib for x64 compression - 360 is using native 360 compression and PS3 a stubbed non-compressing version of this
#if defined __ORBIS__ || defined _DURANGO || defined _WIN64 || defined __PSVITA__
SIZE_T destSize = (SIZE_T)(*pDestSize);
int res = ::compress(static_cast<Bytef *>(pDestination), (uLongf *)&destSize, static_cast<Bytef *>(pSource), SrcSize);
const int res = ::compress(static_cast<Bytef *>(pDestination), (uLongf *)&destSize, static_cast<Bytef *>(pSource), SrcSize);
*pDestSize = static_cast<unsigned int>(destSize);
return ( ( res == Z_OK ) ? S_OK : -1 );
#elif defined __PS3__
@ -330,7 +393,7 @@ HRESULT Compression::Decompress(void *pDestination, unsigned int *pDestSize, voi
// Using zlib for x64 compression - 360 is using native 360 compression and PS3 a stubbed non-compressing version of this
#if defined __ORBIS__ || defined _DURANGO || defined _WIN64 || defined __PSVITA__
SIZE_T destSize = (SIZE_T)(*pDestSize);
int res = ::uncompress(static_cast<Bytef *>(pDestination), (uLongf *)&destSize, static_cast<Bytef *>(pSource), SrcSize);
const int res = ::uncompress(static_cast<Bytef *>(pDestination), (uLongf *)&destSize, static_cast<Bytef *>(pSource), SrcSize);
*pDestSize = static_cast<unsigned int>(destSize);
return ( ( res == Z_OK ) ? S_OK : -1 );
#elif defined __PS3__
@ -350,7 +413,7 @@ HRESULT Compression::Decompress(void *pDestination, unsigned int *pDestSize, voi
#ifndef _XBOX
VOID Compression::VitaVirtualDecompress(void *pDestination, unsigned int *pDestSize, void *pSource, unsigned int SrcSize) // (LPVOID buf, SIZE_T dwSize, LPVOID dst)
{
uint8_t *pSrc = static_cast<uint8_t *>(pSource);
const uint8_t *pSrc = static_cast<uint8_t *>(pSource);
int Offset = 0;
int Page = 0;
int Index = 0;
@ -368,7 +431,7 @@ VOID Compression::VitaVirtualDecompress(void *pDestination, unsigned int *pDestS
{
// how many zeros do we have
Index += 1;
int Count = pSrc[Index];
const int Count = pSrc[Index];
// to do : this should really be a sequence of memsets
for( int i = 0;i < Count;i += 1 )
{
@ -396,7 +459,7 @@ HRESULT Compression::DecompressWithType(void *pDestination, unsigned int *pDestS
{
#if (defined _XBOX || defined _DURANGO || defined _WIN64)
SIZE_T destSize = (SIZE_T)(*pDestSize);
HRESULT res = XMemDecompress(decompressionContext, pDestination, (SIZE_T *)&destSize, pSource, SrcSize);
const HRESULT res = XMemDecompress(decompressionContext, pDestination, (SIZE_T *)&destSize, pSource, SrcSize);
*pDestSize = static_cast<unsigned int>(destSize);
return res;
#else
@ -420,13 +483,13 @@ HRESULT Compression::DecompressWithType(void *pDestination, unsigned int *pDestS
if (pDestination != nullptr)
{
// Read big-endian srcize from array
PBYTE pbDestSize = (PBYTE) pDestSize;
PBYTE pbSource = static_cast<PBYTE>(pSource);
const PBYTE pbDestSize = (PBYTE) pDestSize;
const PBYTE pbSource = static_cast<PBYTE>(pSource);
for (int i = 3; i >= 0; i--) {
pbDestSize[3-i] = pbSource[i];
}
byteArray uncompr = byteArray(*pDestSize);
const byteArray uncompr = byteArray(*pDestSize);
// Build decompression stream
z_stream strm;