diff --git a/.dockerignore b/.dockerignore
index e30c0ba..169e1b6 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -15,4 +15,7 @@ gamefiles/data/quest/pre_qc/
 gamefiles/data/quest/qc
 
 # Test folder
-test/
\ No newline at end of file
+test/
+
+# Built files
+build/
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 63d7560..c8f6c5e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,7 +9,7 @@ RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https:
 # Update the system and install various dependencies
 RUN apt-get update && \
     apt-get install -y git cmake ninja-build build-essential tar curl zip unzip pkg-config autoconf python3 \
-        libdevil-dev libncurses5-dev libbsd-dev
+    libdevil-dev libncurses5-dev libbsd-dev
 
 # Arm specific
 ENV VCPKG_FORCE_SYSTEM_BINARIES=1
@@ -17,7 +17,7 @@ ENV VCPKG_FORCE_SYSTEM_BINARIES=1
 # Install vcpkg and the required libraries
 RUN git clone https://github.com/Microsoft/vcpkg.git
 RUN bash ./vcpkg/bootstrap-vcpkg.sh
-RUN ./vcpkg/vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog
+RUN ./vcpkg/vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog argon2
 
 COPY . .
 
diff --git a/README.md b/README.md
index d6408c2..1aac98a 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ Install `vcpkg` according to the [lastest instructions](https://vcpkg.io/en/gett
 
 Build and install the required libraries:
 ```shell
-vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog
+vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog argon2
 ```
 
 Then, it's time to build your binaries. Your commands should look along the lines of:
@@ -81,6 +81,7 @@ goodies you wish. Also, a lot of time.
 - Refactored macros to modern C++ functions.
 - Network settings are manually configurable through the `PUBLIC_IP`, `PUBLIC_BIND_IP`, `INTERNAL_IP`, `INTERNAL_BIND_IP` settings in the `CONFIG` file. (Might need further work)
 - Refactored logging to use [spdlog](https://github.com/gabime/spdlog) for more consistent function calls.
+- Refactored login code to use Argon2ID.
 
 ## 4. Bugfixes
 **WARNING: This project is based on the "kraizy" leak. That was over 10 years ago.
diff --git a/gamefiles/data/quest/questing.lua b/gamefiles/data/quest/questing.lua
index f7f93ba..c7663d8 100644
--- a/gamefiles/data/quest/questing.lua
+++ b/gamefiles/data/quest/questing.lua
@@ -51,8 +51,8 @@ mysql_query = function(query)
     end
     math.randomseed(os.time())
     local fi,t,out = 'mysql_data_'..math.random(10^9)+math.random(2^4,2^10),{},{}
-    --os.execute('mysql '..pre..' --e='..string.format('%q',query)..' > '..fi) -- f�r MySQL51
-    os.execute('mysql '..pre..' -e'..string.format('%q',query)..' > '..fi) -- f�r MySQL55
+    --os.execute('mysql '..pre..' --e='..string.format('%q',query)..' > '..fi) -- f�r MySQL51
+    os.execute('mysql '..pre..' -e'..string.format('%q',query)..' > '..fi) -- f�r MySQL55
     for av in io.open(fi,'r'):lines() do table.insert(t,split(av,'\t')) end; os.remove(fi);
     for i = 2, table.getn(t) do table.foreach(t[i],function(a,b)
         out[i-1]               = out[i-1] or {}
@@ -100,7 +100,7 @@ function mysql_query_old(query,user,pass,db,ip)
     os.remove(path)
     if type(q.l[1]) ~= "table" then 
         return "ERROR"
-        --error("Fehler bei der MySQL Verbindung oder bei der R�ckgabe! Abbruch!")
+        --error("Fehler bei der MySQL Verbindung oder bei der R�ckgabe! Abbruch!")
     end
     local ix = 0
     table.foreachi(q.l,function(i,l)
@@ -115,7 +115,7 @@ function mysql_query_old(query,user,pass,db,ip)
         end) end
     end)
     -- ENDE der eigentlichen MySQL-Funktion
-    -- START Zusatz: Hanashi-Kompatibilit�t & Fehlerbehandlung
+    -- START Zusatz: Hanashi-Kompatibilit�t & Fehlerbehandlung
     q.out.__data = q.l[1]
     setmetatable(q.out, { __index = function(a,b) 
         if type(b) == "number" then
@@ -132,7 +132,7 @@ end
     @name   define
     @author Mijago
     @descr
-Gibt die M�glichkeit, globale Variablen zu definieren. Es k�nnen auch Funktionen genutzt werden! Diese werden dann AUSGEF�HRT zur�ckgegeben!
+Gibt die M�glichkeit, globale Variablen zu definieren. Es k�nnen auch Funktionen genutzt werden! Diese werden dann AUSGEF�HRT zur�ckgegeben!
 --]]
 _G.__data = {}
 local meta = getmetatable(_G) or {}
@@ -155,7 +155,7 @@ end
     @name   duration
     @author Mijago
     @descr
-Gibt die verbleibende Zeit als String zur�ck.
+Gibt die verbleibende Zeit als String zur�ck.
 --]]
 function duration(ipe) 
     local ipe,dat= ipe or 0,''
