Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 55ed93b6cbc6d5769aed2c3c43e430acc7bed363 @stevemohapibanks stevemohapibanks committed Sep 23, 2011
Showing with 203 additions and 0 deletions.
  1. +16 −0 Makefile
  2. +74 −0 minecraft.erl
  3. +113 −0 minecraft_server.erl
16 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 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 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.