Basic serverlist.

This commit is contained in:
momo5502 2015-12-28 04:02:30 +01:00
parent 2473ce8119
commit 601d4d063e
16 changed files with 385 additions and 10 deletions

View File

@ -276,8 +276,6 @@ namespace Components
// some thing overwriting feeder 2's data
Utils::Hook::Set<BYTE>(0x4A06A9, 0xEB);
Feeder::Add(2.0f, [] () { return 10; }, [] (int index, int column) { return Utils::VA("%d : %d", index, column); }, [] (int index){});
}
Feeder::~Feeder()

View File

@ -26,6 +26,7 @@ namespace Components
Loader::Register(new Singleton());
Loader::Register(new FileSystem());
Loader::Register(new QuickPatch());
Loader::Register(new ServerList());
Loader::Register(new AssetHandler());
Loader::Register(new Localization());
Loader::Register(new MusicalTalent());

View File

@ -39,6 +39,7 @@ namespace Components
#include "Singleton.hpp"
#include "FileSystem.hpp"
#include "QuickPatch.hpp"
#include "ServerList.hpp"
#include "AssetHandler.hpp"
#include "Localization.hpp"
#include "MusicalTalent.hpp"

View File

@ -21,6 +21,14 @@ namespace Components
{
return this->address.port;
}
void Network::Address::SetIP(DWORD ip)
{
*(DWORD*)this->address.ip = ip;
}
DWORD Network::Address::GetIP()
{
return *(DWORD*)this->address.ip;
}
Game::netadr_t* Network::Address::Get()
{
return &this->address;
@ -40,6 +48,11 @@ namespace Components
Game::OOBPrintT(type, *target.Get(), data.data());
}
void Network::Send(Address target, std::string data)
{
Network::Send(Game::netsrc_t::NS_CLIENT, target, data);
}
int Network::PacketInterceptionHandler(const char* packet)
{
// Check if custom handler exists

View File

@ -16,6 +16,10 @@ namespace Components
void SetPort(unsigned short port);
unsigned short GetPort();
void SetIP(DWORD ip);
DWORD GetIP();
Game::netadr_t* Get();
const char* GetString();
@ -31,6 +35,7 @@ namespace Components
const char* GetName() { return "Network"; };
static void Handle(std::string packet, Callback callback);
static void Send(Address target, std::string data);
static void Send(Game::netsrc_t type, Address target, std::string data);
private:

View File

@ -24,7 +24,7 @@ namespace Components
Party::Container.Target = target;
Party::Container.Challenge = Utils::VA("%X", Party::Container.JoinTime);
Network::Send(Game::NS_CLIENT, Party::Container.Target, Utils::VA("getinfo %s\n", Party::Container.Challenge.data()));
Network::Send(Party::Container.Target, Utils::VA("getinfo %s\n", Party::Container.Challenge.data()));
Command::Execute("openmenu popup_reconnectingtoparty");
}
@ -177,11 +177,14 @@ namespace Components
info.Set("matchtype", "0");
}
Network::Send(Game::NS_CLIENT, address, Utils::VA("infoResponse\n%s\n", info.Build().data()));
Network::Send(address, Utils::VA("infoResponse\n%s\n", info.Build().data()));
});
Network::Handle("infoResponse", [] (Network::Address address, std::string data)
{
OutputDebugStringA(data.data());
Utils::InfoString info(data);
// Handle connection
if (Party::Container.Valid)
{
@ -190,8 +193,6 @@ namespace Components
// Invalidate handler for future packets
Party::Container.Valid = false;
Utils::InfoString info(data);
int matchType = atoi(info.Get("matchtype").data());
if (info.Get("challenge") != Party::Container.Challenge)
@ -246,6 +247,8 @@ namespace Components
}
}
}
ServerList::Insert(address, info);
});
}

View File

