feat: headless server

This commit is contained in:
daoge_cmd
2026-03-04 16:18:47 +08:00
parent 8ecfc52547
commit d112090fde
12 changed files with 973 additions and 291 deletions

View File

@@ -17,6 +17,7 @@ function(configure_msvc_target target)
$<$<AND:$<NOT:$<CONFIG:Release>>,$<COMPILE_LANGUAGE:C,CXX>>:/W3>
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/W0>
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
$<$<COMPILE_LANGUAGE:C,CXX>:/FS>
$<$<COMPILE_LANGUAGE:CXX>:/EHsc>
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/GL /O2 /Oi /GT /GF>
)

View File

@@ -362,12 +362,23 @@ void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame,
#ifdef _WINDOWS64
int port = WIN64_NET_DEFAULT_PORT;
const char* bindIp = NULL;
if (g_Win64DedicatedServer)
{
if (g_Win64DedicatedServerPort > 0)
port = g_Win64DedicatedServerPort;
if (g_Win64DedicatedServerBindIP[0] != 0)
bindIp = g_Win64DedicatedServerBindIP;
}
if (!WinsockNetLayer::IsActive())
WinsockNetLayer::HostGame(port);
WinsockNetLayer::HostGame(port, bindIp);
const wchar_t* hostName = IQNet::m_player[0].m_gamertag;
unsigned int settings = app.GetGameHostOption(eGameHostOption_All);
WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION);
if (WinsockNetLayer::IsActive())
{
const wchar_t* hostName = IQNet::m_player[0].m_gamertag;
unsigned int settings = app.GetGameHostOption(eGameHostOption_All);
WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION);
}
#endif
//#endif
}

View File

@@ -1545,6 +1545,7 @@ if not exist "$(TargetDir)\savedata" mkdir "$(TargetDir)\savedata"</Command>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<ShowIncludes>false</ShowIncludes>
<AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -1773,7 +1774,7 @@ xcopy /q /y /i /s /e $(ProjectDir)DurangoMedia\CU $(LayoutDir)Image\Loose\CU</C
<IntrinsicFunctions>true</IntrinsicFunctions>
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
<StringPooling>true</StringPooling>
<AdditionalOptions>/Ob3</AdditionalOptions>
<AdditionalOptions>/FS /Ob3 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -48677,4 +48678,4 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
</Project>
</Project>

View File

