diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index 0cfcc93b..a6333597 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -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 mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()]; + shared_ptr 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 diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.h b/Minecraft.Client/Common/Network/GameNetworkManager.h index 945ae121..1bb532da 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.h +++ b/Minecraft.Client/Common/Network/GameNetworkManager.h @@ -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 diff --git a/Minecraft.Client/Common/Network/PacketRouter.cpp b/Minecraft.Client/Common/Network/PacketRouter.cpp deleted file mode 100644 index 9190a91e..00000000 --- a/Minecraft.Client/Common/Network/PacketRouter.cpp +++ /dev/null @@ -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, 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, 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; -} diff --git a/Minecraft.Client/Common/Network/PacketRouter.h b/Minecraft.Client/Common/Network/PacketRouter.h deleted file mode 100644 index 989aa997..00000000 --- a/Minecraft.Client/Common/Network/PacketRouter.h +++ /dev/null @@ -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, INetworkPlayer* targetPlayer); - - // Route a serialized packet - returns true if sent via P2P - bool RoutePacket(shared_ptr 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; diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h index 901e59e7..4c57a56c 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerInterface.h @@ -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; diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 2f703141..36a2c092 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -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 diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h index 919efd71..7a9ce75e 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h @@ -55,6 +55,7 @@ public: virtual void HandleSignInChange(); virtual bool _RunNetworkGame(); + virtual void SetGamePlayState(); private: bool isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer); diff --git a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp index c5c034f1..ef5a339b 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp @@ -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) diff --git a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h index 589d9b3a..01d94b05 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.h @@ -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); diff --git a/Minecraft.Client/Minecraft.Client.vcxproj b/Minecraft.Client/Minecraft.Client.vcxproj index 72e284ae..a6c8cbea 100644 --- a/Minecraft.Client/Minecraft.Client.vcxproj +++ b/Minecraft.Client/Minecraft.Client.vcxproj @@ -21562,7 +21562,6 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU - diff --git a/Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms new file mode 100644 index 00000000..909daeb6 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304091252/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms new file mode 100644 index 00000000..372c5432 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304093601/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms new file mode 100644 index 00000000..c26fa9cb Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304095754/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms new file mode 100644 index 00000000..0057a319 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304100244/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms new file mode 100644 index 00000000..329b46e8 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304101040/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms new file mode 100644 index 00000000..f132bfc0 Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304103510/saveData.ms differ diff --git a/Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms new file mode 100644 index 00000000..a013b9cf Binary files /dev/null and b/Minecraft.Client/Windows64/GameHDD/20260304103542/saveData.ms differ diff --git a/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp b/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp index ba11c003..19befc99 100644 --- a/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Windows64/Network/GameNetworkManager.cpp @@ -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 mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()]; + shared_ptr 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 diff --git a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h index 901e59e7..4c57a56c 100644 --- a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h +++ b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerInterface.h @@ -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; diff --git a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp index b6b0fe12..abb019c3 100644 --- a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.cpp @@ -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; diff --git a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h index f997dece..e4d25136 100644 --- a/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h +++ b/Minecraft.Client/Windows64/Network/PlatformNetworkManagerStub.h @@ -55,6 +55,7 @@ public: virtual void HandleSignInChange(); virtual bool _RunNetworkGame(); + virtual void SetGamePlayState(); private: bool isSystemPrimaryPlayer(IQNetPlayer *pQNetPlayer); diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index ead5ca30..4f4780db 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -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(); diff --git a/Minecraft.World/Packet.cpp b/Minecraft.World/Packet.cpp index ad045991..0a59204c 100644 --- a/Minecraft.World/Packet.cpp +++ b/Minecraft.World/Packet.cpp @@ -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 Packet::clientReceivedPackets = unordered_set(); unordered_set Packet::serverReceivedPackets = unordered_set(); unordered_set Packet::sendToAnyClientPackets = unordered_set(); -unordered_map Packet::packetRoutingModes = unordered_map(); - -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 Packet::outgoingStatistics = unordered_map(); vector Packet::renderableStats = vector(); diff --git a/Minecraft.World/Packet.h b/Minecraft.World/Packet.h index ea118015..60410df3 100644 --- a/Minecraft.World/Packet.h +++ b/Minecraft.World/Packet.h @@ -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 (*packetCreateFn)(); @@ -61,10 +53,6 @@ public: static unordered_set serverReceivedPackets; static unordered_set sendToAnyClientPackets; - static unordered_map 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 ); diff --git a/Minecraft.World/Socket.cpp b/Minecraft.World/Socket.cpp index bcb26952..4b703403 100644 --- a/Minecraft.World/Socket.cpp +++ b/Minecraft.World/Socket.cpp @@ -6,10 +6,6 @@ #include "..\Minecraft.Client\ServerConnection.h" #include #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); } } diff --git a/Minecraft.World/Socket.h b/Minecraft.World/Socket.h index dae878a4..0019f984 100644 --- a/Minecraft.World/Socket.h +++ b/Minecraft.World/Socket.h @@ -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 }; \ No newline at end of file diff --git a/tools/save_converter/build.bat b/tools/save_converter/build.bat new file mode 100644 index 00000000..4dd52e7b --- /dev/null +++ b/tools/save_converter/build.bat @@ -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 diff --git a/tools/save_converter/convert_x360_save.py b/tools/save_converter/convert_x360_save.py new file mode 100644 index 00000000..4b7a608a --- /dev/null +++ b/tools/save_converter/convert_x360_save.py @@ -0,0 +1,403 @@ +""" +Xbox 360 Minecraft LCE savegame.dat -> Windows .mcs converter + +Usage: python convert_x360_save.py [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("> 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]} [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) diff --git a/tools/save_converter/converted_world.mcs b/tools/save_converter/converted_world.mcs new file mode 100644 index 00000000..4f5e36a3 Binary files /dev/null and b/tools/save_converter/converted_world.mcs differ diff --git a/tools/save_converter/kernelx.def b/tools/save_converter/kernelx.def new file mode 100644 index 00000000..c0cb516c --- /dev/null +++ b/tools/save_converter/kernelx.def @@ -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 diff --git a/tools/save_converter/kernelx.dll b/tools/save_converter/kernelx.dll new file mode 100644 index 00000000..e9e1ebba Binary files /dev/null and b/tools/save_converter/kernelx.dll differ diff --git a/tools/save_converter/kernelx_shim.c b/tools/save_converter/kernelx_shim.c new file mode 100644 index 00000000..8deb608f --- /dev/null +++ b/tools/save_converter/kernelx_shim.c @@ -0,0 +1,25 @@ +// kernelx.dll shim - forwards standard Win32 functions to kernel32.dll +// and provides stub implementations for Xbox-specific XMemAlloc/XMemFree +#include +#include + +// 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; +} diff --git a/tools/save_converter/kernelx_shim.exp b/tools/save_converter/kernelx_shim.exp new file mode 100644 index 00000000..8ae13163 Binary files /dev/null and b/tools/save_converter/kernelx_shim.exp differ diff --git a/tools/save_converter/kernelx_shim.lib b/tools/save_converter/kernelx_shim.lib new file mode 100644 index 00000000..99c9a948 Binary files /dev/null and b/tools/save_converter/kernelx_shim.lib differ diff --git a/tools/save_converter/xcompress.dll b/tools/save_converter/xcompress.dll new file mode 100644 index 00000000..b95e3701 Binary files /dev/null and b/tools/save_converter/xcompress.dll differ