1199 lines
51 KiB
C++
1199 lines
51 KiB
C++
// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPLv2 License
|
|
|
|
#pragma once
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <mysql.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include "lworld.hpp"
|
|
#include "login_account.hpp"
|
|
#include "packet_headers.hpp"
|
|
#include "../common/log.hpp"
|
|
#include "../common/unix.hpp"
|
|
#include "../common/types.hpp"
|
|
#include "../common/debug.hpp"
|
|
#include "../common/servertalk.hpp"
|
|
#include "../common/misc_functions.hpp"
|
|
#include "../common/database/query.hpp"
|
|
#include "../common/database/database.hpp"
|
|
#include "../common/database/database_core.hpp"
|
|
#include "../common/database/database_result.hpp"
|
|
#include "../common/packet/packet_functions.hpp"
|
|
#include "../common/packet/packet_dump.hpp"
|
|
#include "../common/packet/packet_struct.hpp"
|
|
|
|
using namespace std;
|
|
|
|
extern LoginDatabase database;
|
|
extern LWorldList world_list;
|
|
|
|
class LoginDatabase : public Database
|
|
{
|
|
public:
|
|
// Connect to the login database using new database connection
|
|
bool ConnectNewDatabase()
|
|
{
|
|
return dbLogin.Connect();
|
|
}
|
|
|
|
// Remove character data for characters marked as deleted to clean up orphaned records
|
|
void RemoveDeletedCharacterData()
|
|
{
|
|
dbLogin.Query("DELETE FROM login_char_colors WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)");
|
|
dbLogin.Query("DELETE FROM login_equipment WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)");
|
|
}
|
|
|
|
// Set zone information in character select packet based on server and zone IDs
|
|
void SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet)
|
|
{
|
|
if(!packet) return;
|
|
|
|
Query query;
|
|
MYSQL_RES* result = nullptr;
|
|
|
|
if (version >= 1212)
|
|
result = query.RunQuery2(Q_SELECT, "SELECT name, description from ls_world_zones where server_id=%i and zone_id=%i", server_id, zone_id);
|
|
|
|
MYSQL_ROW row;
|
|
if(result && (row = mysql_fetch_row(result))) {
|
|
if (row[0])
|
|
packet->setMediumStringByName("zone", row[0]);
|
|
else
|
|
packet->setMediumStringByName("zone", " ");
|
|
|
|
if(row[1])
|
|
packet->setMediumStringByName("zonedesc", row[1]);
|
|
else
|
|
packet->setMediumStringByName("zonedesc", " ");
|
|
}
|
|
else {
|
|
Query query2;
|
|
MYSQL_RES* result2 = nullptr;
|
|
|
|
if (version < 1212)
|
|
result2 = query2.RunQuery2(Q_SELECT, "SELECT file, description from zones where id=%i", zone_id);
|
|
else
|
|
result2 = query2.RunQuery2(Q_SELECT, "SELECT name, description from zones where id=%i", zone_id);
|
|
|
|
MYSQL_ROW row2;
|
|
if(result2 && (row2 = mysql_fetch_row(result2))) {
|
|
if (version < 546 && version > 561 && version < 1212) {
|
|
if (row2[0]) {
|
|
int len = strlen(row2[0]);
|
|
char* zoneName = new char[len + 2];
|
|
strncpy(zoneName, row2[0], len);
|
|
zoneName[len] = 0x2E;
|
|
zoneName[len + 1] = 0x30;
|
|
|
|
packet->setMediumStringByName("zone", zoneName);
|
|
safe_delete_array(zoneName);
|
|
}
|
|
else
|
|
packet->setMediumStringByName("zone", ".0");
|
|
}
|
|
else {
|
|
if (row2[0])
|
|
packet->setMediumStringByName("zone", row2[0]);
|
|
else
|
|
packet->setMediumStringByName("zone", " ");
|
|
}
|
|
|
|
if(row2[1])
|
|
packet->setMediumStringByName("zonedesc", row2[1]);
|
|
else
|
|
packet->setMediumStringByName("zonedesc", " ");
|
|
}
|
|
}
|
|
packet->setMediumStringByName("zonename2"," ");
|
|
}
|
|
|
|
// Get zone description by zone file name
|
|
string GetZoneDescription(char* name)
|
|
{
|
|
string ret;
|
|
Query query;
|
|
query.escaped_name = getEscapeString(name);
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT description from zones where file=substring_index('%s', '.', 1)", query.escaped_name);
|
|
MYSQL_ROW row;
|
|
if((row = mysql_fetch_row(result))) {
|
|
ret = string(row[0]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Convert world character ID to login character ID for equipment updates
|
|
int32 GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id)
|
|
{
|
|
int32 ret = 0;
|
|
Query query;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id FROM login_characters WHERE server_id = %u AND char_id = %u AND deleted = 0 LIMIT 0,1", server_id, char_id);
|
|
MYSQL_ROW row;
|
|
if((row = mysql_fetch_row(result))) {
|
|
ret = atoi(row[0]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Update character equipment appearance data from world server
|
|
void SetServerEquipmentAppearances(int32 server_id, map<int32, LoginEquipmentUpdate> equip_updates)
|
|
{
|
|
if(equip_updates.size() == 0) return;
|
|
|
|
LogWrite(LOGIN__DEBUG, 0, "Login", "Saving appearance info from world %u...", server_id);
|
|
|
|
map<int32, LoginEquipmentUpdate>::iterator equip_itr;
|
|
stringstream ss;
|
|
ss << "replace into login_equipment (login_characters_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue, slot) values";
|
|
int count = 0;
|
|
int32 char_id = 0;
|
|
|
|
for(equip_itr = equip_updates.begin(); equip_itr != equip_updates.end(); equip_itr++) {
|
|
char_id = GetLoginCharacterIDFromWorldCharID(server_id, (int32)equip_itr->second.world_char_id);
|
|
|
|
if(char_id == 0) continue; // invalid character/world match
|
|
|
|
LogWrite(LOGIN__DEBUG, 5, "Login", "--Processing character %u, slot %i", char_id, (int32)equip_itr->second.slot);
|
|
|
|
if(count > 0) ss << ", ";
|
|
|
|
ss << "(" << char_id << ", ";
|
|
ss << (int32)equip_itr->second.equip_type << ", ";
|
|
ss << (int32)equip_itr->second.red << ", ";
|
|
ss << (int32)equip_itr->second.green << ", ";
|
|
ss << (int32)equip_itr->second.blue << ", ";
|
|
ss << (int32)equip_itr->second.highlight_red << ", ";
|
|
ss << (int32)equip_itr->second.highlight_green << ", ";
|
|
ss << (int32)equip_itr->second.highlight_blue << ", ";
|
|
ss << (int32)equip_itr->second.slot << ")";
|
|
|
|
count++;
|
|
}
|
|
|
|
Query query;
|
|
query.RunQuery2(ss.str(), Q_REPLACE);
|
|
|
|
if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error saving login_equipment data Error: ", query.GetError());
|
|
}
|
|
|
|
// Update zone descriptions for a specific server
|
|
void SetServerZoneDescriptions(int32 server_id, map<int32, LoginZoneUpdate> zone_descriptions)
|
|
{
|
|
if(zone_descriptions.size() == 0) return;
|
|
|
|
map<int32, LoginZoneUpdate>::iterator zone_itr;
|
|
string query_string = "replace into ls_world_zones (server_id, zone_id, name, description) values";
|
|
int count = 0;
|
|
char server_id_str[12] = {0};
|
|
sprintf(server_id_str, "%i", server_id);
|
|
|
|
for(zone_itr = zone_descriptions.begin(); zone_itr != zone_descriptions.end(); zone_itr++, count++) {
|
|
char zone_id_str[12] = {0};
|
|
sprintf(zone_id_str, "%i", zone_itr->first);
|
|
|
|
if(count > 0) query_string.append(", ");
|
|
|
|
query_string.append("(").append(server_id_str).append(",");
|
|
query_string.append(zone_id_str).append(",");
|
|
query_string.append("'").append(getSafeEscapeString(zone_itr->second.name.c_str()).c_str()).append("', '");
|
|
query_string.append(getSafeEscapeString(zone_itr->second.description.c_str()).c_str()).append("')");
|
|
}
|
|
|
|
Query query;
|
|
query.RunQuery2(query_string, Q_REPLACE);
|
|
}
|
|
|
|
// Get server ID for character when not provided in play request (legacy support)
|
|
int32 GetServer(int32 accountID, int32 charID, string name)
|
|
{
|
|
int32 id = 0;
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name.c_str());
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT server_id from login_characters where account_id=%i and char_id=%i and name='%s'", accountID, charID, query.escaped_name);
|
|
|
|
if (result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
id = atoi(row[0]);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
// Load all characters for an account and populate character selection data
|
|
void LoadCharacters(LoginAccount* acct, int16 version)
|
|
{
|
|
if(acct != nullptr) acct->flushCharacters();
|
|
|
|
Query query;
|
|
Query query2;
|
|
int32 id = 0;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lc.char_id, lc.server_id, lc.name, lc.race, lc.class, lc.gender, lc.deity, lc.body_size, lc.body_age, lc.current_zone_id, lc.level, lc.soga_wing_type, lc.soga_chest_type, lc.soga_legs_type, lc.soga_hair_type, lc.legs_type, lc.chest_type, lc.wing_type, lc.hair_type, unix_timestamp(lc.created_date), unix_timestamp(lc.last_played), lc.id, lw.name, lc.facial_hair_type, lc.soga_facial_hair_type, lc.soga_model_type, lc.model_type from login_characters lc, login_worldservers lw where lw.id = lc.server_id and lc.account_id=%i and lc.deleted=0", acct->getLoginAccountID());
|
|
|
|
if(!result) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoadCharacters query '%s': %s", query.GetQuery(), query.GetError());
|
|
return;
|
|
}
|
|
|
|
MYSQL_ROW row;
|
|
MYSQL_ROW row2;
|
|
MYSQL_ROW row3;
|
|
|
|
while ((row = mysql_fetch_row(result))) {
|
|
CharSelectProfile* player = new CharSelectProfile(version);
|
|
id = atoul(row[0]);
|
|
|
|
player->packet->setDataByName("charid", id);
|
|
player->packet->setDataByName("server_id", atoul(row[1]));
|
|
player->packet->setMediumStringByName("name", row[2]);
|
|
player->packet->setDataByName("race", atoi(row[3]));
|
|
player->packet->setDataByName("class", atoi(row[4]));
|
|
player->packet->setDataByName("gender", atoi(row[5]));
|
|
player->packet->setDataByName("deity", atoi(row[6]));
|
|
player->packet->setDataByName("body_size", atof(row[7]));
|
|
player->packet->setDataByName("body_age", atof(row[8]));
|
|
|
|
SetZoneInformation(atoi(row[1]), atoi(row[9]), version, player->packet);
|
|
|
|
player->packet->setDataByName("level", atoi(row[10]));
|
|
|
|
// Handle SOGA appearance fallback to regular appearance if not set
|
|
if(atoi(row[11]) > 0)
|
|
player->packet->setDataByName("soga_wing_type", atoi(row[11]));
|
|
else
|
|
player->packet->setDataByName("soga_wing_type", atoi(row[17]));
|
|
|
|
if(atoi(row[12]) > 0)
|
|
player->packet->setDataByName("soga_chest_type", atoi(row[12]));
|
|
else
|
|
player->packet->setDataByName("soga_chest_type", atoi(row[16]));
|
|
|
|
if(atoi(row[13]) > 0)
|
|
player->packet->setDataByName("soga_legs_type", atoi(row[13]));
|
|
else
|
|
player->packet->setDataByName("soga_legs_type", atoi(row[15]));
|
|
|
|
if(atoi(row[14]) > 0)
|
|
player->packet->setDataByName("soga_hair_type", atoi(row[14]));
|
|
else
|
|
player->packet->setDataByName("soga_hair_type", atoi(row[18]));
|
|
|
|
player->packet->setDataByName("legs_type", atoi(row[15]));
|
|
player->packet->setDataByName("chest_type", atoi(row[16]));
|
|
player->packet->setDataByName("wing_type", atoi(row[17]));
|
|
player->packet->setDataByName("hair_type", atoi(row[18]));
|
|
player->packet->setDataByName("created_date", atol(row[19]));
|
|
|
|
if (row[20]) player->packet->setDataByName("last_played", atol(row[20]));
|
|
|
|
// Set version-specific packet data
|
|
if(version == 546 || version == 561)
|
|
player->packet->setDataByName("version", 11);
|
|
else if(version >= 887)
|
|
player->packet->setDataByName("version", 6);
|
|
else
|
|
player->packet->setDataByName("version", 5);
|
|
|
|
player->packet->setDataByName("account_id", acct->getLoginAccountID());
|
|
player->packet->setDataByName("account_id2", acct->getLoginAccountID());
|
|
|
|
LoadAppearanceData(atoul(row[21]), player->packet);
|
|
|
|
if(row[22]) player->packet->setMediumStringByName("server_name", row[22]);
|
|
|
|
player->packet->setDataByName("hair_face_type", atoi(row[23]));
|
|
|
|
if(atoi(row[24]) > 0)
|
|
player->packet->setDataByName("soga_hair_face_type", atoi(row[24]));
|
|
else
|
|
player->packet->setDataByName("soga_hair_face_type", atoi(row[23]));
|
|
|
|
if(atoi(row[25]) > 0)
|
|
player->packet->setDataByName("soga_race_type", atoi(row[25]));
|
|
else
|
|
player->packet->setDataByName("soga_race_type", atoi(row[26]));
|
|
|
|
player->packet->setDataByName("race_type", atoi(row[26]));
|
|
|
|
// Set unknown packet fields required for proper display
|
|
player->packet->setDataByName("unknown3", 57);
|
|
player->packet->setDataByName("unknown4", 56);
|
|
player->packet->setDataByName("unknown6", 1, 1);
|
|
player->packet->setDataByName("unknown8", 15);
|
|
player->packet->setDataByName("unknown13", 212);
|
|
player->packet->setColorByName("unknown14", 0xFF, 0xFF, 0xFF);
|
|
|
|
uchar tmp[] = {0xFF, 0xFF, 0xFF, 0x61, 0x00, 0x2C, 0x04, 0xA5, 0x09, 0x02, 0x0F, 0x00, 0x00};
|
|
for(size_t y = 0; y < sizeof(tmp); y++)
|
|
player->packet->setDataByName("unknown11", tmp[y], y);
|
|
|
|
// Load equipment appearance data
|
|
MYSQL_RES* result3 = query2.RunQuery2(Q_SELECT, "SELECT slot, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue from login_equipment where login_characters_id=%i order by slot", atoi(row[21]));
|
|
|
|
if(result3) {
|
|
for(int i = 0; (row3 = mysql_fetch_row(result3)) && i < 24; i++) {
|
|
player->packet->setEquipmentByName("equip", atoi(row3[1]), atoi(row3[2]), atoi(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoi(row3[7]), atoi(row3[0]));
|
|
}
|
|
}
|
|
|
|
player->packet->setDataByName("mount", 1377);
|
|
player->packet->setDataByName("mount_color1", 57);
|
|
|
|
acct->addCharacter(player);
|
|
}
|
|
}
|
|
|
|
// Check character timestamps and notify world servers for validation
|
|
void CheckCharacterTimeStamps(LoginAccount* acct)
|
|
{
|
|
Query query;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT char_id, unix_timestamp from login_characters where account_id=%i", acct->getLoginAccountID());
|
|
|
|
if(!result || mysql_num_rows(result) <= 0) return;
|
|
|
|
MYSQL_ROW row;
|
|
ServerPacket* outpack = new ServerPacket(ServerOP_CharTimeStamp, sizeof(CharacterTimeStamp_Struct));
|
|
CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) outpack->pBuffer;
|
|
cts->account_id = acct->getLoginAccountID();
|
|
int32 server_id = 0;
|
|
LWorld* world_server = nullptr;
|
|
|
|
while ((row = mysql_fetch_row(result))) {
|
|
server_id = atoi(row[1]);
|
|
if(server_id != 0)
|
|
world_server = world_list.FindByAccount(server_id, World);
|
|
|
|
if(world_server) { // If the pointer is 0, the world server must be down, we can't do any updates...
|
|
cts->char_id = atoi(row[0]);
|
|
cts->unix_timestamp = atoi(row[1]);
|
|
world_server->SendPacket(outpack);
|
|
|
|
// Reset for next character
|
|
world_server = nullptr;
|
|
server_id = 0;
|
|
}
|
|
}
|
|
safe_delete(outpack);
|
|
}
|
|
|
|
// Save character appearance color/float data for classic client versions
|
|
void SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3, float multiplier = 100.0f)
|
|
{
|
|
Query query;
|
|
string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue, signed_value) values(%i,'%s',%i,%i,%i, 1)");
|
|
query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, (sint8)(float1*multiplier), (sint8)(float2*multiplier), (sint8)(float3*multiplier));
|
|
}
|
|
|
|
// Save character appearance color data for newer client versions
|
|
void SaveCharacterColors(int32 char_id, char* type, EQ2_Color color)
|
|
{
|
|
Query query;
|
|
string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue) values(%i,'%s',%i,%i,%i)");
|
|
query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, color.red, color.green, color.blue);
|
|
}
|
|
|
|
// Load character appearance data and apply to character select packet
|
|
void LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, signed_value, red, green, blue from login_char_colors where login_characters_id = %i", char_id);
|
|
|
|
while((row = mysql_fetch_row(result))) {
|
|
if(atoi(row[1]) == 0)
|
|
char_select_packet->setColorByName(row[0], atoul(row[2]), atoul(row[3]), atoul(row[4]));
|
|
else {
|
|
char_select_packet->setDataByName(row[0], atoi(row[2]), 0);
|
|
char_select_packet->setDataByName(row[0], atoi(row[3]), 1);
|
|
char_select_packet->setDataByName(row[0], atoi(row[4]), 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get appearance ID by name from appearances table
|
|
int16 GetAppearanceID(string name)
|
|
{
|
|
int32 id = 0;
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name.c_str());
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT appearance_id from appearances where name='%s'", query.escaped_name);
|
|
|
|
if(result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
id = atoi(row[0]);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
// Mark characters with duplicate world character IDs as deleted (except the specified exception)
|
|
void DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id)
|
|
{
|
|
Query query;
|
|
query.RunQuery2(Q_UPDATE, "update login_characters set deleted=1 where char_id=%u and server_id=%u and id!=%u", char_id, server_id, exception_id);
|
|
}
|
|
|
|
// Save new character data to database during character creation
|
|
int32 SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version)
|
|
{
|
|
Query query;
|
|
string create_char =
|
|
string("Insert into login_characters (account_id, server_id, char_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_facial_hair_type, legs_type, chest_type, wing_type, hair_type, facial_hair_type, soga_model_type, model_type)"
|
|
" values(%i, %i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)");
|
|
|
|
query.RunQuery2(Q_INSERT, create_char.c_str(),
|
|
acct->getLoginAccountID(),
|
|
create->getType_int32_ByName("server_id"), world_charid,
|
|
create->getType_EQ2_16BitString_ByName("name").data.c_str(),
|
|
create->getType_int8_ByName("race"),
|
|
create->getType_int8_ByName("class"),
|
|
create->getType_int8_ByName("gender"),
|
|
create->getType_int8_ByName("deity"),
|
|
create->getType_float_ByName("body_size"),
|
|
create->getType_float_ByName("body_age"),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_wing_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_chest_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_legs_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_hair_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_face_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("legs_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("chest_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("wing_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("hair_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("face_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_race_file").data),
|
|
GetAppearanceID(create->getType_EQ2_16BitString_ByName("race_file").data));
|
|
|
|
if(query.GetError() && strlen(query.GetError()) > 0) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in SaveCharacter query '%s': %s", query.GetQuery(), query.GetError());
|
|
return 0;
|
|
}
|
|
|
|
int32 last_insert_id = query.GetLastInsertedID();
|
|
|
|
// Mark any remaining characters with same id as deleted (creates problems if world deleted their db and started assigning new char ids)
|
|
DeactivateCharID(create->getType_int32_ByName("server_id"), world_charid, last_insert_id);
|
|
int32 char_id = last_insert_id;
|
|
|
|
// Save appearance data based on client version
|
|
if (client_version <= 561) {
|
|
float classic_multiplier = 250.0f;
|
|
SaveCharacterFloats(char_id, "skin_color", create->getType_float_ByName("skin_color", 0), create->getType_float_ByName("skin_color", 1), create->getType_float_ByName("skin_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "eye_color", create->getType_float_ByName("eye_color", 0), create->getType_float_ByName("eye_color", 1), create->getType_float_ByName("eye_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_color1", create->getType_float_ByName("hair_color1", 0), create->getType_float_ByName("hair_color1", 1), create->getType_float_ByName("hair_color1", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_color2", create->getType_float_ByName("hair_color2", 0), create->getType_float_ByName("hair_color2", 1), create->getType_float_ByName("hair_color2", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_highlight", create->getType_float_ByName("hair_highlight", 0), create->getType_float_ByName("hair_highlight", 1), create->getType_float_ByName("hair_highlight", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_face_color", create->getType_float_ByName("hair_face_color", 0), create->getType_float_ByName("hair_face_color", 1), create->getType_float_ByName("hair_face_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "hair_face_highlight_color", create->getType_float_ByName("hair_face_highlight_color", 0), create->getType_float_ByName("hair_face_highlight_color", 1), create->getType_float_ByName("hair_face_highlight_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "shirt_color", create->getType_float_ByName("shirt_color", 0), create->getType_float_ByName("shirt_color", 1), create->getType_float_ByName("shirt_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "unknown_chest_color", create->getType_float_ByName("unknown_chest_color", 0), create->getType_float_ByName("unknown_chest_color", 1), create->getType_float_ByName("unknown_chest_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "pants_color", create->getType_float_ByName("pants_color", 0), create->getType_float_ByName("pants_color", 1), create->getType_float_ByName("pants_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "unknown_legs_color", create->getType_float_ByName("unknown_legs_color", 0), create->getType_float_ByName("unknown_legs_color", 1), create->getType_float_ByName("unknown_legs_color", 2), classic_multiplier);
|
|
SaveCharacterFloats(char_id, "unknown9", create->getType_float_ByName("unknown9", 0), create->getType_float_ByName("unknown9", 1), create->getType_float_ByName("unknown9", 2), classic_multiplier);
|
|
}
|
|
else {
|
|
SaveCharacterColors(char_id, "skin_color", create->getType_EQ2_Color_ByName("skin_color"));
|
|
SaveCharacterColors(char_id, "model_color", create->getType_EQ2_Color_ByName("model_color"));
|
|
SaveCharacterColors(char_id, "eye_color", create->getType_EQ2_Color_ByName("eye_color"));
|
|
SaveCharacterColors(char_id, "hair_color1", create->getType_EQ2_Color_ByName("hair_color1"));
|
|
SaveCharacterColors(char_id, "hair_color2", create->getType_EQ2_Color_ByName("hair_color2"));
|
|
SaveCharacterColors(char_id, "hair_highlight", create->getType_EQ2_Color_ByName("hair_highlight"));
|
|
SaveCharacterColors(char_id, "hair_type_color", create->getType_EQ2_Color_ByName("hair_type_color"));
|
|
SaveCharacterColors(char_id, "hair_type_highlight_color", create->getType_EQ2_Color_ByName("hair_type_highlight_color"));
|
|
SaveCharacterColors(char_id, "hair_face_color", create->getType_EQ2_Color_ByName("hair_face_color"));
|
|
SaveCharacterColors(char_id, "hair_face_highlight_color", create->getType_EQ2_Color_ByName("hair_face_highlight_color"));
|
|
SaveCharacterColors(char_id, "wing_color1", create->getType_EQ2_Color_ByName("wing_color1"));
|
|
SaveCharacterColors(char_id, "wing_color2", create->getType_EQ2_Color_ByName("wing_color2"));
|
|
SaveCharacterColors(char_id, "shirt_color", create->getType_EQ2_Color_ByName("shirt_color"));
|
|
SaveCharacterColors(char_id, "unknown_chest_color", create->getType_EQ2_Color_ByName("unknown_chest_color"));
|
|
SaveCharacterColors(char_id, "pants_color", create->getType_EQ2_Color_ByName("pants_color"));
|
|
SaveCharacterColors(char_id, "unknown_legs_color", create->getType_EQ2_Color_ByName("unknown_legs_color"));
|
|
SaveCharacterColors(char_id, "unknown9", create->getType_EQ2_Color_ByName("unknown9"));
|
|
|
|
SaveCharacterColors(char_id, "soga_skin_color", create->getType_EQ2_Color_ByName("soga_skin_color"));
|
|
SaveCharacterColors(char_id, "soga_model_color", create->getType_EQ2_Color_ByName("soga_model_color"));
|
|
SaveCharacterColors(char_id, "soga_eye_color", create->getType_EQ2_Color_ByName("soga_eye_color"));
|
|
SaveCharacterColors(char_id, "soga_hair_color1", create->getType_EQ2_Color_ByName("soga_hair_color1"));
|
|
SaveCharacterColors(char_id, "soga_hair_color2", create->getType_EQ2_Color_ByName("soga_hair_color2"));
|
|
SaveCharacterColors(char_id, "soga_hair_highlight", create->getType_EQ2_Color_ByName("soga_hair_highlight"));
|
|
SaveCharacterColors(char_id, "soga_hair_type_color", create->getType_EQ2_Color_ByName("soga_hair_type_color"));
|
|
SaveCharacterColors(char_id, "soga_hair_type_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_type_highlight_color"));
|
|
SaveCharacterColors(char_id, "soga_hair_face_color", create->getType_EQ2_Color_ByName("soga_hair_face_color"));
|
|
SaveCharacterColors(char_id, "soga_hair_face_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_face_highlight_color"));
|
|
SaveCharacterColors(char_id, "soga_wing_color1", create->getType_EQ2_Color_ByName("soga_wing_color1"));
|
|
SaveCharacterColors(char_id, "soga_wing_color2", create->getType_EQ2_Color_ByName("soga_wing_color2"));
|
|
SaveCharacterColors(char_id, "soga_shirt_color", create->getType_EQ2_Color_ByName("soga_shirt_color"));
|
|
SaveCharacterColors(char_id, "soga_unknown_chest_color", create->getType_EQ2_Color_ByName("soga_unknown_chest_color"));
|
|
SaveCharacterColors(char_id, "soga_pants_color", create->getType_EQ2_Color_ByName("soga_pants_color"));
|
|
SaveCharacterColors(char_id, "soga_unknown_legs_color", create->getType_EQ2_Color_ByName("soga_unknown_legs_color"));
|
|
SaveCharacterColors(char_id, "soga_unknown13", create->getType_EQ2_Color_ByName("soga_unknown13"));
|
|
|
|
SaveCharacterFloats(char_id, "soga_eye_type", create->getType_float_ByName("soga_eyes2", 0), create->getType_float_ByName("soga_eyes2", 1), create->getType_float_ByName("soga_eyes2", 2));
|
|
SaveCharacterFloats(char_id, "soga_ear_type", create->getType_float_ByName("soga_ears", 0), create->getType_float_ByName("soga_ears", 1), create->getType_float_ByName("soga_ears", 2));
|
|
SaveCharacterFloats(char_id, "soga_eye_brow_type", create->getType_float_ByName("soga_eye_brows", 0), create->getType_float_ByName("soga_eye_brows", 1), create->getType_float_ByName("soga_eye_brows", 2));
|
|
SaveCharacterFloats(char_id, "soga_cheek_type", create->getType_float_ByName("soga_cheeks", 0), create->getType_float_ByName("soga_cheeks", 1), create->getType_float_ByName("soga_cheeks", 2));
|
|
SaveCharacterFloats(char_id, "soga_lip_type", create->getType_float_ByName("soga_lips", 0), create->getType_float_ByName("soga_lips", 1), create->getType_float_ByName("soga_lips", 2));
|
|
SaveCharacterFloats(char_id, "soga_chin_type", create->getType_float_ByName("soga_chin", 0), create->getType_float_ByName("soga_chin", 1), create->getType_float_ByName("soga_chin", 2));
|
|
SaveCharacterFloats(char_id, "soga_nose_type", create->getType_float_ByName("soga_nose", 0), create->getType_float_ByName("soga_nose", 1), create->getType_float_ByName("soga_nose", 2));
|
|
}
|
|
|
|
SaveCharacterFloats(char_id, "eye_type", create->getType_float_ByName("eyes2", 0), create->getType_float_ByName("eyes2", 1), create->getType_float_ByName("eyes2", 2));
|
|
SaveCharacterFloats(char_id, "ear_type", create->getType_float_ByName("ears", 0), create->getType_float_ByName("ears", 1), create->getType_float_ByName("ears", 2));
|
|
SaveCharacterFloats(char_id, "eye_brow_type", create->getType_float_ByName("eye_brows", 0), create->getType_float_ByName("eye_brows", 1), create->getType_float_ByName("eye_brows", 2));
|
|
SaveCharacterFloats(char_id, "cheek_type", create->getType_float_ByName("cheeks", 0), create->getType_float_ByName("cheeks", 1), create->getType_float_ByName("cheeks", 2));
|
|
SaveCharacterFloats(char_id, "lip_type", create->getType_float_ByName("lips", 0), create->getType_float_ByName("lips", 1), create->getType_float_ByName("lips", 2));
|
|
SaveCharacterFloats(char_id, "chin_type", create->getType_float_ByName("chin", 0), create->getType_float_ByName("chin", 1), create->getType_float_ByName("chin", 2));
|
|
SaveCharacterFloats(char_id, "nose_type", create->getType_float_ByName("nose", 0), create->getType_float_ByName("nose", 1), create->getType_float_ByName("nose", 2));
|
|
SaveCharacterFloats(char_id, "body_size", create->getType_float_ByName("body_size", 0), 0, 0);
|
|
|
|
return last_insert_id;
|
|
}
|
|
|
|
// Delete a character from the database
|
|
bool DeleteCharacter(int32 account_id, int32 character_id, int32 server_id)
|
|
{
|
|
Query query;
|
|
string delete_char = string("delete from login_characters where char_id=%i and account_id=%i and server_id=%i");
|
|
query.RunQuery2(Q_DELETE, delete_char.c_str(), character_id, account_id, server_id);
|
|
|
|
return query.GetAffectedRows() > 0;
|
|
}
|
|
|
|
// Get character name by character ID, server ID, and account ID
|
|
string GetCharacterName(int32 char_id, int32 server_id, int32 account_id)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_characters where char_id=%lu and server_id=%lu and account_id=%lu and deleted = 0 limit 1", char_id, server_id, account_id);
|
|
|
|
if(result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
return string(row[0]);
|
|
}
|
|
return string("");
|
|
}
|
|
|
|
// Update character's last played timestamp
|
|
bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set unix_timestamp=%lu where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), timestamp_update, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Update character's level
|
|
bool UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set level=%i where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), in_level, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterLevel query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Update character's race information
|
|
bool UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set race_type=%i, race=%i where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), in_racetype, in_race, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterRace query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Update character's current zone
|
|
bool UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set current_zone_id=%i where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), zone_id, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterZone query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Update character's class
|
|
bool UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set class=%i where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), in_class, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Update character's gender
|
|
bool UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set gender=%i where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), in_gender, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterGender query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Update character's name
|
|
bool UpdateCharacterName(int32 account_id, int32 character_id, char* newName, int32 server_id)
|
|
{
|
|
Query query;
|
|
string update_chars = string("update login_characters set name='%s' where char_id=%lu and account_id=%lu and server_id=%lu");
|
|
query.RunQuery2(Q_UPDATE, update_chars.c_str(), newName, character_id, account_id, server_id);
|
|
|
|
if(!query.GetAffectedRows()) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterName query '%s': %s", query.GetQuery(), query.GetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Load account credentials and authenticate user login
|
|
LoginAccount* LoadAccount(const char* name, const char* password, bool attemptAccountCreation = true)
|
|
{
|
|
LoginAccount* acct = nullptr;
|
|
Query query;
|
|
query.escaped_name = getEscapeString(name);
|
|
query.escaped_pass = getEscapeString(password);
|
|
time_t now = time(0);
|
|
MYSQL_ROW row;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s' and passwd=sha2('%s',512)", query.escaped_name, query.escaped_pass);
|
|
|
|
if(!result) return nullptr;
|
|
|
|
if (mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
int32 id = atol(row[0]);
|
|
acct = new LoginAccount(id, name, password);
|
|
acct->setAuthenticated(true);
|
|
}
|
|
else if(mysql_num_rows(result) > 0) {
|
|
LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoginAccount: more than one account returned for '%s'", name);
|
|
}
|
|
else if (attemptAccountCreation && !GetAccountIDByName(name)) {
|
|
Query newquery;
|
|
newquery.RunQuery2(Q_INSERT, "insert into account set name='%s',passwd=sha2('%s',512), created_date=%i", query.escaped_name, query.escaped_pass, now);
|
|
// re-run the query for select only not account creation
|
|
return LoadAccount(name, password, false);
|
|
}
|
|
|
|
return acct;
|
|
}
|
|
|
|
// Get account ID by account name
|
|
int32 GetAccountIDByName(const char* name)
|
|
{
|
|
int32 id = 0;
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name);
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s'", query.escaped_name);
|
|
|
|
if (result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
id = atoi(row[0]);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
// Validate world server account credentials
|
|
int32 CheckServerAccount(char* name, char* passwd)
|
|
{
|
|
int32 id = 0;
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name);
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lower(password), id from login_worldservers where account='%s' and disabled = 0", query.escaped_name);
|
|
|
|
LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccount Account=%s\nSHA=%s", (char*)query.escaped_name, passwd);
|
|
|
|
if(result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResult Account=%s\nPassword=%s", (char*)query.escaped_name, (row && row[0]) ? row[0] : "(NULL)");
|
|
|
|
if (memcmp(row[0], passwd, strnlen(row[0], 256)) == 0) {
|
|
LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResultMatch Account=%s", (char*)query.escaped_name);
|
|
id = atoi(row[1]);
|
|
}
|
|
}
|
|
return id;
|
|
}
|
|
|
|
// Check if world server account is disabled
|
|
bool IsServerAccountDisabled(char* name)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name);
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_worldservers where account='%s' and disabled = 1", query.escaped_name);
|
|
|
|
LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerAccountDisabled Account=%s", (char*)query.escaped_name);
|
|
|
|
if(result && mysql_num_rows(result) > 0) {
|
|
row = mysql_fetch_row(result);
|
|
LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerAccountDisabled Match Account=%s", (char*)query.escaped_name);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check if IP address is banned
|
|
bool IsIPBanned(char* ipaddr)
|
|
{
|
|
if(!ipaddr) return false;
|
|
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ip from login_bannedips where '%s' LIKE CONCAT(ip ,'%%')", ipaddr);
|
|
|
|
LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerIPBanned IPPartial=%s", (char*)ipaddr);
|
|
|
|
if(result && mysql_num_rows(result) > 0) {
|
|
row = mysql_fetch_row(result);
|
|
LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerIPBanned Match IPBan=%s", row[0]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Load all configured world server accounts
|
|
void GetServerAccounts(vector<LWorld*>* server_list)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, account, name, admin_id from login_worldservers");
|
|
|
|
while((row = mysql_fetch_row(result))) {
|
|
LWorld* world = new LWorld(atol(row[0]), row[1], row[2], atoi(row[3]));
|
|
world->SetID(world->GetAccountID());
|
|
server_list->push_back(world);
|
|
}
|
|
}
|
|
|
|
// Save client log messages for debugging and support
|
|
void SaveClientLog(const char* type, const char* message, const char* player_name, int16 version)
|
|
{
|
|
Query query;
|
|
query.escaped_data1 = getEscapeString(message);
|
|
query.escaped_name = getEscapeString(player_name);
|
|
query.RunQuery2(Q_INSERT, "insert into log_messages (type, message, name, version) values('%s', '%s', '%s', %i)", type, query.escaped_data1, query.escaped_name, version);
|
|
}
|
|
|
|
// Verify character deletion request by matching account, character, and name
|
|
bool VerifyDelete(int32 account_id, int32 character_id, const char* name)
|
|
{
|
|
Query query;
|
|
query.escaped_name = getEscapeString(name);
|
|
query.RunQuery2(Q_UPDATE, "update login_characters set deleted = 1 where char_id=%i and account_id=%i and name='%s'", character_id, account_id, query.escaped_name);
|
|
|
|
return query.GetAffectedRows() == 1;
|
|
}
|
|
|
|
// Get world server name by server ID
|
|
char* GetServerAccountName(int32 id)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
char* name = nullptr;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_worldservers where id=%lu", id);
|
|
|
|
if(result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
if(strlen(row[0]) > 0) {
|
|
name = new char[strlen(row[0])+1];
|
|
strcpy(name, row[0]);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
// Get race type ID by race name, create if not found
|
|
int32 GetRaceID(char* name)
|
|
{
|
|
int32 ret = 1487;
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name);
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_type from login_races where name='%s'", query.escaped_name);
|
|
|
|
if(result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
ret = atol(row[0]);
|
|
}
|
|
else if(!result || mysql_num_rows(result) == 0) {
|
|
UpdateRaceID(query.escaped_name);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Add new race to race table if not exists
|
|
void UpdateRaceID(char* name)
|
|
{
|
|
Query query;
|
|
query.RunQuery2(Q_UPDATE, "insert into login_races (name) values('%s')", name);
|
|
}
|
|
|
|
// Validate client version against allowed versions
|
|
bool CheckVersion(char* in_version)
|
|
{
|
|
Query query;
|
|
query.escaped_data1 = getEscapeString(in_version);
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_versions where version='%s' or version='*'", query.escaped_data1);
|
|
|
|
return result && mysql_num_rows(result) > 0;
|
|
}
|
|
|
|
// Get latest table versions for client update system
|
|
void GetLatestTableVersions(LatestTableVersions* table_versions)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, max(version) from login_table_versions group by name order by id");
|
|
|
|
if(!result || mysql_num_rows(result) <= 0) return;
|
|
|
|
table_versions->SetTableSize(mysql_num_rows(result));
|
|
|
|
while((row = mysql_fetch_row(result))) {
|
|
if(VerifyDataTable(row[0]))
|
|
table_versions->AddTable(row[0], atoi(row[1]), GetDataVersion(row[0]));
|
|
else
|
|
table_versions->AddTable(row[0], atoi(row[1]), 0);
|
|
}
|
|
}
|
|
|
|
// Verify if table exists in downloadable tables list
|
|
bool VerifyDataTable(char* name)
|
|
{
|
|
Query query;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT table_name from download_tables where table_name='%s'", name);
|
|
return result && mysql_num_rows(result) > 0;
|
|
}
|
|
|
|
// Get column names for a table, excluding internal version column
|
|
string GetColumnNames(char* name)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
string columns = "(";
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "show columns from %s", name);
|
|
|
|
if(result && mysql_num_rows(result) > 0) {
|
|
int16 i = 0;
|
|
while((row = mysql_fetch_row(result))) {
|
|
if(strcmp(row[0], "table_data_version") != 0) {
|
|
if(i > 0) columns.append(",");
|
|
columns.append(row[0]);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
columns.append(") ");
|
|
return columns;
|
|
}
|
|
|
|
// Get table data for client downloads with version filtering
|
|
TableDataQuery* GetTableDataQuery(int32 server_ip, char* name, int16 version)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name);
|
|
TableDataQuery* table_query = nullptr;
|
|
MYSQL_RES* result = nullptr;
|
|
string columns;
|
|
|
|
if(VerifyDataTable(query.escaped_name)) {
|
|
result = query.RunQuery2(Q_SELECT, "SELECT * from %s where table_data_version > %i", query.escaped_name, version);
|
|
columns = GetColumnNames(query.escaped_name);
|
|
}
|
|
|
|
if(result && mysql_num_rows(result) > 0) {
|
|
table_query = new TableDataQuery(query.escaped_name);
|
|
table_query->num_queries = mysql_num_rows(result);
|
|
table_query->columns_size = columns.length() + 1;
|
|
table_query->columns = new char[table_query->columns_size + 1];
|
|
table_query->version = GetDataVersion(query.escaped_name);
|
|
strcpy(table_query->columns, (char*)columns.c_str());
|
|
|
|
string query_data;
|
|
MYSQL_FIELD* field;
|
|
int* int_list = new int[mysql_num_fields(result)];
|
|
int16 ndx = 0;
|
|
|
|
while((field = mysql_fetch_field(result))) {
|
|
int_list[ndx] = IS_NUM(field->type);
|
|
if(strcmp(field->name,"table_data_version") == 0)
|
|
int_list[ndx] = 2;
|
|
ndx++;
|
|
}
|
|
|
|
ndx = 0;
|
|
while((row = mysql_fetch_row(result))) {
|
|
query_data = "";
|
|
for(int i = 0; i < mysql_num_fields(result); i++) {
|
|
if(int_list[i] < 2) {
|
|
if(i > 0) query_data.append(",");
|
|
|
|
if(!int_list[i]) {
|
|
query_data.append("'").append(getEscapeString(row[i])).append("'");
|
|
}
|
|
else
|
|
query_data.append(row[i]);
|
|
}
|
|
}
|
|
|
|
TableData* new_query = new TableData;
|
|
new_query->size = query_data.length() + 1;
|
|
new_query->query = new char[query_data.length() + 1];
|
|
strcpy(new_query->query, query_data.c_str());
|
|
table_query->queries.push_back(new_query);
|
|
ndx++;
|
|
}
|
|
safe_delete_array(int_list);
|
|
}
|
|
else {
|
|
string query2 = string("The user tried to download the following table: ").append(query.escaped_name);
|
|
SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Data", server_ip);
|
|
}
|
|
return table_query;
|
|
}
|
|
|
|
// Get table update queries for client patching system
|
|
TableQuery* GetLatestTableQuery(int32 server_ip, char* name, int16 version)
|
|
{
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
query.escaped_name = getEscapeString(name);
|
|
TableQuery* table_query = nullptr;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT query, version from login_table_versions where name = '%s' and version>=%i order by version", query.escaped_name, version + 1);
|
|
|
|
if(result && mysql_num_rows(result) > 0) {
|
|
table_query = new TableQuery;
|
|
|
|
while((row = mysql_fetch_row(result))) {
|
|
char* rowdata = row[0];
|
|
if(strstr(rowdata, ";")) {
|
|
char* token = strtok(rowdata,";");
|
|
while(token) {
|
|
char* new_query = new char[strlen(token) + 1];
|
|
strcpy(new_query, token);
|
|
table_query->AddQuery(new_query);
|
|
token = strtok(nullptr, ";");
|
|
}
|
|
}
|
|
else
|
|
table_query->AddQuery(rowdata);
|
|
|
|
table_query->latest_version = atoi(row[1]);
|
|
}
|
|
|
|
strcpy(table_query->tablename, name);
|
|
table_query->your_version = version;
|
|
}
|
|
else {
|
|
string query2 = string("The following was the DB Query: ").append(query.GetQuery());
|
|
SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Query", server_ip);
|
|
}
|
|
return table_query;
|
|
}
|
|
|
|
// Get current data version for a table
|
|
sint16 GetDataVersion(char* name)
|
|
{
|
|
Query query;
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(table_data_version) from %s", name);
|
|
sint16 ret_version = 0;
|
|
|
|
if(result && mysql_num_rows(result) > 0) {
|
|
MYSQL_ROW row;
|
|
row = mysql_fetch_row(result);
|
|
if(row[0]) ret_version = atoi(row[0]);
|
|
}
|
|
return ret_version;
|
|
}
|
|
|
|
// Clean up old world server statistics older than 24 hours
|
|
void RemoveOldWorldServerStats()
|
|
{
|
|
Query query;
|
|
query.RunQuery2(Q_DELETE, "delete from login_worldstats where (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 86400");
|
|
}
|
|
|
|
// Update world server statistics including player count and zone count
|
|
void UpdateWorldServerStats(LWorld* world, sint32 status)
|
|
{
|
|
Query query;
|
|
query.RunQuery2(Q_INSERT, "insert into login_worldstats (world_id, world_status, current_players, current_zones, last_update, world_max_level) values(%u, %i, %i, %i, NOW(), %i) ON DUPLICATE KEY UPDATE current_players=%i,current_zones=%i,world_max_level=%i,world_status=%i,last_update=NOW()",
|
|
world->GetAccountID(), status, world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), status);
|
|
|
|
string update_stats = string("update login_worldservers set lastseen=%u where id=%i");
|
|
query.RunQuery2(Q_UPDATE, update_stats.c_str(), Timer::GetUnixTimeStamp(), world->GetAccountID());
|
|
}
|
|
|
|
// Reset world server connection time if server has been offline too long
|
|
bool ResetWorldServerStatsConnectedTime(LWorld* world)
|
|
{
|
|
if(!world || world->GetAccountID() == 0) return false;
|
|
|
|
Query query;
|
|
string update_stats = string("update login_worldstats set connected_time=now() where world_id=%i and (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 300");
|
|
query.RunQuery2(Q_UPDATE, update_stats.c_str(), world->GetAccountID());
|
|
|
|
return true;
|
|
}
|
|
|
|
// Reset all world server statistics to offline state
|
|
void ResetWorldStats()
|
|
{
|
|
Query query;
|
|
string update_stats = string("update login_worldstats set world_status=-4, current_players=0, current_zones=0");
|
|
query.RunQuery2(update_stats.c_str(), Q_UPDATE);
|
|
}
|
|
|
|
// Save bug report submitted by players
|
|
void SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id)
|
|
{
|
|
Query query;
|
|
string bug_report = string("insert into bugs (world_id, category, subcategory, causes_crash, reproducible, summary, description, version, player, account_id, spawn_name, spawn_id, zone_id) values(%lu, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %lu, '%s', %lu, %lu)");
|
|
query.RunQuery2(Q_INSERT, bug_report.c_str(), world_id, database.getSafeEscapeString(category).c_str(), database.getSafeEscapeString(subcategory).c_str(),
|
|
database.getSafeEscapeString(causes_crash).c_str(), database.getSafeEscapeString(reproducible).c_str(), database.getSafeEscapeString(summary).c_str(),
|
|
database.getSafeEscapeString(description).c_str(), database.getSafeEscapeString(version).c_str(), database.getSafeEscapeString(player).c_str(), account_id,
|
|
database.getSafeEscapeString(spawn_name).c_str(), spawn_id, zone_id);
|
|
FixBugReport();
|
|
}
|
|
|
|
// Fix URL encoding in bug reports by converting hex codes to characters
|
|
void FixBugReport()
|
|
{
|
|
Query query;
|
|
string bug_report = string("update bugs set description = REPLACE(description,SUBSTRING(description,INSTR(description,'%'), 3),char(CONV(SUBSTRING(description,INSTR(description,'%')+1, 2), 16, 10))), summary = REPLACE(summary,SUBSTRING(summary,INSTR(summary,'%'), 3),char(CONV(SUBSTRING(summary,INSTR(summary,'%')+1, 2), 16, 10)))");
|
|
query.RunQuery2(bug_report.c_str(), Q_UPDATE);
|
|
}
|
|
|
|
// Update world server IP address in database
|
|
void UpdateWorldIPAddress(int32 world_id, int32 address)
|
|
{
|
|
struct in_addr in;
|
|
in.s_addr = address;
|
|
Query query;
|
|
query.RunQuery2(Q_UPDATE, "update login_worldservers set ip_address='%s' where id=%lu", inet_ntoa(in), world_id);
|
|
}
|
|
|
|
// Update player account IP address for tracking
|
|
void UpdateAccountIPAddress(int32 account_id, int32 address)
|
|
{
|
|
struct in_addr in;
|
|
in.s_addr = address;
|
|
Query query;
|
|
query.RunQuery2(Q_UPDATE, "update account set ip_address='%s' where id=%lu", inet_ntoa(in), account_id);
|
|
}
|
|
|
|
// Get maximum characters allowed per account from configuration
|
|
int8 GetMaxCharsSetting()
|
|
{
|
|
int8 max_chars = 7; // live defaults to 7 for GOLD members
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select config_value from login_config where config_name='max_characters_per_account'");
|
|
|
|
if (result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
if (row[0]) max_chars = atoi(row[0]);
|
|
}
|
|
return max_chars;
|
|
}
|
|
|
|
// Calculate account bonus based on max level characters
|
|
int16 GetAccountBonus(int32 acct_id)
|
|
{
|
|
int32 bonus = 0;
|
|
int16 world_id = 0;
|
|
Query query;
|
|
MYSQL_ROW row;
|
|
Query query2;
|
|
MYSQL_ROW row2;
|
|
|
|
// get the world ID for the character. TODO: Support multi server characters.
|
|
MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "select server_id from login_characters where account_id=%i", acct_id);
|
|
|
|
if (result2 && mysql_num_rows(result2) >= 1) {
|
|
row2 = mysql_fetch_row(result2);
|
|
if (row2[0]) world_id = atoi(row2[0]);
|
|
}
|
|
|
|
// pull all characters greater than the max level from the server
|
|
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM login_characters WHERE LEVEL >= (select world_max_level from login_worldstats where world_id=%i) AND account_id=%i", world_id, acct_id);
|
|
|
|
if (result && mysql_num_rows(result) == 1) {
|
|
row = mysql_fetch_row(result);
|
|
if(row[0]) bonus = atoi(row[0]);
|
|
}
|
|
return bonus;
|
|
}
|
|
|
|
// Update world server client version information
|
|
void UpdateWorldVersion(int32 world_id, char* version)
|
|
{
|
|
Query query;
|
|
query.RunQuery2(Q_UPDATE, "update login_worldservers set login_version='%s' where id=%u", version, world_id);
|
|
}
|
|
|
|
// Update account's last used client version
|
|
void UpdateAccountClientDataVersion(int32 account_id, int16 version)
|
|
{
|
|
Query query;
|
|
query.RunQuery2(Q_UPDATE, "UPDATE account SET last_client_version='%i' WHERE id = %u", version, account_id);
|
|
}
|
|
|
|
// Save character portrait picture data (base64 encoded)
|
|
void SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture)
|
|
{
|
|
stringstream ss_hex;
|
|
stringstream ss_query;
|
|
ss_hex.flags(ios::hex);
|
|
|
|
for (int32 i = 0; i < picture_size; i++)
|
|
ss_hex << setfill('0') << setw(2) << (int32)picture[i];
|
|
|
|
ss_query << "INSERT INTO `ls_character_picture` (`server_id`, `account_id`, `character_id`, `picture`) VALUES ("
|
|
<< server_id << ", " << account_id << ", " << character_id << ", '" << ss_hex.str()
|
|
<< "') ON DUPLICATE KEY UPDATE `picture` = '" << ss_hex.str() << "'";
|
|
|
|
if (!dbLogin.Query(ss_query.str().c_str()))
|
|
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", dbLogin.GetError(), dbLogin.GetErrorMsg());
|
|
}
|
|
|
|
// Database connection instance for login operations
|
|
DatabaseNew dbLogin;
|
|
}; |