Files
mcpe/source/client/network/ClientSideNetworkHandler.cpp
f f83ead9f8d WIP Android Port (#79)
* WIP Android Port

Android port. Still needs touch controls and mouse turning (if that's even possible on android) and file saving and SoundSystemSL
You control the camera and movement with your controller for now. You can navigate the gui using touch.
Options.cpp,LocalPlayer.cpp,Minecraft.cpp is configured to use controller.
Blocked out some code in ControllerTurnInput.cpp,Controller.cpp that didn't make sense.

* Fix glClear

glClear is supossed to use GL_DEPTH_BUFFER_BIT (thx TheBrokenRail)

* * Fix build.

* * Ignore assets.

* * More stuff

* * Fix more build errors.

* * It finally built

What I needed to do is rebuild the debug keystore because apparently android studio created it with sha1 digest alg which isn't supported by ant

* * Clean up filters.

* * Add cramped mode to the pause screen.

* * Fix a bug with the hotbar

* * In NinecraftApp::handleBack, pause the game if there is no screen.

* * AppPlatform_android: Add placeholder SoundSystem instance till we get SoundSystemSL working

* * Add properly working touch code.

* * Oh, remove some testing things

* * Fix state resetting when going in background and back in foreground
* Fix bug where the sky isn't being regenerated on graphics reset
* Fix bug where the m_currBoundTex isn't reset in Textures::clear potentially leaving a texture with that ID unassigned and corrupted
* Fix bug in CThread where the thread is detached and then also joined.
* Don't log anything if the program isn't in debug mode.

* * Add virtual keyboard support.

The screen instance slides so that the focused text box is kept visible.

* Rename from com.minecraftcpp to com.reminecraftpe

---------

Co-authored-by: iProgramInCpp <iprogramincpp@gmail.com>
2023-11-03 12:54:39 +02:00

503 lines
13 KiB
C++

/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include <RakPeer.h>
#include "ClientSideNetworkHandler.hpp"
#include "common/Utils.hpp"
#include "client/gui/screens/StartMenuScreen.hpp"
// This lets you make the client shut up and not log events in the debug console.
#define VERBOSE_CLIENT
#if defined(ORIGINAL_CODE) || defined(VERBOSE_CLIENT)
#define puts_ignorable(str) LOG_I(str)
#define printf_ignorable(str, ...) LOG_I(str, __VA_ARGS__)
#else
#define puts_ignorable(str)
#define printf_ignorable(str, ...)
#endif
ClientSideNetworkHandler::ClientSideNetworkHandler(Minecraft* pMinecraft, RakNetInstance* pRakNetInstance)
{
m_pMinecraft = pMinecraft;
m_pRakNetInstance = pRakNetInstance;
m_pServerPeer = m_pRakNetInstance->getPeer();
m_chunksRequested = 0;
m_serverProtocolVersion = 0;
m_pLevel = nullptr;
m_field_14 = 0;
m_field_24 = 0;
}
void ClientSideNetworkHandler::levelGenerated(Level* level)
{
m_pLevel = level;
requestNextChunk();
}
void ClientSideNetworkHandler::onConnect(const RakNet::RakNetGUID& rakGuid) // server guid
{
RakNet::RakNetGUID localGuid = ((RakNet::RakPeer*)m_pServerPeer)->GetMyGUID();
printf_ignorable("onConnect, server guid: %s, local guid: %s", rakGuid.ToString(), localGuid.ToString());
m_serverGUID = rakGuid;
LoginPacket* pLoginPkt = new LoginPacket;
pLoginPkt->m_str = RakNet::RakString(m_pMinecraft->m_pUser->field_0.c_str());
m_pRakNetInstance->send(pLoginPkt);
}
void ClientSideNetworkHandler::onUnableToConnect()
{
puts_ignorable("onUnableToConnect");
// get rid of the prepare-thread to stop preparation immediately
if (m_pMinecraft->m_pPrepThread)
{
delete m_pMinecraft->m_pPrepThread;
m_pMinecraft->m_pPrepThread = nullptr;
}
// throw to the start menu for now
m_pMinecraft->setScreen(new StartMenuScreen);
}
void ClientSideNetworkHandler::onDisconnect(const RakNet::RakNetGUID& rakGuid)
{
puts_ignorable("onDisconnect");
if (m_pLevel)
m_pLevel->m_bIsMultiplayer = false;
m_pMinecraft->m_gui.addMessage("Disconnected from server");
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MessagePacket* pMsgPkt)
{
puts_ignorable("MessagePacket");
m_pMinecraft->m_gui.addMessage(pMsgPkt->m_str.C_String());
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, StartGamePacket* pStartGamePkt)
{
puts_ignorable("StartGamePacket");
m_pMinecraft->getLevelSource()->deleteLevel("_LastJoinedServer");
m_pLevel = new Level(
m_pMinecraft->getLevelSource()->selectLevel("_LastJoinedServer", true),
"temp",
pStartGamePkt->field_4,
pStartGamePkt->field_8);
m_pLevel->m_bIsMultiplayer = true;
LocalPlayer *pLocalPlayer = new LocalPlayer(m_pMinecraft, m_pLevel, m_pMinecraft->m_pUser, m_pLevel->m_pDimension->field_50);
pLocalPlayer->m_guid = ((RakNet::RakPeer*)m_pServerPeer)->GetMyGUID();
pLocalPlayer->m_EntityID = pStartGamePkt->field_C;
pLocalPlayer->moveTo(
pStartGamePkt->field_10,
pStartGamePkt->field_14,
pStartGamePkt->field_18,
pLocalPlayer->m_yaw,
pLocalPlayer->m_pitch);
pLocalPlayer->m_pInventory->prepareCreativeInventory();
m_pLevel->setTime(pStartGamePkt->m_time);
m_serverProtocolVersion = pStartGamePkt->m_version;
m_pMinecraft->setLevel(m_pLevel, "ClientSideNetworkHandler -> setLevel", pLocalPlayer);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, AddPlayerPacket* pAddPlayerPkt)
{
puts_ignorable("AddPlayerPacket");
if (!m_pLevel) return;
Player* pPlayer = new Player(m_pLevel);
pPlayer->m_EntityID = pAddPlayerPkt->m_id;
m_pLevel->addEntity(pPlayer);
pPlayer->moveTo(
pAddPlayerPkt->m_x,
pAddPlayerPkt->m_y,
pAddPlayerPkt->m_z,
pPlayer->m_yaw,
pPlayer->m_pitch);
pPlayer->m_name = pAddPlayerPkt->m_name;
pPlayer->m_guid = pAddPlayerPkt->m_guid;
pPlayer->m_pInventory->prepareCreativeInventory();
m_pMinecraft->m_gui.addMessage(pPlayer->m_name + " joined the game");
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, RemoveEntityPacket* pRemoveEntityPkt)
{
if (!m_pLevel) return;
Entity* pEnt = m_pLevel->getEntity(pRemoveEntityPkt->m_id);
if (pEnt)
m_pLevel->removeEntity(pEnt);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePlayerPacket* packet)
{
if (!m_pLevel) return;
Entity* pEntity = m_pLevel->getEntity(packet->m_id);
if (!pEntity)
{
LOG_E("No player with id %d", packet->m_id);
return;
}
pEntity->lerpTo(packet->m_x, packet->m_y, packet->m_z, packet->m_yaw, packet->m_pitch, 3);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlaceBlockPacket* pPlaceBlockPkt)
{
puts_ignorable("PlaceBlockPacket");
Player* pPlayer = (Player*)m_pLevel->getEntity(pPlaceBlockPkt->m_playerID);
if (!pPlayer)
{
LOG_E("No player with id %d", pPlaceBlockPkt->m_playerID);
return;
}
pPlayer->swing();
// @BUG: Not buffering the update.
if (!areAllChunksLoaded())
return;
int x = pPlaceBlockPkt->m_x;
int y = pPlaceBlockPkt->m_y;
int z = pPlaceBlockPkt->m_z;
int tile = pPlaceBlockPkt->m_tile;
int face = pPlaceBlockPkt->m_face;
if (!m_pLevel->mayPlace(tile, x, y, z, true))
return;
Tile* pTile = Tile::tiles[tile];
if (!m_pLevel->setTile(x, y, z, tile))
return;
Tile::tiles[tile]->setPlacedOnFace(m_pLevel, x, y, z, face);
Tile::tiles[tile]->setPlacedBy(m_pLevel, x, y, z, pPlayer);
const Tile::SoundType* pSound = pTile->m_pSound;
m_pLevel->playSound(float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f, "step." + pSound->m_name, 0.5f * (1.0f + pSound->field_18), 0.8f * pSound->field_1C);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, RemoveBlockPacket* pRemoveBlockPkt)
{
puts_ignorable("RemoveBlockPacket");
Player* pPlayer = (Player*)m_pLevel->getEntity(pRemoveBlockPkt->m_playerID);
if (!pPlayer)
{
LOG_E("No player with id %d", pRemoveBlockPkt->m_playerID);
return;
}
pPlayer->swing();
// @BUG: Not buffering the update.
if (!areAllChunksLoaded())
return;
int x = pRemoveBlockPkt->m_x;
int y = pRemoveBlockPkt->m_y;
int z = pRemoveBlockPkt->m_z;
Tile* pTile = Tile::tiles[m_pLevel->getTile(x, y, z)];
int data = m_pLevel->getData(x, y, z);
bool setTileResult = m_pLevel->setTile(x, y, z, TILE_AIR);
if (pTile && setTileResult)
{
const Tile::SoundType* pSound = pTile->m_pSound;
m_pLevel->playSound(float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f, "step." + pSound->m_name, 0.5f * (1.0f + pSound->field_18), 0.8f * pSound->field_1C);
pTile->destroy(m_pLevel, x, y, z, data);
}
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, UpdateBlockPacket* pkt)
{
if (!areAllChunksLoaded())
{
m_bufferedBlockUpdates.push_back(SBufferedBlockUpdate(pkt->m_x, pkt->m_y, pkt->m_z, pkt->m_tile, pkt->m_data));
return;
}
m_pLevel->setTileAndData(pkt->m_x, pkt->m_y, pkt->m_z, pkt->m_tile, pkt->m_data);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, ChunkDataPacket* pChunkDataPkt)
{
if (!m_pLevel)
{
LOG_E("Level @ handle ChunkDataPacket is 0");
return;
}
LevelChunk* pChunk = m_pLevel->getChunkSource()->create(pChunkDataPkt->m_x, pChunkDataPkt->m_z);
if (!pChunk || pChunk->isEmpty())
{
LOG_E("Failed to find write-able chunk");
// @BUG: Not trying again.
return;
}
int x16 = 16 * pChunkDataPkt->m_x;
int z16 = 16 * pChunkDataPkt->m_z;
bool updated = false;
int minY = 128, maxY = 0;
int minX = 16, minZ = 16;
int maxX = 0, maxZ = 0;
for (int k = 0; k < 256; k++)
{
uint8_t updMap;
pChunkDataPkt->m_data.Read(updMap);
if (!updMap)
continue;
for (int j = 0; j < 8; j++)
{
if ((updMap >> j) & 1)
{
int yPos = j * 16;
TileID tiles[16];
uint8_t datas[16 / 2];
pChunkDataPkt->m_data.Read((char*)tiles, 16 * sizeof(TileID));
pChunkDataPkt->m_data.Read((char*)datas, 16 / 2);
for (int i = 0; i < 16; i++)
{
m_pLevel->setTileNoUpdate(x16 + (k & 0xF), yPos + i, z16 + (k >> 4), tiles[i]);
}
int idx = ((k & 0xF) << 11) | ((k >> 4) << 7) + yPos;
memcpy(&pChunk->m_tileData[idx >> 1], datas, sizeof datas);
}
int ymin = 16 * (1 << j);
if (minY >= ymin)
minY = ymin;
if (maxY < ymin + 15)
maxY = ymin + 15;
}
if (minX >= (k & 0xF))
minX = k & 0xF;
if (minZ >= (k >> 4))
minZ = k >> 4;
if (maxX <= (k & 0xF))
maxX = k & 0xF;
if (maxZ <= (k >> 4))
maxZ = k >> 4;
updated = true;
}
if (updated)
m_pLevel->setTilesDirty(minX + x16, minY, minZ, maxX + x16, maxY, maxZ + z16);
pChunk->m_bUnsaved = true;
if (m_serverProtocolVersion < 2)
{
if (areAllChunksLoaded())
flushAllBufferedUpdates();
else
requestNextChunk();
}
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlayerEquipmentPacket* pPlayerEquipmentPkt)
{
Player* pPlayer = (Player*)m_pLevel->getEntity(pPlayerEquipmentPkt->m_playerID);
if (!pPlayer)
return;
if (!Item::items[pPlayerEquipmentPkt->m_itemID])
{
LOG_W("That item %d doesn't actually exist!", pPlayerEquipmentPkt->m_itemID);
return;
}
if (pPlayer->m_guid == m_pServerPeer->GetMyGUID())
{
LOG_W("Attempted to modify local player's inventory");
return;
}
pPlayer->m_pInventory->selectItemById(pPlayerEquipmentPkt->m_itemID, C_MAX_HOTBAR_ITEMS);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet)
{
const int uncompMagic = 12847812, compMagic = 58712758, chunkSepMagic = 284787658;
RakNet::BitStream* bs = &packet->m_data, bs2;
int magicNum = 0;
bs->Read(magicNum);
if (magicNum != compMagic && magicNum != uncompMagic)
{
LOG_E("Invalid level data packet with magic %d", magicNum);
return;
}
// If our data is compressed
if (magicNum == compMagic)
{
// Decompress it before we handle it.
int uncompSize = 0, compSize = 0;
bs->Read(uncompSize);
bs->Read(compSize);
LOG_I("Decompressing level data. Compressed: %d bytes, uncompressed: %d bytes", compSize, uncompSize);
// Read the compressed data.
uint8_t* pCompData = new uint8_t[compSize];
bs->Read((char*)pCompData, compSize);
// Inflate it.
uint8_t* pUncompData = ZlibInflateToMemory(pCompData, compSize, uncompSize);
SAFE_DELETE_ARRAY(pCompData);
// If we couldn't, bail
if (!pUncompData)
{
LOG_E("Failed to decompress level data!");
return;
}
// Do some small scale hacks to get bs2 contain the uncompressed data.
bs2.Reset();
bs2.Write((const char*)pUncompData, uncompSize);
bs2.ResetReadPointer();
bs = &bs2;
// Delete the uncompressed data, since we've written it to our bitstream.
SAFE_DELETE_ARRAY(pUncompData);
bs->Read(magicNum);
}
int chunksX = 0, chunksZ = 0;
bs->Read(chunksX);
bs->Read(chunksZ);
if (chunksX != C_MAX_CHUNKS_X || chunksZ != C_MAX_CHUNKS_Z)
{
LOG_E("We don't yet support a level of size %d x %d chunks. Some chunks may disappear or be regenerated.", chunksX, chunksZ);
}
for (int x = 0; x < chunksX; x++)
{
for (int z = 0; z < chunksZ; z++)
{
bs->Read(magicNum);
if (magicNum != chunkSepMagic)
{
_FAIL_BECAUSE_INVALID:
LOG_E("Aborting because level data is invalid, reading chunk %d, %d. Magic: %d", x, z, magicNum);
return;
}
uint8_t ptype = 0;
// read the data size. This'll let us know how much to read.
int dataSize = 0;
bs->Read(dataSize);
LevelChunk* pChunk = m_pLevel->getChunk(x, z);
if (!pChunk || pChunk->isEmpty())
LOG_E("No chunk at %d, %d", x, z);
// continue reading anyway to skip over the offending chunk
// Seems like reading a bitstream from another bitstream reads all the way
// to the end, so we need to duplicate in this fashion.
RakNet::BitStream bs2;
bs2.Write(*bs, 8 * dataSize);
// Ensure the packet type is correct.
bs2.Read(ptype);
if (ptype != PACKET_CHUNK_DATA)
goto _FAIL_BECAUSE_INVALID;
// Read the chunk data packet itself, and handle it.
ChunkDataPacket cdp(x, z, pChunk);
cdp.read(&bs2);
if (pChunk)
handle(guid, &cdp);
// Handle lighting immediately, to ensure it doesn't get out of control.
while (m_pLevel->updateLights());
}
}
// All chunks are loaded. Also flush all the updates we've buffered.
m_chunksRequested = 256;
flushAllBufferedUpdates();
}
bool ClientSideNetworkHandler::areAllChunksLoaded()
{
return m_chunksRequested > 255;
}
void ClientSideNetworkHandler::requestNextChunk()
{
if (areAllChunksLoaded())
return;
// @BUG: The return value of areAllChunksLoaded() is actually true even before the
// 256th chunk is loaded.
if (m_serverProtocolVersion < 2)
{
m_pRakNetInstance->send(new RequestChunkPacket(m_chunksRequested % 16, m_chunksRequested / 16));
m_chunksRequested++;
}
else
{
m_pRakNetInstance->send(new RequestChunkPacket(-9999, -9999));
}
}
void ClientSideNetworkHandler::flushAllBufferedUpdates()
{
for (int i = 0; i < int(m_bufferedBlockUpdates.size()); i++)
{
SBufferedBlockUpdate& u = m_bufferedBlockUpdates[i];
m_pLevel->setTileAndData(u.x, u.y, u.z, u.tile, u.data);
}
}