@@ -27,6 +27,11 @@
#include "..\Minecraft.World\Pos.h"
#include "..\Minecraft.World\System.h"
#include "..\Minecraft.World\StringHelpers.h"
#include "..\Minecraft.World\net.minecraft.world.entity.item.h"
#include "..\Minecraft.World\net.minecraft.world.item.h"
#include "..\Minecraft.World\net.minecraft.world.item.enchantment.h"
#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
#include <sstream>
#ifdef SPLIT_SAVES
#include "..\Minecraft.World\ConsoleSaveFileSplit.h"
#endif
@@ -78,6 +83,438 @@ bool MinecraftServer::s_slowQueuePacketSent = false;
unordered_map<wstring, int> MinecraftServer::ironTimers;
static void PrintConsoleLine(const wchar_t *prefix, const wstring &message)
{
wprintf(L"%ls%ls\n", prefix, message.c_str());
fflush(stdout);
}
static bool TryParseIntValue(const wstring &text, int &value)
{
std::wistringstream stream(text);
stream >> value;
return !stream.fail() && stream.eof();
}
static vector<wstring> SplitConsoleCommand(const wstring &command)
{
vector<wstring> tokens;
std::wistringstream stream(command);
wstring token;
while (stream >> token)
{
tokens.push_back(token);
}
return tokens;
}
static wstring JoinConsoleCommandTokens(const vector<wstring> &tokens, size_t startIndex)
{
wstring joined;
for (size_t i = startIndex; i < tokens.size(); ++i)
{
if (!joined.empty()) joined += L" ";
joined += tokens[i];
}
return joined;
}
static shared_ptr<ServerPlayer> FindPlayerByName(PlayerList *playerList, const wstring &name)
{
if (playerList == NULL) return nullptr;
for (size_t i = 0; i < playerList->players.size(); ++i)
{
shared_ptr<ServerPlayer> player = playerList->players[i];
if (player != NULL && equalsIgnoreCase(player->getName(), name))
{
return player;
}
}
return nullptr;
}
static void SetAllLevelTimes(MinecraftServer *server, int value)
{
for (unsigned int i = 0; i < server->levels.length; ++i)
{
if (server->levels[i] != NULL)
{
server->levels[i]->setDayTime(value);
}
}
}
static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand)
{
if (server == NULL)
return false;
wstring command = trimString(rawCommand);
if (command.empty())
return true;
if (command[0] == L'/')
{
command = trimString(command.substr(1));
}
vector<wstring> tokens = SplitConsoleCommand(command);
if (tokens.empty())
return true;
const wstring action = toLower(tokens[0]);
PlayerList *playerList = server->getPlayers();
if (action == L"help" || action == L"?")
{
server->info(L"Commands: help, stop, list, say <message>, save-all, time <set day|night|ticks|add ticks>, weather <clear|rain|thunder> [seconds], tp <player> <target>, give <player> <itemId> [amount] [aux], enchant <player> <enchantId> [level], kill <player>");
return true;
}
if (action == L"stop")
{
server->info(L"Stopping server...");
MinecraftServer::HaltServer();
return true;
}
if (action == L"list")
{
wstring playerNames = (playerList != NULL) ? playerList->getPlayerNames() : L"";
if (playerNames.empty()) playerNames = L"(none)";
server->info(L"Players (" + _toString((playerList != NULL) ? playerList->getPlayerCount() : 0) + L"): " + playerNames);
return true;
}
if (action == L"say")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: say <message>");
return false;
}
wstring message = L"[Server] " + JoinConsoleCommandTokens(tokens, 1);
if (playerList != NULL)
{
playerList->broadcastAll(shared_ptr<ChatPacket>(new ChatPacket(message)));
}
server->info(message);
return true;
}
if (action == L"save-all")
{
if (playerList != NULL)
{
playerList->saveAll(NULL, false);
}
server->info(L"World saved.");
return true;
}
if (action == L"time")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: time set <day|night|ticks> | time add <ticks>");
return false;
}
if (toLower(tokens[1]) == L"add")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: time add <ticks>");
return false;
}
int delta = 0;
if (!TryParseIntValue(tokens[2], delta))
{
server->warn(L"Invalid tick value: " + tokens[2]);
return false;
}
for (unsigned int i = 0; i < server->levels.length; ++i)
{
if (server->levels[i] != NULL)
{
server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta);
}
}
server->info(L"Added " + _toString(delta) + L" ticks.");
return true;
}
wstring timeValue = toLower(tokens[1]);
if (timeValue == L"set")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: time set <day|night|ticks>");
return false;
}
timeValue = toLower(tokens[2]);
}
int targetTime = 0;
if (timeValue == L"day")
{
targetTime = 0;
}
else if (timeValue == L"night")
{
targetTime = 12500;
}
else if (!TryParseIntValue(timeValue, targetTime))
{
server->warn(L"Invalid time value: " + timeValue);
return false;
}
SetAllLevelTimes(server, targetTime);
server->info(L"Time set to " + _toString(targetTime) + L".");
return true;
}
if (action == L"weather")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
return false;
}
int durationSeconds = 600;
if (tokens.size() >= 3 && !TryParseIntValue(tokens[2], durationSeconds))
{
server->warn(L"Invalid duration: " + tokens[2]);
return false;
}
if (server->levels[0] == NULL)
{
server->warn(L"The overworld is not loaded.");
return false;
}
LevelData *levelData = server->levels[0]->getLevelData();
int duration = durationSeconds * SharedConstants::TICKS_PER_SECOND;
levelData->setRainTime(duration);
levelData->setThunderTime(duration);
wstring weather = toLower(tokens[1]);
if (weather == L"clear")
{
levelData->setRaining(false);
levelData->setThundering(false);
}
else if (weather == L"rain")
{
levelData->setRaining(true);
levelData->setThundering(false);
}
else if (weather == L"thunder")
{
levelData->setRaining(true);
levelData->setThundering(true);
}
else
{
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
return false;
}
server->info(L"Weather set to " + weather + L".");
return true;
}
if (action == L"tp" || action == L"teleport")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: tp <player> <target>");
return false;
}
shared_ptr<ServerPlayer> subject = FindPlayerByName(playerList, tokens[1]);
shared_ptr<ServerPlayer> destination = FindPlayerByName(playerList, tokens[2]);
if (subject == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
if (destination == NULL)
{
server->warn(L"Unknown player: " + tokens[2]);
return false;
}
if (subject->level->dimension->id != destination->level->dimension->id || !subject->isAlive())
{
server->warn(L"Teleport failed because the players are not in the same dimension or the source player is dead.");
return false;
}
subject->ride(nullptr);
subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot);
server->info(L"Teleported " + subject->getName() + L" to " + destination->getName() + L".");
return true;
}
if (action == L"give")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: give <player> <itemId> [amount] [aux]");
return false;
}
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
if (player == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
int itemId = 0;
int amount = 1;
int aux = 0;
if (!TryParseIntValue(tokens[2], itemId))
{
server->warn(L"Invalid item id: " + tokens[2]);
return false;
}
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], amount))
{
server->warn(L"Invalid amount: " + tokens[3]);
return false;
}
if (tokens.size() >= 5 && !TryParseIntValue(tokens[4], aux))
{
server->warn(L"Invalid aux value: " + tokens[4]);
return false;
}
if (itemId <= 0 || Item::items[itemId] == NULL)
{
server->warn(L"Unknown item id: " + _toString(itemId));
return false;
}
if (amount <= 0)
{
server->warn(L"Amount must be positive.");
return false;
}
shared_ptr<ItemInstance> itemInstance(new ItemInstance(itemId, amount, aux));
shared_ptr<ItemEntity> drop = player->drop(itemInstance);
if (drop != NULL)
{
drop->throwTime = 0;
}
server->info(L"Gave item " + _toString(itemId) + L" x" + _toString(amount) + L" to " + player->getName() + L".");
return true;
}
if (action == L"enchant")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: enchant <player> <enchantId> [level]");
return false;
}
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
if (player == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
int enchantmentId = 0;
int enchantmentLevel = 1;
if (!TryParseIntValue(tokens[2], enchantmentId))
{
server->warn(L"Invalid enchantment id: " + tokens[2]);
return false;
}
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], enchantmentLevel))
{
server->warn(L"Invalid enchantment level: " + tokens[3]);
return false;
}
shared_ptr<ItemInstance> selectedItem = player->getSelectedItem();
if (selectedItem == NULL)
{
server->warn(L"The player is not holding an item.");
return false;
}
Enchantment *enchantment = Enchantment::enchantments[enchantmentId];
if (enchantment == NULL)
{
server->warn(L"Unknown enchantment id: " + _toString(enchantmentId));
return false;
}
if (!enchantment->canEnchant(selectedItem))
{
server->warn(L"That enchantment cannot be applied to the selected item.");
return false;
}
if (enchantmentLevel < enchantment->getMinLevel()) enchantmentLevel = enchantment->getMinLevel();
if (enchantmentLevel > enchantment->getMaxLevel()) enchantmentLevel = enchantment->getMaxLevel();
if (selectedItem->hasTag())
{
ListTag<CompoundTag> *enchantmentTags = selectedItem->getEnchantmentTags();
if (enchantmentTags != NULL)
{
for (int i = 0; i < enchantmentTags->size(); i++)
{
int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID);
if (Enchantment::enchantments[type] != NULL && !Enchantment::enchantments[type]->isCompatibleWith(enchantment))
{
server->warn(L"That enchantment conflicts with an existing enchantment on the selected item.");
return false;
}
}
}
}
selectedItem->enchant(enchantment, enchantmentLevel);
server->info(L"Enchanted " + player->getName() + L"'s held item with " + _toString(enchantmentId) + L" " + _toString(enchantmentLevel) + L".");
return true;
}
if (action == L"kill")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: kill <player>");
return false;
}
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
if (player == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
player->hurt(DamageSource::outOfWorld, 3.4e38f);
server->info(L"Killed " + player->getName() + L".");
return true;
}
server->warn(L"Unknown command: " + command);
return false;
}
MinecraftServer::MinecraftServer()
{
// 4J - added initialisers
@@ -107,12 +544,14 @@ MinecraftServer::MinecraftServer()
forceGameType = false;
commandDispatcher = new ServerCommandDispatcher();
InitializeCriticalSection(&m_consoleInputCS);
DispenserBootstrap::bootStrap();
}
MinecraftServer::~MinecraftServer()
{
DeleteCriticalSection(&m_consoleInputCS);
}
bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DWORD initSettings, bool findSeed)
@@ -150,6 +589,15 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
#endif
settings = new Settings(new File(L"server.properties"));
app.SetGameHostOption(eGameHostOption_Difficulty, settings->getInt(L"difficulty", app.GetGameHostOption(eGameHostOption_Difficulty)));
app.SetGameHostOption(eGameHostOption_GameType, settings->getInt(L"gamemode", app.GetGameHostOption(eGameHostOption_GameType)));
app.SetGameHostOption(eGameHostOption_Structures, settings->getBoolean(L"generate-structures", app.GetGameHostOption(eGameHostOption_Structures) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_BonusChest, settings->getBoolean(L"bonus-chest", app.GetGameHostOption(eGameHostOption_BonusChest) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_PvP, settings->getBoolean(L"pvp", app.GetGameHostOption(eGameHostOption_PvP) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TrustPlayers, settings->getBoolean(L"trust-players", app.GetGameHostOption(eGameHostOption_TrustPlayers) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_FireSpreads, settings->getBoolean(L"fire-spreads", app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TNT, settings->getBoolean(L"tnt", app.GetGameHostOption(eGameHostOption_TNT) > 0) ? 1 : 0);
app.DebugPrintf("\n*** SERVER SETTINGS ***\n");
app.DebugPrintf("ServerSettings: host-friends-only is %s\n",(app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0)?"on":"off");
app.DebugPrintf("ServerSettings: game-type is %s\n",(app.GetGameHostOption(eGameHostOption_GameType)==0)?"Survival Mode":"Creative Mode");
@@ -169,11 +617,11 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
setAnimals(settings->getBoolean(L"spawn-animals", true));
setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true));
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false); // settings->getBoolean(L"pvp", true);
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
// 4J Stu - We should never have hacked clients flying when they shouldn't be like the PC version, so enable flying always
// Fix for #46612 - TU5: Code: Multiplayer: A client can be banned for flying when accidentaly being blown by dynamite
setFlightAllowed(true); //settings->getBoolean(L"allow-flight", false);
setFlightAllowed(settings->getBoolean(L"allow-flight", true));
// 4J Stu - Enabling flight to stop it kicking us when we use it
#ifdef _DEBUG_MENUS_ENABLED
@@ -1707,17 +2155,23 @@ void MinecraftServer::tick()
void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source)
{
EnterCriticalSection(&m_consoleInputCS);
consoleInput.push_back(new ConsoleInput(msg, source));
LeaveCriticalSection(&m_consoleInputCS);
}
void MinecraftServer::handleConsoleInputs()
{
while (consoleInput.size() > 0)
vector<ConsoleInput *> pendingInputs;
EnterCriticalSection(&m_consoleInputCS);
pendingInputs.swap(consoleInput);
LeaveCriticalSection(&m_consoleInputCS);
for (size_t i = 0; i < pendingInputs.size(); ++i)
{
AUTO_VAR(it, consoleInput.begin());
ConsoleInput *input = *it;
consoleInput.erase(it);
// commands->handleCommand(input); // 4J - removed - TODO - do we want equivalent of console commands?
ConsoleInput *input = pendingInputs[i];
ExecuteConsoleCommand(this, input->msg);
delete input;
}
}
@@ -1750,10 +2204,12 @@ File *MinecraftServer::getFile(const wstring& name)
void MinecraftServer::info(const wstring& string)
{
PrintConsoleLine(L"[INFO] ", string);
}
void MinecraftServer::warn(const wstring& string)
{
PrintConsoleLine(L"[WARN] ", string);
}
wstring MinecraftServer::getConsoleName()

View File

@@ -103,6 +103,7 @@ private:
// vector<Tickable *> tickables = new ArrayList<Tickable>(); // 4J - removed
CommandDispatcher *commandDispatcher;
vector<ConsoleInput *> consoleInput; // 4J - was synchronizedList - TODO - investigate
CRITICAL_SECTION m_consoleInputCS;
public:
bool onlineMode;
bool animals;

View File

@@ -1,18 +1,90 @@
#include "stdafx.h"
#include "Settings.h"
#include "..\Minecraft.World\File.h"
#include "..\Minecraft.World\StringHelpers.h"
#include <fstream>
static wstring ParsePropertyText(const string &text)
{
return trimString(convStringToWstring(text));
}
static bool TryParseBoolean(const wstring &text, bool defaultValue)
{
wstring lowered = toLower(trimString(text));
if (lowered == L"true" || lowered == L"1" || lowered == L"yes" || lowered == L"on")
return true;
if (lowered == L"false" || lowered == L"0" || lowered == L"no" || lowered == L"off")
return false;
return defaultValue;
}
// 4J - TODO - serialise/deserialise from file
Settings::Settings(File *file)
{
if (file != NULL)
{
filePath = file->getPath();
}
if (filePath.empty())
return;
std::ifstream stream(wstringtofilename(filePath), std::ios::in | std::ios::binary);
if (!stream.is_open())
return;
string line;
while (std::getline(stream, line))
{
if (!line.empty() && line[line.size() - 1] == '\r')
line.erase(line.size() - 1);
if (line.size() >= 3 &&
(unsigned char)line[0] == 0xEF &&
(unsigned char)line[1] == 0xBB &&
(unsigned char)line[2] == 0xBF)
{
line.erase(0, 3);
}
size_t commentPos = line.find_first_of("#;");
if (commentPos != string::npos && line.find_first_not_of(" \t") == commentPos)
continue;
size_t separatorPos = line.find('=');
if (separatorPos == string::npos)
continue;
wstring key = ParsePropertyText(line.substr(0, separatorPos));
if (key.empty())
continue;
wstring value = ParsePropertyText(line.substr(separatorPos + 1));
properties[key] = value;
}
}
void Settings::generateNewProperties()
{
saveProperties();
}
void Settings::saveProperties()
{
if (filePath.empty())
return;
std::ofstream stream(wstringtofilename(filePath), std::ios::out | std::ios::binary | std::ios::trunc);
if (!stream.is_open())
return;
stream << "# MinecraftConsoles dedicated server properties\r\n";
for (unordered_map<wstring, wstring>::const_iterator it = properties.begin(); it != properties.end(); ++it)
{
string key = string(wstringtochararray(it->first));
string value = string(wstringtochararray(it->second));
stream << key << "=" << value << "\r\n";
}
}
wstring Settings::getString(const wstring& key, const wstring& defaultValue)
@@ -39,17 +111,17 @@ bool Settings::getBoolean(const wstring& key, bool defaultValue)
{
if(properties.find(key) == properties.end())
{
properties[key] = _toString<bool>(defaultValue);
properties[key] = defaultValue ? L"true" : L"false";
saveProperties();
}
MemSect(35);
bool retval = _fromString<bool>(properties[key]);
bool retval = TryParseBoolean(properties[key], defaultValue);
MemSect(0);
return retval;
}
void Settings::setBooleanAndSave(const wstring& key, bool value)
{
properties[key] = _toString<bool>(value);
properties[key] = value ? L"true" : L"false";
saveProperties();
}

View File

@@ -7,8 +7,8 @@ class Settings
// public static Logger logger = Logger.getLogger("Minecraft");
// private Properties properties = new Properties();
private:
unordered_map<wstring,wstring> properties; // 4J - TODO was Properties type, will need to implement something we can serialise/deserialise too
//File *file;
unordered_map<wstring,wstring> properties;
wstring filePath;
public:
Settings(File *file);
@@ -18,4 +18,4 @@ public:
int getInt(const wstring& key, int defaultValue);
bool getBoolean(const wstring& key, bool defaultValue);
void setBooleanAndSave(const wstring& key, bool value);
};
};

