/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "launcher_link.h"
#include "common/md5.h"
#include "common/misc_functions.h"
#include "common/packet_dump.h"
#include "common/servertalk.h"
#include "common/strings.h"
#include "world/eql_config.h"
#include "world/launcher_list.h"
#include "world/world_config.h"
#include "world/worlddb.h"
#include
#include
extern LauncherList launcher_list;
LauncherLink::LauncherLink(int id, std::shared_ptr c)
: ID(id),
tcpc(c),
m_name(""),
m_bootTimer(2000)
{
m_dynamicCount = 0;
m_bootTimer.Disable();
tcpc->OnMessage(std::bind(&LauncherLink::ProcessMessage, this, std::placeholders::_1, std::placeholders::_2));
m_process_timer = std::make_unique(100, true, std::bind(&LauncherLink::Process, this, std::placeholders::_1));
}
LauncherLink::~LauncherLink() {
}
void LauncherLink::Process(EQ::Timer *t) {
if (m_bootTimer.Check(false)) {
//force a boot on any zone which isnt running.
std::map::iterator cur, end;
cur = m_states.begin();
end = m_states.end();
for (; cur != end; ++cur) {
if (!cur->second.up) {
StartZone(cur->first.c_str(), cur->second.port);
}
}
m_bootTimer.Disable();
}
}
void LauncherLink::ProcessMessage(uint16 opcode, EQ::Net::Packet &p)
{
ServerPacket tpack(opcode, p);
ServerPacket *pack = &tpack;
switch (opcode) {
case 0:
break;
case ServerOP_KeepAlive: {
// ignore this
break;
}
case ServerOP_ZAAuth: {
LogInfo("Got authentication from [{}] when they are already authenticated", m_name.c_str());
break;
}
case ServerOP_LauncherConnectInfo: {
const LauncherConnectInfo *it = (const LauncherConnectInfo *)pack->pBuffer;
if (HasName()) {
LogInfo("Launcher [{}] received an additional connect packet with name [{}]. Ignoring", m_name.c_str(), it->name);
break;
}
m_name = it->name;
EQLConfig *config = launcher_list.GetConfig(m_name.c_str());
if (config == nullptr) {
LogInfo("Unknown launcher [{}] connected. Disconnecting", it->name);
Disconnect();
break;
}
LogInfo("Launcher Identified itself as [{}]. Loading zone list", it->name);
std::vector result;
//database.GetLauncherZones(it->name, result);
config->GetZones(result);
std::vector::iterator cur, end;
cur = result.begin();
end = result.end();
ZoneState zs;
for (; cur != end; ++cur) {
zs.port = cur->port;
zs.up = false;
zs.starts = 0;
LogInfo("[{}] Loaded zone [{}] on port [{}]", m_name.c_str(), cur->name.c_str(), zs.port);
m_states[cur->name] = zs;
}
//now we add all the dynamics.
BootDynamics(config->GetDynamicCount());
m_bootTimer.Start();
break;
}
case ServerOP_LauncherZoneStatus: {
const LauncherZoneStatus *it = (const LauncherZoneStatus *)pack->pBuffer;
std::map::iterator res;
res = m_states.find(it->short_name);
if (res == m_states.end()) {
LogInfo("[{}] reported state for zone [{}] which it does not have", m_name.c_str(), it->short_name);
break;
}
LogInfo("[{}] [{}] reported state [{}] ([{}] starts)", m_name.c_str(), it->short_name, it->running ? "STARTED" : "STOPPED", it->start_count);
res->second.up = it->running;
res->second.starts = it->start_count;
break;
}
default:
{
LogInfo("Unknown ServerOPcode from launcher {:#04x}, size [{}]", pack->opcode, pack->size);
DumpPacket(pack->pBuffer, pack->size);
break;
}
}
}
bool LauncherLink::ContainsZone(const char *short_name) const {
return(m_states.find(short_name) != m_states.end());
}
void LauncherLink::BootZone(const char *short_name, uint16 port) {
ZoneState zs;
zs.port = port;
zs.up = false;
zs.starts = 0;
LogInfo("[{}] Loaded zone [{}] on port [{}]", m_name.c_str(), short_name, zs.port);
m_states[short_name] = zs;
StartZone(short_name, port);
}
void LauncherLink::StartZone(const char *short_name) {
StartZone(short_name, 0);
}
void LauncherLink::StartZone(const char *short_name, uint16 port) {
auto pack = new ServerPacket(ServerOP_LauncherZoneRequest, sizeof(LauncherZoneRequest));
LauncherZoneRequest* s = (LauncherZoneRequest *)pack->pBuffer;
strn0cpy(s->short_name, short_name, 32);
s->command = ZR_Start;
s->port = port;
SendPacket(pack);
delete pack;
}
void LauncherLink::RestartZone(const char *short_name) {
auto pack = new ServerPacket(ServerOP_LauncherZoneRequest, sizeof(LauncherZoneRequest));
LauncherZoneRequest* s = (LauncherZoneRequest *)pack->pBuffer;
strn0cpy(s->short_name, short_name, 32);
s->command = ZR_Restart;
s->port = 0;
SendPacket(pack);
delete pack;
}
void LauncherLink::StopZone(const char *short_name) {
auto pack = new ServerPacket(ServerOP_LauncherZoneRequest, sizeof(LauncherZoneRequest));
LauncherZoneRequest* s = (LauncherZoneRequest *)pack->pBuffer;
strn0cpy(s->short_name, short_name, 32);
s->command = ZR_Stop;
s->port = 0;
SendPacket(pack);
delete pack;
}
void LauncherLink::BootDynamics(uint8 new_count) {
if (m_dynamicCount == new_count)
return;
ZoneState zs;
if (m_dynamicCount < new_count) {
//we are booting more dynamics.
zs.port = 0;
zs.up = false;
zs.starts = 0;
int r;
char nbuf[20];
uint8 index;
//"for each zone we need to boot"
for (r = m_dynamicCount; r < new_count; r++) {
//find an idle ID
for (index = m_dynamicCount + 1; index < 255; index++) {
sprintf(nbuf, "dynamic_%02d", index);
if (m_states.find(nbuf) != m_states.end())
continue;
m_states[nbuf] = zs;
StartZone(nbuf);
break;
}
}
m_dynamicCount = new_count;
}
else if (new_count == 0) {
//kill all zones...
std::map::iterator cur, end;
cur = m_states.begin();
end = m_states.end();
for (; cur != end; ++cur) {
StopZone(cur->first.c_str());
}
}
else {
//need to get rid of some zones...
//quick and dirty way to do this.. should do better (like looking for idle zones)
int found = 0;
std::map::iterator cur, end;
cur = m_states.begin();
end = m_states.end();
for (; cur != end; ++cur) {
if (cur->first.find("dynamic_") == 0) {
if (found >= new_count) {
//this zone exceeds the number of allowed booted zones.
StopZone(cur->first.c_str());
}
else {
found++;
}
}
}
m_dynamicCount = new_count;
}
}
void LauncherLink::GetZoneList(std::vector &l) {
std::map::iterator cur, end;
cur = m_states.begin();
end = m_states.end();
for (; cur != end; ++cur) {
l.push_back(cur->first.c_str());
}
}
void LauncherLink::GetZoneDetails(const char *short_name, std::map &res) {
res.clear();
std::map::iterator r;
r = m_states.find(short_name);
if (r == m_states.end()) {
res["error"] = "Zone Not Found";
res["name"] = short_name;
res["up"] = "0";
res["starts"] = "0";
res["port"] = "0";
}
else {
res["name"] = r->first;
res["up"] = r->second.up ? "1" : "0";
res["starts"] = itoa(r->second.starts);
res["port"] = itoa(r->second.port);
}
}
void LauncherLink::Shutdown() {
auto pack = new ServerPacket(ServerOP_ShutdownAll);
SendPacket(pack);
delete pack;
}