summaryrefslogtreecommitdiff
path: root/src/login/account.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/login/account.c')
-rw-r--r--src/login/account.c871
1 files changed, 871 insertions, 0 deletions
diff --git a/src/login/account.c b/src/login/account.c
new file mode 100644
index 000000000..66ede6cfa
--- /dev/null
+++ b/src/login/account.c
@@ -0,0 +1,871 @@
+/**
+ * This file is part of Hercules.
+ * http://herc.ws - http://github.com/HerculesWS/Hercules
+ *
+ * Copyright (C) 2012-2016 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 <http://www.gnu.org/licenses/>.
+ */
+#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 <stdlib.h>
+
+/// global defines
+#define ACCOUNT_SQL_DB_VERSION 20110114
+
+/// internal structure
+typedef struct AccountDB_SQL
+{
+ AccountDB vtable; // public interface
+
+ struct Sql *accounts; // SQL accounts storage
+
+ // Sql settings
+ char db_hostname[32];
+ uint16 db_port;
+ char db_username[32];
+ char db_password[100];
+ char db_database[32];
+ char codepage[32];
+ // other settings
+ bool case_sensitive;
+ char account_db[32];
+ char global_acc_reg_num_db[32];
+ char global_acc_reg_str_db[32];
+
+
+} AccountDB_SQL;
+
+/// internal structure
+typedef struct AccountDBIterator_SQL
+{
+ AccountDBIterator vtable; // public interface
+
+ AccountDB_SQL* db;
+ int last_account_id;
+} AccountDBIterator_SQL;
+
+/// internal functions
+static bool account_db_sql_init(AccountDB* self);
+static void account_db_sql_destroy(AccountDB* self);
+static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen);
+static bool account_db_sql_set_property(AccountDB* self, struct config_t *config, bool imported);
+static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc);
+static bool account_db_sql_remove(AccountDB* self, const int account_id);
+static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc);
+static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const int account_id);
+static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid);
+static AccountDBIterator* account_db_sql_iterator(AccountDB* self);
+static void account_db_sql_iter_destroy(AccountDBIterator* self);
+static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc);
+
+static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int account_id);
+static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new);
+
+/// public constructor
+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.
+ */
+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 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 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 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( 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 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 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;
+}
+
+struct Sql *account_db_sql_up(AccountDB* self)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ return db ? db->accounts : NULL;
+}
+void 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;
+ }
+ }
+ }
+}
+
+void 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(<keyLength>), index(L), valLength(B), val(<valLength>) }
+ **/
+ 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(<keyLength>), 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);
+}