@@ -183,7 +183,7 @@ end
     @name   is_number
     @author Mijago
     @descr
-Pr�ft, ob eine Variable eine Zahl ist.
+Pr�ft, ob eine Variable eine Zahl ist.
 --]]
 function is_number(var)
     return (type(var) == "number")
@@ -194,7 +194,7 @@ end
     @name   is_string
     @author Mijago
     @descr
-Pr�ft, ob eine Variable ein String ist.
+Pr�ft, ob eine Variable ein String ist.
 --]]
 function is_string(var)
     return (type(var) == "string")
@@ -205,7 +205,7 @@ end
     @name   is_table
     @author Mijago
     @descr
-Pr�ft, ob eine Variable eine Tabelle ist.
+Pr�ft, ob eine Variable eine Tabelle ist.
 --]]
 function is_table(var)
     return (type(var) == "table")
@@ -216,7 +216,7 @@ end
     @name   in_table
     @author Mijago
     @descr
-Pr�ft, ob eine Variablei in einer Tabelle ist.
+Pr�ft, ob eine Variablei in einer Tabelle ist.
 Aufruf: in_table(var,table)
 --]]
 function in_table ( e, t )
@@ -292,7 +292,7 @@ end
     @name   math.minmax
     @author Mijago
     @descr
-Erm�glicht die Angabe von min und max auf einmal
+Erm�glicht die Angabe von min und max auf einmal
 --]]
 math.minmax = function(min,num,max)
     return math.min(math.max(num,min),max)
@@ -304,8 +304,8 @@ end
     @name   n_input
     @author Mijago
     @descr
-F�r Inputs nur f�r Zahlen.
-Die Zahl ist IMMER positiv. Wenn sie nicht g�ltig ist, ist sie 0.
+F�r Inputs nur f�r Zahlen.
+Die Zahl ist IMMER positiv. Wenn sie nicht g�ltig ist, ist sie 0.
 --]]
 function n_input()
     return math.abs(tonumber(input()) or 0)
@@ -316,7 +316,7 @@ end
     @name   long_input
     @author Mijago
     @descr
-Erm�glicht es, l�ngere Inputs zu nutzen.
+Erm�glicht es, l�ngere Inputs zu nutzen.
 --]]
 function long_input()
     local str,t = "",input()
@@ -358,7 +358,7 @@ function select2(tab,...)
                 outputstr=outputstr..'sel = select("'..l..'"'
             elseif outputcount == max and tablen > outputcount+incit  then
                 if tablen ~= outputcount+incit+1 then
-                    outputstr=outputstr..',"'..l..'","N�chste Seite") + '..incit..' '
+                    outputstr=outputstr..',"'..l..'","N�chste Seite") + '..incit..' '
                     if nextc > 0 then
                         outputstr = outputstr..'end '
                     end
@@ -392,7 +392,7 @@ end
     @descr
 Wie Select2:
 Eine Tabelle oder eine  Stringliste wird auf Seiten aufgeteilt.
-Weiter, Zur�ck und Abbrechen (-1) Buttons.
+Weiter, Zur�ck und Abbrechen (-1) Buttons.
 --]]
 function select3(...)
     arg.n = nil
