Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e0d6547
Ongoing Srp6
devw4r Dec 28, 2024
16a1688
Ongoing Srp6
devw4r Jan 10, 2025
93fab63
Ongoing Srp6
devw4r Jan 11, 2025
7fa9af4
Update AuthSessionHandler.py
devw4r Jan 11, 2025
cece567
Ongoing Srp6
devw4r Jan 12, 2025
43a0491
Ongoing Srp6
devw4r Jan 12, 2025
069993f
Ongoing Srp6
devw4r Jan 12, 2025
c012c1c
Merge remote-tracking branch 'upstream/master' into A
devw4r Jan 12, 2025
a5119ff
Update AuthSessionHandler.py
devw4r Jan 12, 2025
0667615
Update AuthSessionHandler.py
devw4r Jan 12, 2025
8c809e0
Update RealmDatabaseManager.py
devw4r Jan 12, 2025
6eab0d4
Update AuthSessionHandler.py
devw4r Jan 12, 2025
161cf0b
Update MiscCodes.py
devw4r Jan 12, 2025
344d2fb
Update RealmDatabaseManager.py
devw4r Jan 12, 2025
a4d97d9
Bump config version.
devw4r Jan 12, 2025
807f7b4
Minor refactor.
devw4r Jan 14, 2025
0aabfe6
Update RealmDatabaseManager.py
devw4r Jan 14, 2025
44941ce
Add dummy update server for launcher.
devw4r Jan 14, 2025
2699195
Update RealmDatabaseManager.py
devw4r Feb 4, 2025
97a7f6c
Fix #1476
devw4r Feb 4, 2025
b9cdaa3
Fix #1478
devw4r Feb 5, 2025
a80b5c3
Fix #1480
devw4r Feb 5, 2025
2d69996
Update UnitManager.py
devw4r Feb 5, 2025
bbb3253
Fix #1479
devw4r Feb 5, 2025
30ed301
Fix #1481
devw4r Feb 5, 2025
60a9411
Fix #1482
devw4r Feb 5, 2025
bfa0671
Fix #1466
devw4r Feb 5, 2025
3539f23
Spell miss threat.
devw4r Feb 5, 2025
4a2f518
Handle Sleep/Sap threat ignore through ExtendedSpellData
devw4r Feb 15, 2025
cf7afaa
Update AuraManager.py
devw4r Feb 15, 2025
b6fb35c
er
devw4r Feb 15, 2025
3bef0da
Update ExtendedSpellData.py
devw4r Feb 15, 2025
0adede9
Update ExtendedSpellData.py
devw4r Feb 15, 2025
d11f2f9
Prevent non players from forcing updates.
devw4r Feb 15, 2025
21dbdd2
More changes regarding #1480
devw4r Feb 15, 2025
d986aec
Minor.
devw4r Feb 16, 2025
215ca79
Extract build_socket func.
devw4r Feb 19, 2025
87adca7
Update UpdateSessionStateHandler.py
devw4r Feb 19, 2025
7abe6dd
Update ObjectManager.py
devw4r Feb 20, 2025
0327e90
Update ObjectManager.py
devw4r Feb 20, 2025
431b4ad
Srp6 - Use explicit variable names.
devw4r Mar 14, 2025
95e108e
Revert change to read_string().
devw4r Mar 14, 2025
f67fbd4
Update Srp6.py
devw4r Mar 14, 2025
a72c324
Merge remote-tracking branch 'upstream/master' into A
devw4r Mar 19, 2025
72b7441
Merge remote-tracking branch 'upstream/master' into A
devw4r Mar 21, 2025
d39bc0c
Allow building nav files using namigator mapbuild binding.
devw4r Mar 23, 2025
c779449
Update NavExtractor.py
devw4r Mar 23, 2025
addc298
Update NavExtractor.py
devw4r Mar 23, 2025
3a3e208
Update NavExtractor.py
devw4r Mar 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions database/realm/RealmDatabaseManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from utils.constants.ItemCodes import InventorySlots
from utils.constants.MiscCodes import HighGuid


DB_USER = os.getenv('MYSQL_USERNAME', config.Database.Connection.username)
DB_PASSWORD = os.getenv('MYSQL_PASSWORD', config.Database.Connection.password)
DB_HOST = os.getenv('MYSQL_HOST', config.Database.Connection.host)
Expand All @@ -35,13 +34,23 @@ def realm_get_list():
# Account-

