diff --git a/ebin/egs.app b/ebin/egs.app
index fc27abb..b3053ff 100644
--- a/ebin/egs.app
+++ b/ebin/egs.app
@@ -10,7 +10,7 @@
egs_cron,
egs_db,
egs_game,
- egs_login,
+ psu_login,
psu_patch,
egs_proto,
psu_appearance,
diff --git a/conf/motd.txt b/priv/psu_login/motd.txt
similarity index 100%
rename from conf/motd.txt
rename to priv/psu_login/motd.txt
diff --git a/ssl/servercert.pem b/priv/ssl/servercert.pem
similarity index 100%
rename from ssl/servercert.pem
rename to priv/ssl/servercert.pem
diff --git a/ssl/serverkey.pem b/priv/ssl/serverkey.pem
similarity index 100%
rename from ssl/serverkey.pem
rename to priv/ssl/serverkey.pem
diff --git a/src/egs_sup.erl b/src/egs_sup.erl
index e7ce2b9..cd031f5 100644
--- a/src/egs_sup.erl
+++ b/src/egs_sup.erl
@@ -51,7 +51,9 @@ init([]) ->
%% Start egs_cron, egs_game, egs_login, egs_patch. To be replaced by configurable modules.
Processes = [{egs_cron, {egs_cron, start, []}, permanent, 5000, worker, dynamic},
{egs_game, {egs_game, start, []}, permanent, 5000, worker, dynamic},
- {egs_login, {egs_login, start, []}, permanent, 5000, worker, dynamic},
+ {psu_login_jp1, {psu_login, start_link, [?LOGIN_PORT_JP_ONE, 10000001]}, permanent, 5000, worker, dynamic},
+ {psu_login_jp2, {psu_login, start_link, [?LOGIN_PORT_JP_TWO, 20000001]}, permanent, 5000, worker, dynamic},
+ {psu_login_us, {psu_login, start_link, [?LOGIN_PORT_US, 30000001]}, permanent, 5000, worker, dynamic},
{psu_patch_jp, {psu_patch, start_link, [?PATCH_PORT_JP]}, permanent, 5000, worker, dynamic},
{psu_patch_us, {psu_patch, start_link, [?PATCH_PORT_US]}, permanent, 5000, worker, dynamic}],
{ok, {{one_for_one, 10, 10}, Processes}}.
diff --git a/src/egs_login.erl b/src/psu/psu_login.erl
similarity index 58%
rename from src/egs_login.erl
rename to src/psu/psu_login.erl
index 65e8a79..3c58b84 100644
--- a/src/egs_login.erl
+++ b/src/psu/psu_login.erl
@@ -1,77 +1,75 @@
-% EGS: Erlang Game Server
-% Copyright (C) 2010 Loic Hoguin
-%
-% This file is part of EGS.
-%
-% EGS is free software: you can redistribute it and/or modify
-% it under the terms of the GNU General Public License as published by
-% the Free Software Foundation, either version 3 of the License, or
-% (at your option) any later version.
-%
-% EGS is distributed in the hope that it will be useful,
-% but WITHOUT ANY WARRANTY; without even the implied warranty of
-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-% GNU General Public License for more details.
-%
-% You should have received a copy of the GNU General Public License
-% along with EGS. If not, see .
+%% @author Loïc Hoguin
+%% @copyright 2010 Loïc Hoguin.
+%% @doc Process login requests.
+%%
+%% This file is part of EGS.
+%%
+%% EGS is free software: you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published by
+%% the Free Software Foundation, either version 3 of the License, or
+%% (at your option) any later version.
+%%
+%% EGS is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%% GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public License
+%% along with EGS. If not, see .
--module(egs_login).
--export([start/0]). % external
--export([listen/2, accept/2, process/2, loop/2]). % internal
+-module(psu_login).
+-export([start_link/2]). %% External.
+-export([listen/2, accept/2, process/2, loop/2]). %% Internal.
-include("include/records.hrl").
-include("include/network.hrl").
-%% @doc Start the login server. Currently AOTI JP and US only.
+-define(OPTIONS, [binary, {active, false}, {certfile, "priv/ssl/servercert.pem"}, {keyfile, "priv/ssl/serverkey.pem"}, {password, "alpha"}]).
-start() ->
- spawn_link(?MODULE, listen, [?LOGIN_PORT_JP_ONE, 10000001]),
- spawn_link(?MODULE, listen, [?LOGIN_PORT_JP_TWO, 20000001]),
- USPid = spawn_link(?MODULE, listen, [?LOGIN_PORT_US, 30000001]),
- {ok, USPid}.
+%% @spec start_link(Port) -> {ok,Pid::pid()}
+%% @doc Start the PSU login server for inclusion in a supervisor tree.
+start_link(Port, SessionID) ->
+ Pid = spawn(?MODULE, listen, [Port, SessionID]),
+ {ok, Pid}.
+%% @spec listen(Port, SessionID) -> ok
%% @doc Listen for connections.
-
listen(Port, SessionID) ->
- process_flag(trap_exit, true),
- {ok, LSocket} = ssl:listen(Port, ?LOGIN_LISTEN_OPTIONS),
+ {ok, LSocket} = ssl:listen(Port, ?OPTIONS),
?MODULE:accept(LSocket, SessionID).
+%% @spec accept(LSocket, SessionID) -> ok
%% @doc Accept connections.
-
accept(LSocket, SessionID) ->
case ssl:transport_accept(LSocket, 5000) of
{ok, CSocket} ->
- % in the future, modulo to avoid conflicts future with real GIDs
- %~ NextID = (SessionID + 1) rem 1000000,
+ %% in the future, modulo to avoid conflicts future with real GIDs
+ %% NextID = (SessionID + 1) rem 1000000,
NextID = SessionID + 1,
ssl:ssl_accept(CSocket),
- spawn_link(?MODULE, process, [CSocket, SessionID]);
- _ ->
+ spawn(?MODULE, process, [CSocket, SessionID]);
+ {error, _Reason} ->
NextID = SessionID,
reload
end,
?MODULE:accept(LSocket, NextID).
+%% @spec process(CSocket, SessionID) -> ok
%% @doc Process the new connections. Send an hello packet and start the loop.
-
process(CSocket, SessionID) ->
log(SessionID, "hello"),
egs_proto:packet_send(CSocket, << 16#02020300:32, 0:288, SessionID:32/little-unsigned-integer >>),
?MODULE:loop(CSocket, SessionID).
+%% @spec loop(CSocket, SessionID) -> ok
%% @doc Main loop for the login server.
-
loop(CSocket, SessionID) ->
case egs_proto:packet_recv(CSocket, 5000) of
{ok, Orig} ->
<< _:32, Command:16/unsigned-integer, _/bits >> = Orig,
case handle(Command, CSocket, SessionID, Orig) of
- closed ->
- ignore;
- _ ->
- ?MODULE:loop(CSocket, SessionID)
+ closed -> ok;
+ _ -> ?MODULE:loop(CSocket, SessionID)
end;
{error, timeout} ->
reload,
@@ -81,8 +79,11 @@ loop(CSocket, SessionID) ->
egs_db:users_delete(SessionID)
end.
-%% @doc Game server info request handler.
+%% @spec handle(Command, CSocket, SessionID, Orig) -> ok | closed
+%% @doc Login server client commands handler.
+%% Game server info request handler.
+%% @todo Remove the dependency on network.hrl
handle(16#0217, CSocket, SessionID, _) ->
log(SessionID, "forward to game server"),
IP = ?GAME_IP,
@@ -92,12 +93,11 @@ handle(16#0217, CSocket, SessionID, _) ->
ssl:close(CSocket),
closed;
-%% @doc Authentication request handler. Currently always succeed.
-%% Use the temporary session ID as the GID for now.
-%% Use username and password as a folder name for saving character data.
+%% Authentication request handler. Currently always succeed.
+%% Use the temporary session ID as the GID for now.
+%% Use username and password as a folder name for saving character data.
%% @todo Handle real GIDs whenever there's real authentication. GID is the second SessionID in the reply.
%% @todo Apparently it's possible to ask a question in the reply here. Used for free course on JP.
-
handle(16#0219, CSocket, SessionID, Orig) ->
<< _:352, UsernameBlob:192/bits, PasswordBlob:192/bits, _/bits >> = Orig,
Username = re:replace(UsernameBlob, "\\0", "", [global, {return, binary}]),
@@ -110,35 +110,34 @@ handle(16#0219, CSocket, SessionID, Orig) ->
Packet = << 16#02230300:32, 0:192, SessionID:32/little-unsigned-integer, 0:64, SessionID:32/little-unsigned-integer, Auth:32/bits >>,
egs_proto:packet_send(CSocket, Packet);
-%% @doc MOTD request handler. Handles both forms of MOTD requests, US and JP. Page number starts at 0.
+%% MOTD request handler. Handles both forms of MOTD requests, US and JP. Page number starts at 0.
%% @todo Currently ignore the language and send the same MOTD file to everyone. Language is 8 bits next to Page.
%% @todo Use a normal ASCII file rather than an UCS2 one?
-
handle(Command, CSocket, SessionID, Orig) when Command =:= 16#0226; Command =:= 16#023f ->
<< _:352, Page:8/little-unsigned-integer, _/bits >> = Orig,
log(SessionID, "send MOTD page ~.10b", [Page + 1]),
- {ok, File} = file:read_file("conf/motd.txt"),
+ {ok, File} = file:read_file("priv/psu_login/motd.txt"),
Tokens = re:split(File, "\n."),
MOTD = << << Line/binary, "\n", 0 >> || Line <- lists:sublist(Tokens, 1 + Page * 15, 15) >>,
NbPages = 1 + length(Tokens) div 15,
Packet = << 16#0225:16, 0:304, NbPages:8, Page:8, 16#8200:16/unsigned-integer, MOTD/binary, 0:16 >>,
egs_proto:packet_send(CSocket, Packet);
-%% @doc Silently ignore packets 0227 and 080e.
-
+%% Silently ignore packets 0227 and 080e.
handle(Command, _, _, _) when Command =:= 16#0227; Command =:= 16#080e ->
ignore;
-%% @doc Unknown command handler. Print a log message about it.
-
+%% Unknown command handler. Print a log message about it.
handle(Command, _, SessionID, _) ->
log(SessionID, "dismissed packet ~4.16.0b", [Command]).
+%% @spec log(SessionID, Message) -> ok
%% @doc Log message to the console.
-
log(SessionID, Message) ->
io:format("login (~.10b): ~s~n", [SessionID, Message]).
+%% @spec log(SessionID, Message, Format) -> ok
+%% @doc Format a message and log it to the console.
log(SessionID, Message, Format) ->
RealMessage = io_lib:format(Message, Format),
log(SessionID, RealMessage).
diff --git a/src/psu/psu_patch.erl b/src/psu/psu_patch.erl
index 7da31dc..ce4459c 100644
--- a/src/psu/psu_patch.erl
+++ b/src/psu/psu_patch.erl
@@ -24,7 +24,7 @@
-define(OPTIONS, [binary, {send_timeout, 5000}, {packet, 0}, {active, false}, {reuseaddr, true}]).
%% @spec start_link(Port) -> {ok,Pid::pid()}
-%% @doc Starts the patch server for inclusion in a supervisor tree.
+%% @doc Start the PSU patch server for inclusion in a supervisor tree.
start_link(Port) ->
Pid = spawn(?MODULE, listen, [Port]),
{ok, Pid}.