@ -0,0 +1,195 @@
#include "..\STDInclude.hpp"
namespace Components
{
int ServerList::CurrentServer = 0;
ServerList::Container ServerList::RefreshContainer;
std::vector<ServerList::ServerInfo> ServerList::OnlineList;
int ServerList::GetServerCount()
{
return ServerList::OnlineList.size();
}
const char* ServerList::GetServerText(int index, int column)
{
if (index >= (int)ServerList::OnlineList.size()) return "";
ServerList::ServerInfo* Server = &ServerList::OnlineList[index];
switch (column)
{
case Column::Password:
{
return (Server->Password ? "X" : "");
}
case Column::Hostname:
{
return Server->Hostname.data();
}
case Column::Mapname:
{
return Game::UI_LocalizeMapName(Server->Mapname.data());
}
case Column::Players:
{
return Utils::VA("%i (%i)", Server->Clients, Server->MaxClients);
}
case Column::Gametype:
{
if (Server->Mod != "")
{
return (Server->Mod.data() + 5);
}
return Game::UI_LocalizeGameType(Server->Gametype.data());
}
case Column::Ping:
{
return Utils::VA("%i", Server->Ping);
}
}
return "";
}
void ServerList::SelectServer(int index)
{
ServerList::CurrentServer = index;
}
void ServerList::Refresh()
{
ServerList::OnlineList.clear();
ServerList::RefreshContainer.Mutex.lock();
ServerList::RefreshContainer.Servers.clear();
ServerList::RefreshContainer.Mutex.unlock();
int masterPort = Dvar::Var("masterPort").Get<int>();
const char* masterServerName = Dvar::Var("masterServerName").Get<const char*>();
ServerList::RefreshContainer.Host = Network::Address(Utils::VA("%s:%u", masterServerName, masterPort));
ServerList::RefreshContainer.AwaitingList = true;
Network::Send(ServerList::RefreshContainer.Host, "getservers IW4 145 full empty");
}
void ServerList::Insert(Network::Address address, Utils::InfoString info)
{
// Do not enter any new servers, if we are awaiting a new list from the master
if (ServerList::RefreshContainer.AwaitingList) return;
ServerList::RefreshContainer.Mutex.lock();
for (auto i = ServerList::RefreshContainer.Servers.begin(); i != ServerList::RefreshContainer.Servers.end(); i++)
{
// Our desired server
if (i->Target == address)
{
// Challenge did not match
if (i->Challenge != info.Get("challenge"))
{
// Shall we remove the server from the queue?
// Better not, it might send a second response with the correct challenge.
// This might happen when users refresh twice (or more often) in a short period of time
break;
}
// TODO: Implement deeper check like version and game
ServerInfo server;
server.Hostname = info.Get("hostname");
server.Mapname = info.Get("mapname");
server.Gametype = info.Get("gametype");
server.Mod = info.Get("fs_game");
server.MatchType = atoi(info.Get("matchtype").data());
server.Clients = atoi(info.Get("clients").data());
server.MaxClients = atoi(info.Get("sv_maxclients").data());
server.Password = 0; // No info yet
server.Ping = (Game::Com_Milliseconds() - i->SendTime);
ServerList::OnlineList.push_back(server);
ServerList::RefreshContainer.Servers.erase(i);
break;
}
}
ServerList::RefreshContainer.Mutex.unlock();
}
ServerList::ServerList()
{
ServerList::OnlineList.clear();
// Add server list feeder
Feeder::Add(2.0f, ServerList::GetServerCount, ServerList::GetServerText, ServerList::SelectServer);
Network::Handle("getServersResponse", [] (Network::Address address, std::string data)
{
if (!ServerList::RefreshContainer.AwaitingList) return; // Only parse if we are awaiting a list
if (ServerList::RefreshContainer.Host != address) return; // Only parse from host we sent to
ServerList::RefreshContainer.AwaitingList = false;
ServerList::RefreshContainer.Mutex.lock();
// Is this safe? If server ip/port is encoded as '\', it might fail
// TODO: Rather parse six bytes and skip the seventh
auto servers = Utils::Explode(data, '\\');
if (servers.size())
{
if (servers[servers.size() - 1] == "EOT")
{
Logger::Print("We got an invalid response from the master. Trying to parse it anyways...\n");
}
for (unsigned int i = 0; i < servers.size() - 1; i++)
{
const char* server = servers[i].data();
DWORD ip = *(DWORD*)server;
uint16_t port = *(uint16_t*)(server + 4);
Network::Address serverAddr = address;
serverAddr.SetIP(ip);
serverAddr.SetPort(port);
serverAddr.Get()->type = Game::NA_IP;
ServerList::Container::ServerContainer container;
container.SendTime = Game::Com_Milliseconds();
container.Challenge = Utils::VA("%d", container.SendTime);
container.Sent = true;
container.Target = serverAddr;
ServerList::RefreshContainer.Servers.push_back(container);
Network::Send(container.Target, Utils::VA("getinfo %s\n", container.Challenge.data()));
}
}
ServerList::RefreshContainer.Mutex.unlock();
});
Command::Add("refreshList", [] (Command::Params params)
{
ServerList::Refresh();
});
// Temporary overwriting
strcpy((char*)0x6D9CBC, "localhost");
}
ServerList::~ServerList()
{
ServerList::OnlineList.clear();
}
}

