From 2a258a80f0c86cd6b62daaf145c52b4ee53000fc Mon Sep 17 00:00:00 2001 From: CPunch Date: Fri, 28 Aug 2020 19:10:26 -0500 Subject: [PATCH] Database.cpp refactoring --- src/CNLoginServer.cpp | 9 +-- src/Database.cpp | 184 ++++++++++++++++++++++++++++++------------ 2 files changed, 135 insertions(+), 58 deletions(-) diff --git a/src/CNLoginServer.cpp b/src/CNLoginServer.cpp index 871a04a..3633502 100644 --- a/src/CNLoginServer.cpp +++ b/src/CNLoginServer.cpp @@ -349,15 +349,14 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) { sP_CL2LS_REQ_CHAR_DELETE* del = (sP_CL2LS_REQ_CHAR_DELETE*)data->buf; int operationResult = Database::deleteCharacter(del->iPC_UID, loginSessions[sock].userID); - //success + // success if (operationResult > 0) { INITSTRUCT(sP_LS2CL_REP_CHAR_DELETE_SUCC, resp); resp.iSlotNum = operationResult; sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_DELETE_SUCC, sizeof(sP_LS2CL_REP_CHAR_DELETE_SUCC)); - } - //failure - else { - //client doesnt't care about this packet and softlocks + } else { + // failure + // client doesnt't care about this packet and softlocks INITSTRUCT(sP_LS2CL_REP_CHAR_DELETE_FAIL, resp); resp.iErrorCode = 0; sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_DELETE_FAIL, sizeof(sP_LS2CL_REP_CHAR_DELETE_FAIL)); diff --git a/src/Database.cpp b/src/Database.cpp index 3d84588..5327c27 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -18,129 +18,187 @@ void Database::open() { //checking if database file exists std::ifstream file; file.open("data.db"); + if (file) { file.close(); - //if exists, assign it + // if exists, assign it db = sqlite3pp::database("data.db"); DEBUGLOG(std::cout << "Database in operation" << std::endl; ) - } - else { - //if doesn't, create all the tables + } else { + // if doesn't, create all the tables DEBUGLOG(std::cout << "Creating new database" << std::endl;) - db = sqlite3pp::database("data.db"); + db = sqlite3pp::database("data.db"); + + // creates accounts db.execute("CREATE TABLE Accounts(AccountID INTEGER PRIMARY KEY AUTOINCREMENT, Login TEXT NOT NULL, Password TEXT NOT NULL);"); + // creates characters db.execute("CREATE TABLE Players(PlayerID INTEGER PRIMARY KEY AUTOINCREMENT, AccountID INTEGER NOT NULL, Slot INTEGER NOT NULL, FirstName TEXT NOT NULL, LastName TEXT NOT NULL, CharData TEXT NOT NULL);"); + DEBUGLOG(std::cout << "Done" << std::endl;) } - - } +// verifies that the username & passwords are valid bool Database::isLoginDataGood(std::string login, std::string password) { std::regex loginRegex("^([A-Za-z\\d_\\-]){5,20}$"); - std::regex passwordRegex("^([A-Za-z\\d_\\-@$!%*#?&,.+:;<=>]){8,20}$"); + std::regex passwordRegex("^([A-Za-z\\d_\\-@$!%*#?&,.+:;<=>]){2,20}$"); return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex)); } void Database::addAccount(std::string login, std::string password) { + // generates prepared statment sqlite3pp::command cmd(db, "INSERT INTO Accounts (Login, Password) VALUES (:login, :password)"); + + // generates a hashed password! password = BCrypt::generateHash(password); + + // binds args to the command cmd.bind(":login", login, sqlite3pp::nocopy); cmd.bind(":password", password, sqlite3pp::nocopy); cmd.execute(); } bool Database::doesUserExist(std::string login) { - std::string q = "SELECT COUNT(AccountID) FROM Accounts WHERE Login = :login"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); - qry.bind(":login", login, sqlite3pp::nocopy); + // generates prepared statement + sqlite3pp::query qry(db, "SELECT COUNT(AccountID) FROM Accounts WHERE Login = :login"); + // binds to the query + qry.bind(":login", login, sqlite3pp::nocopy); + + // executes sqlite3pp::query::iterator i = qry.begin(); + + // grabs the first result (the count) int result; std::tie(result) = (*i).get_columns(0); - return (result != 0); + + // if there is more than 0 results, the user exists :eyes: + return (result > 0); } bool Database::isPasswordCorrect(std::string login, std::string password) { - std::string q = "SELECT Password FROM Accounts WHERE Login = :login"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + // generates prepared statement + sqlite3pp::query qry(db, "SELECT Password FROM Accounts WHERE Login = :login"); + // binds username to the query qry.bind(":login", login, sqlite3pp::nocopy); + + // executes sqlite3pp::query::iterator i = qry.begin(); + + // grabs the first result const char* actual; std::tie(actual) = (*i).get_columns(0); + + // validate password hash with provded password return BCrypt::validatePassword(password, actual); } int Database::getUserID(std::string login) { - std::string q = "SELECT AccountID FROM Accounts WHERE Login = :login"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + // generates prep statement + sqlite3pp::query qry(db, "SELECT AccountID FROM Accounts WHERE Login = :login"); + // binds the username to the login param qry.bind(":login", login, sqlite3pp::nocopy); + + // executes the query sqlite3pp::query::iterator i = qry.begin(); + + // grabs the first result of the query int result; std::tie(result) = (*i).get_columns(0); + + // returns the result return result; } int Database::getUserSlotsNum(int AccountId) { - std::string q = "SELECT COUNT(PlayerID) FROM Players WHERE AccountID = :ID"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + // generates the prepared statement + sqlite3pp::query qry(db, "SELECT COUNT(PlayerID) FROM Players WHERE AccountID = :ID"); + + // binds the ID to the param qry.bind(":ID", AccountId); + + // executes sqlite3pp::query::iterator i = qry.begin(); + + // grabs the first result int result; std::tie(result) = (*i).get_columns(0); + + // returns the result return result; } bool Database::isNameFree(std::string First, std::string Second) { - std::string q = "SELECT COUNT(PlayerID) FROM Players WHERE FirstName = :First COLLATE nocase AND LastName = :Second COLLATE nocase"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + // generates the prepared statement + sqlite3pp::query qry(db, "SELECT COUNT(PlayerID) FROM Players WHERE FirstName = :First COLLATE nocase AND LastName = :Second COLLATE nocase"); + + // binds the params qry.bind(":First", First, sqlite3pp::nocopy); qry.bind(":Second", Second, sqlite3pp::nocopy); + + // executes the query sqlite3pp::query::iterator i = qry.begin(); + + // grabs the result int result; std::tie(result) = (*i).get_columns(0); + + // if no results return, the the username is unused return (result == 0); } void Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) { - std::string charData = CharacterToJson(save); + // generate the command sqlite3pp::command cmd(db, "INSERT INTO Players (AccountID, Slot, FirstName, LastName, CharData) VALUES (:AccountID, :Slot, :FirstName, :LastName, :CharData)"); + + // generate character data + std::string charData = CharacterToJson(save); std::string first = U16toU8(save->szFirstName); std::string last = U16toU8(save->szLastName); + + // bind to command cmd.bind(":AccountID", AccountID); cmd.bind(":Slot", save->iSlotNum); cmd.bind(":CharData", charData, sqlite3pp::nocopy); cmd.bind(":FirstName", first, sqlite3pp::nocopy); cmd.bind(":LastName", last, sqlite3pp::nocopy); + + // run cmd.execute(); } void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character) { + sqlite3pp::command cmd(db, "UPDATE Players SET CharData = :data WHERE PlayerID = :id"); + + // grab the data to add to the command int id = character->PCStyle.iPC_UID; std::string charData = CharacterToJson(character); - sqlite3pp::command cmd(db, "UPDATE Players SET CharData = :data WHERE PlayerID = :id"); + + // bind to the command & execute cmd.bind(":data", charData, sqlite3pp::nocopy); cmd.bind(":id", id); cmd.execute(); } void Database::finishTutorial(int PlayerID) { - std::string json; - std::string q = "SELECT CharData FROM Players WHERE PlayerID = :ID"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + sqlite3pp::query qry(db, "SELECT CharData FROM Players WHERE PlayerID = :ID"); + + // bind to the query and execute qry.bind(":ID", PlayerID); sqlite3pp::query::iterator i = qry.begin(); + + // grab the player json from the database + std::string json; std::tie(json) = (*i).get_columns(0); + + // parse the json into a Player Player player = JsonToPlayer(json, PlayerID); + + // set the tutorial flag & equip lightning gun player.PCStyle2.iTutorialFlag = 1; - //equip lightning gun player.Equip[0].iID = 328; json = PlayerToJson(player); + + // update the database sqlite3pp::command cmd(db, "UPDATE Players SET CharData = :data WHERE PlayerID = :id"); cmd.bind(":data", json, sqlite3pp::nocopy); cmd.bind(":id", PlayerID); @@ -148,61 +206,81 @@ void Database::finishTutorial(int PlayerID) { } int Database::getCharacterID(int AccountID, int slot) { - std::string q = "SELECT PlayerID FROM Players WHERE AccountID = :ID AND Slot = :Slot"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + // make the query + sqlite3pp::query qry(db, "SELECT PlayerID FROM Players WHERE AccountID = :ID AND Slot = :Slot"); + + // bind the params & execute qry.bind(":ID", AccountID); qry.bind(":Slot", slot); sqlite3pp::query::iterator i = qry.begin(); + + // grab the result int result; std::tie(result) = (*i).get_columns(0); + return result; } int Database::deleteCharacter(int characterID, int accountID) { - //checking if requested player exist and is bound to the account - std::string q = "SELECT COUNT(AccountID) FROM Players WHERE AccountID = :AccID AND PlayerID = :PID"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); + // checking if requested player exist and is bound to the account + + sqlite3pp::query qry(db, "SELECT COUNT(AccountID) FROM Players WHERE AccountID = :AccID AND PlayerID = :PID"); + qry.bind(":AccID", accountID); qry.bind(":PID", characterID); sqlite3pp::query::iterator i = qry.begin(); + int result; std::tie(result) = (*i).get_columns(0); + if (result > 0) { - //get player character slot - q = "SELECT Slot FROM Players WHERE PlayerID = :PID"; - query = q.c_str(); - sqlite3pp::query qry(db, query); + // get player character slot + sqlite3pp::query qry(db, "SELECT Slot FROM Players WHERE PlayerID = :PID"); + + // bind & execute qry.bind(":PID", characterID); sqlite3pp::query::iterator i = qry.begin(); + + // grab the slot to return int slot; std::tie(slot) = (*i).get_columns(0); - //delete + + // actually delete the record sqlite3pp::command cmd(db, "DELETE FROM Players WHERE PlayerID = :ID"); cmd.bind(":ID", characterID); cmd.execute(); + + // finally, return the grabbed slot return slot; } - else return -1; + + return -1; } -std::list Database::getCharacters(int userID) { - std::list Result = std::list(); - std::string q = "SELECT * FROM Players WHERE AccountID = :ID"; - const char* query = q.c_str(); - sqlite3pp::query qry(db, query); +// TODO: this should really be std::vector, but for the sake of compatibility with PRs, I'll change this after merging +std::list Database::getCharacters(int userID) { + std::list result = std::list(); + + sqlite3pp::query qry(db, "SELECT * FROM Players WHERE AccountID = :ID"); + qry.bind(":ID", userID); + + // for each character owned by the account, for (sqlite3pp::query::iterator i = qry.begin(); i != qry.end(); ++i) { + // grab the data int ID, AccountID, slot; char const* charData, * first, * second; std::tie(ID, AccountID, slot, first, second, charData) = (*i).get_columns(0, 1, 2, 3, 4, 5); + + // convert to player Player toadd = JsonToPlayer(charData, ID); toadd.slot = slot; - Result.push_back(toadd); + + // add it to the results + result.push_back(toadd); } - return Result; + return result; } std::string Database::CharacterToJson(sP_CL2LS_REQ_SAVE_CHAR_NAME* save) { @@ -241,7 +319,7 @@ std::string Database::PlayerToJson(Player player) { nlohmann::json json = { {"Level",1}, //to check - {"HP",100}, + {"HP",1000}, {"NameCheck", 1}, {"FirstName",U16toU8(player.PCStyle.szFirstName)}, {"LastName",U16toU8(player.PCStyle.szLastName)},