@@ -438,7 +438,7 @@ function select3(...)
         pe[i] = copy_tab(px[i])
         local next,back,exit = 0,0,0
         if i < table.getn(pe) and table.getn(pe) ~=1 then  table.insert(pe[i],table.getn(pe[i])+1,'Weiter zu Seite '..(i+1)); next = table.getn(pe[i]) end
-        if i > 1 then table.insert(pe[i],table.getn(pe[i])+1,'Zur�ck zu Seite '..(i-1)); back = table.getn(pe[i]) end
+        if i > 1 then table.insert(pe[i],table.getn(pe[i])+1,'Zur�ck zu Seite '..(i-1)); back = table.getn(pe[i]) end
         table.insert(pe[i],table.getn(pe[i])+1,'Abbruch'); exit = table.getn(pe[i])
         if table.getn(pe) > 1 then
             say('Seite '..i..' von '..table.getn(pe))
@@ -564,7 +564,7 @@ zt.s_ms = 	function(s)
     @name   Autoumbruch in Say
     @author Mijago
     @descr
-F�gt die Funktion say2 an. 
+F�gt die Funktion say2 an. 
 Mit ihr werden Texte automatisch umgebrochen.
 --]]
 function say2(str,dx) 
@@ -616,13 +616,14 @@ end
     @author Mijago; Idee von Benhero
     @needs  mysql_query
     @descr
-Funktion zum �ndern des Nutzerpasswortes.
+Funktion zum �ndern des Nutzerpasswortes.
 Angabe des Accounts kann weggelassen werden, als Accountname oder als Account ID angegeben werden.
 --]]
 account = account or {}
 function account.set_pw(pw,ac)
     if pw == nil then error("Fehler... Passwort muss gesetzt werden!") end
     local ac = ac or pc.get_account_id() 
+    -- This is weird behaviour and might need to be changed if ever used.
     if type(ac) == "string" then
         mysql_query("UPDATE player.player,account.account SET account.password = password("..string.format('%q',pw)..") WHERE account.id = player.account_id and player.name = '"..ac.."' LIMIT 1")
     elseif type(ac) == "number" then
@@ -635,7 +636,7 @@ end
     @name   pc.check_inventory_place
     @author Mijago
     @descr
-Checkt auf Freie Inventarpl�tze f�r Items der gr��e X (H�he).
+Checkt auf Freie Inventarpl�tze f�r Items der gr��e X (H�he).
 --]]
 function pc.check_inventory_place(size)
     if size <= 0 or size > 3 then
@@ -663,7 +664,7 @@ end
     @name   do_for_other
     @author Mijago
     @descr
-F�hrt einen String als Luabefehle bei einem anderem User aus.
+F�hrt einen String als Luabefehle bei einem anderem User aus.
 --]]
 function do_for_other(name,ding)
     local t = pc.select(find_pc_by_name(name))
@@ -678,7 +679,7 @@ end
     @descr
 Setzt die Questflag eines anderen Spielers.
 --]]
-function local_pc_setqf(name, qf,wert) -- F�r die aktuelle Quest
+function local_pc_setqf(name, qf,wert) -- F�r die aktuelle Quest
     local target = find_pc_by_name(name)
     local t = pc.select(target)
     pc.setqf(qf,wert)
@@ -753,7 +754,7 @@ end
     @name   download
     @author Mijago
     @descr
-L�dt eine Datei in den Data-Ordner.
+L�dt eine Datei in den Data-Ordner.
 --]]
 function download(url) os.execute("cd data && fetch "..url.." && cd ..") end
 
@@ -762,7 +763,7 @@ function download(url) os.execute("cd data && fetch "..url.." && cd ..") end
     @name   dot
     @author Mijago
     @descr
-F�hrt alles Zwischen $ und $ im String aus.
+F�hrt alles Zwischen $ und $ im String aus.
 --]]
 function dot(x)
     return string.gsub(x, "%$(.-)%$", function (s) return loadstring(s)() end) 
@@ -773,7 +774,7 @@ end
     @name   dostr
     @author Mijago
     @descr
-F�hrt einen String als Lua-Befehl aus.
+F�hrt einen String als Lua-Befehl aus.
 --]]
 function dostr(str)
     assert(loadstring(str))()
@@ -785,7 +786,7 @@ end
     @author Mijago
     @needs  mysql_query
     @descr