@staticmethod
def account_try_login(username, password, ip):
def account_try_get(username):
realm_db_session = SessionHolder()
account_mgr = None
account = realm_db_session.query(Account).filter_by(name=username).first()
if account:
account_mgr = AccountManager(account)
realm_db_session.close()
return account_mgr

@staticmethod
def account_try_login(username, password, ip, client_digest, server_digest):
realm_db_session = SessionHolder()
account = realm_db_session.query(Account).filter_by(name=username).first()
status = -1
account_mgr = None
if account:
if account.password == password:
if (password and account.password == password) or (client_digest and client_digest == server_digest):
status = 1
account.ip = ip
account_mgr = AccountManager(account)
Expand All @@ -56,17 +65,39 @@ def account_try_login(username, password, ip):
return status, account_mgr

@staticmethod
def account_create(username, password, ip):
def account_create(username, password, ip, salt, verifier):
realm_db_session = SessionHolder()
account = Account(name=username, password=password, ip=ip,
gmlevel=int(config.Server.Settings.auto_create_gm_accounts))
gmlevel=int(config.Server.Settings.auto_create_gm_accounts),
salt=salt,
verifier=verifier,
sessionkey=""
)
realm_db_session.add(account)
realm_db_session.flush()
realm_db_session.commit()
realm_db_session.refresh(account)
realm_db_session.close()
return AccountManager(account)

@staticmethod
def account_try_update_session_key(username, session_key):
realm_db_session = SessionHolder()
try:
account = realm_db_session.query(Account).filter_by(name=username).first()
if not account:
return False

account.sessionkey = session_key

realm_db_session.merge(account)
realm_db_session.flush()
realm_db_session.commit()
finally:
realm_db_session.close()

return True

@staticmethod
def account_try_update_password(username, old_password, new_password):
realm_db_session = SessionHolder()
Expand Down
3 changes: 3 additions & 0 deletions database/realm/RealmModels.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Account(Base):
password = Column(String(256), nullable=False)
ip = Column(String(256), nullable=False)
gmlevel = Column(TINYINT(4), nullable=False, server_default=text("1"))
salt = Column(String(256), nullable=False)
verifier = Column(String(256), nullable=False)
sessionkey = Column(String(256), nullable=False)


class AppliedUpdate(Base):
Expand Down
10 changes: 9 additions & 1 deletion etc/config/config.yml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Version:
current: 18
current: 19

Database:
Connection:
Expand All @@ -15,6 +15,14 @@ Database:

Server:
Connection: # Change 0.0.0.0 for 127.0.0.1 if it doesn't work
Update:
host: 0.0.0.0
port: 9081

Login:
host: 0.0.0.0
port: 3724

Realm:
local_realm_id: 1 # id of the realm running on this machine (realmlist table)

Expand Down
10 changes: 10 additions & 0 deletions etc/databases/realm/updates/updates.sql
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,15 @@ begin not atomic
insert into applied_updates values ('120620241');
end if;

-- 10/01/2025 1
if (select count(*) from applied_updates where id='100120251') = 0 then
ALTER TABLE `accounts`
ADD COLUMN `salt` VARCHAR(256) NOT NULL AFTER `gmlevel`,
ADD COLUMN `verifier` VARCHAR(256) NOT NULL AFTER `salt`,
ADD COLUMN `sessionkey` VARCHAR(256) NOT NULL AFTER `verifier`;

insert into applied_updates values ('100120251');
end if;

end $
delimiter ;
19 changes: 19 additions & 0 deletions etc/databases/world/updates/updates.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1171,5 +1171,24 @@ begin not atomic
insert into applied_updates values ('041220241');
end if;

-- 02/05/2025 1
if (select count(*) from applied_updates where id='020520251') = 0 then
-- Events list for Venture Co. Taskmaster
DELETE FROM `creature_ai_events` WHERE `creature_id`=2977;
INSERT INTO `creature_ai_events` (`id`, `creature_id`, `condition_id`, `event_type`, `event_inverse_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `action1_script`, `action2_script`, `action3_script`, `comment`) VALUES (297701, 2977, 0, 2, 0, 100, 0, 15, 0, 0, 0, 297701, 0, 0, 'Venture Co. Taskmaster - Flee at 15% HP');
INSERT INTO `creature_ai_events` (`id`, `creature_id`, `condition_id`, `event_type`, `event_inverse_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `action1_script`, `action2_script`, `action3_script`, `comment`) VALUES (297702, 2977, 0, 11, 0, 100, 0, 0, 0, 0, 0, 297702, 0, 0, 'Venture Co. Taskmaster - Cast Torch Burn Upon Spawn');

