aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordtentiion <dtentiongit@gmail.com>2026-03-02 21:13:11 +0000
committerdtentiion <dtentiongit@gmail.com>2026-03-02 21:13:11 +0000
commit63e590d783cc3302ec6a5b639dd831e930a557c6 (patch)
tree1739b7757c571313601defdbb25867ef41b8869c
parent5b5b9f60721f40f1ddebc4629b9e2802de646fb6 (diff)
Win64: show actual world names in save list, sort newest-first, preserve level name on load/resave
-rw-r--r--Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp1
-rw-r--r--Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp13
-rw-r--r--Minecraft.Client/Common/UI/UIScene_LoadMenu.h1
-rw-r--r--Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp150
-rw-r--r--Minecraft.Client/MinecraftServer.cpp4
-rw-r--r--Minecraft.Client/MinecraftServer.h1
-rw-r--r--Minecraft.World/ConsoleSaveFileOriginal.cpp2
-rw-r--r--README.md19
8 files changed, 183 insertions, 8 deletions
diff --git a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp
index 6be67bed..a9ff02f5 100644
--- a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp
@@ -1110,6 +1110,7 @@ void UIScene_CreateWorldMenu::CreateGame(UIScene_CreateWorldMenu* pClass, DWORD
__int64 seedValue = 0;
NetworkGameInitData *param = new NetworkGameInitData();
+ param->levelName = wWorldName;
if (wSeed.length() != 0)
{
diff --git a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp
index 1c07e540..bb399b97 100644
--- a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp
@@ -231,13 +231,23 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye
#endif
m_bShowTimer = true;
}
-#if defined(_DURANGO)
+#if defined(_DURANGO)
m_labelGameName.init(params->saveDetails->UTF16SaveName);
#else
m_labelGameName.init(params->saveDetails->UTF8SaveName);
#endif
#endif
+#ifdef _WINDOWS64
+ if (params->saveDetails != NULL && params->saveDetails->UTF8SaveName[0] != '\0')
+ {
+ wchar_t wSaveName[128];
+ ZeroMemory(wSaveName, sizeof(wSaveName));
+ mbstowcs(wSaveName, params->saveDetails->UTF8SaveName, 127);
+ m_levelName = wstring(wSaveName);
+ m_labelGameName.init(m_levelName);
+ }
+#endif
}
TelemetryManager->RecordMenuShown(m_iPad, eUIScene_LoadMenu, 0);
@@ -1448,6 +1458,7 @@ void UIScene_LoadMenu::StartGameFromSave(UIScene_LoadMenu* pClass, DWORD dwLocal
param->seed = pClass->m_seed;
param->saveData = NULL;
param->texturePackId = pClass->m_MoreOptionsParams.dwTexturePack;
+ param->levelName = pClass->m_levelName;
Minecraft *pMinecraft = Minecraft::GetInstance();
pMinecraft->skins->selectTexturePackById(pClass->m_MoreOptionsParams.dwTexturePack);
diff --git a/Minecraft.Client/Common/UI/UIScene_LoadMenu.h b/Minecraft.Client/Common/UI/UIScene_LoadMenu.h
index e45fa09c..e245e3be 100644
--- a/Minecraft.Client/Common/UI/UIScene_LoadMenu.h
+++ b/Minecraft.Client/Common/UI/UIScene_LoadMenu.h
@@ -62,6 +62,7 @@ private:
bool m_bIsCorrupt;
bool m_bThumbnailGetFailed;
__int64 m_seed;
+ wstring m_levelName;
#ifdef __PS3__
std::vector<SonyCommerce::ProductInfo>*m_pvProductInfo;
diff --git a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp
index 1d90da77..e3749dcb 100644
--- a/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_LoadOrJoinMenu.cpp
@@ -25,6 +25,98 @@
#include "message_dialog.h"
#endif
+#ifdef _WINDOWS64
+#include "..\..\..\Minecraft.World\NbtIo.h"
+#include "..\..\..\Minecraft.World\compression.h"
+
+static wstring ReadLevelNameFromSaveFile(const wstring& filePath)
+{
+ HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ if (hFile == INVALID_HANDLE_VALUE) return L"";
+
+ DWORD fileSize = GetFileSize(hFile, NULL);
+ if (fileSize < 12 || fileSize == INVALID_FILE_SIZE) { CloseHandle(hFile); return L""; }
+
+ unsigned char *rawData = new unsigned char[fileSize];
+ DWORD bytesRead = 0;
+ if (!ReadFile(hFile, rawData, fileSize, &bytesRead, NULL) || bytesRead != fileSize)
+ {
+ CloseHandle(hFile);
+ delete[] rawData;
+ return L"";
+ }
+ CloseHandle(hFile);
+
+ unsigned char *saveData = NULL;
+ unsigned int saveSize = 0;
+ bool freeSaveData = false;
+
+ if (*(unsigned int*)rawData == 0)
+ {
+ // Compressed format: bytes 0-3=0, bytes 4-7=decompressed size, bytes 8+=compressed data
+ unsigned int decompSize = *(unsigned int*)(rawData + 4);
+ if (decompSize == 0 || decompSize > 128 * 1024 * 1024)
+ {
+ delete[] rawData;
+ return L"";
+ }
+ saveData = new unsigned char[decompSize];
+ Compression::getCompression()->Decompress(saveData, &decompSize, rawData + 8, fileSize - 8);
+ saveSize = decompSize;
+ freeSaveData = true;
+ }
+ else
+ {
+ saveData = rawData;
+ saveSize = fileSize;
+ }
+
+ wstring result = L"";
+ if (saveSize >= 12)
+ {
+ unsigned int headerOffset = *(unsigned int*)saveData;
+ unsigned int numEntries = *(unsigned int*)(saveData + 4);
+ const unsigned int entrySize = sizeof(FileEntrySaveData);
+
+ if (headerOffset < saveSize && numEntries > 0 && numEntries < 10000 &&
+ headerOffset + numEntries * entrySize <= saveSize)
+ {
+ FileEntrySaveData *table = (FileEntrySaveData *)(saveData + headerOffset);
+ for (unsigned int i = 0; i < numEntries; i++)
+ {
+ if (wcscmp(table[i].filename, L"level.dat") == 0)
+ {
+ unsigned int off = table[i].startOffset;
+ unsigned int len = table[i].length;
+ if (off >= 12 && off + len <= saveSize && len > 0 && len < 4 * 1024 * 1024)
+ {
+ byteArray ba;
+ ba.data = (byte*)(saveData + off);
+ ba.length = len;
+ CompoundTag *root = NbtIo::decompress(ba);
+ if (root != NULL)
+ {
+ CompoundTag *dataTag = root->getCompound(L"Data");
+ if (dataTag != NULL)
+ result = dataTag->getString(L"LevelName");
+ delete root;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (freeSaveData) delete[] saveData;
+ delete[] rawData;
+ // "world" is the engine default — it means no real name was ever set, so
+ // return empty to let the caller fall back to the save filename (timestamp).
+ if (result == L"world") result = L"";
+ return result;
+}
+#endif
+
#ifdef SONY_REMOTE_STORAGE_DOWNLOAD
unsigned long UIScene_LoadOrJoinMenu::m_ulFileSize=0L;
@@ -158,7 +250,7 @@ UIScene_LoadOrJoinMenu::UIScene_LoadOrJoinMenu(int iPad, void *initData, UILayer
}
#endif
-#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) || defined(_DURANGO)
+#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) || defined(_DURANGO) || defined(_WINDOWS64)
// Always clear the saves when we enter this menu
StorageManager.ClearSavesInfo();
#endif
@@ -603,6 +695,22 @@ void UIScene_LoadOrJoinMenu::tick()
m_saveDetails = new SaveListDetails[m_pSaveDetails->iSaveC];
m_iSaveDetailsCount = m_pSaveDetails->iSaveC;
+#ifdef _WINDOWS64
+ // Build sorted index array (newest-first by filename timestamp YYYYMMDDHHMMSS)
+ int *sortedIdx = new int[m_pSaveDetails->iSaveC];
+ for (int si = 0; si < (int)m_pSaveDetails->iSaveC; ++si) sortedIdx[si] = si;
+ for (int si = 1; si < (int)m_pSaveDetails->iSaveC; ++si)
+ {
+ int key = sortedIdx[si];
+ int sj = si - 1;
+ while (sj >= 0 && strcmp(m_pSaveDetails->SaveInfoA[sortedIdx[sj]].UTF8SaveFilename, m_pSaveDetails->SaveInfoA[key].UTF8SaveFilename) < 0)
+ {
+ sortedIdx[sj + 1] = sortedIdx[sj];
+ --sj;
+ }
+ sortedIdx[sj + 1] = key;
+ }
+#endif
for(unsigned int i = 0; i < m_pSaveDetails->iSaveC; ++i)
{
#if defined(_XBOX_ONE)
@@ -617,13 +725,39 @@ void UIScene_LoadOrJoinMenu::tick()
memcpy(m_saveDetails[i].UTF16SaveName, m_pSaveDetails->SaveInfoA[i].UTF16SaveTitle, 128);
memcpy(m_saveDetails[i].UTF16SaveFilename, m_pSaveDetails->SaveInfoA[i].UTF16SaveFilename, MAX_SAVEFILENAME_LENGTH);
#else
+#ifdef _WINDOWS64
+ {
+ int origIdx = sortedIdx[i];
+ wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
+ ZeroMemory(wFilename, sizeof(wFilename));
+ mbstowcs(wFilename, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH - 1);
+ wstring filePath = wstring(L"Windows64\\GameHDD\\") + wstring(wFilename) + wstring(L"\\saveData.ms");
+ wstring levelName = ReadLevelNameFromSaveFile(filePath);
+ if (!levelName.empty())
+ {
+ m_buttonListSaves.addItem(levelName, wstring(L""));
+ wcstombs(m_saveDetails[i].UTF8SaveName, levelName.c_str(), 127);
+ m_saveDetails[i].UTF8SaveName[127] = '\0';
+ }
+ else
+ {
+ m_buttonListSaves.addItem(m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, L"");
+ memcpy(m_saveDetails[i].UTF8SaveName, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, 128);
+ }
+ m_saveDetails[i].saveId = origIdx;
+ memcpy(m_saveDetails[i].UTF8SaveFilename, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH);
+ }
+#else
m_buttonListSaves.addItem(m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, L"");
-
- m_saveDetails[i].saveId = i;
memcpy(m_saveDetails[i].UTF8SaveName, m_pSaveDetails->SaveInfoA[i].UTF8SaveTitle, 128);
+ m_saveDetails[i].saveId = i;
memcpy(m_saveDetails[i].UTF8SaveFilename, m_pSaveDetails->SaveInfoA[i].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH);
#endif
+#endif
}
+#ifdef _WINDOWS64
+ delete[] sortedIdx;
+#endif
m_controlSavesTimer.setVisible( false );
// set focus on the first button
@@ -639,7 +773,11 @@ void UIScene_LoadOrJoinMenu::tick()
app.DebugPrintf("Requesting the first thumbnail\n");
// set the save to load
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
+#ifdef _WINDOWS64
+ C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
+#else
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
+#endif
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
{
@@ -702,7 +840,11 @@ void UIScene_LoadOrJoinMenu::tick()
app.DebugPrintf("Requesting another thumbnail\n");
// set the save to load
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
+#ifdef _WINDOWS64
+ C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
+#else
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
+#endif
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
{
// something went wrong
@@ -1310,7 +1452,7 @@ void UIScene_LoadOrJoinMenu::handlePress(F64 controlId, F64 childId)
LoadMenuInitData *params = new LoadMenuInitData();
params->iPad = m_iPad;
// need to get the iIndex from the list item, since the position in the list doesn't correspond to the GetSaveGameInfo list because of sorting
- params->iSaveGameInfoIndex=((int)childId)-m_iDefaultButtonsC;
+ params->iSaveGameInfoIndex=m_saveDetails[((int)childId)-m_iDefaultButtonsC].saveId;
//params->pbSaveRenamed=&m_bSaveRenamed;
params->levelGen = NULL;
params->saveDetails = &m_saveDetails[ ((int)childId)-m_iDefaultButtonsC ];
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index ceb9554b..5e7ce794 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -149,7 +149,7 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
//localIp = settings->getString(L"server-ip", L"");
//onlineMode = settings->getBoolean(L"online-mode", true);
//motd = settings->getString(L"motd", L"A Minecraft Server");
- //motd.replace('§', '$');
+ //motd.replace('�', '$');
setAnimals(settings->getBoolean(L"spawn-animals", true));
setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true));
@@ -203,7 +203,7 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
__int64 levelNanoTime = System::nanoTime();
- wstring levelName = settings->getString(L"level-name", L"world");
+ wstring levelName = (initData && !initData->levelName.empty()) ? initData->levelName : settings->getString(L"level-name", L"world");
wstring levelTypeString;
bool gameRuleUseFlatWorld = false;
diff --git a/Minecraft.Client/MinecraftServer.h b/Minecraft.Client/MinecraftServer.h
index e61001a3..ac99a415 100644
--- a/Minecraft.Client/MinecraftServer.h
+++ b/Minecraft.Client/MinecraftServer.h
@@ -39,6 +39,7 @@ typedef struct _NetworkGameInitData
unsigned int xzSize;
unsigned char hellScale;
ESavePlatform savePlatform;
+ wstring levelName;
_NetworkGameInitData()
{
diff --git a/Minecraft.World/ConsoleSaveFileOriginal.cpp b/Minecraft.World/ConsoleSaveFileOriginal.cpp
index 139d99ac..7a11b5e1 100644
--- a/Minecraft.World/ConsoleSaveFileOriginal.cpp
+++ b/Minecraft.World/ConsoleSaveFileOriginal.cpp
@@ -740,7 +740,7 @@ void ConsoleSaveFileOriginal::Flush(bool autosave, bool updateThumbnail )
PBYTE pbDataSaveImage=NULL;
DWORD dwDataSizeSaveImage=0;
-#if ( defined _XBOX || defined _DURANGO )
+#if ( defined _XBOX || defined _DURANGO || defined _WINDOWS64 )
app.GetSaveThumbnail(&pbThumbnailData,&dwThumbnailDataSize);
#elif ( defined __PS3__ || defined __ORBIS__ || defined __PSVITA__ )
app.GetSaveThumbnail(&pbThumbnailData,&dwThumbnailDataSize,&pbDataSaveImage,&dwDataSizeSaveImage);
diff --git a/README.md b/README.md
index dd9a7965..5be0c8fb 100644
--- a/README.md
+++ b/README.md
@@ -16,23 +16,32 @@ This project contains the source code of Minecraft Legacy Console Edition v1.3.0
- Disabled V-Sync for better performance
- Auto-detect native monitor resolution with DPI awareness, resulting in sharper visuals on high-resolution displays
- Full support for keyboard and mouse input
+- **Configurable player username/nametag** — edit `username.txt` next to the exe to set your in-game name
+- **Persistent game settings** — gamma, music, sound, difficulty, HUD options, debug flags and all other settings now survive restarts (saved to `settings.dat` next to the exe)
+- **Correct world save names** — save slots now display the actual world name instead of a raw timestamp; save list is sorted newest-first and refreshes without restarting
## Controls (Keyboard & Mouse)
- **Movement**: `W` `A` `S` `D`
- **Jump / Fly (Up)**: `Space`
- **Sneak / Fly (Down)**: `Shift` (Hold)
+- **Toggle Fly**: `F`
- **Sprint**: `Ctrl` (Hold) or Double-tap `W`
- **Inventory**: `E`
- **Drop Item**: `Q`
- **Crafting**: `C`
- **Toggle View (FPS/TPS)**: `F5`
+- **Toggle Debug Info**: `F3`
+- **Open Debug Overlay**: `F4` (Debug builds only)
- **Fullscreen**: `F11`
- **Pause Menu**: `Esc`
- **Toggle Mouse Capture**: `Left Alt` (for debugging)
- **Attack / Destroy**: `Left Click`
- **Use / Place**: `Right Click`
- **Select Item**: `Mouse Wheel` or keys `1` to `9`
+- **Accept Tutorial Hint**: `Enter`
+- **Decline Tutorial Hint**: `B`
+- **Host Options / Player List**: `Tab`
## Build & Run
@@ -49,7 +58,17 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Debug --target MinecraftClient
```
+## Runtime Files
+
+Some features require files placed next to the built executable (`x64\Debug\` or `x64\Release\`):
+
+| File | Purpose |
+|------|---------|
+| `username.txt` | Plain text file — first line becomes your in-game name and nametag. Created automatically with default value `Windows` on first run if absent. |
+| `settings.dat` | Binary save of all game settings. Written automatically whenever you change a setting; loaded on startup. Delete it to reset all settings to defaults. |
+
## Known Issues
- Builds for other platforms have not been tested and are most likely non-functional
- There are some render bugs in the Release mode build
+- Changing the resource pack on an existing world while loading it may crash (`reloadAll` called during world load) — use the default resource pack or select it when creating a new world