View File

@@ -51,6 +51,9 @@ bool g_Win64MultiplayerHost = false;
bool g_Win64MultiplayerJoin = false;
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
char g_Win64MultiplayerIP[256] = "127.0.0.1";
bool g_Win64DedicatedServer = false;
int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
char g_Win64DedicatedServerBindIP[256] = "";
bool WinsockNetLayer::Initialize()
{
@@ -139,7 +142,7 @@ void WinsockNetLayer::Shutdown()
}
}
bool WinsockNetLayer::HostGame(int port)
bool WinsockNetLayer::HostGame(int port, const char* bindIp)
{
if (!s_initialized && !Initialize()) return false;
@@ -159,15 +162,19 @@ bool WinsockNetLayer::HostGame(int port)
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
hints.ai_flags = (bindIp == NULL || bindIp[0] == 0) ? AI_PASSIVE : 0;
char portStr[16];
sprintf_s(portStr, "%d", port);
int iResult = getaddrinfo(NULL, portStr, &hints, &result);
const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL;
int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result);
if (iResult != 0)
{
app.DebugPrintf("getaddrinfo failed: %d\n", iResult);
app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n",
resolvedBindIp != NULL ? resolvedBindIp : "*",
port,
iResult);
return false;
}
@@ -206,7 +213,9 @@ bool WinsockNetLayer::HostGame(int port)
s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL);
app.DebugPrintf("Win64 LAN: Hosting on port %d\n", port);
app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n",
resolvedBindIp != NULL ? resolvedBindIp : "*",
port);
return true;
}
@@ -908,4 +917,4 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
return 0;
}
#endif
#endif