View File

@ -0,0 +1,64 @@
namespace Components
{
class ServerList : public Component
{
public:
struct ServerInfo
{
Network::Address Addr;
bool Visible;
std::string Hostname;
std::string Mapname;
std::string Gametype;
std::string Mod;
int Clients;
int MaxClients;
bool Password;
int Ping;
int MatchType;
bool Hardcore;
};
ServerList();
~ServerList();
const char* GetName() { return "ServerList"; };
static void Refresh();
static void Insert(Network::Address address, Utils::InfoString info);
private:
enum Column
{
Password,
Hostname,
Mapname,
Players,
Gametype,
Ping,
};
struct Container
{
struct ServerContainer
{
bool Sent;
int SendTime;
std::string Challenge;
Network::Address Target;
};
bool AwaitingList;
Network::Address Host;
std::vector<ServerContainer> Servers;
std::mutex Mutex;
};
static int GetServerCount();
static const char* GetServerText(int index, int column);
static void SelectServer(int index);
static int CurrentServer;
static Container RefreshContainer;
static std::vector<ServerInfo> OnlineList;
};
}

View File

@ -66,6 +66,9 @@ namespace Game
LoadInitialFF_t LoadInitialFF = (LoadInitialFF_t)0x506AC0;
LoadModdableRawfile_t LoadModdableRawfile = (LoadModdableRawfile_t)0x61ABC0;
LocalizeString_t LocalizeString = (LocalizeString_t)0x4FB010;
LocalizeMapString_t LocalizeMapString = (LocalizeMapString_t)0x44BB30;
sendOOB_t OOBPrint = (sendOOB_t)0x4AEF00;
PC_ReadToken_t PC_ReadToken = (PC_ReadToken_t)0x4ACCD0;
@ -101,6 +104,12 @@ namespace Game
UiContext *uiContext = (UiContext *)0x62E2858;
int* arenaCount = (int*)0x62E6930;
mapArena_t* arenas = (mapArena_t*)0x62E6934;
int* gameTypeCount = (int*)0x62E50A0;
gameTypeName_t* gameTypes = (gameTypeName_t*)0x62E50A4;
void* ReallocateAssetPool(XAssetType type, unsigned int newSize)
{
int elSize = DB_GetXAssetSizeHandlers[type]();
@ -126,4 +135,43 @@ namespace Game
OOBPrint(type, *adr, *(adr + 1), *(adr + 2), 0xFFFFFFFF, *(adr + 4), message);
}
const char* UI_LocalizeMapName(const char* mapName)
{
for (int i = 0; i < *arenaCount; i++)
{
if (!_stricmp(arenas[i].mapName, mapName))
{
char* uiName = &arenas[i].uiName[0];
if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH
{
char* name = LocalizeMapString(uiName);
return name;
}
return uiName;
}
}
return mapName;
}
const char* UI_LocalizeGameType(const char* gameType)
{
if (gameType == 0 || *gameType == '\0')
{
return "";
}
for (int i = 0; i < *gameTypeCount; i++)
{
if (!_stricmp(gameTypes[i].gameType, gameType))
{
char* name = LocalizeMapString(gameTypes[i].uiName);
return name;
}
}
return gameType;
}
}

View File

