mirror of
https://git.xeondev.com/LR/S.git
synced 2026-02-03 22:45:06 +01:00
feat(auth): support alphanumeric UIDs
Allows using alphanumeric strings up to 16 characters as UID. Makes it less clunky to use, since the game suggests alphanumeric one by default.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user