/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2018 Hercules Dev Team * Copyright (C) Athena Dev Teams * * Hercules 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 . */ #define HERCULES_CORE #include "config/core.h" // CONSOLE_INPUT #include "account.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/console.h" #include "common/memmgr.h" #include "common/mmo.h" #include "common/nullpo.h" #include "common/showmsg.h" #include "common/socket.h" #include "common/sql.h" #include "common/strlib.h" #include /// global defines #define ACCOUNT_SQL_DB_VERSION 20110114 static struct account_interface account_s; struct account_interface *account; /// public constructor static AccountDB *account_db_sql(void) { AccountDB_SQL* db = (AccountDB_SQL*)aCalloc(1, sizeof(AccountDB_SQL)); // set up the vtable db->vtable.init = account->db_sql_init; db->vtable.destroy = account->db_sql_destroy; db->vtable.get_property = account->db_sql_get_property; db->vtable.set_property = account->db_sql_set_property; db->vtable.save = account->db_sql_save; db->vtable.create = account->db_sql_create; db->vtable.remove = account->db_sql_remove; db->vtable.load_num = account->db_sql_load_num; db->vtable.load_str = account->db_sql_load_str; db->vtable.iterator = account->db_sql_iterator; // initialize to default values db->accounts = NULL; // Sql settings safestrncpy(db->db_hostname, "127.0.0.1", sizeof(db->db_hostname)); db->db_port = 3306; safestrncpy(db->db_username, "ragnarok", sizeof(db->db_username)); safestrncpy(db->db_password, "ragnarok", sizeof(db->db_password)); safestrncpy(db->db_database, "ragnarok", sizeof(db->db_database)); safestrncpy(db->codepage, "", sizeof(db->codepage)); // other settings db->case_sensitive = false; safestrncpy(db->account_db, "login", sizeof(db->account_db)); safestrncpy(db->global_acc_reg_num_db, "global_acc_reg_num_db", sizeof(db->global_acc_reg_num_db)); safestrncpy(db->global_acc_reg_str_db, "global_acc_reg_str_db", sizeof(db->global_acc_reg_str_db)); return &db->vtable; } /* ------------------------------------------------------------------------- */ /// establishes database connection static bool account_db_sql_init(AccountDB *self) { AccountDB_SQL* db = (AccountDB_SQL*)self; struct Sql *sql_handle = NULL; nullpo_ret(db); db->accounts = SQL->Malloc(); sql_handle = db->accounts; if (SQL_ERROR == SQL->Connect(sql_handle, db->db_username, db->db_password, db->db_hostname, db->db_port, db->db_database)) { Sql_ShowDebug(sql_handle); SQL->Free(db->accounts); db->accounts = NULL; return false; } if (db->codepage[0] != '\0' && SQL_ERROR == SQL->SetEncoding(sql_handle, db->codepage)) Sql_ShowDebug(sql_handle); Sql_HerculesUpdateCheck(db->accounts); #ifdef CONSOLE_INPUT console->input->setSQL(db->accounts); #endif return true; } /// disconnects from database static void account_db_sql_destroy(AccountDB *self) { AccountDB_SQL* db = (AccountDB_SQL*)self; nullpo_retv(db); SQL->Free(db->accounts); db->accounts = NULL; aFree(db); } /// Gets a property from this database. static bool account_db_sql_get_property(AccountDB *self, const char *key, char *buf, size_t buflen) { /* TODO: * This functionality is not being used as of now, it was removed in * commit 5479f9631f8579d03fbfd14d8a49c7976226a156, it is meant to get * engine properties when more than one engine is available. I'll * re-add it as soon as I can, following the new standards. If anyone * is interested in this functionality you can contact me in our boards * and I'll try to add it sooner (Pan) [Panikon] */ #if 0 AccountDB_SQL* db = (AccountDB_SQL*)self; const char* signature; nullpo_ret(db); nullpo_ret(key); nullpo_ret(buf); signature = "engine."; if( strncmpi(key, signature, strlen(signature)) == 0 ) { key += strlen(signature); if( strcmpi(key, "name") == 0 ) safesnprintf(buf, buflen, "sql"); else if( strcmpi(key, "version") == 0 ) safesnprintf(buf, buflen, "%d", ACCOUNT_SQL_DB_VERSION); else if( strcmpi(key, "comment") == 0 ) safesnprintf(buf, buflen, "SQL Account Database"); else return false;// not found return true; } signature = "account.sql."; if( strncmpi(key, signature, strlen(signature)) == 0 ) { key += strlen(signature); if( strcmpi(key, "db_hostname") == 0 ) safesnprintf(buf, buflen, "%s", db->db_hostname); else if( strcmpi(key, "db_port") == 0 ) safesnprintf(buf, buflen, "%d", db->db_port); else if( strcmpi(key, "db_username") == 0 ) safesnprintf(buf, buflen, "%s", db->db_username); else if( strcmpi(key, "db_password") == 0 ) safesnprintf(buf, buflen, "%s", db->db_password); else if( strcmpi(key, "db_database") == 0 ) safesnprintf(buf, buflen, "%s", db->db_database); else if( strcmpi(key, "codepage") == 0 ) safesnprintf(buf, buflen, "%s", db->codepage); else if( strcmpi(key, "case_sensitive") == 0 ) safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); else if( strcmpi(key, "account_db") == 0 ) safesnprintf(buf, buflen, "%s", db->account_db); else if( strcmpi(key, "global_acc_reg_str_db") == 0 ) safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_db); else if( strcmpi(key, "global_acc_reg_num_db") == 0 ) safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_db); else return false;// not found return true; } return false;// not found #endif // 0 return false; } /** * Reads the 'inter_configuration' config file and initializes required variables. * * @param db Self. * @param filename Path to configuration file * @param imported Whether the current config is imported from another file. * * @retval false in case of error. */ static bool account_db_read_inter(AccountDB_SQL *db, const char *filename, bool imported) { struct config_t config; struct config_setting_t *setting = NULL; nullpo_retr(false, db); nullpo_retr(false, filename); if (!libconfig->load_file(&config, filename)) return false; // Error message is already shown by libconfig->load_file if ((setting = libconfig->lookup(&config, "inter_configuration/database_names")) == NULL) { libconfig->destroy(&config); if (imported) return true; ShowError("account_db_sql_set_property: inter_configuration/database_names was not found!\n"); return false; } libconfig->setting_lookup_mutable_string(setting, "account_db", db->account_db, sizeof(db->account_db)); if ((setting = libconfig->lookup(&config, "inter_configuration/database_names/registry")) == NULL) { libconfig->destroy(&config); if (imported) return true; ShowError("account_db_sql_set_property: inter_configuration/database_names/registry was not found!\n"); return false; } libconfig->setting_lookup_mutable_string(setting, "global_acc_reg_str_db", db->global_acc_reg_str_db, sizeof(db->global_acc_reg_str_db)); libconfig->setting_lookup_mutable_string(setting, "global_acc_reg_num_db", db->global_acc_reg_num_db, sizeof(db->global_acc_reg_num_db)); // TODO: Proper import mechanism for this file libconfig->destroy(&config); return true; } /** * Loads the sql configuration. * * @param self Self. * @param config The current config being parsed. * @param imported Whether the current config is imported from another file. * * @retval false in case of error. */ static bool account_db_sql_set_property(AccountDB *self, struct config_t *config, bool imported) { AccountDB_SQL* db = (AccountDB_SQL*)self; struct config_setting_t *setting = NULL; nullpo_ret(db); nullpo_ret(config); if ((setting = libconfig->lookup(config, "login_configuration/account/sql_connection")) == NULL) { if (imported) return true; ShowError("account_db_sql_set_property: login_configuration/account/sql_connection was not found!\n"); ShowWarning("account_db_sql_set_property: Defaulting sql_connection...\n"); return false; } libconfig->setting_lookup_mutable_string(setting, "db_hostname", db->db_hostname, sizeof(db->db_hostname)); libconfig->setting_lookup_mutable_string(setting, "db_username", db->db_username, sizeof(db->db_username)); libconfig->setting_lookup_mutable_string(setting, "db_password", db->db_password, sizeof(db->db_password)); libconfig->setting_lookup_mutable_string(setting, "db_database", db->db_database, sizeof(db->db_database)); libconfig->setting_lookup_mutable_string(setting, "codepage", db->codepage, sizeof(db->codepage)); // FIXME: Why do we need both codepage and default_codepage? libconfig->setting_lookup_uint16(setting, "db_port", &db->db_port); libconfig->setting_lookup_bool_real(setting, "case_sensitive", &db->case_sensitive); account->db_read_inter(db, "conf/common/inter-server.conf", imported); return true; } /// create a new account entry /// If acc->account_id is -1, the account id will be auto-generated, /// and its value will be written to acc->account_id if everything succeeds. static bool account_db_sql_create(AccountDB *self, struct mmo_account *acc) { AccountDB_SQL* db = (AccountDB_SQL*)self; struct Sql *sql_handle; // decide on the account id to assign int account_id; nullpo_ret(db); nullpo_ret(acc); sql_handle = db->accounts; if( acc->account_id != -1 ) {// caller specifies it manually account_id = acc->account_id; } else {// ask the database char* data; size_t len; if( SQL_SUCCESS != SQL->Query(sql_handle, "SELECT MAX(`account_id`)+1 FROM `%s`", db->account_db) ) { Sql_ShowDebug(sql_handle); return false; } if( SQL_SUCCESS != SQL->NextRow(sql_handle) ) { Sql_ShowDebug(sql_handle); SQL->FreeResult(sql_handle); return false; } SQL->GetData(sql_handle, 0, &data, &len); account_id = ( data != NULL ) ? atoi(data) : 0; SQL->FreeResult(sql_handle); if( account_id < START_ACCOUNT_NUM ) account_id = START_ACCOUNT_NUM; } // zero value is prohibited if( account_id == 0 ) return false; // absolute maximum if( account_id > END_ACCOUNT_NUM ) return false; // insert the data into the database acc->account_id = account_id; return account->mmo_auth_tosql(db, acc, true); } /// delete an existing account entry + its regs static bool account_db_sql_remove(AccountDB *self, const int account_id) { AccountDB_SQL* db = (AccountDB_SQL*)self; struct Sql *sql_handle; bool result = false; nullpo_ret(db); sql_handle = db->accounts; if( SQL_SUCCESS != SQL->QueryStr(sql_handle, "START TRANSACTION") || SQL_SUCCESS != SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->account_db, account_id) || SQL_SUCCESS != SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_num_db, account_id) || SQL_SUCCESS != SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_str_db, account_id) ) Sql_ShowDebug(sql_handle); else result = true; result &= ( SQL_SUCCESS == SQL->QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); return result; } /// update an existing account with the provided new data (both account and regs) static bool account_db_sql_save(AccountDB *self, const struct mmo_account *acc) { AccountDB_SQL* db = (AccountDB_SQL*)self; return account->mmo_auth_tosql(db, acc, false); } /// retrieve data from db and store it in the provided data structure static bool account_db_sql_load_num(AccountDB *self, struct mmo_account *acc, const int account_id) { AccountDB_SQL* db = (AccountDB_SQL*)self; return account->mmo_auth_fromsql(db, acc, account_id); } /// retrieve data from db and store it in the provided data structure static bool account_db_sql_load_str(AccountDB *self, struct mmo_account *acc, const char *userid) { AccountDB_SQL* db = (AccountDB_SQL*)self; struct Sql *sql_handle; char esc_userid[2*NAME_LENGTH+1]; int account_id; char* data; nullpo_ret(db); sql_handle = db->accounts; SQL->EscapeString(sql_handle, esc_userid, userid); // get the list of account IDs for this user ID if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `userid`= %s '%s'", db->account_db, (db->case_sensitive ? "BINARY" : ""), esc_userid) ) { Sql_ShowDebug(sql_handle); return false; } if( SQL->NumRows(sql_handle) > 1 ) {// serious problem - duplicate account ShowError("account_db_sql_load_str: multiple accounts found when retrieving data for account '%s'!\n", userid); SQL->FreeResult(sql_handle); return false; } if( SQL_SUCCESS != SQL->NextRow(sql_handle) ) {// no such entry SQL->FreeResult(sql_handle); return false; } SQL->GetData(sql_handle, 0, &data, NULL); account_id = atoi(data); return account->db_sql_load_num(self, acc, account_id); } /// Returns a new forward iterator. static AccountDBIterator *account_db_sql_iterator(AccountDB *self) { AccountDB_SQL* db = (AccountDB_SQL*)self; AccountDBIterator_SQL* iter; nullpo_retr(NULL, db); iter = (AccountDBIterator_SQL*)aCalloc(1, sizeof(AccountDBIterator_SQL)); // set up the vtable iter->vtable.destroy = account->db_sql_iter_destroy; iter->vtable.next = account->db_sql_iter_next; // fill data iter->db = db; iter->last_account_id = -1; return &iter->vtable; } /// Destroys this iterator, releasing all allocated memory (including itself). static void account_db_sql_iter_destroy(AccountDBIterator *self) { AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; aFree(iter); } /// Fetches the next account in the database. static bool account_db_sql_iter_next(AccountDBIterator *self, struct mmo_account *acc) { AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; AccountDB_SQL* db; struct Sql *sql_handle; char* data; nullpo_ret(iter); db = (AccountDB_SQL*)iter->db; nullpo_ret(db); sql_handle = db->accounts; // get next account ID if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `account_id` > '%d' ORDER BY `account_id` ASC LIMIT 1", db->account_db, iter->last_account_id) ) { Sql_ShowDebug(sql_handle); return false; } if( SQL_SUCCESS == SQL->NextRow(sql_handle) && SQL_SUCCESS == SQL->GetData(sql_handle, 0, &data, NULL) && data != NULL ) {// get account data int account_id; account_id = atoi(data); if (account->mmo_auth_fromsql(db, acc, account_id)) { iter->last_account_id = account_id; SQL->FreeResult(sql_handle); return true; } } SQL->FreeResult(sql_handle); return false; } static bool account_mmo_auth_fromsql(AccountDB_SQL *db, struct mmo_account *acc, int account_id) { struct Sql *sql_handle; char* data; nullpo_ret(db); nullpo_ret(acc); sql_handle = db->accounts; // retrieve login entry for the specified account if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `account_id`,`userid`,`user_pass`,`sex`,`email`,`group_id`,`state`,`unban_time`,`expiration_time`,`logincount`,`lastlogin`,`last_ip`,`birthdate`,`character_slots`,`pincode`,`pincode_change` FROM `%s` WHERE `account_id` = %d", db->account_db, account_id ) ) { Sql_ShowDebug(sql_handle); return false; } if( SQL_SUCCESS != SQL->NextRow(sql_handle) ) {// no such entry SQL->FreeResult(sql_handle); return false; } SQL->GetData(sql_handle, 0, &data, NULL); acc->account_id = atoi(data); SQL->GetData(sql_handle, 1, &data, NULL); safestrncpy(acc->userid, data, sizeof(acc->userid)); SQL->GetData(sql_handle, 2, &data, NULL); safestrncpy(acc->pass, data, sizeof(acc->pass)); SQL->GetData(sql_handle, 3, &data, NULL); acc->sex = data[0]; SQL->GetData(sql_handle, 4, &data, NULL); safestrncpy(acc->email, data, sizeof(acc->email)); SQL->GetData(sql_handle, 5, &data, NULL); acc->group_id = atoi(data); SQL->GetData(sql_handle, 6, &data, NULL); acc->state = (unsigned int)strtoul(data, NULL, 10); SQL->GetData(sql_handle, 7, &data, NULL); acc->unban_time = atol(data); SQL->GetData(sql_handle, 8, &data, NULL); acc->expiration_time = atol(data); SQL->GetData(sql_handle, 9, &data, NULL); acc->logincount = (unsigned int)strtoul(data, NULL, 10); SQL->GetData(sql_handle, 10, &data, NULL); safestrncpy(acc->lastlogin, data != NULL ? data : "(never)", sizeof(acc->lastlogin)); SQL->GetData(sql_handle, 11, &data, NULL); safestrncpy(acc->last_ip, data, sizeof(acc->last_ip)); SQL->GetData(sql_handle, 12, &data, NULL); safestrncpy(acc->birthdate, data != NULL ? data : "0000-00-00", sizeof(acc->birthdate)); SQL->GetData(sql_handle, 13, &data, NULL); acc->char_slots = (uint8)atoi(data); SQL->GetData(sql_handle, 14, &data, NULL); safestrncpy(acc->pincode, data, sizeof(acc->pincode)); SQL->GetData(sql_handle, 15, &data, NULL); acc->pincode_change = (unsigned int)atol(data); SQL->FreeResult(sql_handle); return true; } static bool account_mmo_auth_tosql(AccountDB_SQL *db, const struct mmo_account *acc, bool is_new) { struct Sql *sql_handle; struct SqlStmt *stmt; bool result = false; nullpo_ret(db); nullpo_ret(acc); sql_handle = db->accounts; stmt = SQL->StmtMalloc(sql_handle); // try do { if( SQL_SUCCESS != SQL->QueryStr(sql_handle, "START TRANSACTION") ) { Sql_ShowDebug(sql_handle); break; } if( is_new ) {// insert into account table if( SQL_SUCCESS != SQL->StmtPrepare(stmt, "INSERT INTO `%s` (`account_id`, `userid`, `user_pass`, `sex`, `email`, `group_id`, `state`, `unban_time`, `expiration_time`, `logincount`, `lastlogin`, `last_ip`, `birthdate`, `character_slots`, `pincode`, `pincode_change`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", db->account_db) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 0, SQLDT_INT, &acc->account_id, sizeof acc->account_id) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 1, SQLDT_STRING, acc->userid, strlen(acc->userid)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 2, SQLDT_STRING, acc->pass, strlen(acc->pass)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 3, SQLDT_ENUM, &acc->sex, sizeof acc->sex) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 4, SQLDT_STRING, &acc->email, strlen(acc->email)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 5, SQLDT_INT, &acc->group_id, sizeof acc->group_id) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 6, SQLDT_UINT, &acc->state, sizeof acc->state) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 7, SQLDT_TIME, &acc->unban_time, sizeof acc->unban_time) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 8, SQLDT_TIME, &acc->expiration_time, sizeof acc->expiration_time) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 9, SQLDT_UINT, &acc->logincount, sizeof acc->logincount) || SQL_SUCCESS != (acc->lastlogin[0] < '1' || acc->lastlogin[0] > '9' ? SQL->StmtBindParam(stmt, 10, SQLDT_NULL, NULL, 0) : SQL->StmtBindParam(stmt, 10, SQLDT_STRING, &acc->lastlogin, strlen(acc->lastlogin)) ) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 11, SQLDT_STRING, &acc->last_ip, strlen(acc->last_ip)) || SQL_SUCCESS != (acc->birthdate[0] == '0' ? SQL->StmtBindParam(stmt, 12, SQLDT_NULL, NULL, 0) : SQL->StmtBindParam(stmt, 12, SQLDT_STRING, &acc->birthdate, strlen(acc->birthdate)) ) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 13, SQLDT_UINT8, &acc->char_slots, sizeof acc->char_slots) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 14, SQLDT_STRING, &acc->pincode, strlen(acc->pincode)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 15, SQLDT_UINT, &acc->pincode_change, sizeof acc->pincode_change) || SQL_SUCCESS != SQL->StmtExecute(stmt) ) { SqlStmt_ShowDebug(stmt); break; } } else {// update account table if( SQL_SUCCESS != SQL->StmtPrepare(stmt, "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`group_id`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=?,`birthdate`=?,`character_slots`=?,`pincode`=?,`pincode_change`=? WHERE `account_id` = '%d'", db->account_db, acc->account_id) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 0, SQLDT_STRING, acc->userid, strlen(acc->userid)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 1, SQLDT_STRING, acc->pass, strlen(acc->pass)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 2, SQLDT_ENUM, &acc->sex, sizeof acc->sex) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 3, SQLDT_STRING, acc->email, strlen(acc->email)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 4, SQLDT_INT, &acc->group_id, sizeof acc->group_id) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 5, SQLDT_UINT, &acc->state, sizeof acc->state) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 6, SQLDT_TIME, &acc->unban_time, sizeof acc->unban_time) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 7, SQLDT_TIME, &acc->expiration_time, sizeof acc->expiration_time) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 8, SQLDT_UINT, &acc->logincount, sizeof acc->logincount) || SQL_SUCCESS != (acc->lastlogin[0] < '1' || acc->lastlogin[0] > '9' ? SQL->StmtBindParam(stmt, 9, SQLDT_NULL, NULL, 0) : SQL->StmtBindParam(stmt, 9, SQLDT_STRING, &acc->lastlogin, strlen(acc->lastlogin)) ) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 10, SQLDT_STRING, &acc->last_ip, strlen(acc->last_ip)) || SQL_SUCCESS != (acc->birthdate[0] == '0' ? SQL->StmtBindParam(stmt, 11, SQLDT_NULL, NULL, 0) : SQL->StmtBindParam(stmt, 11, SQLDT_STRING, &acc->birthdate, strlen(acc->birthdate)) ) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 12, SQLDT_UINT8, &acc->char_slots, sizeof acc->char_slots) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 13, SQLDT_STRING, &acc->pincode, strlen(acc->pincode)) || SQL_SUCCESS != SQL->StmtBindParam(stmt, 14, SQLDT_UINT, &acc->pincode_change, sizeof acc->pincode_change) || SQL_SUCCESS != SQL->StmtExecute(stmt) ) { SqlStmt_ShowDebug(stmt); break; } } // if we got this far, everything was successful result = true; } while(0); // finally result &= ( SQL_SUCCESS == SQL->QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); SQL->StmtFree(stmt); return result; } static struct Sql *account_db_sql_up(AccountDB *self) { AccountDB_SQL* db = (AccountDB_SQL*)self; return db ? db->accounts : NULL; } static void account_mmo_save_accreg2(AccountDB *self, int fd, int account_id, int char_id) { struct Sql *sql_handle; AccountDB_SQL* db = (AccountDB_SQL*)self; int count = RFIFOW(fd, 12); nullpo_retv(db); sql_handle = db->accounts; if (count) { int cursor = 14, i; char key[SCRIPT_VARNAME_LENGTH+1], sval[254]; for (i = 0; i < count; i++) { unsigned int index; int len = RFIFOB(fd, cursor); safestrncpy(key, RFIFOP(fd, cursor + 1), min((int)sizeof(key), len)); cursor += len + 1; index = RFIFOL(fd, cursor); cursor += 4; switch (RFIFOB(fd, cursor++)) { /* int */ case 0: if( SQL_ERROR == SQL->Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%u')", db->global_acc_reg_num_db, account_id, key, index, RFIFOL(fd, cursor)) ) Sql_ShowDebug(sql_handle); cursor += 4; break; case 1: if( SQL_ERROR == SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", db->global_acc_reg_num_db, account_id, key, index) ) Sql_ShowDebug(sql_handle); break; /* str */ case 2: len = RFIFOB(fd, cursor); safestrncpy(sval, RFIFOP(fd, cursor + 1), min((int)sizeof(sval), len)); cursor += len + 1; if( SQL_ERROR == SQL->Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", db->global_acc_reg_str_db, account_id, key, index, sval) ) Sql_ShowDebug(sql_handle); break; case 3: if( SQL_ERROR == SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", db->global_acc_reg_str_db, account_id, key, index) ) Sql_ShowDebug(sql_handle); break; default: ShowError("mmo_save_accreg2: DA HOO UNKNOWN TYPE %d\n",RFIFOB(fd, cursor - 1)); return; } } } } static void account_mmo_send_accreg2(AccountDB *self, int fd, int account_id, int char_id) { struct Sql *sql_handle; AccountDB_SQL* db = (AccountDB_SQL*)self; char* data; int plen = 0; size_t len; nullpo_retv(db); sql_handle = db->accounts; if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", db->global_acc_reg_str_db, account_id) ) Sql_ShowDebug(sql_handle); WFIFOHEAD(fd, 60000 + 300); WFIFOW(fd, 0) = 0x3804; /* 0x2 = length, set prior to being sent */ WFIFOL(fd, 4) = account_id; WFIFOL(fd, 8) = char_id; WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ WFIFOB(fd, 13) = 1;/* is string type */ WFIFOW(fd, 14) = 0;/* count */ plen = 16; /** * Vessel! * * str type * { keyLength(B), key(), index(L), valLength(B), val() } **/ while ( SQL_SUCCESS == SQL->NextRow(sql_handle) ) { SQL->GetData(sql_handle, 0, &data, NULL); len = strlen(data)+1; WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */ plen += 1; safestrncpy(WFIFOP(fd,plen), data, len); plen += len; SQL->GetData(sql_handle, 1, &data, NULL); WFIFOL(fd, plen) = (unsigned int)atol(data); plen += 4; SQL->GetData(sql_handle, 2, &data, NULL); len = strlen(data)+1; WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 254 */ plen += 1; safestrncpy(WFIFOP(fd,plen), data, len); plen += len; WFIFOW(fd, 14) += 1; if( plen > 60000 ) { WFIFOW(fd, 2) = plen; WFIFOSET(fd, plen); /* prepare follow up */ WFIFOHEAD(fd, 60000 + 300); WFIFOW(fd, 0) = 0x3804; /* 0x2 = length, set prior to being sent */ WFIFOL(fd, 4) = account_id; WFIFOL(fd, 8) = char_id; WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ WFIFOB(fd, 13) = 1;/* is string type */ WFIFOW(fd, 14) = 0;/* count */ plen = 16; } } /* mark & go. */ WFIFOW(fd, 2) = plen; WFIFOSET(fd, plen); SQL->FreeResult(sql_handle); if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", db->global_acc_reg_num_db, account_id) ) Sql_ShowDebug(sql_handle); WFIFOHEAD(fd, 60000 + 300); WFIFOW(fd, 0) = 0x3804; /* 0x2 = length, set prior to being sent */ WFIFOL(fd, 4) = account_id; WFIFOL(fd, 8) = char_id; WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ WFIFOB(fd, 13) = 0;/* is int type */ WFIFOW(fd, 14) = 0;/* count */ plen = 16; /** * Vessel! * * int type * { keyLength(B), key(), index(L), value(L) } **/ while ( SQL_SUCCESS == SQL->NextRow(sql_handle) ) { SQL->GetData(sql_handle, 0, &data, NULL); len = strlen(data)+1; WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */ plen += 1; safestrncpy(WFIFOP(fd,plen), data, len); plen += len; SQL->GetData(sql_handle, 1, &data, NULL); WFIFOL(fd, plen) = (unsigned int)atol(data); plen += 4; SQL->GetData(sql_handle, 2, &data, NULL); WFIFOL(fd, plen) = atoi(data); plen += 4; WFIFOW(fd, 14) += 1; if( plen > 60000 ) { WFIFOW(fd, 2) = plen; WFIFOSET(fd, plen); /* prepare follow up */ WFIFOHEAD(fd, 60000 + 300); WFIFOW(fd, 0) = 0x3804; /* 0x2 = length, set prior to being sent */ WFIFOL(fd, 4) = account_id; WFIFOL(fd, 8) = char_id; WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ WFIFOB(fd, 13) = 0;/* is int type */ WFIFOW(fd, 14) = 0;/* count */ plen = 16; } } /* mark as complete & go. */ WFIFOB(fd, 12) = 1; WFIFOW(fd, 2) = plen; WFIFOSET(fd, plen); SQL->FreeResult(sql_handle); } void account_defaults(void) { account = &account_s; account->db_sql_up = account_db_sql_up; account->mmo_send_accreg2 = account_mmo_send_accreg2; account->mmo_save_accreg2 = account_mmo_save_accreg2; account->mmo_auth_fromsql = account_mmo_auth_fromsql; account->mmo_auth_tosql = account_mmo_auth_tosql; account->db_sql = account_db_sql; account->db_sql_init = account_db_sql_init; account->db_sql_destroy = account_db_sql_destroy; account->db_sql_get_property = account_db_sql_get_property; account->db_sql_set_property = account_db_sql_set_property; account->db_sql_create = account_db_sql_create; account->db_sql_remove = account_db_sql_remove; account->db_sql_save = account_db_sql_save; account->db_sql_load_num = account_db_sql_load_num; account->db_sql_load_str = account_db_sql_load_str; account->db_sql_iterator = account_db_sql_iterator; account->db_sql_iter_destroy = account_db_sql_iter_destroy; account->db_sql_iter_next = account_db_sql_iter_next; account->db_read_inter = account_db_read_inter; }