View File

@@ -65,7 +65,7 @@ public:
static bool Initialize();
static void Shutdown();
static bool HostGame(int port);
static bool HostGame(int port, const char* bindIp = NULL);
static bool JoinGame(const char* ip, int port);
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
@@ -147,5 +147,8 @@ extern bool g_Win64MultiplayerHost;
extern bool g_Win64MultiplayerJoin;
extern int g_Win64MultiplayerPort;
extern char g_Win64MultiplayerIP[256];
extern bool g_Win64DedicatedServer;
extern int g_Win64DedicatedServerPort;
extern char g_Win64DedicatedServerBindIP[256];
#endif

View File

@@ -4,7 +4,9 @@
#include "stdafx.h"
#include <assert.h>
#include <iostream>
#include <ShellScalingApi.h>
#include <shellapi.h>
#include "GameConfig\Minecraft.spa.h"
#include "..\MinecraftServer.h"
#include "..\LocalPlayer.h"
@@ -34,6 +36,7 @@
#include "Sentient\SentientManager.h"
#include "..\..\Minecraft.World\IntCache.h"
#include "..\Textures.h"
#include "..\Settings.h"
#include "Resource.h"
#include "..\..\Minecraft.World\compression.h"
#include "..\..\Minecraft.World\OldChunkStorage.h"
@@ -95,6 +98,148 @@ wchar_t g_Win64UsernameW[17] = { 0 };
static bool g_isFullscreen = false;
static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) };
struct Win64LaunchOptions
{
int screenMode;
bool serverMode;
};
static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize)
{
if (destSize == 0)
return;
dest[0] = 0;
if (source == NULL)
return;
WideCharToMultiByte(CP_ACP, 0, source, -1, dest, (int)destSize, NULL, NULL);
dest[destSize - 1] = 0;
}
static void ApplyScreenMode(int screenMode)
{
switch (screenMode)
{
case 1:
g_iScreenWidth = 1280;
g_iScreenHeight = 720;
break;
case 2:
g_iScreenWidth = 640;
g_iScreenHeight = 480;
break;
case 3:
g_iScreenWidth = 720;
g_iScreenHeight = 408;
break;
default:
break;
}
}
static Win64LaunchOptions ParseLaunchOptions()
{
Win64LaunchOptions options = {};
options.screenMode = 0;
options.serverMode = false;
g_Win64MultiplayerJoin = false;
g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
g_Win64DedicatedServer = false;
g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
g_Win64DedicatedServerBindIP[0] = 0;
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argv == NULL)
return options;
if (argc > 1 && lstrlenW(argv[1]) == 1)
{
if (argv[1][0] >= L'1' && argv[1][0] <= L'3')
options.screenMode = argv[1][0] - L'0';
}
for (int i = 1; i < argc; ++i)
{
if (_wcsicmp(argv[i], L"-server") == 0)
{
options.serverMode = true;
break;
}
}
g_Win64DedicatedServer = options.serverMode;
for (int i = 1; i < argc; ++i)
{
if (_wcsicmp(argv[i], L"-name") == 0 && (i + 1) < argc)
{
CopyWideArgToAnsi(argv[++i], g_Win64Username, sizeof(g_Win64Username));
}
else if (_wcsicmp(argv[i], L"-ip") == 0 && (i + 1) < argc)
{
char ipBuf[256];
CopyWideArgToAnsi(argv[++i], ipBuf, sizeof(ipBuf));
if (options.serverMode)
{
strncpy_s(g_Win64DedicatedServerBindIP, sizeof(g_Win64DedicatedServerBindIP), ipBuf, _TRUNCATE);
}
else
{
strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), ipBuf, _TRUNCATE);
g_Win64MultiplayerJoin = true;
}
}
else if (_wcsicmp(argv[i], L"-port") == 0 && (i + 1) < argc)
{
wchar_t* endPtr = NULL;
long port = wcstol(argv[++i], &endPtr, 10);
if (endPtr != argv[i] && *endPtr == 0 && port > 0 && port <= 65535)
{
if (options.serverMode)
g_Win64DedicatedServerPort = (int)port;
else
g_Win64MultiplayerPort = (int)port;
}
}
}
LocalFree(argv);
return options;
}
static BOOL WINAPI HeadlessServerCtrlHandler(DWORD ctrlType)
{
switch (ctrlType)
{
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_SHUTDOWN_EVENT:
app.m_bShutdown = true;
MinecraftServer::HaltServer();
return TRUE;
default:
return FALSE;
}
}
static void SetupHeadlessServerConsole()
{
if (AllocConsole())
{
FILE* stream = NULL;
freopen_s(&stream, "CONIN$", "r", stdin);
freopen_s(&stream, "CONOUT$", "w", stdout);
freopen_s(&stream, "CONOUT$", "w", stderr);
SetConsoleTitleA("Minecraft Server");
}
SetConsoleCtrlHandler(HeadlessServerCtrlHandler, TRUE);
}
void DefineActions(void)
{
// The app needs to define the actions required, and the possible mappings for these
@@ -722,6 +867,225 @@ void CleanupDevice()
if( g_pd3dDevice ) g_pd3dDevice->Release();
}
static Minecraft* InitialiseMinecraftRuntime()
{
app.loadMediaArchive();
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
app.loadStringTable();
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
KMInput.Init(g_hWnd);
DefineActions();
InputManager.SetJoypadMapVal(0, 0);
InputManager.SetKeyRepeatRate(0.3f, 0.2f);
ProfileManager.Initialise(TITLEID_MINECRAFT,
app.m_dwOfferID,
PROFILE_VERSION_10,
NUM_PROFILE_VALUES,
NUM_PROFILE_SETTINGS,
dwProfileSettingsA,
app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT,
&app.uiGameDefinedDataChangedBitmask
);
ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app);
g_NetworkManager.Initialise();
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
{
IQNet::m_player[i].m_smallId = (BYTE)i;
IQNet::m_player[i].m_isRemote = false;
IQNet::m_player[i].m_isHostPlayer = (i == 0);
swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
}
wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
WinsockNetLayer::Initialize();
ProfileManager.SetDebugFullOverride(true);
Tesselator::CreateNewThreadStorage(1024 * 1024);
AABB::CreateNewThreadStorage();
Vec3::CreateNewThreadStorage();
IntCache::CreateNewThreadStorage();
Compression::CreateNewThreadStorage();
OldChunkStorage::CreateNewThreadStorage();
Level::enableLightingCache();
Tile::CreateNewThreadStorage();
Minecraft::main();
Minecraft* pMinecraft = Minecraft::GetInstance();
if (pMinecraft == NULL)
return NULL;
app.InitGameSettings();
app.InitialiseTips();
pMinecraft->options->set(Options::Option::MUSIC, 1.0f);
pMinecraft->options->set(Options::Option::SOUND, 1.0f);
return pMinecraft;
}
static int HeadlessServerConsoleThreadProc(void* lpParameter)
{
UNREFERENCED_PARAMETER(lpParameter);
std::string line;
while (!app.m_bShutdown)
{
if (!std::getline(std::cin, line))
{
if (std::cin.eof())
{
break;
}
std::cin.clear();
Sleep(50);
continue;
}
wstring command = trimString(convStringToWstring(line));
if (command.empty())
continue;
MinecraftServer* server = MinecraftServer::getInstance();
if (server != NULL)
{
server->handleConsoleInput(command, server);
}
}
return 0;
}
static int RunHeadlessServer()
{
SetupHeadlessServerConsole();
Settings serverSettings(new File(L"server.properties"));
wstring configuredBindIp = serverSettings.getString(L"server-ip", L"");
const char* bindIp = "*";
if (g_Win64DedicatedServerBindIP[0] != 0)
{
bindIp = g_Win64DedicatedServerBindIP;
}
else if (!configuredBindIp.empty())
{
bindIp = wstringtochararray(configuredBindIp);
}
const int port = g_Win64DedicatedServerPort > 0 ? g_Win64DedicatedServerPort : serverSettings.getInt(L"server-port", WIN64_NET_DEFAULT_PORT);
printf("Starting headless server on %s:%d\n", bindIp, port);
fflush(stdout);
Minecraft* pMinecraft = InitialiseMinecraftRuntime();
if (pMinecraft == NULL)
{
fprintf(stderr, "Failed to initialise the Minecraft runtime.\n");
return 1;
}
app.SetGameHostOption(eGameHostOption_Difficulty, serverSettings.getInt(L"difficulty", 1));
app.SetGameHostOption(eGameHostOption_Gamertags, 1);
app.SetGameHostOption(eGameHostOption_GameType, serverSettings.getInt(L"gamemode", 0));
app.SetGameHostOption(eGameHostOption_LevelType, 0);
app.SetGameHostOption(eGameHostOption_Structures, serverSettings.getBoolean(L"generate-structures", true) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_BonusChest, serverSettings.getBoolean(L"bonus-chest", false) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_PvP, serverSettings.getBoolean(L"pvp", true) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TrustPlayers, serverSettings.getBoolean(L"trust-players", true) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_FireSpreads, serverSettings.getBoolean(L"fire-spreads", true) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TNT, serverSettings.getBoolean(L"tnt", true) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_HostCanFly, 1);
app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, 1);
app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 1);
app.SetGameHostOption(eGameHostOption_MobGriefing, 1);
app.SetGameHostOption(eGameHostOption_KeepInventory, 0);
app.SetGameHostOption(eGameHostOption_DoMobSpawning, 1);
app.SetGameHostOption(eGameHostOption_DoMobLoot, 1);
app.SetGameHostOption(eGameHostOption_DoTileDrops, 1);
app.SetGameHostOption(eGameHostOption_NaturalRegeneration, 1);
app.SetGameHostOption(eGameHostOption_DoDaylightCycle, 1);
MinecraftServer::resetFlags();
g_NetworkManager.HostGame(0, false, true, MINECRAFT_NET_MAX_PLAYERS, 0);
if (!WinsockNetLayer::IsActive())
{
fprintf(stderr, "Failed to bind the server socket on %s:%d.\n", bindIp, port);
return 1;
}
g_NetworkManager.FakeLocalPlayerJoined();
NetworkGameInitData* param = new NetworkGameInitData();
param->seed = 0;
param->settings = app.GetGameHostOption(eGameHostOption_All);
g_NetworkManager.ServerStoppedCreate(true);
g_NetworkManager.ServerReadyCreate(true);
C4JThread* thread = new C4JThread(&CGameNetworkManager::ServerThreadProc, param, "Server", 256 * 1024);
thread->SetProcessor(CPU_CORE_SERVER);
thread->Run();
g_NetworkManager.ServerReadyWait();
g_NetworkManager.ServerReadyDestroy();
if (MinecraftServer::serverHalted())
{
fprintf(stderr, "The server halted during startup.\n");
g_NetworkManager.LeaveGame(false);
return 1;
}
app.SetGameStarted(true);
g_NetworkManager.DoWork();
printf("Server ready on %s:%d\n", bindIp, port);
printf("Type 'help' for server commands.\n");
fflush(stdout);
C4JThread* consoleThread = new C4JThread(&HeadlessServerConsoleThreadProc, NULL, "Server console", 128 * 1024);
consoleThread->Run();
MSG msg = { 0 };
while (WM_QUIT != msg.message && !app.m_bShutdown && !MinecraftServer::serverHalted())
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
app.UpdateTime();
ProfileManager.Tick();
StorageManager.Tick();
RenderManager.Tick();
ui.tick();
g_NetworkManager.DoWork();
app.HandleXuiActions();
Sleep(10);
}
printf("Stopping server...\n");
fflush(stdout);
app.m_bShutdown = true;
MinecraftServer::HaltServer();
g_NetworkManager.LeaveGame(false);
return 0;
}
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
@@ -742,70 +1106,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
SetProcessDPIAware();
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
if(lpCmdLine)
{
if(lpCmdLine[0] == '1')
{
g_iScreenWidth = 1280;
g_iScreenHeight = 720;
}
else if(lpCmdLine[0] == '2')
{
g_iScreenWidth = 640;
g_iScreenHeight = 480;
}
else if(lpCmdLine[0] == '3')
{
// Vita
g_iScreenWidth = 720;
g_iScreenHeight = 408;
// Vita native
//g_iScreenWidth = 960;
//g_iScreenHeight = 544;
}
char cmdLineA[1024];
strncpy_s(cmdLineA, sizeof(cmdLineA), lpCmdLine, _TRUNCATE);
char *nameArg = strstr(cmdLineA, "-name ");
if (nameArg)
{
nameArg += 6;
while (*nameArg == ' ') nameArg++;
char nameBuf[17];
int n = 0;
while (nameArg[n] && nameArg[n] != ' ' && n < 16) { nameBuf[n] = nameArg[n]; n++; }
nameBuf[n] = 0;
strncpy_s(g_Win64Username, 17, nameBuf, _TRUNCATE);
}
char *ipArg = strstr(cmdLineA, "-ip ");
if (ipArg)
{
ipArg += 4;
while (*ipArg == ' ') ipArg++;
char ipBuf[256];
int n = 0;
while (ipArg[n] && ipArg[n] != ' ' && n < 255) { ipBuf[n] = ipArg[n]; n++; }
ipBuf[n] = 0;
strncpy_s(g_Win64MultiplayerIP, 256, ipBuf, _TRUNCATE);
g_Win64MultiplayerJoin = true;
}
char *portArg = strstr(cmdLineA, "-port ");
if (portArg)
{
portArg += 6;
while (*portArg == ' ') portArg++;
char portBuf[16];
int n = 0;
while (portArg[n] && portArg[n] != ' ' && n < 15) { portBuf[n] = portArg[n]; n++; }
portBuf[n] = 0;
g_Win64MultiplayerPort = atoi(portBuf);
}
}
Win64LaunchOptions launchOptions = ParseLaunchOptions();
ApplyScreenMode(launchOptions.screenMode);
if (g_Win64Username[0] == 0)
{
@@ -821,7 +1123,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
if (!InitInstance (hInstance, launchOptions.serverMode ? SW_HIDE : nCmdShow))
{
return FALSE;
}
@@ -834,6 +1136,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
return 0;
}
if (launchOptions.serverMode)
{
int serverResult = RunHeadlessServer();
CleanupDevice();
return serverResult;
}
#if 0
// Main message loop
MSG msg = {0};
@@ -886,202 +1195,12 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
}
#endif
app.loadMediaArchive();
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
app.loadStringTable();
ui.init(g_pd3dDevice,g_pImmediateContext,g_pRenderTargetView,g_pDepthStencilView,g_iScreenWidth,g_iScreenHeight);
////////////////
// Initialise //
////////////////
// Set the number of possible joypad layouts that the user can switch between, and the number of actions
InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
// Initialize keyboard/mouse input
KMInput.Init(g_hWnd);
// Set the default joypad action mappings for Minecraft
DefineActions();
InputManager.SetJoypadMapVal(0,0);
InputManager.SetKeyRepeatRate(0.3f,0.2f);
// Initialise the profile manager with the game Title ID, Offer ID, a profile version number, and the number of profile values and settings
ProfileManager.Initialise(TITLEID_MINECRAFT,
app.m_dwOfferID,
PROFILE_VERSION_10,
NUM_PROFILE_VALUES,
NUM_PROFILE_SETTINGS,
dwProfileSettingsA,
app.GAME_DEFINED_PROFILE_DATA_BYTES*XUSER_MAX_COUNT,
&app.uiGameDefinedDataChangedBitmask
);
#if 0
// register the awards
ProfileManager.RegisterAward(eAward_TakingInventory, ACHIEVEMENT_01, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_GettingWood, ACHIEVEMENT_02, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_Benchmarking, ACHIEVEMENT_03, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_TimeToMine, ACHIEVEMENT_04, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_HotTopic, ACHIEVEMENT_05, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_AquireHardware, ACHIEVEMENT_06, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_TimeToFarm, ACHIEVEMENT_07, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_BakeBread, ACHIEVEMENT_08, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_TheLie, ACHIEVEMENT_09, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_GettingAnUpgrade, ACHIEVEMENT_10, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_DeliciousFish, ACHIEVEMENT_11, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_OnARail, ACHIEVEMENT_12, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_TimeToStrike, ACHIEVEMENT_13, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_MonsterHunter, ACHIEVEMENT_14, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_CowTipper, ACHIEVEMENT_15, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_WhenPigsFly, ACHIEVEMENT_16, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_LeaderOfThePack, ACHIEVEMENT_17, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_MOARTools, ACHIEVEMENT_18, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_DispenseWithThis, ACHIEVEMENT_19, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_InToTheNether, ACHIEVEMENT_20, eAwardType_Achievement);
ProfileManager.RegisterAward(eAward_mine100Blocks, GAMER_PICTURE_GAMERPIC1, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC1,IDS_CONFIRM_OK);
ProfileManager.RegisterAward(eAward_kill10Creepers, GAMER_PICTURE_GAMERPIC2, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC2,IDS_CONFIRM_OK);
ProfileManager.RegisterAward(eAward_eatPorkChop, AVATARASSETAWARD_PORKCHOP_TSHIRT, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR1,IDS_CONFIRM_OK);
ProfileManager.RegisterAward(eAward_play100Days, AVATARASSETAWARD_WATCH, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR2,IDS_CONFIRM_OK);
ProfileManager.RegisterAward(eAward_arrowKillCreeper, AVATARASSETAWARD_CAP, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR3,IDS_CONFIRM_OK);
ProfileManager.RegisterAward(eAward_socialPost, 0, eAwardType_Theme,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_THEME,IDS_CONFIRM_OK,THEME_NAME,THEME_FILESIZE);
// Rich Presence init - number of presences, number of contexts
ProfileManager.RichPresenceInit(4,1);
ProfileManager.RegisterRichPresenceContext(CONTEXT_GAME_STATE);
// initialise the storage manager with a default save display name, a Minimum save size, and a callback for displaying the saving message
StorageManager.Init(app.GetString(IDS_DEFAULT_SAVENAME),"savegame.dat",FIFTY_ONE_MB,&CConsoleMinecraftApp::DisplaySavingMessage,(LPVOID)&app);
// Set up the global title storage path
StorageManager.StoreTMSPathName();
// set a function to be called when there's a sign in change, so we can exit a level if the primary player signs out
ProfileManager.SetSignInChangeCallback(&CConsoleMinecraftApp::SignInChangeCallback,(LPVOID)&app);
// set a function to be called when the ethernet is disconnected, so we can back out if required
ProfileManager.SetNotificationsCallback(&CConsoleMinecraftApp::NotificationsCallback,(LPVOID)&app);
#endif
// Set a callback for the default player options to be set - when there is no profile data for the player
ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback,(LPVOID)&app);
#if 0
// Set a callback to deal with old profile versions needing updated to new versions
ProfileManager.SetOldProfileVersionCallback(&CConsoleMinecraftApp::OldProfileVersionCallback,(LPVOID)&app);
// Set a callback for when there is a read error on profile data
ProfileManager.SetProfileReadErrorCallback(&CConsoleMinecraftApp::ProfileReadErrorCallback,(LPVOID)&app);
#endif
// QNet needs to be setup after profile manager, as we do not want its Notify listener to handle
// XN_SYS_SIGNINCHANGED notifications. This does mean that we need to have a callback in the
// ProfileManager for XN_LIVE_INVITE_ACCEPTED for QNet.
g_NetworkManager.Initialise();
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
Minecraft *pMinecraft = InitialiseMinecraftRuntime();
if (pMinecraft == NULL)
{
IQNet::m_player[i].m_smallId = (BYTE)i;
IQNet::m_player[i].m_isRemote = false;
IQNet::m_player[i].m_isHostPlayer = (i == 0);
swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
CleanupDevice();
return 1;
}
extern wchar_t g_Win64UsernameW[17];
wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
WinsockNetLayer::Initialize();
// 4J-PB moved further down
//app.InitGameSettings();
// debug switch to trial version
ProfileManager.SetDebugFullOverride(true);
#if 0
//ProfileManager.AddDLC(2);
StorageManager.SetDLCPackageRoot("DLCDrive");
StorageManager.RegisterMarketplaceCountsCallback(&CConsoleMinecraftApp::MarketplaceCountsCallback,(LPVOID)&app);
// Kinect !
if(XNuiGetHardwareStatus()!=0)
{
// If the Kinect Sensor is not physically connected, this function returns 0.
NuiInitialize(NUI_INITIALIZE_FLAG_USES_HIGH_QUALITY_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH |
NUI_INITIALIZE_FLAG_EXTRAPOLATE_FLOOR_PLANE | NUI_INITIALIZE_FLAG_USES_FITNESS | NUI_INITIALIZE_FLAG_NUI_GUIDE_DISABLED | NUI_INITIALIZE_FLAG_SUPPRESS_AUTOMATIC_UI,NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD );
}
// Sentient !
hr = TelemetryManager->Init();
#endif
// Initialise TLS for tesselator, for this main thread
Tesselator::CreateNewThreadStorage(1024*1024);
// Initialise TLS for AABB and Vec3 pools, for this main thread
AABB::CreateNewThreadStorage();
Vec3::CreateNewThreadStorage();
IntCache::CreateNewThreadStorage();
Compression::CreateNewThreadStorage();
OldChunkStorage::CreateNewThreadStorage();
Level::enableLightingCache();
Tile::CreateNewThreadStorage();
Minecraft::main();
Minecraft *pMinecraft=Minecraft::GetInstance();
app.InitGameSettings();
#if 0
//bool bDisplayPauseMenu=false;
// set the default gamma level
float fVal=50.0f*327.68f;
RenderManager.UpdateGamma((unsigned short)fVal);
// load any skins
//app.AddSkinsToMemoryTextureFiles();
// set the achievement text for a trial achievement, now we have the string table loaded
ProfileManager.SetTrialTextStringTable(app.GetStringTable(),IDS_CONFIRM_OK, IDS_CONFIRM_CANCEL);
ProfileManager.SetTrialAwardText(eAwardType_Achievement,IDS_UNLOCK_TITLE,IDS_UNLOCK_ACHIEVEMENT_TEXT);
ProfileManager.SetTrialAwardText(eAwardType_GamerPic,IDS_UNLOCK_TITLE,IDS_UNLOCK_GAMERPIC_TEXT);
ProfileManager.SetTrialAwardText(eAwardType_AvatarItem,IDS_UNLOCK_TITLE,IDS_UNLOCK_AVATAR_TEXT);
ProfileManager.SetTrialAwardText(eAwardType_Theme,IDS_UNLOCK_TITLE,IDS_UNLOCK_THEME_TEXT);
ProfileManager.SetUpsellCallback(&app.UpsellReturnedCallback,&app);
// Set up a debug character press sequence
#ifndef _FINAL_BUILD
app.SetDebugSequence("LRLRYYY");
#endif
// Initialise the social networking manager.
CSocialManager::Instance()->Initialise();
// Update the base scene quick selects now that the minecraft class exists
//CXuiSceneBase::UpdateScreenSettings(0);
#endif
app.InitialiseTips();
#if 0
DWORD initData=0;
#ifndef _FINAL_BUILD
#ifndef _DEBUG
#pragma message(__LOC__"Need to define the _FINAL_BUILD before submission")
#endif
#endif
// Set the default sound levels
pMinecraft->options->set(Options::Option::MUSIC,1.0f);
pMinecraft->options->set(Options::Option::SOUND,1.0f);
app.NavigateToScene(XUSER_INDEX_ANY,eUIScene_Intro,&initData);
#endif
// Set the default sound levels
pMinecraft->options->set(Options::Option::MUSIC,1.0f);
pMinecraft->options->set(Options::Option::SOUND,1.0f);
//app.TemporaryCreateGameStart();
@@ -1637,4 +1756,4 @@ void MemPixStuff()
PIXAddNamedCounter(((float)allSectsTotal)/(4096.0f),"MemSect total pages");
}
#endif
#endif