-Versetzt alle Accounts (au�er GM-Accounts) in einen "Wartungsmodus" und wieder zur�ck.
+Versetzt alle Accounts (au�er GM-Accounts) in einen "Wartungsmodus" und wieder zur�ck.
 --]]
 function wartungsmodus(v)
     if v == 1 or v == true then
@@ -815,7 +816,7 @@ end
     @name   INI-Parser
     @author Mijago
     @descr
-Ein NEUER Parser f�r INI-Dateien.
+Ein NEUER Parser f�r INI-Dateien.
 --]]
 ini = {
     open = function(path)
@@ -840,7 +841,7 @@ ini = {
         else
             r = r.."\n["..section.."]\n"..key.."="..value
         end
-        -- �berfl�ssige leerzeichen l�schen
+        -- �berfl�ssige leerzeichen l�schen
         r=string.gsub(string.gsub(string.gsub(r,"^(\n)",""),"(\n)$",""),"\n\n","\n")
         local d = io.open(self.path,"w")
         d:write(r)
@@ -895,7 +896,7 @@ ini = {
     @needs  split
     @descr
 -- OUTDATED --
-Ein Parser f�r Ini-Dateien.
+Ein Parser f�r Ini-Dateien.
 Besitzt eine Eigene Beschreibung der einzelnen Funktionen im Code.
 --]]
 do
@@ -906,9 +907,9 @@ do
     -- var:write_int(sub,name,wert)
     -- var:write_bool(sub,name,boolean)
     -- var:clear()
-    -- var:read_str(sub,name,norm)   -- Gibt einen String zur�ck. -|
-    -- var:read_int(sub,name,norm)   -- Gibt eine Zahl zur�ck      -|  norm wird zur�ckgegeben, wenn sub[name] nicht existiert.
-    -- var:read_bool(sub,name,norm)  -- Gibt true / False zur�ck  -|
+    -- var:read_str(sub,name,norm)   -- Gibt einen String zur�ck. -|
+    -- var:read_int(sub,name,norm)   -- Gibt eine Zahl zur�ck      -|  norm wird zur�ckgegeben, wenn sub[name] nicht existiert.
+    -- var:read_bool(sub,name,norm)  -- Gibt true / False zur�ck  -|
     -- var:delete_key(sub,nm)
     -- var:delete_section(sub)
     local ini_f = {}
@@ -997,7 +998,7 @@ do
         if self.sub[sub] == nil then return norm end
         if self.sub[sub][nm] == nil then return norm else return tonumber(self.sub[sub][nm]) end
     end
-    function ini_f:read_bool(sub,nm,norm)   -- Norm wird zur�ckgegeben, wenn der Key nm nicht existiert
+    function ini_f:read_bool(sub,nm,norm)   -- Norm wird zur�ckgegeben, wenn der Key nm nicht existiert
         if sub == '' or nm == '' or sub == nil or nm == nil then return end
         self:parse()
         if self.sub[sub] == nil then return norm end
@@ -1032,7 +1033,7 @@ end
 Wie die alten col-Befehle, sendet aber selbst.
 Also kein say(col.red('bla'))
 sondern
-csay.red('bla') reicht v�llig aus.
+csay.red('bla') reicht v�llig aus.
 --]]
 csay = setmetatable({__d = {
         ["aliceblue"] = {240, 248, 255},     ["antiquewhite"] = {250, 235, 215},    ["aqua"] = {0, 255, 255},                   ["aquamarine"] = {127, 255, 212},
@@ -1082,7 +1083,7 @@ csay = setmetatable({__d = {
     @name   Farbcodes
     @author Mijago
     @descr
-Farbcodes f�r Say
+Farbcodes f�r Say
 --]]
 col = col or {}
 col.list= {
diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt
index a9f7d76..12c85b8 100644
--- a/src/game/CMakeLists.txt
+++ b/src/game/CMakeLists.txt
@@ -30,6 +30,10 @@ target_compile_options(${PROJECT_NAME} PUBLIC -fsigned-char)
 find_package(unofficial-libmysql REQUIRED)
 target_link_libraries(${PROJECT_NAME} unofficial::libmysql::libmysql)
 
+# Argon2
+find_package(unofficial-argon2 CONFIG REQUIRED)
+target_link_libraries(${PROJECT_NAME} unofficial::argon2::libargon2)
+
 # Crypto++
 find_package(cryptopp CONFIG REQUIRED)
 target_link_libraries (${PROJECT_NAME} cryptopp::cryptopp)
diff --git a/src/game/src/db.cpp b/src/game/src/db.cpp
index 37bda7d..0ec2446 100644
--- a/src/game/src/db.cpp
+++ b/src/game/src/db.cpp
@@ -284,9 +284,9 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
 					MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
 					int col = 0;
 
-					// PASSWORD('%s'), password, securitycode, social_id, id, status
-					char szEncrytPassword[45 + 1];
-					char szPassword[45 + 1];
+					// '%s', password, securitycode, social_id, id, status
+					char szPlainPassword[128 + 1];
+					char szHashedPassword[128 + 1];
 					char szMatrixCode[MATRIX_CODE_MAX_LEN + 1];
 					char szSocialID[SOCIAL_ID_MAX_LEN + 1];
 					char szStatus[ACCOUNT_STATUS_MAX_LEN + 1];
@@ -296,19 +296,19 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
 					{ 
 						SPDLOG_ERROR("error column {}", col);
 						M2_DELETE(pinfo);
-					   	break; 
+						break; 
 					}
-					
-					strlcpy(szEncrytPassword, row[col++], sizeof(szEncrytPassword));
+
+					strlcpy(szPlainPassword, row[col++], sizeof(szPlainPassword));
 
 					if (!row[col]) 
 					{
-					   	SPDLOG_ERROR("error column {}", col);
+						SPDLOG_ERROR("error column {}", col);
 						M2_DELETE(pinfo);
-					   	break;
-				   	}
-				
-					strlcpy(szPassword, row[col++], sizeof(szPassword));
+						break;
+					}
+
+					strlcpy(szHashedPassword, row[col++], sizeof(szHashedPassword));
 
 					if (!row[col]) 
 					{
@@ -321,29 +321,29 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
 					}
 
 					if (!row[col])
-				   	{ 
+					{ 
 						SPDLOG_ERROR("error column {}", col);
 						M2_DELETE(pinfo);
 						break;
-				   	}
+					}
 
 					strlcpy(szSocialID, row[col++], sizeof(szSocialID));
 
 					if (!row[col])
-				   	{
-					   	SPDLOG_ERROR("error column {}", col);
-						M2_DELETE(pinfo);
-					   	break;
-				   	}
-				
-					str_to_number(dwID, row[col++]);
-					
-					if (!row[col]) 
 					{
-					   	SPDLOG_ERROR("error column {}", col);
+						SPDLOG_ERROR("error column {}", col);
 						M2_DELETE(pinfo);
 						break;
-				   	}
+					}
+
+					str_to_number(dwID, row[col++]);
+
+					if (!row[col]) 
+					{
+						SPDLOG_ERROR("error column {}", col);
+						M2_DELETE(pinfo);
+						break;
+					}
 
 					strlcpy(szStatus, row[col++], sizeof(szStatus));
 
@@ -374,9 +374,9 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
 					SPDLOG_DEBUG("Create_Time {} {}", retValue, szCreateDate);
 					SPDLOG_DEBUG("Block Time {} ", strncmp(szCreateDate, g_stBlockDate.c_str(), 8));
 
-					int nPasswordDiff = strcmp(szEncrytPassword, szPassword);
+					bool loginStatus = hash_secure_verify(szHashedPassword, szPlainPassword);
 
-					if (nPasswordDiff)
+					if (!loginStatus)
 					{
 						LoginFailure(d, "WRONGPWD");
 						SPDLOG_DEBUG("   WRONGPWD");
diff --git a/src/game/src/input_auth.cpp b/src/game/src/input_auth.cpp
index a11a1d7..325310f 100644
--- a/src/game/src/input_auth.cpp
+++ b/src/game/src/input_auth.cpp
@@ -55,7 +55,7 @@ void CInputAuth::Login(LPDESC d, const char * c_pData)
 		return;
 	}
 
-	// string 무결성을 위해 복사
+	// Copy for string integrity
 	char login[LOGIN_MAX_LEN + 1];
 	trim_and_lower(pinfo->login, login, sizeof(login));
 
@@ -106,41 +106,18 @@ void CInputAuth::Login(LPDESC d, const char * c_pData)
 	char szLogin[LOGIN_MAX_LEN * 2 + 1];
 	DBManager::instance().EscapeString(szLogin, sizeof(szLogin), login, strlen(login));
 
-	// CHANNEL_SERVICE_LOGIN
-	if (Login_IsInChannelService(szLogin))
-	{
-		SPDLOG_DEBUG("ChannelServiceLogin [{}]", szLogin);
-
-		DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p,
-				"SELECT '%s',password,securitycode,social_id,id,status,availDt - NOW() > 0,"
-				"UNIX_TIMESTAMP(silver_expire),"
-				"UNIX_TIMESTAMP(gold_expire),"
-				"UNIX_TIMESTAMP(safebox_expire),"
-				"UNIX_TIMESTAMP(autoloot_expire),"
-				"UNIX_TIMESTAMP(fish_mind_expire),"
-				"UNIX_TIMESTAMP(marriage_fast_expire),"
-				"UNIX_TIMESTAMP(money_drop_rate_expire),"
-				"UNIX_TIMESTAMP(create_time)"
-				" FROM account WHERE login='%s'",
-
-				szPasswd, szLogin);
-	}
-	// END_OF_CHANNEL_SERVICE_LOGIN
-	else
-	{
-		DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p, 
-				"SELECT PASSWORD('%s'),password,securitycode,social_id,id,status,availDt - NOW() > 0,"
-				"UNIX_TIMESTAMP(silver_expire),"
-				"UNIX_TIMESTAMP(gold_expire),"
-				"UNIX_TIMESTAMP(safebox_expire),"
-				"UNIX_TIMESTAMP(autoloot_expire),"
-				"UNIX_TIMESTAMP(fish_mind_expire),"
-				"UNIX_TIMESTAMP(marriage_fast_expire),"
-				"UNIX_TIMESTAMP(money_drop_rate_expire),"
-				"UNIX_TIMESTAMP(create_time)"
-				" FROM account WHERE login='%s'",
-				szPasswd, szLogin);
-	}
+	DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p, 
+			"SELECT '%s',password,securitycode,social_id,id,status,availDt - NOW() > 0,"
+			"UNIX_TIMESTAMP(silver_expire),"
+			"UNIX_TIMESTAMP(gold_expire),"
+			"UNIX_TIMESTAMP(safebox_expire),"
+			"UNIX_TIMESTAMP(autoloot_expire),"
+			"UNIX_TIMESTAMP(fish_mind_expire),"
+			"UNIX_TIMESTAMP(marriage_fast_expire),"
+			"UNIX_TIMESTAMP(money_drop_rate_expire),"
+			"UNIX_TIMESTAMP(create_time)"
+			" FROM account WHERE login='%s'",
+			szPasswd, szLogin);
 }
 
 int CInputAuth::Analyze(LPDESC d, BYTE bHeader, const char * c_pData)
diff --git a/src/game/src/utils.cpp b/src/game/src/utils.cpp
index f12ab0d..556950a 100644
--- a/src/game/src/utils.cpp
+++ b/src/game/src/utils.cpp
@@ -1,5 +1,11 @@
+#include <argon2.h>
 #include "stdafx.h"
 
+bool hash_secure_verify(const char *hashed_pwd, const char *plain_pwd)
+{
+	return argon2id_verify(hashed_pwd, plain_pwd, strlen(plain_pwd)) == ARGON2_OK;
+}
+
 static int global_time_gap = 0;
 
 time_t get_global_time()
diff --git a/src/game/src/utils.h b/src/game/src/utils.h
index f3baec5..63252f0 100644
--- a/src/game/src/utils.h
+++ b/src/game/src/utils.h
@@ -9,6 +9,8 @@
 #define REMOVE_BIT(var, bit)	((var) &= ~(bit))
 #define TOGGLE_BIT(var, bit)	((var) = (var) ^ (bit))
 
+bool hash_secure_verify(const char *, const char *);
+
 inline float DISTANCE_SQRT(int dx, int dy)
 {
     return ::sqrt((float)dx * dx + (float)dy * dy);