mirror of
https://github.com/Minecraft-Community-Edition/client.git
synced 2026-05-22 17:14:35 +00:00
Whisper Yelled At me 😢
This commit is contained in:
@@ -40,10 +40,6 @@
|
||||
#include "..\Minecraft.World\DurangoStats.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#include "..\..\Windows64\Network\P2PConnectionManagerWin.h"
|
||||
#endif
|
||||
|
||||
// Global instance
|
||||
CGameNetworkManager g_NetworkManager;
|
||||
CPlatformNetworkManager *CGameNetworkManager::s_pPlatformNetworkManager;
|
||||
@@ -62,10 +58,6 @@ CGameNetworkManager::CGameNetworkManager()
|
||||
m_pUpsell = NULL;
|
||||
m_pInviteInfo = NULL;
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
m_p2pManager = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CGameNetworkManager::Initialise()
|
||||
@@ -91,9 +83,6 @@ void CGameNetworkManager::Terminate()
|
||||
{
|
||||
if( m_bInitialised )
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
TeardownP2PMesh();
|
||||
#endif
|
||||
s_pPlatformNetworkManager->Terminate();
|
||||
}
|
||||
}
|
||||
@@ -129,10 +118,6 @@ void CGameNetworkManager::DoWork()
|
||||
#endif
|
||||
s_pPlatformNetworkManager->DoWork();
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
TickP2P();
|
||||
#endif
|
||||
|
||||
#ifdef __ORBIS__
|
||||
if (m_pUpsell != NULL && m_pUpsell->hasResponse())
|
||||
{
|
||||
@@ -181,16 +166,14 @@ bool CGameNetworkManager::_RunNetworkGame(LPVOID lpParameter)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Client needs QNET_STATE_GAME_PLAY so that IsInGameplay() returns true
|
||||
s_pPlatformNetworkManager->SetGamePlayState();
|
||||
}
|
||||
|
||||
if( g_NetworkManager.IsLeavingGame() ) return false;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
if (!g_NetworkManager.IsLocalGame())
|
||||
{
|
||||
g_NetworkManager.EstablishP2PMesh();
|
||||
}
|
||||
#endif
|
||||
|
||||
app.SetGameStarted(true);
|
||||
|
||||
// 4J-PB - if this is the trial game, start the trial timer
|
||||
@@ -1283,15 +1266,7 @@ bool CGameNetworkManager::SystemFlagGet(INetworkPlayer *pNetworkPlayer, int inde
|
||||
|
||||
wstring CGameNetworkManager::GatherStats()
|
||||
{
|
||||
wstring stats = s_pPlatformNetworkManager->GatherStats();
|
||||
#ifdef _WINDOWS64
|
||||
if (m_p2pManager != NULL)
|
||||
{
|
||||
stats += L"\n";
|
||||
stats += GatherP2PStats();
|
||||
}
|
||||
#endif
|
||||
return stats;
|
||||
return s_pPlatformNetworkManager->GatherStats();
|
||||
}
|
||||
|
||||
void CGameNetworkManager::renderQueueMeter()
|
||||
@@ -1421,7 +1396,10 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
|
||||
Socket *socket = NULL;
|
||||
shared_ptr<MultiplayerLocalPlayer> mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()];
|
||||
shared_ptr<MultiplayerLocalPlayer> mpPlayer = nullptr;
|
||||
int userIdx = pNetworkPlayer->GetUserIndex();
|
||||
if (userIdx >= 0 && userIdx < XUSER_MAX_COUNT)
|
||||
mpPlayer = pMinecraft->localplayers[userIdx];
|
||||
if( localPlayer && mpPlayer != NULL && mpPlayer->connection != NULL)
|
||||
{
|
||||
// If we already have a MultiplayerLocalPlayer here then we are doing a session type change
|
||||
@@ -1518,7 +1496,7 @@ void CGameNetworkManager::PlayerJoining( INetworkPlayer *pNetworkPlayer )
|
||||
else
|
||||
{
|
||||
if( !pNetworkPlayer->IsHost() )
|
||||
{
|
||||
{
|
||||
for(int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
|
||||
{
|
||||
if(Minecraft::GetInstance()->localplayers[idx] != NULL)
|
||||
@@ -1529,29 +1507,10 @@ void CGameNetworkManager::PlayerJoining( INetworkPlayer *pNetworkPlayer )
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
if (m_p2pManager != NULL && !pNetworkPlayer->IsLocal())
|
||||
{
|
||||
INetworkPlayer* localPlayer = GetLocalPlayerByUserIndex(0);
|
||||
if (localPlayer != NULL)
|
||||
{
|
||||
m_p2pManager->EstablishDirectConnection(localPlayer, pNetworkPlayer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CGameNetworkManager::PlayerLeaving( INetworkPlayer *pNetworkPlayer )
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
// Disconnect P2P with the leaving player
|
||||
if (m_p2pManager != NULL && !pNetworkPlayer->IsLocal())
|
||||
{
|
||||
m_p2pManager->DisconnectPeer(pNetworkPlayer);
|
||||
}
|
||||
#endif
|
||||
|
||||
if( pNetworkPlayer->IsLocal() )
|
||||
{
|
||||
ProfileManager.SetCurrentGameActivity(pNetworkPlayer->GetUserIndex(),CONTEXT_PRESENCE_IDLE,false);
|
||||
@@ -2047,101 +2006,3 @@ void CGameNetworkManager::startAdhocMatching()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
///////////////////////////////////// P2P Networking /////////////////////////////////////
|
||||
|
||||
void CGameNetworkManager::InitializeP2PConnections()
|
||||
{
|
||||
if (m_p2pManager != NULL)
|
||||
return; // Already initialized
|
||||
|
||||
CP2PConnectionManagerWin* winP2P = new CP2PConnectionManagerWin();
|
||||
if (winP2P->Initialize())
|
||||
{
|
||||
m_p2pManager = winP2P;
|
||||
|
||||
// Wire up the P2P manager to the socket layer and packet router
|
||||
Socket::SetP2PManager(m_p2pManager);
|
||||
g_PacketRouter.Initialize(m_p2pManager);
|
||||
|
||||
app.DebugPrintf("P2P: Network manager initialized\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
delete winP2P;
|
||||
app.DebugPrintf("P2P: Failed to initialize network manager\n");
|
||||
}
|
||||
}
|
||||
|
||||
void CGameNetworkManager::EstablishP2PMesh()
|
||||
{
|
||||
if (m_p2pManager == NULL)
|
||||
InitializeP2PConnections();
|
||||
|
||||
if (m_p2pManager == NULL)
|
||||
return;
|
||||
|
||||
// Discover our public endpoint
|
||||
m_p2pManager->DiscoverPublicEndpoint();
|
||||
|
||||
INetworkPlayer* localPlayer = GetLocalPlayerByUserIndex(0);
|
||||
if (localPlayer == NULL)
|
||||
return;
|
||||
|
||||
int playerCount = GetPlayerCount();
|
||||
for (int i = 0; i < playerCount; i++)
|
||||
{
|
||||
INetworkPlayer* player = GetPlayerByIndex(i);
|
||||
if (player != NULL && !player->IsLocal() && player != localPlayer)
|
||||
{
|
||||
m_p2pManager->EstablishDirectConnection(localPlayer, player);
|
||||
}
|
||||
}
|
||||
|
||||
app.DebugPrintf("P2P: Mesh establishment initiated for %d players\n", playerCount);
|
||||
}
|
||||
|
||||
void CGameNetworkManager::TeardownP2PMesh()
|
||||
{
|
||||
if (m_p2pManager == NULL)
|
||||
return;
|
||||
|
||||
// Disconnect the P2P manager from socket layer and packet router
|
||||
Socket::SetP2PManager(NULL);
|
||||
g_PacketRouter.Shutdown();
|
||||
|
||||
// Shutdown and delete
|
||||
m_p2pManager->Shutdown();
|
||||
delete m_p2pManager;
|
||||
m_p2pManager = NULL;
|
||||
|
||||
app.DebugPrintf("P2P: Mesh torn down\n");
|
||||
}
|
||||
|
||||
void CGameNetworkManager::TickP2P()
|
||||
{
|
||||
if (m_p2pManager != NULL)
|
||||
{
|
||||
m_p2pManager->Tick();
|
||||
}
|
||||
}
|
||||
|
||||
EP2PConnectionState CGameNetworkManager::GetP2PConnectionState(INetworkPlayer* player)
|
||||
{
|
||||
if (m_p2pManager == NULL || player == NULL)
|
||||
return P2P_STATE_DISCONNECTED;
|
||||
|
||||
return m_p2pManager->GetConnectionState(player);
|
||||
}
|
||||
|
||||
wstring CGameNetworkManager::GatherP2PStats()
|
||||
{
|
||||
if (m_p2pManager == NULL)
|
||||
return L"P2P: Not active\n";
|
||||
|
||||
return m_p2pManager->GetDebugStats();
|
||||
}
|
||||
|
||||
#endif // _WINDOWS64
|
||||
|
||||
@@ -15,11 +15,6 @@ using namespace std;
|
||||
#endif
|
||||
#include "SessionInfo.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#include "P2PConnectionManager.h"
|
||||
#include "PacketRouter.h"
|
||||
#endif
|
||||
|
||||
#ifdef __ORBIS__
|
||||
#include "..\..\Orbis\Network\PsPlusUpsellWrapper_Orbis.h"
|
||||
#endif
|
||||
@@ -164,17 +159,6 @@ public:
|
||||
void renderQueueMeter();
|
||||
wstring GatherRTTStats();
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
void InitializeP2PConnections();
|
||||
void EstablishP2PMesh();
|
||||
void TeardownP2PMesh();
|
||||
void TickP2P();
|
||||
IP2PConnectionManager* GetP2PManager() { return m_p2pManager; }
|
||||
EP2PConnectionState GetP2PConnectionState(INetworkPlayer* player);
|
||||
bool IsP2PActive() { return m_p2pManager != NULL; }
|
||||
wstring GatherP2PStats();
|
||||
#endif
|
||||
|
||||
// GUI debug output
|
||||
|
||||
// Used for debugging output
|
||||
@@ -228,10 +212,6 @@ private:
|
||||
int GetJoiningReadyPercentage();
|
||||
bool m_bLastDisconnectWasLostRoomOnly;
|
||||
bool m_bFullSessionMessageOnNextSessionChange;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
IP2PConnectionManager* m_p2pManager;
|
||||
#endif
|
||||
#if defined __PS3__ || defined __PSVITA__ || defined __ORBIS__
|
||||
bool m_bSignedOutofPSN;
|
||||
#endif
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "PacketRouter.h"
|
||||
|
||||
PacketRouter g_PacketRouter;
|
||||
|
||||
PacketRouter::PacketRouter()
|
||||
{
|
||||
m_p2pManager = NULL;
|
||||
m_p2pEnabled = false;
|
||||
m_p2pRoutedCount = 0;
|
||||
m_hostRoutedCount = 0;
|
||||
}
|
||||
|
||||
void PacketRouter::Initialize(IP2PConnectionManager* p2pManager)
|
||||
{
|
||||
m_p2pManager = p2pManager;
|
||||
m_p2pEnabled = (p2pManager != NULL);
|
||||
}
|
||||
|
||||
void PacketRouter::Shutdown()
|
||||
{
|
||||
m_p2pManager = NULL;
|
||||
m_p2pEnabled = false;
|
||||
ResetStats();
|
||||
}
|
||||
|
||||
ERouteDecision PacketRouter::GetRouteDecision(shared_ptr<Packet> packet, INetworkPlayer* targetPlayer)
|
||||
{
|
||||
// If P2P is disabled or no manager, always use host
|
||||
if (!m_p2pEnabled || m_p2pManager == NULL || targetPlayer == NULL)
|
||||
return ROUTE_VIA_HOST;
|
||||
|
||||
// Local players don't need P2P
|
||||
if (targetPlayer->IsLocal())
|
||||
return ROUTE_VIA_HOST;
|
||||
|
||||
int packetId = packet->getId();
|
||||
EPacketRoutingMode routingMode = Packet::GetRoutingMode(packetId);
|
||||
|
||||
switch (routingMode)
|
||||
{
|
||||
case ROUTING_HOST_ONLY:
|
||||
return ROUTE_VIA_HOST;
|
||||
|
||||
case ROUTING_PREFER_DIRECT:
|
||||
// Use P2P if we have a direct connection to this player
|
||||
if (m_p2pManager->IsDirectConnectionAvailable(targetPlayer))
|
||||
return ROUTE_VIA_P2P;
|
||||
return ROUTE_VIA_HOST;
|
||||
|
||||
case ROUTING_DIRECT_ONLY:
|
||||
if (m_p2pManager->IsDirectConnectionAvailable(targetPlayer))
|
||||
return ROUTE_VIA_P2P;
|
||||
return ROUTE_VIA_HOST; // Fallback to host if no direct link
|
||||
|
||||
case ROUTING_BROADCAST:
|
||||
return ROUTE_VIA_HOST; // Broadcasts always go through host
|
||||
|
||||
default:
|
||||
return ROUTE_VIA_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
bool PacketRouter::RoutePacket(shared_ptr<Packet> packet, INetworkPlayer* targetPlayer,
|
||||
const void* serializedData, int dataSize)
|
||||
{
|
||||
ERouteDecision decision = GetRouteDecision(packet, targetPlayer);
|
||||
|
||||
if (decision == ROUTE_VIA_P2P && m_p2pManager != NULL)
|
||||
{
|
||||
EP2PChannel channel = GetChannelForPacket(packet->getId());
|
||||
m_p2pManager->SendDirect(targetPlayer, serializedData, dataSize, channel);
|
||||
m_p2pRoutedCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_hostRoutedCount++;
|
||||
return false; // Caller should use existing host path
|
||||
}
|
||||
|
||||
EP2PChannel PacketRouter::GetChannelForPacket(int packetId)
|
||||
{
|
||||
// Movement packets
|
||||
if ((packetId >= 10 && packetId <= 13) || // MovePlayerPacket variants
|
||||
(packetId >= 30 && packetId <= 34) || // MoveEntityPacket variants
|
||||
(packetId >= 162 && packetId <= 165)) // MoveEntityPacketSmall variants
|
||||
{
|
||||
return P2P_CHANNEL_MOVEMENT;
|
||||
}
|
||||
|
||||
// Animation packets
|
||||
if (packetId == 18 || packetId == 35) // AnimatePacket, RotateHeadPacket
|
||||
{
|
||||
return P2P_CHANNEL_ANIMATION;
|
||||
}
|
||||
|
||||
// Effects/sounds
|
||||
if (packetId == 62) // LevelSoundPacket
|
||||
{
|
||||
return P2P_CHANNEL_EFFECTS;
|
||||
}
|
||||
|
||||
// Default
|
||||
return P2P_CHANNEL_MOVEMENT;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
#include "..\..\..\Minecraft.World\Packet.h"
|
||||
#include "P2PConnectionManager.h"
|
||||
#include "NetworkPlayerInterface.h"
|
||||
|
||||
// Packet routing decision
|
||||
enum ERouteDecision
|
||||
{
|
||||
ROUTE_VIA_HOST = 0, // Send through host TCP (existing path)
|
||||
ROUTE_VIA_P2P, // Send directly via P2P UDP
|
||||
ROUTE_VIA_BOTH // Send via both paths (redundancy for critical packets)
|
||||
};
|
||||
|
||||
class PacketRouter
|
||||
{
|
||||
public:
|
||||
PacketRouter();
|
||||
|
||||
// Initialize with P2P manager reference
|
||||
void Initialize(IP2PConnectionManager* p2pManager);
|
||||
void Shutdown();
|
||||
|
||||
// Determine how to route a packet to a specific player
|
||||
ERouteDecision GetRouteDecision(shared_ptr<Packet> packet, INetworkPlayer* targetPlayer);
|
||||
|
||||
// Route a serialized packet - returns true if sent via P2P
|
||||
bool RoutePacket(shared_ptr<Packet> packet, INetworkPlayer* targetPlayer,
|
||||
const void* serializedData, int dataSize);
|
||||
|
||||
// Enable/disable P2P routing (global toggle)
|
||||
void SetP2PEnabled(bool enabled) { m_p2pEnabled = enabled; }
|
||||
bool IsP2PEnabled() const { return m_p2pEnabled; }
|
||||
|
||||
// Map packet IDs to P2P channels
|
||||
static EP2PChannel GetChannelForPacket(int packetId);
|
||||
|
||||
// Stats
|
||||
int GetP2PRoutedCount() const { return m_p2pRoutedCount; }
|
||||
int GetHostRoutedCount() const { return m_hostRoutedCount; }
|
||||
void ResetStats() { m_p2pRoutedCount = 0; m_hostRoutedCount = 0; }
|
||||
|
||||
private:
|
||||
IP2PConnectionManager* m_p2pManager;
|
||||
bool m_p2pEnabled;
|
||||
int m_p2pRoutedCount;
|
||||
int m_hostRoutedCount;
|
||||
};
|
||||
|
||||
extern PacketRouter g_PacketRouter;
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0) = 0;
|
||||
virtual int JoinGame(FriendSessionInfo *searchResult, int dwLocalUsersMask, int dwPrimaryUserIndex ) = 0;
|
||||
virtual void CancelJoinGame() {};
|
||||
virtual void SetGamePlayState() {};
|
||||
virtual bool SetLocalGame(bool isLocal) = 0;
|
||||
virtual bool IsLocalGame() = 0;
|
||||
virtual void SetPrivateGame(bool isPrivate) = 0;
|
||||
|
||||
@@ -480,6 +480,14 @@ void CPlatformNetworkManagerStub::HandleSignInChange()
|
||||
return;
|
||||
}
|
||||
|
||||
void CPlatformNetworkManagerStub::SetGamePlayState()
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
extern QNET_STATE _iQNetStubState;
|
||||
_iQNetStubState = QNET_STATE_GAME_PLAY;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CPlatformNetworkManagerStub::_RunNetworkGame()
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
virtual void HandleSignInChange();
|
||||
|
||||
virtual bool _RunNetworkGame();
|
||||
virtual void SetGamePlayState();
|
||||
|
||||
private:
|
||||
bool isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer);
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
#include "UI.h"
|
||||
#include "UIScene_LoadOrJoinMenu.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#include "..\Network\P2PConnectionManager.h"
|
||||
#include "..\Network\GameNetworkManager.h"
|
||||
#endif
|
||||
|
||||
#include "..\..\..\Minecraft.World\StringHelpers.h"
|
||||
#include "..\..\..\Minecraft.World\net.minecraft.world.item.h"
|
||||
#include "..\..\..\Minecraft.World\net.minecraft.world.level.h"
|
||||
@@ -288,13 +283,8 @@ UIScene_LoadOrJoinMenu::~UIScene_LoadOrJoinMenu()
|
||||
g_NetworkManager.SetSessionsUpdatedCallback( NULL, NULL );
|
||||
app.SetLiveLinkRequired( false );
|
||||
|
||||
if(m_currentSessions)
|
||||
{
|
||||
for(AUTO_VAR(it, m_currentSessions->begin()); it < m_currentSessions->end(); ++it)
|
||||
{
|
||||
delete (*it);
|
||||
}
|
||||
}
|
||||
delete m_currentSessions;
|
||||
m_currentSessions = NULL;
|
||||
|
||||
#if TO_BE_IMPLEMENTED
|
||||
// Reset the background downloading, in case we changed it by attempting to download a texture pack
|
||||
@@ -1739,24 +1729,6 @@ void UIScene_LoadOrJoinMenu::UpdateGamesList()
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
if (g_NetworkManager.IsP2PActive() && g_NetworkManager.IsInGameplay())
|
||||
{
|
||||
int directPeers = g_NetworkManager.GetP2PManager()->GetDirectPeerCount();
|
||||
int totalOnline = g_NetworkManager.GetOnlinePlayerCount();
|
||||
if (directPeers > 0 && totalOnline > 1)
|
||||
{
|
||||
wchar_t labelWithP2P[256];
|
||||
swprintf_s(labelWithP2P, 256, L"%s [P2P:%d/%d]", sessionInfo->displayLabel, directPeers, totalOnline - 1);
|
||||
m_buttonListGames.addItem(labelWithP2P, textureName);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buttonListGames.addItem(sessionInfo->displayLabel, textureName);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
m_buttonListGames.addItem( sessionInfo->displayLabel, textureName );
|
||||
|
||||
if(memcmp( &selectedSessionId, &sessionInfo->sessionId, sizeof(SessionID) ) == 0)
|
||||
|
||||
@@ -105,7 +105,6 @@ private:
|
||||
bool m_bSaveTransferCancelled;
|
||||
#endif
|
||||
bool m_bUpdateSaveSize;
|
||||
wstring m_directConnectAddress;
|
||||
|
||||
public:
|
||||
UIScene_LoadOrJoinMenu(int iPad, void *initData, UILayer *parentLayer);
|
||||
@@ -154,7 +153,6 @@ public:
|
||||
static int DeleteSaveDataReturned(LPVOID lpParam,bool bRes);
|
||||
static int RenameSaveDataReturned(LPVOID lpParam,bool bRes);
|
||||
static int KeyboardCompleteWorldNameCallback(LPVOID lpParam,bool bRes);
|
||||
static int KeyboardCompleteDirectConnectCallback(LPVOID lpParam,bool bRes);
|
||||
protected:
|
||||
void handlePress(F64 controlId, F64 childId);
|
||||
void LoadLevelGen(LevelGenerationOptions *levelGen);
|
||||
|
||||
@@ -21562,7 +21562,6 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
|
||||
<ClCompile Include="Common\Network\GameNetworkManager.cpp" />
|
||||
<ClCompile Include="Common\Network\NATTraversal.cpp" />
|
||||
<ClCompile Include="Common\Network\LANSessionManager.cpp" />
|
||||
<ClCompile Include="Common\Network\PacketRouter.cpp" />
|
||||
<ClCompile Include="Common\Network\STUNClient.cpp" />
|
||||
<ClCompile Include="ConnectScreen.cpp" />
|
||||
<ClCompile Include="KeyboardMouseInput.cpp" />
|
||||
|
||||
BIN
Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms
Normal file
Binary file not shown.
BIN
Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms
Normal file
Binary file not shown.
BIN
Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms
Normal file
Binary file not shown.
BIN
Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms
Normal file
Binary file not shown.
BIN
Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms
Normal file
Binary file not shown.
BIN
Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms
Normal file
Binary file not shown.
BIN
Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms
Normal file
BIN
Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms
Normal file
Binary file not shown.
@@ -161,12 +161,17 @@ bool CGameNetworkManager::_RunNetworkGame(LPVOID lpParameter)
|
||||
|
||||
success = s_pPlatformNetworkManager->_RunNetworkGame();
|
||||
if(!success)
|
||||
{
|
||||
{
|
||||
app.SetAction(ProfileManager.GetPrimaryPad(),eAppAction_ExitWorld,(void *)TRUE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Client needs QNET_STATE_GAME_PLAY so that IsInGameplay() returns true
|
||||
s_pPlatformNetworkManager->SetGamePlayState();
|
||||
}
|
||||
|
||||
if( g_NetworkManager.IsLeavingGame() ) return false;
|
||||
|
||||
app.SetGameStarted(true);
|
||||
@@ -1391,7 +1396,10 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
|
||||
Socket *socket = NULL;
|
||||
shared_ptr<MultiplayerLocalPlayer> mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()];
|
||||
shared_ptr<MultiplayerLocalPlayer> mpPlayer = nullptr;
|
||||
int userIdx = pNetworkPlayer->GetUserIndex();
|
||||
if (userIdx >= 0 && userIdx < XUSER_MAX_COUNT)
|
||||
mpPlayer = pMinecraft->localplayers[userIdx];
|
||||
if( localPlayer && mpPlayer != NULL && mpPlayer->connection != NULL)
|
||||
{
|
||||
// If we already have a MultiplayerLocalPlayer here then we are doing a session type change
|
||||
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0) = 0;
|
||||
virtual int JoinGame(FriendSessionInfo *searchResult, int dwLocalUsersMask, int dwPrimaryUserIndex ) = 0;
|
||||
virtual void CancelJoinGame() {};
|
||||
virtual void SetGamePlayState() {};
|
||||
virtual bool SetLocalGame(bool isLocal) = 0;
|
||||
virtual bool IsLocalGame() = 0;
|
||||
virtual void SetPrivateGame(bool isPrivate) = 0;
|
||||
|
||||
@@ -313,6 +313,12 @@ void CPlatformNetworkManagerStub::HandleSignInChange()
|
||||
return;
|
||||
}
|
||||
|
||||
void CPlatformNetworkManagerStub::SetGamePlayState()
|
||||
{
|
||||
extern QNET_STATE _iQNetStubState;
|
||||
_iQNetStubState = QNET_STATE_GAME_PLAY;
|
||||
}
|
||||
|
||||
bool CPlatformNetworkManagerStub::_RunNetworkGame()
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
virtual void HandleSignInChange();
|
||||
|
||||
virtual bool _RunNetworkGame();
|
||||
virtual void SetGamePlayState();
|
||||
|
||||
private:
|
||||
bool isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer);
|
||||
|
||||
@@ -1353,7 +1353,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
PIXEndNamedEvent();
|
||||
|
||||
PIXBeginNamedEvent(0,"Network manager do work #1");
|
||||
// g_NetworkManager.DoWork();
|
||||
g_NetworkManager.DoWork();
|
||||
PIXEndNamedEvent();
|
||||
|
||||
// LeaderboardManager::Instance()->Tick();
|
||||
|
||||
@@ -142,36 +142,6 @@ void Packet::staticCtor()
|
||||
//map(253, true, false, ServerAuthDataPacket.class);
|
||||
map(254, false, true, false, false, typeid(GetInfoPacket), GetInfoPacket::create); // TODO New for 1.8.2 - Needs sendToAny?
|
||||
map(255, true, true, true, false, typeid(DisconnectPacket), DisconnectPacket::create);
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// P2P Routing Modes: Mark movement/visual packets as P2P-eligible
|
||||
// These packets are safe for direct peer delivery (visual only, no game state)
|
||||
|
||||
// Player movement - high frequency, latency-sensitive
|
||||
SetRoutingMode(10, ROUTING_PREFER_DIRECT); // MovePlayerPacket
|
||||
SetRoutingMode(11, ROUTING_PREFER_DIRECT); // MovePlayerPacket::Pos
|
||||
SetRoutingMode(12, ROUTING_PREFER_DIRECT); // MovePlayerPacket::Rot
|
||||
SetRoutingMode(13, ROUTING_PREFER_DIRECT); // MovePlayerPacket::PosRot
|
||||
|
||||
// Entity movement - visual interpolation
|
||||
SetRoutingMode(30, ROUTING_PREFER_DIRECT); // MoveEntityPacket
|
||||
SetRoutingMode(31, ROUTING_PREFER_DIRECT); // MoveEntityPacket::Pos
|
||||
SetRoutingMode(32, ROUTING_PREFER_DIRECT); // MoveEntityPacket::Rot
|
||||
SetRoutingMode(33, ROUTING_PREFER_DIRECT); // MoveEntityPacket::PosRot
|
||||
SetRoutingMode(162, ROUTING_PREFER_DIRECT); // MoveEntityPacketSmall
|
||||
SetRoutingMode(163, ROUTING_PREFER_DIRECT); // MoveEntityPacketSmall::Pos
|
||||
SetRoutingMode(164, ROUTING_PREFER_DIRECT); // MoveEntityPacketSmall::Rot
|
||||
SetRoutingMode(165, ROUTING_PREFER_DIRECT); // MoveEntityPacketSmall::PosRot
|
||||
|
||||
// Animations and visual effects
|
||||
SetRoutingMode(18, ROUTING_PREFER_DIRECT); // AnimatePacket
|
||||
SetRoutingMode(35, ROUTING_PREFER_DIRECT); // RotateHeadPacket
|
||||
|
||||
// Non-critical audio
|
||||
SetRoutingMode(62, ROUTING_PREFER_DIRECT); // LevelSoundPacket
|
||||
|
||||
// All other packets default to ROUTING_HOST_ONLY (game state, inventory, spawns, etc.)
|
||||
#endif
|
||||
}
|
||||
|
||||
IllegalArgumentException::IllegalArgumentException(const wstring& information)
|
||||
@@ -195,21 +165,6 @@ unordered_set<int> Packet::clientReceivedPackets = unordered_set<int>();
|
||||
unordered_set<int> Packet::serverReceivedPackets = unordered_set<int>();
|
||||
unordered_set<int> Packet::sendToAnyClientPackets = unordered_set<int>();
|
||||
|
||||
unordered_map<int, EPacketRoutingMode> Packet::packetRoutingModes = unordered_map<int, EPacketRoutingMode>();
|
||||
|
||||
EPacketRoutingMode Packet::GetRoutingMode(int packetId)
|
||||
{
|
||||
auto it = packetRoutingModes.find(packetId);
|
||||
if (it != packetRoutingModes.end())
|
||||
return it->second;
|
||||
return ROUTING_HOST_ONLY; // Default: everything goes through host
|
||||
}
|
||||
|
||||
void Packet::SetRoutingMode(int packetId, EPacketRoutingMode mode)
|
||||
{
|
||||
packetRoutingModes[packetId] = mode;
|
||||
}
|
||||
|
||||
// 4J Added
|
||||
unordered_map<int, Packet::PacketStatistics *> Packet::outgoingStatistics = unordered_map<int, Packet::PacketStatistics *>();
|
||||
vector<Packet::PacketStatistics *> Packet::renderableStats = vector<Packet::PacketStatistics *>();
|
||||
|
||||
@@ -8,14 +8,6 @@ class DataOutputStream;
|
||||
|
||||
#define PACKET_ENABLE_STAT_TRACKING 0
|
||||
|
||||
enum EPacketRoutingMode
|
||||
{
|
||||
ROUTING_HOST_ONLY = 0, // Must go through host (game state, actions, spawns)
|
||||
ROUTING_PREFER_DIRECT, // Use P2P if available, fall back to host
|
||||
ROUTING_DIRECT_ONLY, // Only send via P2P (keepalives, P2P-specific)
|
||||
ROUTING_BROADCAST // Send to all peers (host broadcasts)
|
||||
};
|
||||
|
||||
class Packet;
|
||||
|
||||
typedef shared_ptr<Packet> (*packetCreateFn)();
|
||||
@@ -61,10 +53,6 @@ public:
|
||||
static unordered_set<int> serverReceivedPackets;
|
||||
static unordered_set<int> sendToAnyClientPackets;
|
||||
|
||||
static unordered_map<int, EPacketRoutingMode> packetRoutingModes;
|
||||
static EPacketRoutingMode GetRoutingMode(int packetId);
|
||||
static void SetRoutingMode(int packetId, EPacketRoutingMode mode);
|
||||
|
||||
// 4J Stu - Added the sendToAnyClient param so we can limit some packets to be only sent to one player on a system
|
||||
// 4J Stu - Added renderStats param for use in debugging
|
||||
static void map(int id, bool receiveOnClient, bool receiveOnServer, bool sendToAnyClient, bool renderStats, const type_info& clazz, packetCreateFn );
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
#include "..\Minecraft.Client\ServerConnection.h"
|
||||
#include <algorithm>
|
||||
#include "..\Minecraft.Client\PS3\PS3Extras\ShutdownManager.h"
|
||||
#ifdef _WINDOWS64
|
||||
#include "..\Minecraft.Client\Common\Network\PacketRouter.h"
|
||||
#include "..\Minecraft.Client\Common\Network\P2PConnectionManager.h"
|
||||
#endif
|
||||
|
||||
// This current socket implementation is for the creation of a single local link. 2 sockets can be created, one for either end of this local
|
||||
// link, the end (0 or 1) is passed as a parameter to the ctor.
|
||||
@@ -20,10 +16,6 @@ Socket::SocketOutputStreamLocal *Socket::s_hostOutStream[2];
|
||||
Socket::SocketInputStreamLocal *Socket::s_hostInStream[2];
|
||||
ServerConnection *Socket::s_serverConnection = NULL;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
IP2PConnectionManager* Socket::s_p2pManager = NULL;
|
||||
#endif
|
||||
|
||||
void Socket::Initialise(ServerConnection *serverConnection)
|
||||
{
|
||||
s_serverConnection = serverConnection;
|
||||
@@ -508,36 +500,29 @@ void Socket::SocketOutputStreamNetwork::writeWithFlags(byteArray b, unsigned int
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// Only attempt P2P for server->client sends (host broadcasting to clients)
|
||||
if( m_queueIdx == SOCKET_SERVER_END && s_p2pManager != NULL && g_PacketRouter.IsP2PEnabled() )
|
||||
{
|
||||
// Try to identify the packet ID from the serialized data
|
||||
// Packet format: first byte(s) contain the packet ID
|
||||
if( length >= 1 )
|
||||
{
|
||||
int packetId = (int)b[offset];
|
||||
EPacketRoutingMode routingMode = Packet::GetRoutingMode(packetId);
|
||||
|
||||
if( routingMode == ROUTING_PREFER_DIRECT || routingMode == ROUTING_DIRECT_ONLY )
|
||||
{
|
||||
if( s_p2pManager->IsDirectConnectionAvailable(socketPlayer) )
|
||||
{
|
||||
EP2PChannel channel = PacketRouter::GetChannelForPacket(packetId);
|
||||
s_p2pManager->SendDirect(socketPlayer, buffer.pbyData, buffer.dwDataSize, channel);
|
||||
return; // Sent via P2P, skip QNet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( m_queueIdx == SOCKET_SERVER_END )
|
||||
{
|
||||
//printf( "Sent %u bytes of data from \"%ls\" to \"%ls\"\n",
|
||||
//buffer.dwDataSize,
|
||||
//hostPlayer->GetGamertag(),
|
||||
//m_socket->networkPlayer->GetGamertag());
|
||||
|
||||
hostPlayer->SendData(socketPlayer, buffer.pbyData, buffer.dwDataSize, QNET_SENDDATA_RELIABLE | QNET_SENDDATA_SEQUENTIAL | flags);
|
||||
|
||||
// DWORD queueSize = hostPlayer->GetSendQueueSize( NULL, QNET_GETSENDQUEUESIZE_BYTES );
|
||||
// if( queueSize > 24000 )
|
||||
// {
|
||||
// //printf("Queue size is: %d, forcing doWork()\n",queueSize);
|
||||
// g_NetworkManager.DoWork();
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
//printf( "Sent %u bytes of data from \"%ls\" to \"%ls\"\n",
|
||||
//buffer.dwDataSize,
|
||||
//m_socket->networkPlayer->GetGamertag(),
|
||||
//hostPlayer->GetGamertag());
|
||||
|
||||
socketPlayer->SendData(hostPlayer, buffer.pbyData, buffer.dwDataSize, QNET_SENDDATA_RELIABLE | QNET_SENDDATA_SEQUENTIAL | flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
#define SOCKET_CLIENT_END 0
|
||||
#define SOCKET_SERVER_END 1
|
||||
|
||||
// Forward declare P2P types
|
||||
class IP2PConnectionManager;
|
||||
enum EP2PChannel;
|
||||
|
||||
class SocketAddress;
|
||||
class ServerConnection;
|
||||
|
||||
@@ -135,11 +131,4 @@ public:
|
||||
|
||||
bool isClosing() { return m_endClosed[SOCKET_CLIENT_END] || m_endClosed[SOCKET_SERVER_END]; }
|
||||
BYTE getSmallId() { return networkPlayerSmallId; }
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// P2P direct connection support
|
||||
static IP2PConnectionManager* s_p2pManager;
|
||||
static void SetP2PManager(IP2PConnectionManager* manager) { s_p2pManager = manager; }
|
||||
static IP2PConnectionManager* GetP2PManager() { return s_p2pManager; }
|
||||
#endif
|
||||
};
|
||||
4
tools/save_converter/build.bat
Normal file
4
tools/save_converter/build.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
cd /d "C:\Users\cole\Documents\GitHub\client\tools\save_converter"
|
||||
cl /LD /O2 kernelx_shim.c /link /DEF:kernelx.def /OUT:kernelx.dll
|
||||
403
tools/save_converter/convert_x360_save.py
Normal file
403
tools/save_converter/convert_x360_save.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
Xbox 360 Minecraft LCE savegame.dat -> Windows .mcs converter
|
||||
|
||||
Usage: python convert_x360_save.py <savegame.dat> [output.mcs]
|
||||
|
||||
This tool:
|
||||
1. Strips the 4-byte STFS size prefix from the Xbox 360 save
|
||||
2. Decompresses LZXRLE data (LZX via xcompress.dll, then custom RLE)
|
||||
3. Byte-swaps the FileHeader and FileEntry structures (big-endian -> little-endian)
|
||||
4. Byte-swaps region file data (chunk offsets and timestamps)
|
||||
5. Re-compresses as ZLIBRLE (zlib + RLE) for the Windows build
|
||||
6. Writes a .mcs file loadable by the LCE Windows build
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import struct
|
||||
import zlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# --- LZX Decompression via xcompress.dll ---
|
||||
|
||||
def load_xcompress():
|
||||
"""Load xcompress.dll with the kernelx.dll shim."""
|
||||
os.add_dll_directory(SCRIPT_DIR)
|
||||
ctypes.WinDLL(os.path.join(SCRIPT_DIR, "kernelx.dll"))
|
||||
return ctypes.WinDLL(os.path.join(SCRIPT_DIR, "xcompress.dll"))
|
||||
|
||||
|
||||
def lzx_decompress(xcomp, compressed_data, decompressed_size):
|
||||
"""Decompress LZX data using XMemDecompress."""
|
||||
XMEMCODEC_LZX = 1
|
||||
|
||||
class XMEMCODEC_PARAMETERS_LZX(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("Flags", ctypes.c_uint32),
|
||||
("WindowSize", ctypes.c_uint32),
|
||||
("CompressionPartitionSize", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
params = XMEMCODEC_PARAMETERS_LZX()
|
||||
params.Flags = 0
|
||||
params.WindowSize = 128 * 1024
|
||||
params.CompressionPartitionSize = 128 * 1024
|
||||
|
||||
context = ctypes.c_void_p()
|
||||
hr = xcomp.XMemCreateDecompressionContext(
|
||||
XMEMCODEC_LZX,
|
||||
ctypes.byref(params),
|
||||
0,
|
||||
ctypes.byref(context),
|
||||
)
|
||||
if hr != 0:
|
||||
raise RuntimeError(f"XMemCreateDecompressionContext failed: 0x{hr:08X}")
|
||||
|
||||
src_buf = (ctypes.c_ubyte * len(compressed_data))(*compressed_data)
|
||||
dst_buf = (ctypes.c_ubyte * decompressed_size)()
|
||||
dst_size = ctypes.c_size_t(decompressed_size)
|
||||
|
||||
hr = xcomp.XMemDecompress(
|
||||
context,
|
||||
dst_buf,
|
||||
ctypes.byref(dst_size),
|
||||
src_buf,
|
||||
ctypes.c_size_t(len(compressed_data)),
|
||||
)
|
||||
|
||||
xcomp.XMemDestroyDecompressionContext(context)
|
||||
|
||||
if hr != 0:
|
||||
raise RuntimeError(f"XMemDecompress failed: 0x{hr:08X}")
|
||||
|
||||
return bytes(dst_buf[: dst_size.value])
|
||||
|
||||
|
||||
# --- RLE Decompression (from compression.cpp DecompressLZXRLE) ---
|
||||
|
||||
def rle_decompress(data):
|
||||
"""
|
||||
Decompress the custom RLE format used by LCE.
|
||||
Format:
|
||||
0-254: literal byte
|
||||
255 followed by 0,1,2: 1,2,3 literal 255s
|
||||
255 followed by 3-255 followed by byte: run of (count+1) copies of byte
|
||||
"""
|
||||
result = bytearray()
|
||||
i = 0
|
||||
length = len(data)
|
||||
|
||||
while i < length:
|
||||
b = data[i]
|
||||
i += 1
|
||||
if b == 255:
|
||||
if i >= length:
|
||||
break
|
||||
count = data[i]
|
||||
i += 1
|
||||
if count < 3:
|
||||
result.extend(b"\xff" * (count + 1))
|
||||
else:
|
||||
if i >= length:
|
||||
break
|
||||
val = data[i]
|
||||
i += 1
|
||||
result.extend(bytes([val]) * (count + 1))
|
||||
else:
|
||||
result.append(b)
|
||||
|
||||
return bytes(result)
|
||||
|
||||
|
||||
# --- RLE Compression (from compression.cpp CompressRLE / CompressLZXRLE) ---
|
||||
|
||||
def rle_compress(data):
|
||||
"""
|
||||
Compress with the custom RLE format used by LCE.
|
||||
"""
|
||||
result = bytearray()
|
||||
i = 0
|
||||
length = len(data)
|
||||
|
||||
while i < length:
|
||||
b = data[i]
|
||||
i += 1
|
||||
count = 1
|
||||
while i < length and data[i] == b and count < 256:
|
||||
i += 1
|
||||
count += 1
|
||||
|
||||
if count <= 3:
|
||||
if b == 255:
|
||||
result.append(255)
|
||||
result.append(count - 1)
|
||||
else:
|
||||
result.extend(bytes([b]) * count)
|
||||
else:
|
||||
result.append(255)
|
||||
result.append(count - 1)
|
||||
result.append(b)
|
||||
|
||||
return bytes(result)
|
||||
|
||||
|
||||
# --- Endianness Conversion ---
|
||||
|
||||
def swap16(data, offset):
|
||||
"""Swap a 16-bit value in-place."""
|
||||
data[offset], data[offset + 1] = data[offset + 1], data[offset]
|
||||
|
||||
|
||||
def swap32(data, offset):
|
||||
"""Swap a 32-bit value in-place."""
|
||||
data[offset : offset + 4] = data[offset : offset + 4][::-1]
|
||||
|
||||
|
||||
def swap64(data, offset):
|
||||
"""Swap a 64-bit value in-place."""
|
||||
data[offset : offset + 8] = data[offset : offset + 8][::-1]
|
||||
|
||||
|
||||
def swap_wchar_array(data, offset, count):
|
||||
"""Swap an array of wchar_t (2 bytes each)."""
|
||||
for i in range(count):
|
||||
swap16(data, offset + i * 2)
|
||||
|
||||
|
||||
def convert_file_header(data):
|
||||
"""
|
||||
Convert the FileHeader from big-endian (Xbox 360) to little-endian (Windows).
|
||||
|
||||
Layout of decompressed save data:
|
||||
Bytes 0-3: headerOffset (uint32)
|
||||
Bytes 4-7: headerSize (uint32) = number of file entries
|
||||
Bytes 8-9: originalSaveVersion (int16)
|
||||
Bytes 10-11: saveVersion (int16)
|
||||
Bytes 12+: file data
|
||||
At headerOffset: file table (headerSize entries, each 144 bytes)
|
||||
"""
|
||||
buf = bytearray(data)
|
||||
|
||||
# Read header values in big-endian
|
||||
header_offset = struct.unpack(">I", buf[0:4])[0]
|
||||
header_size = struct.unpack(">I", buf[4:8])[0]
|
||||
orig_version = struct.unpack(">h", buf[8:10])[0]
|
||||
save_version = struct.unpack(">h", buf[10:12])[0]
|
||||
|
||||
print(f" Header offset: {header_offset}")
|
||||
print(f" File count: {header_size}")
|
||||
print(f" Original version: {orig_version}")
|
||||
print(f" Save version: {save_version}")
|
||||
|
||||
# Swap the header fields to little-endian
|
||||
swap32(buf, 0) # headerOffset
|
||||
swap32(buf, 4) # headerSize
|
||||
swap16(buf, 8) # originalSaveVersion
|
||||
swap16(buf, 10) # saveVersion
|
||||
|
||||
# Swap each FileEntry in the file table
|
||||
# FileEntrySaveDataV2 = 144 bytes:
|
||||
# wchar_t filename[64] = 128 bytes
|
||||
# uint32 length = 4 bytes
|
||||
# uint32 startOffset = 4 bytes
|
||||
# int64 lastModified = 8 bytes
|
||||
ENTRY_SIZE = 144
|
||||
|
||||
print(f"\n Files in save:")
|
||||
for i in range(header_size):
|
||||
entry_offset = header_offset + i * ENTRY_SIZE
|
||||
|
||||
# Read filename before swapping (big-endian wchar)
|
||||
raw_name = buf[entry_offset : entry_offset + 128]
|
||||
name_chars = []
|
||||
for j in range(64):
|
||||
c = struct.unpack(">H", raw_name[j * 2 : j * 2 + 2])[0]
|
||||
if c == 0:
|
||||
break
|
||||
name_chars.append(chr(c))
|
||||
filename = "".join(name_chars)
|
||||
|
||||
length = struct.unpack(">I", buf[entry_offset + 128 : entry_offset + 132])[0]
|
||||
start = struct.unpack(">I", buf[entry_offset + 132 : entry_offset + 136])[0]
|
||||
|
||||
print(f" {filename!r}: offset={start}, length={length}")
|
||||
|
||||
# Swap the fields
|
||||
swap_wchar_array(buf, entry_offset, 64) # filename
|
||||
swap32(buf, entry_offset + 128) # length
|
||||
swap32(buf, entry_offset + 132) # startOffset
|
||||
swap64(buf, entry_offset + 136) # lastModifiedTime
|
||||
|
||||
return bytes(buf), header_size, header_offset, save_version
|
||||
|
||||
|
||||
def convert_region_files(data, header_offset, header_size):
|
||||
"""
|
||||
Convert region file data from big-endian to little-endian.
|
||||
Region files (.mcr) contain:
|
||||
- Sector 0: offset table (1024 uint32 entries)
|
||||
- Sector 1: timestamp table (1024 uint32 entries)
|
||||
- Sectors 2+: chunk data with per-chunk headers
|
||||
|
||||
This converts the offset/timestamp tables and chunk headers.
|
||||
"""
|
||||
buf = bytearray(data)
|
||||
ENTRY_SIZE = 144
|
||||
SECTOR_SIZE = 4096
|
||||
SECTOR_INTS = 1024
|
||||
|
||||
for i in range(header_size):
|
||||
entry_offset = header_offset + i * ENTRY_SIZE
|
||||
|
||||
# Read filename (now in little-endian after header conversion)
|
||||
name_chars = []
|
||||
for j in range(64):
|
||||
c = struct.unpack("<H", buf[entry_offset + j * 2 : entry_offset + j * 2 + 2])[0]
|
||||
if c == 0:
|
||||
break
|
||||
name_chars.append(chr(c))
|
||||
filename = "".join(name_chars)
|
||||
|
||||
if not filename.endswith(".mcr"):
|
||||
continue
|
||||
|
||||
file_start = struct.unpack("<I", buf[entry_offset + 132 : entry_offset + 136])[0]
|
||||
file_length = struct.unpack("<I", buf[entry_offset + 128 : entry_offset + 132])[0]
|
||||
|
||||
print(f" Converting region file: {filename} (offset={file_start}, size={file_length})")
|
||||
|
||||
if file_length < 2 * SECTOR_SIZE:
|
||||
print(f" Region file too small, skipping")
|
||||
continue
|
||||
|
||||
# Swap offset table (sector 0: 1024 uint32 values)
|
||||
for j in range(SECTOR_INTS):
|
||||
swap32(buf, file_start + j * 4)
|
||||
|
||||
# Swap timestamp table (sector 1: 1024 uint32 values)
|
||||
for j in range(SECTOR_INTS):
|
||||
swap32(buf, file_start + SECTOR_SIZE + j * 4)
|
||||
|
||||
# Swap chunk data headers
|
||||
# Each chunk entry in the offset table encodes: (sector_count & 0xFF) | (sector_offset << 8)
|
||||
# The chunk data at each sector has an 8-byte header:
|
||||
# uint32 compressed_length (MSB = RLE flag)
|
||||
# uint32 decompressed_length
|
||||
for j in range(SECTOR_INTS):
|
||||
offset_entry = struct.unpack("<I", buf[file_start + j * 4 : file_start + j * 4 + 4])[0]
|
||||
if offset_entry == 0:
|
||||
continue
|
||||
|
||||
sector_count = offset_entry & 0xFF
|
||||
sector_offset = offset_entry >> 8
|
||||
|
||||
if sector_offset < 2:
|
||||
continue
|
||||
|
||||
chunk_data_offset = file_start + sector_offset * SECTOR_SIZE
|
||||
if chunk_data_offset + 8 > len(buf):
|
||||
continue
|
||||
|
||||
# Swap the chunk header (compressed_length and decompressed_length)
|
||||
swap32(buf, chunk_data_offset)
|
||||
swap32(buf, chunk_data_offset + 4)
|
||||
|
||||
return bytes(buf)
|
||||
|
||||
|
||||
# --- Main Conversion ---
|
||||
|
||||
def convert_save(input_path, output_path):
|
||||
print(f"Loading {input_path}...")
|
||||
with open(input_path, "rb") as f:
|
||||
raw_data = f.read()
|
||||
|
||||
file_size = len(raw_data)
|
||||
print(f"File size: {file_size} bytes")
|
||||
|
||||
# Check for 4-byte STFS size prefix
|
||||
# The first 4 bytes (BE) should match approximately (file_size - padding - 4)
|
||||
prefix_size = struct.unpack(">I", raw_data[0:4])[0]
|
||||
comp_flag = struct.unpack(">I", raw_data[4:8])[0]
|
||||
|
||||
if comp_flag == 0 and prefix_size > 0 and prefix_size < file_size:
|
||||
print(f"Detected 4-byte STFS prefix (size={prefix_size})")
|
||||
save_data = raw_data[4:]
|
||||
else:
|
||||
save_data = raw_data
|
||||
|
||||
# Parse compression header
|
||||
compressed_flag = struct.unpack(">I", save_data[0:4])[0]
|
||||
if compressed_flag != 0:
|
||||
print("Save data does not appear to be compressed (flag != 0)")
|
||||
print("Treating as raw uncompressed save data")
|
||||
decompressed = save_data
|
||||
else:
|
||||
decomp_size = struct.unpack(">I", save_data[4:8])[0]
|
||||
compressed_data = save_data[8:]
|
||||
print(f"Compressed save: decompressed size = {decomp_size} ({decomp_size/1024/1024:.1f} MB)")
|
||||
print(f"Compressed data size: {len(compressed_data)} bytes")
|
||||
|
||||
# LZX decompress (whole-file level is pure LZX, not LZXRLE)
|
||||
# The RLE layer is only used per-chunk inside region files
|
||||
print("\nStep 1: LZX decompression...")
|
||||
xcomp = load_xcompress()
|
||||
decompressed = lzx_decompress(xcomp, compressed_data, decomp_size)
|
||||
print(f" LZX decompressed: {len(decompressed)} bytes")
|
||||
|
||||
# Step 2: Inspect the raw save structure (big-endian)
|
||||
print("\nStep 2: Inspecting raw save data (big-endian Xbox 360 format)...")
|
||||
header_offset = struct.unpack(">I", decompressed[0:4])[0]
|
||||
file_count = struct.unpack(">I", decompressed[4:8])[0]
|
||||
orig_version = struct.unpack(">h", decompressed[8:10])[0]
|
||||
save_version = struct.unpack(">h", decompressed[10:12])[0]
|
||||
print(f" Header offset: {header_offset}")
|
||||
print(f" File count: {file_count}")
|
||||
print(f" Original version: {orig_version}, Save version: {save_version}")
|
||||
|
||||
# List files in the save
|
||||
ENTRY_SIZE = 144
|
||||
print(f"\n Files in save:")
|
||||
for i in range(file_count):
|
||||
entry_off = header_offset + i * ENTRY_SIZE
|
||||
name_chars = []
|
||||
for j in range(64):
|
||||
c = struct.unpack(">H", decompressed[entry_off + j * 2 : entry_off + j * 2 + 2])[0]
|
||||
if c == 0:
|
||||
break
|
||||
name_chars.append(chr(c))
|
||||
filename = "".join(name_chars)
|
||||
length = struct.unpack(">I", decompressed[entry_off + 128 : entry_off + 132])[0]
|
||||
start = struct.unpack(">I", decompressed[entry_off + 132 : entry_off + 136])[0]
|
||||
print(f" {filename}: offset={start}, size={length}")
|
||||
|
||||
# Step 3: Write raw decompressed data
|
||||
# The game code will handle endianness conversion when loaded with plat=X360
|
||||
# Write as uncompressed (first 4 bytes != 0 tells the game it's uncompressed)
|
||||
print(f"\nStep 3: Writing {output_path}...")
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(decompressed)
|
||||
|
||||
print(f"\nDone! Output: {output_path} ({len(decompressed)} bytes)")
|
||||
print(f"\nTo use this save with the LCE source code:")
|
||||
print(f" 1. Ensure xcompress.dll is available to the Windows build")
|
||||
print(f" 2. Replace XMemDecompress stubs with real implementation")
|
||||
print(f" 3. Load with: ConsoleSaveFileOriginal(name, data, size, false, SAVE_FILE_PLATFORM_X360)")
|
||||
print(f" 4. Call pSave->ConvertToLocalPlatform() after loading")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <savegame.dat> [output.mcs]")
|
||||
sys.exit(1)
|
||||
|
||||
input_file = sys.argv[1]
|
||||
if len(sys.argv) >= 3:
|
||||
output_file = sys.argv[2]
|
||||
else:
|
||||
base = os.path.splitext(input_file)[0]
|
||||
output_file = base + ".mcs"
|
||||
|
||||
convert_save(input_file, output_file)
|
||||
BIN
tools/save_converter/converted_world.mcs
Normal file
BIN
tools/save_converter/converted_world.mcs
Normal file
Binary file not shown.
14
tools/save_converter/kernelx.def
Normal file
14
tools/save_converter/kernelx.def
Normal file
@@ -0,0 +1,14 @@
|
||||
LIBRARY kernelx
|
||||
EXPORTS
|
||||
XMemAlloc
|
||||
XMemFree
|
||||
SetUnhandledExceptionFilter = kernel32.SetUnhandledExceptionFilter
|
||||
UnhandledExceptionFilter = kernel32.UnhandledExceptionFilter
|
||||
GetTickCount = kernel32.GetTickCount
|
||||
GetSystemTimeAsFileTime = kernel32.GetSystemTimeAsFileTime
|
||||
GetCurrentThreadId = kernel32.GetCurrentThreadId
|
||||
GetCurrentProcessId = kernel32.GetCurrentProcessId
|
||||
QueryPerformanceCounter = kernel32.QueryPerformanceCounter
|
||||
Sleep = kernel32.Sleep
|
||||
TerminateProcess = kernel32.TerminateProcess
|
||||
GetCurrentProcess = kernel32.GetCurrentProcess
|
||||
BIN
tools/save_converter/kernelx.dll
Normal file
BIN
tools/save_converter/kernelx.dll
Normal file
Binary file not shown.
25
tools/save_converter/kernelx_shim.c
Normal file
25
tools/save_converter/kernelx_shim.c
Normal file
@@ -0,0 +1,25 @@
|
||||
// kernelx.dll shim - forwards standard Win32 functions to kernel32.dll
|
||||
// and provides stub implementations for Xbox-specific XMemAlloc/XMemFree
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Forward declarations for exports
|
||||
__declspec(dllexport) void* __stdcall XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes);
|
||||
__declspec(dllexport) void __stdcall XMemFree(void* pAddress, DWORD dwAllocAttributes);
|
||||
|
||||
// Xbox-specific memory functions - map to standard heap
|
||||
__declspec(dllexport) void* __stdcall XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes) {
|
||||
(void)dwAllocAttributes;
|
||||
return malloc(dwSize);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall XMemFree(void* pAddress, DWORD dwAllocAttributes) {
|
||||
(void)dwAllocAttributes;
|
||||
free(pAddress);
|
||||
}
|
||||
|
||||
// All other functions are forwarded to kernel32.dll via the .def file
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
|
||||
(void)hinstDLL; (void)fdwReason; (void)lpReserved;
|
||||
return TRUE;
|
||||
}
|
||||
BIN
tools/save_converter/kernelx_shim.exp
Normal file
BIN
tools/save_converter/kernelx_shim.exp
Normal file
Binary file not shown.
BIN
tools/save_converter/kernelx_shim.lib
Normal file
BIN
tools/save_converter/kernelx_shim.lib
Normal file
Binary file not shown.
BIN
tools/save_converter/xcompress.dll
Normal file
BIN
tools/save_converter/xcompress.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user