View File

@@ -1257,6 +1257,7 @@
<UseFullPaths>false</UseFullPaths>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -1355,7 +1356,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
<StringPooling>true</StringPooling>
<AdditionalOptions>/Ob3</AdditionalOptions>
<AdditionalOptions>/FS /Ob3 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -4957,4 +4958,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@@ -41,14 +41,22 @@ This feature is based on [LCEMP](https://github.com/LCEMP/LCEMP/)
| Argument | Description |
|--------------------|----------------------------------------------------------------------------------------------------------------|
| `-name <username>` | Sets your in-game username |
| `-ip <address>` | Manually connect to an IP if LAN advertising does not work or if the server cannot be discovered automatically |
| `-port <port>` | Override the default port if it was changed in the source |
| `-server` | Launches a headless server instead of the client |
| `-ip <address>` | Client mode: manually connect to an IP. Server mode: override the bind IP from `server.properties` |
| `-port <port>` | Client mode: override the join port. Server mode: override the listen port from `server.properties` |
Example:
```
Minecraft.Client.exe -name Steve -ip 192.168.0.25 -port 25565
```
Headless server example:
```
Minecraft.Client.exe -server -ip 0.0.0.0 -port 25565
```
The headless server also reads and writes `server.properties` in the working directory. If `-ip` / `-port` are omitted in `-server` mode, it falls back to `server-ip` / `server-port` from that file. Dedicated-server host options such as `trust-players`, `pvp`, `fire-spreads`, `tnt`, `difficulty`, `gamemode`, `spawn-animals`, and `spawn-npcs` are persisted there as well.
## Controls (Keyboard & Mouse)
- **Movement**: `W` `A` `S` `D`