Skip to content
This repository has been archived by the owner on May 30, 2019. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
stevemohapibanks committed Sep 23, 2011
0 parents commit 55ed93b
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Makefile
@@ -0,0 +1,16 @@
ERLC=/usr/local/bin/erlc
ERLCFLAGS=-o
SRCDIR=.
BEAMDIR=./ebin

compile:
$(ERLC) $(ERLCFLAGS) $(BEAMDIR) $(SRCDIR)/*.erl ;

run: compile
erl -pa ./ebin/ -boot start_sasl -s minecraft_server

server_download:
@mkdir -p server; cd server; test -f minecraft_server.jar || curl -O "https://s3.amazonaws.com/MinecraftDownload/launcher/minecraft_server.jar"

server: server_download
@cd server; java -Xmx1024M -Xms1024M -jar minecraft_server.jar nogui
74 changes: 74 additions & 0 deletions minecraft.erl
@@ -0,0 +1,74 @@
-module(minecraft).
-compile(export_all).

-define(KEEPALIVE, 16#00).
-define(LOGIN, 16#01).
-define(HANDSHAKE, 16#02).
-define(TIME_UPDATE, 16#04).
-define(KICK, 16#FF).

-define(HANDSHAKE_MESSAGE(Username), list_to_binary([<<?HANDSHAKE:8>>, string_to_unicode_binary(Username)])).

-define(KEEPALIVE_PATTERN, <<?KEEPALIVE:8, KeepAliveID:32/signed>>).
-define(HANDSHAKE_PATTERN, <<?HANDSHAKE:8, HashLen:16, Hash:HashLen/binary-unit:16>>).
-define(KICK_PATTERN, <<?KICK:8, Len:16, Reason:Len/binary-unit:16>>).

start(MinecraftHost) ->
application:start(inets),
application:start(crypto),
application:start(public_key),
application:start(ssl),
Pid = spawn(fun() -> connect(MinecraftHost) end),
register(minecraft_socket, Pid).

connect(MinecraftHost) ->
{ok,Socket} = gen_tcp:connect(MinecraftHost, 25565, [binary, {packet, 0}]),
loop(Socket, []).

loop(Socket, Listeners) ->
receive
{tcp,Socket,Bin} ->
case Bin of
?KEEPALIVE_PATTERN -> gen_tcp:send(Socket, <<?KEEPALIVE:8, KeepAliveID:32/signed>>);
?HANDSHAKE_PATTERN -> notify(Listeners, {handshake, utf16_binary_to_list(Hash)});
?KICK_PATTERN -> notify(Listeners, {kick, utf16_binary_to_list(Reason)})
end,
loop(Socket, Listeners);
{tcp_send,Packet} ->
gen_tcp:send(Socket,Packet),
loop(Socket, Listeners);
{tcp_listen, Listener} ->
loop(Socket, [Listener|Listeners])
end.

notify([], _) -> [];
notify([Listener|Rest], Message) ->
Listener ! Message,
notify(Rest, Message).

login(User, Password) ->
case login_request(User, Password) of
{ok, {_, 200, _}, _, Body} ->
[_,_,_,_SessionID] = string:tokens(Body, ":")
end,
minecraft_socket ! {tcp_send, ?HANDSHAKE_MESSAGE(User)}.

login_request(User, Password) ->
httpc:request(post,
{"https://login.minecraft.net/",
[],
"application/x-www-form-urlencoded",
lists:flatten(io_lib:format("user=~s&password=~s&version=9999", [User, Password]))},
[], []).

listen() ->
minecraft_socket ! {tcp_listen, self()}.

%% Utility functions
string_to_unicode_binary(Str) ->
StrLen = string:len(Str),
StrBin = unicode:characters_to_binary(Str, utf8, utf16),
<<StrLen:16/unsigned, StrBin/binary>>.

utf16_binary_to_list(Bin) ->
unicode:characters_to_list(Bin, utf16).
113 changes: 113 additions & 0 deletions minecraft_server.erl
@@ -0,0 +1,113 @@
-module(minecraft_server).

-behaviour(gen_server).
-define(SERVER, ?MODULE).

-define(KEEPALIVE, 16#00).
-define(LOGIN, 16#01).
-define(HANDSHAKE, 16#02).
-define(TIME_UPDATE, 16#04).
-define(KICK, 16#FF).

-define(HANDSHAKE_MESSAGE(Username), list_to_binary([<<?HANDSHAKE:8>>, minecraft:string_to_unicode_binary(Username)])).

-define(KEEPALIVE_PATTERN, <<?KEEPALIVE:8, KeepAliveID:32/signed>>).
-define(HANDSHAKE_PATTERN, <<?HANDSHAKE:8, HashLen:16, Hash:HashLen/binary-unit:16>>).
-define(KICK_PATTERN, <<?KICK:8, Len:16, Reason:Len/binary-unit:16>>).

%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------

-export([start_link/0]).
-export([start/0, connect/2, connect_local/0]).

%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-record(state, {
socket,
session_id,
user,
listeners
}).

%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------

start_link() ->
application:start(inets),
application:start(crypto),
application:start(public_key),
application:start(ssl),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

start() ->
start_link().

connect(IP, Port) ->
gen_server:call(?MODULE, {connect, IP, Port}),
case minecraft:login_request("user", "password") of
{ok, {{_, 200, _}, _, Body}} ->
[_,_,_,SessionID] = string:tokens(Body, ":")
end,
gen_server:cast(?MODULE, {handshake_user, "spanx75", SessionID}).

connect_local() ->
connect("127.0.0.1", 25565).

%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------

init([]) ->
process_flag(trap_exit, true),
%%io:format("Connecting to ~s~n", [MinecraftHost]),
%%{ok, Socket} = gen_tcp:connect(MinecraftHost, 25565, [binary, {packet, 0}]),
%%o:format("Socket: ~p~n", [Socket]),
{ok, #state{listeners = []}}.

handle_call({connect, IP, Port}, _From, State) ->
case gen_tcp:connect(IP, Port, [binary, {active, once}]) of
{ok, Socket} ->
%gen_tcp:controlling_process(Socket, ?MODULE),
NewState = State#state{socket=Socket},
io:format("Socket ar: ~p~n", [Socket]),
{reply, ok, NewState};
{error, Reason} ->
{reply, {error, Reason}, State}
end;

handle_call(_Request, _From, State) ->
{noreply, ok, State}.

handle_cast({handshake_user, User, SessionID}, State) ->
gen_tcp:send(State#state.socket, ?HANDSHAKE_MESSAGE(User)),
{noreply, State#state{session_id = SessionID, user = User}};

handle_cast(_Msg, State) ->
{noreply, State}.

handle_info({tcp, _, ?HANDSHAKE_PATTERN}, State = #state{session_id = SessionID, user = User}) ->
StringHash = minecraft:utf16_binary_to_list(Hash),
io:format("Handshake hash: ~p~n", [StringHash]),
case httpc:request(get,
{lists:flatten(io_lib:format("http://session.minecraft.net/game/joinserver.jsp?user=~s&sessionId=~s&serverId=~s", [User, SessionID, StringHash])), []}, [], []) of
{ok, {{_, 200, _}, _, Body}} ->
io:format("Body: ~p~n", [Body])
end,
{noreply, State};

handle_info(Info, State) ->
io:format("Received info: ~p~n", [Info]),
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

0 comments on commit 55ed93b

Please sign in to comment.