Merge branch 'master' into master

This commit is contained in:
iProgramInCpp
2023-08-02 14:05:02 +03:00
committed by GitHub
29 changed files with 1810 additions and 32 deletions

View File

@@ -10,6 +10,7 @@
#include "PauseScreen.hpp"
#include "StartMenuScreen.hpp"
#include "RenameMPLevelScreen.hpp"
#include "SavingWorldScreen.hpp"
#include "ServerSideNetworkHandler.hpp"
#include "ClientSideNetworkHandler.hpp"
@@ -299,7 +300,7 @@ void Minecraft::tickInput()
{
if (!field_D14->field_10)
{
field_DB0 = 1;
field_DB0 = true;
field_D14->updateEvents();
field_DB0 = false;
if (field_DB1)
@@ -850,6 +851,10 @@ void Minecraft::leaveGame(bool bCopyMap)
delete m_pNetEventCallback;
#endif
#ifdef ENH_IMPROVED_SAVING
field_288 = true;
setScreen(new SavingWorldScreen(bCopyMap));
#else
if (m_pLevel)
{
LevelStorage* pStorage = m_pLevel->getLevelStorage();
@@ -858,6 +863,7 @@ void Minecraft::leaveGame(bool bCopyMap)
m_pLevel = nullptr;
}
#endif
#ifdef ORIGINAL_CODE
delete m_pNetEventCallback;
@@ -866,10 +872,12 @@ void Minecraft::leaveGame(bool bCopyMap)
m_pNetEventCallback = nullptr;
field_D9C = 0;
#ifndef ENH_IMPROVED_SAVING
if (bCopyMap)
setScreen(new RenameMPLevelScreen("_LastJoinedServer"));
else
setScreen(new StartMenuScreen);
#endif
}
void Minecraft::hostMultiplayer()

View File

@@ -8,9 +8,14 @@
#include "NinecraftApp.hpp"
#include "StartMenuScreen.hpp"
#include "MemoryLevelStorageSource.hpp"
#include "Item.hpp"
#ifdef DEMO
#include "MemoryLevelStorageSource.hpp"
#else
#include "ExternalFileLevelStorageSource.hpp"
#endif
bool NinecraftApp::_hasInitedStatics;
bool NinecraftApp::handleBack(bool b)
@@ -66,7 +71,13 @@ void NinecraftApp::init()
initGLStates();
Tesselator::instance.init();
Minecraft::init();
#ifdef DEMO
m_pLevelStorageSource = new MemoryLevelStorageSource;
#else
m_pLevelStorageSource = new ExternalFileLevelStorageSource(m_externalStorageDir);
#endif
field_D9C = 0;
setScreen(new StartMenuScreen);

View File

@@ -11,17 +11,125 @@
#include "Utils.hpp"
#ifdef _WIN32
#include <Windows.h>
#include <io.h>
#include <direct.h>
// Why are we not using GetTickCount64()? It's simple -- getTimeMs has the exact same problem as using regular old GetTickCount.
#pragma warning(disable : 28159)
#else
#include <sys/time.h>
#include <unistd.h>
#include <sys/stat.h>
#define XPL_ACCESS access
#define XPL_MKDIR(path, mode) mkdir(path, mode)
#endif
#include "compat/GL.hpp"
int g_TimeSecondsOnInit = 0;
DIR* opendir(const char* name)
{
size_t len = strlen(name);
if (len == 0)
return NULL;
char buf[1024];
if (len >= 1024 - 5)
return NULL;
strcpy(buf, name);
if (name[len - 1] != '/')
strcpy(&buf[len], "/*");
else
strcpy(&buf[len], "*");
DIR* pDir = (DIR*)malloc(sizeof(DIR));
if (!pDir)
return pDir;
memset(pDir, 0, sizeof * pDir);
pDir->current = FindFirstFile(buf, &pDir->findData);
if (pDir->current == INVALID_HANDLE_VALUE)
{
free(pDir);
return NULL;
}
return pDir;
}
dirent* readdir(DIR* dir)
{
if (dir->current == INVALID_HANDLE_VALUE)
return NULL;
static dirent de;
if (!dir->returnedFirstFileData)
{
dir->returnedFirstFileData = true;
}
else
{
if (!FindNextFile(dir->current, &dir->findData))
return NULL;
}
strcpy(de.d_name, dir->findData.cFileName);
de.d_type = (dir->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG;
return &de;
}
void closedir(DIR* dir)
{
if (dir->current != INVALID_HANDLE_VALUE)
FindClose(dir->current);
free(dir);
}
bool createFolderIfNotExists(const char* pDir)
{
if (!XPL_ACCESS(pDir, 0))
return true;
return XPL_MKDIR(pDir, 0755) == 0;
}
bool DeleteDirectory(const std::string& name, bool unused)
{
DIR* dir = opendir(name.c_str());
if (!dir)
return false;
char buffer[1024];
while (true)
{
dirent* de = readdir(dir);
if (!de)
break;
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
continue;
snprintf(buffer, sizeof buffer, "%s/%s", name.c_str(), de->d_name);
remove(buffer);
}
closedir(dir);
return remove(name.c_str()) == 0;
}
const char* GetTerrainName()
{
return "terrain.png";

View File

@@ -15,16 +15,51 @@
#include <cassert>
#include <climits>
#include <string>
#ifdef _WIN32
// @HACK: Include WinSock2.h also
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
#include <direct.h>
#include <io.h>
// XPL means "Cross PLatform"
#define XPL_ACCESS _access
#define XPL_MKDIR(path, mode) _mkdir(path)
// Bare bones non conforming implementation, but good enough
struct dirent
{
int d_type;
char d_name[_MAX_PATH + 1];
};
struct DIR
{
HANDLE current;
WIN32_FIND_DATA findData;
bool returnedFirstFileData;
};
#define DT_UNKNOWN (0)
#define DT_DIR (4)
#define DT_REG (8)
DIR* opendir(const char* name);
dirent* readdir(DIR* dir);
void closedir(DIR* dir);
#else
#include <unistd.h>
#include <dirent.h>
// XPL means "Cross PLatform"
#define XPL_ACCESS access
#define XPL_MKDIR(path, mode) mkdir(path, mode)
#endif
@@ -504,6 +539,8 @@ constexpr float Lerp(float a, float b, float progress)
return a + progress * (b - a);
}
bool createFolderIfNotExists(const char* pDir);
bool DeleteDirectory(const std::string& name, bool unused);
// things that we added:
#ifndef ORIGINAL_CODE
@@ -537,3 +574,23 @@ void SetHWND(HWND hwnd);
#define LogMsgNoCR(...)
#endif
#ifdef MC_DEBUG
#ifdef PLATFORM_ANDROID
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MinecraftPE", __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MinecraftPE", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MinecraftPE", __VA_ARGS__)
#else
#define LOGI(...) printf("Info: " __VA_ARGS__)
#define LOGW(...) printf("WARN: " __VA_ARGS__)
#define LOGE(...) printf("ERROR: " __VA_ARGS__)
#endif
#else
#define LOGI(...) printf(__VA_ARGS__)
#define LOGW(...) printf(__VA_ARGS__)
#define LOGE(...) printf(__VA_ARGS__)
#endif

View File

@@ -0,0 +1,63 @@
#include "SavingWorldScreen.hpp"
#include "RenameMPLevelScreen.hpp"
#include "StartMenuScreen.hpp"
#ifdef ENH_IMPROVED_SAVING
SavingWorldScreen::SavingWorldScreen(bool bCopyMap)
{
m_bCopyMapAtEnd = bCopyMap;
m_timer = 0;
}
void SavingWorldScreen::render(int mouseX, int mouseY, float f)
{
renderDirtBackground(0);
int x_width = int(Minecraft::width * Gui::InvGuiScale);
int x_height = int(Minecraft::height * Gui::InvGuiScale);
int yPos = x_height / 2;
int width = m_pFont->width("Saving chunks");
m_pFont->drawShadow("Saving chunks", (x_width - width) / 2, yPos + 4, 0xFFFFFF);
}
void SavingWorldScreen::tick()
{
if (m_timer < 0)
return;
m_timer++;
if (m_timer >= 5)
{
m_timer = -1;
Level* pLevel = m_pMinecraft->m_pLevel;
if (pLevel)
{
pLevel->saveAllChunks();
pLevel->saveLevelData();
pLevel->savePlayerData();
LevelStorage* pStorage = pLevel->getLevelStorage();
SAFE_DELETE(pStorage);
SAFE_DELETE(pLevel);
m_pMinecraft->m_pLevel = nullptr;
}
m_pMinecraft->field_DB0 = true;
if (m_bCopyMapAtEnd)
m_pMinecraft->setScreen(new RenameMPLevelScreen("_LastJoinedServer"));
else
m_pMinecraft->setScreen(new StartMenuScreen);
m_pMinecraft->field_DB0 = false;
m_pMinecraft->field_288 = false;
}
}
#endif

View File

@@ -0,0 +1,20 @@
#pragma once
#include "Screen.hpp"
#ifdef ENH_IMPROVED_SAVING
class SavingWorldScreen : public Screen
{
public:
SavingWorldScreen(bool bCopyMap);
void render(int mouseX, int mouseY, float f) override;
void tick() override;
public:
bool m_bCopyMapAtEnd;
int m_timer = 0;
};
#endif

View File

@@ -126,7 +126,10 @@ void SelectWorldScreen::tick()
m_pMinecraft->hostMultiplayer();
m_pMinecraft->setScreen(new ProgressScreen);
// @BUG: Use of deallocated memory. SetScreen frees us
#ifdef ORIGINAL_CODE
field_130 = 0;
#endif
return;
}

View File

@@ -48,16 +48,8 @@ void StartMenuScreen::buttonClicked(Button* pButton)
{
if (pButton->field_30 == m_startButton.field_30)
{
#if defined(DEMO) || !defined(ORIGINAL_CODE)
# ifdef DEMO
# define DEMO_SEED int(getEpochTimeS())
# else
// 1942892620 = long(12345678901324)
# define DEMO_SEED 123456
# endif
m_pMinecraft->selectLevel("_DemoLevel", "_DemoLevel", DEMO_SEED);
#if defined(DEMO)
m_pMinecraft->selectLevel("_DemoLevel", "_DemoLevel", int(getEpochTimeS()));
m_pMinecraft->hostMultiplayer();
m_pMinecraft->setScreen(new ProgressScreen);
#else

View File

@@ -45,6 +45,7 @@ void Level::_init(const std::string& str, TLong seed, int x, Dimension* pDimens
field_B0C = pData == 0;
// @BUG: leaking a Dimension*?
if (pDimension)
m_pDimension = pDimension;
else

View File

@@ -19,6 +19,36 @@ LevelData::LevelData(TLong seed, const std::string& name, int x)
field_78 = name;
}
void LevelData::read(RakNet::BitStream& bs, int version)
{
field_20 = version;
bs.Read(m_seed);
bs.Read(m_spawnPos.x);
bs.Read(m_spawnPos.y);
bs.Read(m_spawnPos.z);
bs.Read(field_10);
bs.Read(field_18);
bs.Read(field_14);
RakNet::RakString rs;
bs.Read(rs);
field_78 = std::string(rs.C_String());
}
void LevelData::write(RakNet::BitStream& bs)
{
bs.Write(m_seed);
bs.Write(m_spawnPos.x);
bs.Write(m_spawnPos.y);
bs.Write(m_spawnPos.z);
bs.Write(field_10);
bs.Write(field_18);
bs.Write(int(getEpochTimeS()));
RakNet::RakString rs(field_78.c_str());
bs.Write(rs);
}
void PlayerData::loadPlayer(Player* player)
{
player->setPos(0.0f, 0.0f, 0.0f);
@@ -43,3 +73,18 @@ void PlayerData::loadPlayer(Player* player)
for (int i = 0; i < C_MAX_HOTBAR_ITEMS; i++)
player->m_pInventory->setSelectionSlotItemId(i, m_hotbar[i]);
}
void PlayerData::savePlayer(Player* player)
{
m_pos = player->m_pos;
m_vel = player->m_vel;
m_pitch = player->m_pitch;
m_yaw = player->m_yaw;
m_distanceFallen = player->m_distanceFallen;
field_24 = player->field_C0;
field_26 = player->field_BC;
field_28 = player->field_7C;
for (int i = 0; i < C_MAX_HOTBAR_ITEMS; i++)
m_hotbar[i] = player->m_pInventory->getSelectionSlotItemId(i);
}

View File

@@ -13,6 +13,7 @@
#include "Utils.hpp"
#include "Vec3.hpp"
#include "Inventory.hpp"
#include "BitStream.h"
struct PlayerData
{
@@ -27,6 +28,7 @@ struct PlayerData
int m_hotbar[C_MAX_HOTBAR_ITEMS];
void loadPlayer(Player* player);
void savePlayer(Player* player);
};
struct LevelData
@@ -34,15 +36,29 @@ struct LevelData
LevelData();
LevelData(TLong seed, const std::string&, int);
void read(RakNet::BitStream& bs, int d);
void write(RakNet::BitStream& bs);
TLong m_seed = 0;
Pos m_spawnPos;
TLong field_10 = 0;
int field_14 = 0;
int field_18 = 0;
TLong field_18 = 0;
int field_1C = 0;
int field_20 = 0;
PlayerData m_LocalPlayerData;
int m_nPlayers = -1;
std::string field_78;
// inlined in 0.1.0 demo
int getVersion() const
{
return field_20;
}
void setLevelName(const std::string& name)
{
field_78 = name;
}
};

174
source/World/RegionFile.cpp Normal file
View File

@@ -0,0 +1,174 @@
#include "RegionFile.hpp"
#define SECTOR_BYTES (4096)
static void void_sub(int a, int b)
{
}
#define WRITE(data, elemsize, elemcnt, file) void_sub(int(fwrite(data, elemsize, elemcnt, file)), elemcnt)
#define READ( data, elemsize, elemcnt, file) void_sub(int(fread (data, elemsize, elemcnt, file)), elemcnt)
RegionFile::RegionFile(const std::string fileName)
{
m_fileName = fileName + "/" + "chunks.dat";
field_20 = new int[1024];
field_24 = new int[1024];
memset(field_24, 0, 1024 * sizeof(int));
}
RegionFile::~RegionFile()
{
close();
if (field_20) delete[] field_20;
if (field_24) delete[] field_24;
}
void RegionFile::close()
{
if (m_pFile)
{
fclose(m_pFile);
m_pFile = nullptr;
}
}
bool RegionFile::open()
{
close();
memset(field_20, 0, 1024 * sizeof(int));
m_pFile = fopen(m_fileName.c_str(), "r+b");
if (m_pFile)
{
READ(field_20, sizeof(int), 1024, m_pFile);
field_28[0] = false;
for (int i = 0; i < 1024; i++)
{
int v13 = this->field_20[i];
if (v13)
{
int v12 = v13 >> 8;
int v11 = uint8_t(v13);
for (int j = 0; j < v11; ++j)
{
field_28[j + v12] = false;
}
}
}
return m_pFile != nullptr;
}
m_pFile = fopen(m_fileName.c_str(), "w+b");
if (!m_pFile)
return false;
WRITE(field_20, sizeof(int), 1024, m_pFile);
field_28[0] = false;
return true;
}
bool RegionFile::readChunk(int x, int z, RakNet::BitStream** pBitStream)
{
int idx = field_20[32 * z + x];
if (!idx)
return false;
int thing = (idx >> 8);
int offset = (idx & 0xFF);
int length = 0;
fseek(m_pFile, thing * SECTOR_BYTES, SEEK_SET);
fread(&length, sizeof(int), 1, m_pFile);
assert(length < ((offset & 0xff) * SECTOR_BYTES));
length -= 4;
uint8_t* data = new uint8_t[length];
READ(data, 1, length, m_pFile);
*pBitStream = new RakNet::BitStream(data, length, false);
return true;
}
bool RegionFile::write(int index, RakNet::BitStream& bitStream)
{
fseek(m_pFile, index * SECTOR_BYTES, 0);
int length = sizeof(int) + bitStream.GetNumberOfBytesUsed();
WRITE(&length, sizeof(length), 1, m_pFile);
WRITE(bitStream.GetData(), 1, bitStream.GetNumberOfBytesUsed(), m_pFile);
return true;
}
bool RegionFile::writeChunk(int x, int z, RakNet::BitStream& bitStream)
{
int length = bitStream.GetNumberOfBytesUsed();
int field20i = field_20[32 * z + x];
int lowerIndex = (length + 4) / SECTOR_BYTES + 1;
if (lowerIndex > 256)
return false;
int field20iU = field20i >> 8, field20iL = field20i & 0xff;
if (field20iU && lowerIndex == field20iL)
{
write(field20iU, bitStream);
return true;
}
for (int i = 0; i < field20iL; i++)
{
field_28[i + field20iU] = true;
}
bool bNeedWrite = false;
int v22 = 0, i = 0;
while (i < lowerIndex)
{
if (field_28.find(i + v22) == field_28.end())
{
bNeedWrite = true;
break;
}
if (field_28[i + v22])
{
i++;
}
else
{
v22 += i + 1;
i = 0;
}
}
if (bNeedWrite)
{
fseek(m_pFile, 0, SEEK_END);
for (int j = 0; lowerIndex - i > j; j++)
{
fwrite(field_24, sizeof(int), 1024, m_pFile);
field_28[j + v22] = true;
}
}
field_20[32 * z + x] = (v22 << 8) | lowerIndex;
for (int k = 0; k < lowerIndex; k++)
{
field_28[k + v22] = false;
}
write(v22, bitStream);
fseek(m_pFile, sizeof(int) * (x + 32 * z), SEEK_SET);
fwrite(&field_20[x + 32 * z], sizeof(int), 1, m_pFile);
return true;
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstdio>
#include <string>
#include <cassert>
#include <map>
#include "BitStream.h"
class RegionFile
{
public:
RegionFile(const std::string fileName);
~RegionFile();
void close();
bool open();
bool readChunk(int x, int z, RakNet::BitStream**);
bool write(int index, RakNet::BitStream&);
bool writeChunk(int x, int z, RakNet::BitStream&);
public:
FILE* m_pFile = nullptr;
std::string m_fileName;
int* field_20;
int* field_24;
std::map<int, bool> field_28;
};

View File

@@ -0,0 +1,306 @@
/********************************************************************
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 "ExternalFileLevelStorage.hpp"
#include "Level.hpp"
#include "GetTime.h"
#define C_CHUNKS_TO_SAVE_PER_TICK (2)
ExternalFileLevelStorage::ExternalFileLevelStorage(const std::string& a, const std::string& path) :
field_8(a),
m_levelDirPath(path)
{
createFolderIfNotExists(m_levelDirPath.c_str());
std::string datLevel = m_levelDirPath + "/" + "level.dat";
std::string datPlayer = m_levelDirPath + "/" + "player.dat";
m_pLevelData = new LevelData;
if (!readLevelData(datLevel, m_pLevelData))
{
delete m_pLevelData;
m_pLevelData = nullptr;
return;
}
readPlayerData(datPlayer, m_pLevelData);
}
LevelData* ExternalFileLevelStorage::prepareLevel(Level* level)
{
m_pLevel = level;
return m_pLevelData;
}
ChunkStorage* ExternalFileLevelStorage::createChunkStorage(Dimension* pDim)
{
return this;
}
void ExternalFileLevelStorage::saveLevelData(LevelData* levelData, std::vector<Player*>& players)
{
writeLevelData(m_levelDirPath + "/" + "level.dat", levelData);
savePlayerData(levelData, players);
SAFE_DELETE(m_pLevelData);
m_pLevelData = new LevelData(*levelData);
}
void ExternalFileLevelStorage::savePlayerData(LevelData* levelData, std::vector<Player*>& players)
{
if (players.empty())
return;
FILE* pFile = fopen((m_levelDirPath + "/" + "player.dat").c_str(), "wb");
if (!pFile)
{
LogMsg("Not saving player data");
return;
}
levelData->m_LocalPlayerData.savePlayer(players[0]);
int nPlayers = 1;
fwrite(&nPlayers, sizeof nPlayers, 1, pFile);
int nSizePD = 80;
fwrite(&nSizePD, sizeof nSizePD, 1, pFile);
// @NOTE: No reason to swap elementCount and elementSize here. I understood it the
// last time - to check whether the data loaded all the way. However, no checks are
// done here.
fwrite(&levelData->m_LocalPlayerData, 1, nSizePD, pFile);
fclose(pFile);
}
void ExternalFileLevelStorage::closeAll()
{
}
void ExternalFileLevelStorage::tick()
{
m_timer++;
if (m_timer % 50 != 0 || !m_pLevel)
return;
for (int z = 0; z < C_MAX_CHUNKS_Z; z++)
{
for (int x = 0; x < C_MAX_CHUNKS_X; x++)
{
LevelChunk* pChunk = m_pLevel->getChunk(x, z);
if (!pChunk || !pChunk->m_bUnsaved)
continue;
int index = x + z * 16;
auto iter = m_unsavedLevelChunks.begin();
for (; iter != m_unsavedLevelChunks.end(); ++iter)
{
if (iter->m_index == index)
{
iter->m_foundTime = RakNet::GetTimeMS();
break;
}
}
if (iter == m_unsavedLevelChunks.end())
{
UnsavedLevelChunk ulc = { index, RakNet::GetTimeMS(), pChunk };
m_unsavedLevelChunks.push_back(ulc);
}
pChunk->m_bUnsaved = false;
}
}
int count = 0;
while (count < C_CHUNKS_TO_SAVE_PER_TICK && !m_unsavedLevelChunks.empty())
{
count++;
auto iter = m_unsavedLevelChunks.begin();
for (auto it2 = m_unsavedLevelChunks.begin(); it2 != m_unsavedLevelChunks.end(); ++it2)
{
if (iter->m_foundTime > it2->m_foundTime)
iter = it2;
}
LevelChunk* pChunk = iter->m_pChunk;
m_unsavedLevelChunks.erase(iter);
save(m_pLevel, pChunk);
}
}
void ExternalFileLevelStorage::flush()
{
}
LevelChunk* ExternalFileLevelStorage::load(Level* level, int x, int z)
{
if (!m_pRegionFile)
{
m_pRegionFile = new RegionFile(m_levelDirPath);
if (!m_pRegionFile->open())
{
SAFE_DELETE(m_pRegionFile);
m_pRegionFile = nullptr;
return nullptr;
}
}
RakNet::BitStream* pBitStream = nullptr;
if (!m_pRegionFile->readChunk(x, z, &pBitStream))
return nullptr;
pBitStream->ResetReadPointer();
TileID* pData = new TileID[16 * 16 * 128];
pBitStream->Read((char*)pData, 16 * 16 * 128 * sizeof(TileID));
LevelChunk* pChunk = new LevelChunk(level, pData, x, z);
pBitStream->Read((char*)pChunk->m_tileData, 16 * 16 * 128 / 2);
if (m_pLevelData->getVersion() == 1)
{
pBitStream->Read((char*)pChunk->m_lightSky, 16 * 16 * 128 / 2);
pBitStream->Read((char*)pChunk->m_lightBlk, 16 * 16 * 128 / 2);
}
pBitStream->Read((char*)pChunk->m_updateMap, sizeof pChunk->m_updateMap);
delete pBitStream->GetData();
delete pBitStream;
pChunk->recalcHeightmap();
pChunk->m_bUnsaved = false;
pChunk->field_234 = true;
pChunk->field_237 = true;
return pChunk;
}
void ExternalFileLevelStorage::save(Level* level, LevelChunk* chunk)
{
if (!m_pRegionFile)
m_pRegionFile = new RegionFile(m_levelDirPath);
if (!m_pRegionFile->open())
{
SAFE_DELETE(m_pRegionFile);
m_pRegionFile = nullptr;
LogMsg("Not saving :( (x: %d z: %d)", chunk->m_chunkX, chunk->m_chunkZ);
return;
}
RakNet::BitStream bs;
bs.Write((const char*)chunk->m_pBlockData, 16 * 16 * 128 * sizeof(TileID));
bs.Write((const char*)chunk->m_tileData, 16 * 16 * 128 / 2);
if (m_pLevelData->field_20 == 1)
{
bs.Write((const char*)chunk->m_lightSky, 16 * 16 * 128 / 2);
bs.Write((const char*)chunk->m_lightBlk, 16 * 16 * 128 / 2);
}
bs.Write((const char*)chunk->m_updateMap, sizeof chunk->m_updateMap);
m_pRegionFile->writeChunk(chunk->m_chunkX, chunk->m_chunkZ, bs);
}
void ExternalFileLevelStorage::saveEntities(Level* level, LevelChunk* chunk)
{
// no op
}
bool ExternalFileLevelStorage::readLevelData(const std::string& path, LevelData* pLevelData)
{
FILE* pFile = fopen(path.c_str(), "rb");
if (!pFile)
return false;
int version = 0, length = 0;
if (fread(&version, sizeof(int), 1, pFile) != 1)
{
_cleanup:
fclose(pFile);
return false;
}
if (fread(&length, sizeof(int), 1, pFile) != 1)
goto _cleanup;
uint8_t* data = new uint8_t[length];
if (fread(data, sizeof(uint8_t), length, pFile) != length)
{
SAFE_DELETE_ARRAY(data);
goto _cleanup;
}
RakNet::BitStream bs(data, length, false);
pLevelData->read(bs, version);
SAFE_DELETE_ARRAY(data);
fclose(pFile);
return true;
}
bool ExternalFileLevelStorage::readPlayerData(const std::string& path, LevelData* pLevelData)
{
FILE* pFile = fopen(path.c_str(), "rb");
if (!pFile)
return false;
// don't know if it's actually nPlayers or version
int nPlayers = 0, size = 0;
if (fread(&nPlayers, sizeof(int), 1, pFile) != 1)
goto _cleanup;
if (fread(&size, sizeof(int), 1, pFile) != 1)
goto _cleanup;
if (nPlayers != 1)
goto _cleanup;
if (fread(&pLevelData->m_LocalPlayerData, 1, sizeof pLevelData->m_LocalPlayerData, pFile) == size)
pLevelData->m_nPlayers = nPlayers;
fclose(pFile);
return true;
_cleanup:
fclose(pFile);
return false;
}
bool ExternalFileLevelStorage::writeLevelData(const std::string& path, LevelData* pLevelData)
{
FILE* pFile = fopen(path.c_str(), "wb");
if (!pFile)
return false;
RakNet::BitStream bs;
pLevelData->write(bs);
fwrite(&pLevelData->field_20, sizeof(int), 1, pFile);
int length = bs.GetNumberOfBytesUsed();
fwrite(&length, sizeof(int), 1, pFile);
fwrite(bs.GetData(), 1, length, pFile);
fclose(pFile);
return true;
}

View File

@@ -0,0 +1,58 @@
/********************************************************************
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
********************************************************************/
#pragma once
#include <list>
#include "LevelStorage.hpp"
#include "ChunkStorage.hpp"
#include "RegionFile.hpp"
#ifndef DEMO
struct UnsavedLevelChunk
{
int m_index;
int m_foundTime;
LevelChunk* m_pChunk;
};
class ExternalFileLevelStorage : public LevelStorage, ChunkStorage
{
public:
ExternalFileLevelStorage(const std::string& a, const std::string& path);
// LevelStorage
LevelData* prepareLevel(Level* level) override;
ChunkStorage* createChunkStorage(Dimension*) override;
void saveLevelData(LevelData* levelData, std::vector<Player*>& players) override;
void savePlayerData(LevelData* levelData, std::vector<Player*>& players) override;
void closeAll() override;
void tick() override;
void flush() override;
// ChunkStorage
LevelChunk* load(Level* level, int x, int z) override;
void save(Level* level, LevelChunk* chunk) override;
void saveEntities(Level* level, LevelChunk* chunk) override;
static bool readLevelData(const std::string& path, LevelData* pLevelData);
static bool readPlayerData(const std::string& path, LevelData* pLevelData);
static bool writeLevelData(const std::string& path, LevelData* pLevelData);
public:
std::string field_8;
std::string m_levelDirPath;
LevelData* m_pLevelData = nullptr;
RegionFile* m_pRegionFile = nullptr;
Level* m_pLevel = nullptr;
int m_timer = 0;
std::list<UnsavedLevelChunk> m_unsavedLevelChunks;
};
#endif

View File

@@ -0,0 +1,164 @@
/********************************************************************
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 "ExternalFileLevelStorageSource.hpp"
#include "ExternalFileLevelStorage.hpp"
#include "Util.hpp"
ExternalFileLevelStorageSource::ExternalFileLevelStorageSource(const std::string& path)
{
m_worldsPath = path;
m_worldsPath += "/games";
if (createFolderIfNotExists(m_worldsPath.c_str()))
{
m_worldsPath += "/com.mojang";
if (createFolderIfNotExists(m_worldsPath.c_str()))
{
m_worldsPath += "/minecraftWorlds";
if (createFolderIfNotExists(m_worldsPath.c_str()))
{
std::vector<LevelSummary> vls;
getLevelList(vls);
}
}
}
m_worldsPath = path + "/games" + "/com.mojang" + "/minecraftWorlds";
}
std::string ExternalFileLevelStorageSource::getName()
{
return "External File Level Storage";
}
LevelStorage* ExternalFileLevelStorageSource::selectLevel(const std::string& name, bool b)
{
return new ExternalFileLevelStorage(name, m_worldsPath + "/" + name);
}
void ExternalFileLevelStorageSource::getLevelList(std::vector<LevelSummary>& vls)
{
DIR* dir = opendir(m_worldsPath.c_str());
if (!dir)
return;
while (true)
{
dirent* de = readdir(dir);
if (!de)
break;
LogMsg("Entry: %s", de->d_name);
if (de->d_type == DT_DIR)
{
addLevelSummaryIfExists(vls, de->d_name);
}
}
closedir(dir);
}
void ExternalFileLevelStorageSource::clearAll()
{
}
int ExternalFileLevelStorageSource::getDataTagFor(const std::string& str)
{
return 0;
}
bool ExternalFileLevelStorageSource::isNewLevelIdAcceptable(const std::string& levelID)
{
return true;
}
static char g_EFLSSFilterArray[] = { '/','\n','\r','\x09','\0','\xC','`','?','*','\\','<','>','|','"',':' };
void ExternalFileLevelStorageSource::deleteLevel(const std::string& levelName)
{
std::stringstream ss;
ss << m_worldsPath << "/" << levelName;
std::string path = ss.str();
if (DeleteDirectory(path, true))
return;
remove((path + "/chunks.dat").c_str());
remove((path + "/player.dat").c_str());
remove((path + "/level.dat").c_str());
}
void ExternalFileLevelStorageSource::renameLevel(const std::string& oldName, const std::string& newName)
{
int accessResult = XPL_ACCESS((m_worldsPath + "/" + oldName).c_str(), 0);
if (accessResult)
return;
std::string levelName = Util::stringTrim(newName);
for (int i = 0; i < sizeof(g_EFLSSFilterArray); i++)
{
std::string str;
str.push_back(g_EFLSSFilterArray[i]);
Util::stringReplace(levelName, str, "");
}
std::vector<LevelSummary> vls;
getLevelList(vls);
std::set<std::string> maps;
for (const auto& ls : vls)
maps.insert(ls.field_0);
std::string levelUniqueName = levelName;
while (maps.find(levelUniqueName) != maps.end())
levelUniqueName += "-";
int renameResult = rename((m_worldsPath + "/" + oldName).c_str(), (m_worldsPath + "/" + levelUniqueName).c_str());
if (renameResult != 0)
levelUniqueName = oldName;
LevelData ld;
ExternalFileLevelStorage::readLevelData(m_worldsPath + "/" + levelUniqueName + "/" + "level.dat", &ld);
ld.setLevelName(levelName);
ExternalFileLevelStorage::writeLevelData(m_worldsPath + "/" + levelUniqueName + "/" + "level.dat", &ld);
}
bool ExternalFileLevelStorageSource::isConvertible(const std::string&)
{
return false;
}
bool ExternalFileLevelStorageSource::requiresConversion(const std::string&)
{
return false;
}
int ExternalFileLevelStorageSource::convertLevel(const std::string&, ProgressListener*)
{
return 0;
}
void ExternalFileLevelStorageSource::addLevelSummaryIfExists(std::vector<LevelSummary>& vls, const char* name)
{
std::string levelDat = m_worldsPath + "/" + name + "/" + "level.dat";
LevelData ld;
if (!ExternalFileLevelStorage::readLevelData(levelDat, &ld))
return;
LevelSummary ls;
ls.field_0 = name;
ls.field_18 = ld.field_78;
ls.field_30 = ld.field_14;
ls.field_34 = ld.field_18;
vls.push_back(ls);
}

View File

@@ -0,0 +1,40 @@
/********************************************************************
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
********************************************************************/
#pragma once
#include <set>
#include <sstream>
#include "LevelStorageSource.hpp"
#ifndef DEMO
class ExternalFileLevelStorageSource : public LevelStorageSource
{
public:
ExternalFileLevelStorageSource(const std::string& path);
std::string getName() override;
LevelStorage* selectLevel(const std::string&, bool) override;
void getLevelList(std::vector<LevelSummary>&);
void clearAll() override;
int getDataTagFor(const std::string&) override;
bool isNewLevelIdAcceptable(const std::string&) override;
void deleteLevel(const std::string&) override;
void renameLevel(const std::string&, const std::string&) override;
bool isConvertible(const std::string&) override;
bool requiresConversion(const std::string&) override;
int convertLevel(const std::string&, ProgressListener*) override;
void addLevelSummaryIfExists(std::vector<LevelSummary>& vls, const char* name);
public:
std::string m_worldsPath;
};
#endif

View File

@@ -14,6 +14,8 @@ LevelStorage::~LevelStorage()
void LevelStorage::saveLevelData(LevelData* levelData)
{
std::vector<Player*> nothing;
saveLevelData(levelData, nothing);
}
void LevelStorage::savePlayerData(LevelData* levelData, std::vector<Player*>& players)

View File

@@ -48,6 +48,6 @@ public:
virtual void renameLevel(const std::string&, const std::string&) = 0;
virtual bool isConvertible(const std::string&) = 0;
virtual bool requiresConversion(const std::string&) = 0;
virtual void convertLevel(const std::string&, ProgressListener*) = 0;
virtual int convertLevel(const std::string&, ProgressListener*) = 0;
};

View File

@@ -9,6 +9,8 @@
#include "MemoryLevelStorage.hpp"
#include "MemoryLevelStorageSource.hpp"
#ifdef DEMO
std::string MemoryLevelStorageSource::getName()
{
return "Memory Storage";
@@ -52,7 +54,9 @@ bool MemoryLevelStorageSource::requiresConversion(const std::string& x)
return false;
}
void MemoryLevelStorageSource::convertLevel(const std::string& x, ProgressListener* y)
int MemoryLevelStorageSource::convertLevel(const std::string& x, ProgressListener* y)
{
return 0;
}
#endif

View File

@@ -10,6 +10,7 @@
#include "LevelStorageSource.hpp"
#ifdef DEMO
class MemoryLevelStorageSource : public LevelStorageSource
{
std::string getName() override;
@@ -21,6 +22,6 @@ class MemoryLevelStorageSource : public LevelStorageSource
void renameLevel(const std::string&, const std::string&) override;
bool isConvertible(const std::string&) override;
bool requiresConversion(const std::string&) override;
void convertLevel(const std::string&, ProgressListener*) override;
int convertLevel(const std::string&, ProgressListener*) override;
};
#endif