@ -148,6 +148,12 @@ namespace Game
typedef void* (__cdecl * LoadModdableRawfile_t)(int a1, const char* filename);
extern LoadModdableRawfile_t LoadModdableRawfile;
typedef char* (__cdecl * LocalizeString_t)(char*, char*);
extern LocalizeString_t LocalizeString;
typedef char* (__cdecl * LocalizeMapString_t)(char*);
extern LocalizeMapString_t LocalizeMapString;
typedef void(__cdecl* sendOOB_t)(int, int, int, int, int, int, const char*);
extern sendOOB_t OOBPrint;
@ -199,7 +205,15 @@ namespace Game
extern UiContext *uiContext;
extern int* arenaCount;
extern mapArena_t* arenas;
extern int* gameTypeCount;
extern gameTypeName_t* gameTypes;
void* ReallocateAssetPool(XAssetType type, unsigned int newSize);
void Menu_FreeItemMemory(Game::itemDef_t* item);
void OOBPrintT(int type, netadr_t netadr, const char* message);
const char* UI_LocalizeMapName(const char* mapName);
const char* UI_LocalizeGameType(const char* gameType);
}

View File

@ -922,4 +922,17 @@ namespace Game
XNADDR hostAddress;
XNKEY keyExchangeKey;
};
struct mapArena_t
{
char uiName[32];
char mapName[16];
char pad[2768];
};
struct gameTypeName_t
{
char gameType[12];
char uiName[32];
};
}

View File

@ -23,8 +23,6 @@ namespace Steam
void Callbacks::RegisterCallResult(uint64_t call, Callbacks::Base* result)
{
OutputDebugStringA(::Utils::VA("Registering result: %d", result->GetICallback()));
Callbacks::ResultHandlers[call] = result;
}

View File

@ -37,7 +37,15 @@ namespace Utils
for (std::string token; std::getline(iss, token, delim);)
{
result.push_back(std::move(token));
std::string _entry = std::move(token);
// Remove trailing 0x0 bytes
while (_entry.size() && !_entry[_entry.size() - 1])
{
_entry = _entry.substr(0, _entry.size() - 1);
}
result.push_back(_entry);
}
return result;
@ -114,6 +122,11 @@ namespace Utils
void InfoString::Parse(std::string buffer)
{
if (buffer[0] == '\\')
{
buffer = buffer.substr(1);
}
std::vector<std::string> KeyValues = Utils::Explode(buffer, '\\');
for (unsigned int i = 0; i < (KeyValues.size() - 1); i+=2)

View File

@ -12,6 +12,7 @@ namespace Utils
public:
InfoString() {};
InfoString(std::string buffer) :InfoString() { this->Parse(buffer); };
InfoString(const InfoString &obj) { this->KeyValuePairs = obj.KeyValuePairs; };
void Set(std::string key, std::string value);
std::string Get(std::string key);

View File

@ -28,7 +28,7 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Normal|Win32'">
<LinkIncremental>false</LinkIncremental>
<TargetName>iw4m</TargetName>
<TargetName>iw4x</TargetName>
<OutDir>$(SolutionDir)$(Configuration)\Bin\</OutDir>
<IntDir>$(SolutionDir)$(Configuration)\Obj\</IntDir>
</PropertyGroup>
@ -75,6 +75,7 @@
<ClInclude Include="Components\QuickPatch.hpp" />
<ClInclude Include="Components\RawFiles.hpp" />
<ClInclude Include="Components\Renderer.hpp" />
<ClInclude Include="Components\ServerList.hpp" />
<ClInclude Include="Components\Singleton.hpp" />
<ClInclude Include="Components\Window.hpp" />
<ClInclude Include="Game\Functions.hpp" />
@ -114,6 +115,7 @@
<ClCompile Include="Components\QuickPatch.cpp" />
<ClCompile Include="Components\RawFiles.cpp" />
<ClCompile Include="Components\Renderer.cpp" />
<ClCompile Include="Components\ServerList.cpp" />
<ClCompile Include="Components\Singleton.cpp" />
<ClCompile Include="Components\Window.cpp" />
<ClCompile Include="Game\Functions.cpp" />

View File

@ -137,6 +137,9 @@
<ClCompile Include="Components\Feeder.cpp">
<Filter>Source\Components\Modules</Filter>
</ClCompile>
<ClCompile Include="Components\ServerList.cpp">
<Filter>Source\Components\Modules</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Steam\Interfaces\SteamUser.hpp">
@ -250,5 +253,8 @@
<ClInclude Include="Components\Feeder.hpp">
<Filter>Source\Components\Modules</Filter>
</ClInclude>
<ClInclude Include="Components\ServerList.hpp">
<Filter>Source\Components\Modules</Filter>
</ClInclude>
</ItemGroup>
</Project>