diff --git a/gamesv/src/Session.zig b/gamesv/src/Session.zig index bc2afa9..597af12 100644 --- a/gamesv/src/Session.zig +++ b/gamesv/src/Session.zig @@ -107,17 +107,20 @@ pub fn process( error.Canceled => |e| return e, }; - const player = fs.persistence.loadPlayer(io, gpa, assets, result.uid) catch |err| switch (err) { + const player = fs.persistence.loadPlayer(io, gpa, assets, result.uid.view()) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| { - log.err("failed to load data for player with uid {d}: {t}, disconnecting", .{ result.uid, e }); + log.err( + "failed to load data for player with uid {s}: {t}, disconnecting", + .{ result.uid.view(), e }, + ); return; }, }; log.info( - "client from '{f}' has successfully logged into account with uid: {d}", - .{ stream.socket.address, result.uid }, + "client from '{f}' has successfully logged into account with uid: {s}", + .{ stream.socket.address, result.uid.view() }, ); world = logic.World.init(&session, assets, result.uid, player, gpa, io); diff --git a/gamesv/src/Session/auth.zig b/gamesv/src/Session/auth.zig index da6733c..5245cb6 100644 --- a/gamesv/src/Session/auth.zig +++ b/gamesv/src/Session/auth.zig @@ -1,6 +1,8 @@ const std = @import("std"); const pb = @import("proto").pb; +const mem = @import("common").mem; const Session = @import("../Session.zig"); +const PlayerId = @import("../logic.zig").World.PlayerId; const Io = std.Io; const Allocator = std.mem.Allocator; @@ -10,14 +12,30 @@ const log = std.log.scoped(.auth); pub const Error = error{LoginFailed} || Session.SendError || Allocator.Error || Io.Cancelable; pub const Result = struct { - uid: u64, // It's a string in SC_LOGIN tho + uid: mem.LimitedString(PlayerId.max_length), + + pub const FromUidSliceError = error{ + TooLongString, + InvalidCharacters, + }; + + pub fn fromUidSlice(slice: []const u8) FromUidSliceError!Result { + const result: Result = .{ .uid = try .init(slice) }; + for (slice) |c| if (!std.ascii.isAlphanumeric(c)) { + return error.InvalidCharacters; + }; + + return result; + } }; pub fn processLoginRequest(io: Io, session: *Session, request: *const pb.CS_LOGIN) Error!Result { log.info("login request received: {any}", .{request}); - const uid = std.fmt.parseInt(u64, request.uid, 10) catch + const result = Result.fromUidSlice(request.uid) catch |err| { + log.err("invalid UID received: {t}", .{err}); return error.LoginFailed; + }; try session.send(pb.SC_LOGIN{ .uid = request.uid, @@ -25,5 +43,5 @@ pub fn processLoginRequest(io: Io, session: *Session, request: *const pb.CS_LOGI .server_time_zone = 3, }); - return .{ .uid = uid }; + return result; } diff --git a/gamesv/src/fs/persistence.zig b/gamesv/src/fs/persistence.zig index 8277d94..af537f1 100644 --- a/gamesv/src/fs/persistence.zig +++ b/gamesv/src/fs/persistence.zig @@ -6,6 +6,7 @@ const Assets = @import("../Assets.zig"); const Io = std.Io; const Allocator = std.mem.Allocator; const Player = logic.Player; +const PlayerId = logic.World.PlayerId; const player_data_dir = "store/player/"; const base_component_file = "base_data"; @@ -35,10 +36,12 @@ const LoadPlayerError = error{ const log = std.log.scoped(.persistence); // Opens or creates data directory for the player with specified uid. -pub fn openPlayerDataDir(io: Io, uid: u64) !Io.Dir { - var dir_path_buf: [player_data_dir.len + 20]u8 = undefined; - const dir_path = std.fmt.bufPrint(&dir_path_buf, player_data_dir ++ "{d}", .{uid}) catch - unreachable; // Since we're printing a u64, it shouldn't exceed the buffer. +pub fn openPlayerDataDir(io: Io, uid: []const u8) !Io.Dir { + std.debug.assert(uid.len <= PlayerId.max_length); + + var dir_path_buf: [player_data_dir.len + PlayerId.max_length]u8 = undefined; + const dir_path = std.fmt.bufPrint(&dir_path_buf, player_data_dir ++ "{s}", .{uid}) catch + unreachable; const cwd: Io.Dir = .cwd(); return cwd.openDir(io, dir_path, .{}) catch |open_err| switch (open_err) { @@ -53,7 +56,7 @@ pub fn openPlayerDataDir(io: Io, uid: u64) !Io.Dir { // Loads player data. Creates components that do not exist. // Resets component to default if its data is corrupted. -pub fn loadPlayer(io: Io, gpa: Allocator, assets: *const Assets, uid: u64) !Player { +pub fn loadPlayer(io: Io, gpa: Allocator, assets: *const Assets, uid: []const u8) !Player { const data_dir = try openPlayerDataDir(io, uid); defer data_dir.close(io); @@ -88,20 +91,20 @@ pub fn loadPlayer(io: Io, gpa: Allocator, assets: *const Assets, uid: u64) !Play fn loadBaseComponent( io: Io, data_dir: Io.Dir, - uid: u64, + uid: []const u8, ) !Player.Base { return fs.loadStruct(Player.Base, io, data_dir, base_component_file) catch |err| switch (err) { inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| reset: { if (e == error.ChecksumMismatch) { log.err( - "checksum mismatched for base_data of player {d}, resetting to defaults.", + "checksum mismatched for base_data of player {s}, resetting to defaults.", .{uid}, ); } if (e == error.ReprSizeMismatch) { log.err( - "struct layout mismatched for base_data of player {d}, resetting to defaults.", + "struct layout mismatched for base_data of player {s}, resetting to defaults.", .{uid}, ); } @@ -116,7 +119,7 @@ fn loadBaseComponent( }; } -fn loadGameVarsComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Player.GameVars { +fn loadGameVarsComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: []const u8) !Player.GameVars { var game_vars: Player.GameVars = undefined; game_vars.server_vars = try loadArray( @@ -146,7 +149,7 @@ fn loadGameVarsComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Pl return game_vars; } -fn loadUnlockComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Player.Unlock { +fn loadUnlockComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: []const u8) !Player.Unlock { var unlock: Player.Unlock = undefined; unlock.unlocked_systems = try loadArray( @@ -164,7 +167,7 @@ fn loadUnlockComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Play return unlock; } -fn loadCharBagComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Player.CharBag { +fn loadCharBagComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: []const u8) !Player.CharBag { const char_bag_dir = data_dir.openDir(io, char_bag_path, .{}) catch |err| switch (err) { error.FileNotFound => return error.NeedsReset, error.Canceled => |e| return e, @@ -195,14 +198,14 @@ fn loadCharBagComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Pla inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| reset: { if (e == error.ChecksumMismatch) { log.err( - "checksum mismatched for char bag metadata of player {d}, resetting to defaults.", + "checksum mismatched for char bag metadata of player {s}, resetting to defaults.", .{uid}, ); } if (e == error.ReprSizeMismatch) { log.err( - "struct layout mismatched for char bag metadata of player {d}, resetting to defaults.", + "struct layout mismatched for char bag metadata of player {s}, resetting to defaults.", .{uid}, ); } @@ -398,19 +401,19 @@ pub fn saveCharBagComponent( } } -fn loadBitsetComponent(io: Io, data_dir: Io.Dir, uid: u64) !Player.Bitset { +fn loadBitsetComponent(io: Io, data_dir: Io.Dir, uid: []const u8) !Player.Bitset { return fs.loadStruct(Player.Bitset, io, data_dir, bitset_file) catch |err| switch (err) { inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| { if (e == error.ChecksumMismatch) { log.err( - "checksum mismatched for bitset of player {d}, resetting to defaults.", + "checksum mismatched for bitset of player {s}, resetting to defaults.", .{uid}, ); } if (e == error.ReprSizeMismatch) { log.err( - "struct layout mismatched for bitset of player {d}, resetting to defaults.", + "struct layout mismatched for bitset of player {s}, resetting to defaults.", .{uid}, ); } @@ -448,7 +451,7 @@ fn loadArray( io: Io, gpa: Allocator, data_dir: Io.Dir, - uid: u64, + uid: []const u8, sub_path: []const u8, defaults: []const T, ) ![]T { @@ -456,14 +459,14 @@ fn loadArray( inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| reset: { if (e == error.ChecksumMismatch) { log.err( - "checksum mismatched for '{s}' of player {d}, resetting to defaults.", + "checksum mismatched for '{s}' of player {s}, resetting to defaults.", .{ sub_path, uid }, ); } if (e == error.ReprSizeMismatch) { log.err( - "struct layout mismatched for '{s}' of player {d}, resetting to defaults.", + "struct layout mismatched for '{s}' of player {s}, resetting to defaults.", .{ sub_path, uid }, ); } diff --git a/gamesv/src/logic/World.zig b/gamesv/src/logic/World.zig index abd16b4..43aa83c 100644 --- a/gamesv/src/logic/World.zig +++ b/gamesv/src/logic/World.zig @@ -1,6 +1,7 @@ // Describes player-local state of the world. const World = @This(); const std = @import("std"); +const mem = @import("common").mem; const logic = @import("../logic.zig"); const Session = @import("../Session.zig"); const Assets = @import("../Assets.zig"); @@ -8,7 +9,11 @@ const Assets = @import("../Assets.zig"); const Io = std.Io; const Allocator = std.mem.Allocator; -pub const PlayerId = struct { uid: u64 }; +pub const PlayerId = struct { + pub const max_length: usize = 16; + + uid: mem.LimitedString(max_length), +}; player_id: PlayerId, session: *Session, // TODO: should it be here this way? Do we need an abstraction? @@ -18,7 +23,7 @@ player: logic.Player, pub fn init( session: *Session, assets: *const Assets, - uid: u64, + uid: mem.LimitedString(PlayerId.max_length), player: logic.Player, gpa: Allocator, io: Io, diff --git a/gamesv/src/logic/systems/player_saves.zig b/gamesv/src/logic/systems/player_saves.zig index 647f2b9..46a6e1e 100644 --- a/gamesv/src/logic/systems/player_saves.zig +++ b/gamesv/src/logic/systems/player_saves.zig @@ -15,12 +15,12 @@ pub fn saveCharBagTeams( player_id: logic.World.PlayerId, io: Io, ) !void { - const data_dir = fs.persistence.openPlayerDataDir(io, player_id.uid) catch |err| switch (err) { + const data_dir = fs.persistence.openPlayerDataDir(io, player_id.uid.view()) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| { log.err( - "failed to open data dir for player with uid {d}: {t}", - .{ player_id.uid, e }, + "failed to open data dir for player with uid {s}: {t}", + .{ player_id.uid.view(), e }, ); return; },