Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Maple2.Model/Game/User/PlayerInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ public class CharacterInfo {
public DeathState DeathState { get; set; }
// Location
public int MapId { get; set; }
public short Channel { get; set; }
public short Channel { get; set; } = -1;
public long LastOnlineTime { get; set; }

// Guild
public long GuildId { get; set; }
public string GuildName { get; set; }

public long UpdateTime { get; set; }
public bool Online => Channel != 0;
public bool Online => Channel >= 0;

public CharacterInfo(long accountId, long characterId, string name, string motto, string picture, Gender gender, Job job, short level) {
AccountId = accountId;
Expand Down
2 changes: 2 additions & 0 deletions Maple2.Model/Metadata/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ public static class Constant {
public const bool EnableRollEverywhere = false;
public const bool HideHomeCommands = true;

public static int MaxAllowedLatency = TimeSpan.FromSeconds(2).Milliseconds;
Comment thread
AngeloTadeucci marked this conversation as resolved.

public static IReadOnlyDictionary<string, int> ContentRewards { get; } = new Dictionary<string, int> {
{"miniGame", 1005},
{"dungeonHelper", 1006},
Expand Down
2 changes: 2 additions & 0 deletions Maple2.Server.Core/Constants/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public static class Target {
public static readonly IPAddress LoginIp = IPAddress.Loopback;
public static readonly ushort LoginPort = 20001;

public static readonly ushort GrpcLoginPort = 21000;

public static readonly IPAddress GameIp = IPAddress.Loopback;
public static readonly ushort BaseGamePort = 20002;

Expand Down
5 changes: 1 addition & 4 deletions Maple2.Server.Core/Network/Session.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System;
using System.Buffers;
using System.Buffers;
using System.Collections.Concurrent;
using System.IO.Pipelines;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Maple2.Model.Enum;
using Maple2.PacketLib.Crypto;
using Maple2.PacketLib.Tools;
Expand Down
4 changes: 4 additions & 0 deletions Maple2.Server.Core/proto/channel/channel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ service Channel {
rpc PlayerWarp(maple2.PlayerWarpRequest) returns (maple2.PlayerWarpResponse);
// Admin
rpc Admin(maple2.AdminRequest) returns (maple2.AdminResponse);
// Disconnect
rpc Disconnect(maple2.DisconnectRequest) returns (maple2.DisconnectResponse);
// Heartbeat
rpc Heartbeat(maple2.HeartbeatRequest) returns (maple2.HeartbeatResponse);
}

message GuildRequest {
Expand Down
16 changes: 16 additions & 0 deletions Maple2.Server.Core/proto/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,19 @@ message AdminResponse {
int32 error = 1;
string message = 2;
}

message DisconnectRequest {
int64 character_id = 1;
}

message DisconnectResponse {
bool success = 1;
}

message HeartbeatRequest {
int64 character_id = 1;
}

message HeartbeatResponse {
bool success = 1;
}
10 changes: 10 additions & 0 deletions Maple2.Server.Core/proto/login/login.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";

package maple2.server.login.service;

import "google/protobuf/empty.proto";
import "common.proto";

service Login {
rpc Heartbeat(maple2.HeartbeatRequest) returns (maple2.HeartbeatResponse);
}
1 change: 1 addition & 0 deletions Maple2.Server.Core/proto/sync.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ message PlayerUpdateResponse {

message PlayerInfoRequest {
int64 character_id = 1;
int64 account_id = 2;
Comment thread
AngeloTadeucci marked this conversation as resolved.
}

message PlayerInfoResponse {
Expand Down
3 changes: 3 additions & 0 deletions Maple2.Server.Core/proto/world/world.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ service World {
rpc GroupChat(GroupChatRequest) returns (GroupChatResponse);
// Retrieve player info from online player.
rpc PlayerInfo(maple2.PlayerInfoRequest) returns (maple2.PlayerInfoResponse);
rpc AccountInfo(maple2.PlayerInfoRequest) returns (maple2.PlayerInfoResponse);
// Update player info
rpc UpdatePlayer(maple2.PlayerUpdateRequest) returns (maple2.PlayerUpdateResponse);
// Notify character about new mail.
Expand All @@ -56,6 +57,8 @@ service World {
rpc Admin(maple2.AdminRequest) returns (maple2.AdminResponse);
// Buff
rpc PlayerConfig(PlayerConfigRequest) returns (PlayerConfigResponse);
// Disconnect
rpc Disconnect(maple2.DisconnectRequest) returns (maple2.DisconnectResponse);
}

enum Server {
Expand Down
40 changes: 40 additions & 0 deletions Maple2.Server.Game/PacketHandlers/ResponseHeartbeatHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Maple2.Model.Metadata;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;
using Maple2.Server.Core.PacketHandlers;
using Maple2.Server.Game.Session;

namespace Maple2.Server.Game.PacketHandlers;

public class ResponseHeartbeatHandler : PacketHandler<GameSession> {
public override RecvOp OpCode => RecvOp.ResponseHeartbeat;

public override void Handle(GameSession session, IByteReader packet) {
int serverTick = packet.ReadInt();
int clientTick = packet.ReadInt();

int latency = Environment.TickCount - serverTick;
if (latency > Constant.MaxAllowedLatency) {
#if !DEBUG
session.Disconnect();
#endif
return;
}

if (serverTick == 0 || clientTick == 0) {
return;
}

if (session is { ClientTick: 0, ServerTick: 0 }) {
session.ClientTick = clientTick;
session.ServerTick = serverTick;
return;
}

int serverDelta = serverTick - session.ServerTick;
int clientDelta = clientTick - session.ClientTick;

session.ClientTick = clientTick;
session.ServerTick = serverTick;
}
}
21 changes: 21 additions & 0 deletions Maple2.Server.Game/Service/ChannelService.Heartbeat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Grpc.Core;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Session;

namespace Maple2.Server.Game.Service;

public partial class ChannelService {
public override Task<HeartbeatResponse> Heartbeat(HeartbeatRequest request, ServerCallContext context) {
if (request.CharacterId == 0) {
throw new RpcException(new Status(StatusCode.NotFound, "Character ID is 0."));
}
if (!server.GetSession(request.CharacterId, out GameSession? session)) {
throw new RpcException(new Status(StatusCode.NotFound, "Session not found."));
}

session.Send(RequestPacket.Heartbeat());
return Task.FromResult(new HeartbeatResponse {
Success = true,
});
}
}
24 changes: 23 additions & 1 deletion Maple2.Server.Game/Service/ChannelService.Sync.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Grpc.Core;
using Maple2.Model.Game;
using Maple2.Server.Channel.Service;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Session;

namespace Maple2.Server.Game.Service;
Expand All @@ -11,11 +14,30 @@ public override Task<MailNotificationResponse> MailNotification(MailNotification

session.Mail.Notify(true);

return Task.FromResult(new MailNotificationResponse { Delivered = true });
return Task.FromResult(new MailNotificationResponse {
Delivered = true,
});
}

public override Task<PlayerUpdateResponse> UpdatePlayer(PlayerUpdateRequest request, ServerCallContext context) {
playerInfos.ReceiveUpdate(request);
return Task.FromResult(new PlayerUpdateResponse());
}

public override Task<DisconnectResponse> Disconnect(DisconnectRequest request, ServerCallContext context) {
if (request is { CharacterId: <= 0 }) {
throw new RpcException(new Status(StatusCode.InvalidArgument, $"CharacterId not specified"));
}

if (!server.GetSession(request.CharacterId, out GameSession? session)) {
return Task.FromResult(new DisconnectResponse {
Success = false,
});
}

session.Disconnect();
return Task.FromResult(new DisconnectResponse {
Success = true,
});
}
}
9 changes: 7 additions & 2 deletions Maple2.Server.Game/Session/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public sealed partial class GameSession : Core.Network.Session {
public readonly CommandRouter CommandHandler;
public readonly EventQueue Scheduler;

public int ServerTick;
public int ClientTick;

public int Latency;
Comment thread
AngeloTadeucci marked this conversation as resolved.
Comment thread
AngeloTadeucci marked this conversation as resolved.

public long AccountId { get; private set; }
public long CharacterId { get; private set; }
public string PlayerName => Player.Value.Character.Name;
Expand Down Expand Up @@ -711,7 +716,7 @@ protected override void Dispose(bool disposing) {
CharacterId = CharacterId,
LastOnlineTime = DateTime.UtcNow.ToEpochSeconds(),
MapId = 0,
Channel = 0,
Channel = -1,
Async = true,
});

Expand All @@ -722,7 +727,7 @@ protected override void Dispose(bool disposing) {
Scheduler.Stop();
server.OnDisconnected(this);
LeaveField();
Player.Value.Character.Channel = 0;
Player.Value.Character.Channel = -1;
Player.Value.Account.Online = false;
State = SessionState.Disconnected;
Complete();
Expand Down
2 changes: 2 additions & 0 deletions Maple2.Server.Login/LoginServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,6 @@ public override Task StopAsync(CancellationToken cancellationToken) {

return base.StopAsync(cancellationToken);
}

public List<LoginSession> GetSessions() => sessions.Values.ToList();
Comment thread
AngeloTadeucci marked this conversation as resolved.
}
67 changes: 43 additions & 24 deletions Maple2.Server.Login/PacketHandlers/LoginHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,36 +30,55 @@ private enum Command : byte {
public override void Handle(LoginSession session, IByteReader packet) {
var command = packet.Read<Command>();

string user = packet.ReadUnicodeString();
string pass = packet.ReadUnicodeString();
packet.ReadShort(); // 1
var machineId = packet.Read<Guid>();

Logger.Debug("Logging in with user:{User}", user);
LoginResponse response = Global.Login(new LoginRequest {
Username = user,
Password = pass,
MachineId = machineId.ToString(),
});

if (response.Code != LoginResponse.Types.Code.Ok) {
session.Send(LoginResultPacket.Error((byte) response.Code, response.Message, response.AccountId));
return;
}

// Account is already logged into login server.
if (session.Server.GetSession(response.AccountId, out LoginSession? existing) && existing != session) {
existing.Disconnect();
session.Send(LoginResultPacket.Error((byte) LoginResponse.Types.Code.AlreadyLogin, "", response.AccountId));
return;
}

PlayerInfoResponse? playerInfo = World.AccountInfo(new PlayerInfoRequest {
AccountId = response.AccountId,
});

if (playerInfo is not null && playerInfo.Channel > 0) {
DisconnectResponse? disconnectResponse = World.Disconnect(new DisconnectRequest {
CharacterId = playerInfo.CharacterId,
});
if (disconnectResponse is null || !disconnectResponse.Success) {
Logger.Error("Failed to disconnect character: {CharacterId}", playerInfo.CharacterId);
} else {
Logger.Debug("Disconnected character: {CharacterId}", playerInfo.CharacterId);
}

session.Send(LoginResultPacket.Error((byte) LoginResponse.Types.Code.AlreadyLogin, "", response.AccountId));
return;
}

try {
switch (command) {
case Command.ServerList:
session.ListServers();
session.Disconnect();
return;
case Command.CharacterList:
string user = packet.ReadUnicodeString();
string pass = packet.ReadUnicodeString();
packet.ReadShort(); // 1
var machineId = packet.Read<Guid>();

Logger.Debug("Logging in with user:{User}", user);
LoginResponse response = Global.Login(new LoginRequest {
Username = user,
Password = pass,
MachineId = machineId.ToString(),
});

if (response.Code != LoginResponse.Types.Code.Ok) {
session.Send(LoginResultPacket.Error((byte) response.Code, response.Message, response.AccountId));
session.Disconnect();
return;
}

// TODO: Account is already logged into game server.
// if (World.PlayerInfo(new PlayerInfoRequest {AccountId = response.AccountId}).Location.Channel > 0) {
// session.Send(LoginResultPacket.Error((byte) LoginResponse.Types.Code.AlreadyLogin, "", response.AccountId));
// return;
// }

session.Init(response.AccountId, machineId);

session.Send(LoginResultPacket.Success(response.AccountId));
Expand Down
14 changes: 14 additions & 0 deletions Maple2.Server.Login/PacketHandlers/QuitHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;
using Maple2.Server.Core.PacketHandlers;
using Maple2.Server.Login.Session;

namespace Maple2.Server.Login.PacketHandlers;

public class QuitHandler : PacketHandler<LoginSession> {
public override RecvOp OpCode => RecvOp.RequestQuit;

public override void Handle(LoginSession session, IByteReader packet) {
session.Disconnect();
}
}
33 changes: 33 additions & 0 deletions Maple2.Server.Login/PacketHandlers/ResponseHeartbeatHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;
using Maple2.Server.Core.PacketHandlers;
using Maple2.Server.Login.Session;

namespace Maple2.Server.Login.PacketHandlers;

public class ResponseHeartbeatHandler : PacketHandler<LoginSession> {
public override RecvOp OpCode => RecvOp.ResponseHeartbeat;

public override void Handle(LoginSession session, IByteReader packet) {
int serverTick = packet.ReadInt();
int clientTick = packet.ReadInt();



if (serverTick == 0 || clientTick == 0) {
return;
}
Comment thread
AngeloTadeucci marked this conversation as resolved.

if (session is { ClientTick: 0, ServerTick: 0 }) {
session.ClientTick = clientTick;
session.ServerTick = serverTick;
return;
}

int serverDelta = serverTick - session.ServerTick;
int clientDelta = clientTick - session.ClientTick;
session.ClientTick = clientTick;
session.ServerTick = serverTick;
}
}
Loading