DELETE FROM `creature_ai_scripts` WHERE `id`=297702;
INSERT INTO `creature_ai_scripts` (`id`, `delay`, `priority`, `command`, `datalong`, `datalong2`, `datalong3`, `datalong4`, `target_param1`, `target_param2`, `target_type`, `data_flags`, `dataint`, `dataint2`, `dataint3`, `dataint4`, `x`, `y`, `z`, `o`, `condition_id`, `comments`) VALUES
(297702, 0, 0, 15, 5680, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Taskmaster - Cast Torch Burn');

UPDATE `quest_template` SET `Details` = 'I would charge you with a task, $N.$B$BI was on my boat, rowing over the submerged ruins of Zoram, when naga attacked me, surging from the water and tearing at me with their claws! I fled, carrying what supplies I could to make this meager camp.$B$BBut when I reached the shore and ran... my prized possession was lost.$B$BPlease, $N, find the site of my ambush and search for an ancient statuette. It is the reason I have braved the dangers of the Zoram Strand.', `RewOrReqMoney` = '750' WHERE (`entry` = '1007');

UPDATE `creature_template` SET `level_min` = '15', `level_max` = '15' WHERE (`entry` = '3681');

UPDATE `spawns_creatures` SET `position_x` = '10686.3', `position_y` = '1917.5', `position_z` = '1336.62', `orientation` = '0.998' WHERE (`spawn_id` = '46193');

insert into applied_updates values ('020520251');
end if;
end $
delimiter ;
37 changes: 37 additions & 0 deletions game/login/LoginManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import socket
import threading
import traceback

from game.login.LoginSessionStateHandler import LoginSessionStateHandler
from network.sockets.SocketBuilder import SocketBuilder
from utils.ConfigManager import config
from utils.Logger import Logger


class LoginManager:
@staticmethod
def start_login(running, login_server_ready):
login_host = config.Server.Connection.Login.host
login_port = config.Server.Connection.Login.port
with SocketBuilder.build_socket(login_host, login_port, timeout=2) as server_socket:
server_socket.listen()
real_binding = server_socket.getsockname()
Logger.success(f'Login server started, listening on {real_binding[0]}:{real_binding[1]}')
login_server_ready.value = 1

try:
while running.value:
try:
client_socket, client_address = server_socket.accept()
server_handler = LoginSessionStateHandler(client_socket, client_address)
auth_session_thread = threading.Thread(target=server_handler.handle)
auth_session_thread.daemon = True
auth_session_thread.start()
except socket.timeout:
pass # Non blocking.
except OSError:
Logger.warning(traceback.format_exc())
except KeyboardInterrupt:
pass

Logger.info("Login server turned off.")
98 changes: 98 additions & 0 deletions game/login/LoginSessionStateHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import socket
from network.packet.PacketReader import PacketReader
from utils.Logger import Logger

MAX_PACKET_BYTES = 4096


class LoginSessionStateHandler:
def __init__(self, client_socket, client_address):
self.client_socket = client_socket
self.client_address = client_address

def handle(self):
try:
self.keep_alive = True
self.client_socket.settimeout(120) # 2 minutes timeout should be more than enough.

while self.receive(self.client_socket) != -1 and self.keep_alive:
continue

finally:
self.disconnect()

def receive(self, sck):
try:
reader = self.receive_client_message(sck)
if reader and self.keep_alive and reader.opcode:
return self.process_incoming(reader)
else:
return -1
except (socket.timeout, OSError, ConnectionResetError, ValueError):
self.disconnect()
return -1

def process_incoming(self, reader):
from game.world.opcode_handling.Definitions import Definitions
res = -1
try:
handler, found = Definitions.get_handler_from_packet(self, reader.opcode)
if handler:
res = handler(self, reader)
if res == 0:
Logger.debug(f'[{self.client_address[0]}] Handling {reader.opcode_str()}')
elif res == 1:
Logger.debug(f'[{self.client_address[0]}] Ignoring {reader.opcode_str()}')
elif res < 0:
Logger.warning(f'[{self.client_address[0]}] Handling {reader.opcode_str()} failed.')
res = -1
elif not found:
Logger.warning(f'[{self.client_address[0]}] Received unknown data: {reader.data}')
except:
pass

return res

def receive_client_message(self, sck):
header_bytes = self.receive_all(sck, 6) # 6 = header size
if not header_bytes:
return None

reader = PacketReader(header_bytes)
reader.data = self.receive_all(sck, int(reader.size))
return reader

def receive_all(self, sck, expected_size):
# Prevent wrong size because of malformed packets.
if expected_size <= 0:
return b''

# Try to fill at once.
received = sck.recv(expected_size)
if not received:
return b''

# We got what we expect, return buffer.
if received == expected_size:
return received

# If we got incomplete data, request missing payload.
buffer = bytearray(received)
current_buffer_size = len(buffer)
while current_buffer_size < expected_size:
received = sck.recv(expected_size - current_buffer_size)
if not received:
return b''
buffer.extend(received) # Keep appending to our buffer until we're done.
current_buffer_size = len(buffer)
# Avoid handling any packet that's above the maximum packet size.
if current_buffer_size > MAX_PACKET_BYTES:
return b''
return buffer

def disconnect(self):
try:
self.client_socket.shutdown(socket.SHUT_RDWR)
self.client_socket.close()
except OSError:
pass
File renamed without changes.
52 changes: 52 additions & 0 deletions game/realm/AccountManager.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import os
from struct import pack

from network.packet.PacketWriter import PacketWriter
from utils.Srp6 import Srp6
from utils.constants import CustomCodes
from utils.constants.AuthCodes import Srp6ResponseType, AuthCode


class AccountManager(object):

def __init__(self, account):
self.account = account
self._server_public_key = b''
self._server_private_key = b''
self._client_public_key = b''
self._session_key = b''
self._client_server_proof = b''

def get_security_level(self) -> CustomCodes.AccountSecurityLevel:
return self.account.gmlevel
Expand All @@ -17,3 +28,44 @@ def is_gm(self):

def is_dev(self):
return self.get_security_level() >= CustomCodes.AccountSecurityLevel.DEV

# Srp6 - AuthSession.

def update_server_public_private_keys(self):
self._server_private_key = os.urandom(32)
self._server_public_key = Srp6.calculate_server_public_key(self.get_verifier_bytes(), self._server_private_key)

def get_salt_bytes(self) -> bytes:
return bytes.fromhex(self.account.salt)

def get_verifier_bytes(self) -> bytes:
return bytes.fromhex(self.account.verifier)

def calculate_client_server_proof(self, client_public_key):
self._client_public_key = client_public_key
u = Srp6.calculate_u(client_public_key, self._server_public_key)
s_key = Srp6.calculate_server_s_key(client_public_key, self.get_verifier_bytes(), u, self._server_private_key)
self._session_key = Srp6.calculate_interleaved(s_key)
self._client_server_proof = Srp6.calculate_client_proof(Srp6.xorNg, self.account.name, self._session_key,
client_public_key, self._server_public_key,
self.get_salt_bytes())
return self._client_server_proof

def get_srp6_server_proof_packet(self) -> bytes:
s_m2 = Srp6.calculate_server_proof(self._client_public_key, self._client_server_proof, self._session_key)
data = pack('<2B', AuthCode.AUTH_OK, Srp6ResponseType.AuthProof)
data += s_m2
data += pack('<I', 0)
return PacketWriter.get_srp6_packet(data)

def get_srp6_logon_challenge_packet(self) -> bytes:
data = pack('<2B', AuthCode.AUTH_OK, Srp6ResponseType.AuthChallenge)
data += pack('<B', len(Srp6.g_bytes)) + Srp6.g_bytes
data += pack('<B', len(Srp6.N_Bytes)) + Srp6.N_Bytes
data += self.get_salt_bytes()
data += self._server_public_key
return PacketWriter.get_srp6_packet(data)

def save_session_key(self):
from database.realm.RealmDatabaseManager import RealmDatabaseManager
return RealmDatabaseManager.account_try_update_session_key(self.account.name, self._session_key.hex())
Loading