Package com.gvaneyck.rtmp

Source Code of com.gvaneyck.rtmp.LoLRTMPSClient

package com.gvaneyck.rtmp;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;

import com.gvaneyck.rtmp.encoding.Base64;
import com.gvaneyck.rtmp.encoding.JSON;
import com.gvaneyck.rtmp.encoding.ObjectMap;
import com.gvaneyck.rtmp.encoding.TypedObject;
import com.kolakcc.loljclient.model.ServerInfo;

/**
* A very basic RTMPS client for connecting to League of Legends
*
* @author Gabriel Van Eyck
*/
public class LoLRTMPSClient extends RTMPSClient {
    /** Server information */
    private static final int port = 2099; // Must be 2099
    private ServerInfo serverInfo;
    private String server;
    private String region;

    /** Login information */
    private boolean loggedIn = false;
    private String loginQueue;
    private String user;
    private String pass;

    /** Garena information */
    private boolean useGarena = false;
    private String garenaToken;
    private String userID;

    /** Secondary login information */
    private String clientVersion;
    private String ipAddress;
    private String locale;

    /** Connection information */
    private String authToken;
    private String sessionToken;
    private int accountID;

    /**
     * Hidden constructor
     */
    @SuppressWarnings("unused")
    private LoLRTMPSClient() {
        super();
    }

    /**
     * Sets up a RTMPSClient for this client to use
     *
     * @param region The region to connect to (NA/EUW/EUN)
     * @param clientVersion The current client version for LoL (top left of
     *            client)
     * @param user The user to login as
     * @param pass The user's password
     */
    public LoLRTMPSClient(ServerInfo serverInfo, String clientVersion, String user, String pass) {
        this.serverInfo = serverInfo;
        this.region = serverInfo.region;
        this.server = serverInfo.server;
        this.loginQueue = serverInfo.loginQueue;
        this.useGarena = serverInfo.useGarena;

        this.clientVersion = clientVersion;
        this.user = user;
        this.pass = pass;

        // I believe this matters for running the game client
        this.locale = "en_US";

        setConnectionInfo(this.server, port, "", "app:/mod_ser.dat", null);
    }

    /**
     * Sets the locale. I believe this matters for starting the game (looks for
     * fontconfig_locale.txt)
     *
     * @param locale The locale to use
     */
    public void setLocale(String locale) {
        this.locale = locale;
    }

    /**
     * Retrieves the server info used to create this client
     *
     * @return The client's server info
     */
    public ServerInfo getServerInfo() {
        return serverInfo;
    }

    /**
     * Connects and logs in using the information previously provided
     *
     * @throws IOException
     */
    public void connectAndLogin() throws IOException {
        connect();
        login();
    }

    /**
     * Logs into Riot's servers
     *
     * @throws IOException
     */
    public void login() throws IOException {
        if (useGarena)
            getGarenaToken();

        getIPAddress();
        getAuthToken();

        TypedObject result, body;

        // Login 1
        body = new TypedObject("com.riotgames.platform.login.AuthenticationCredentials");
        if (useGarena)
            body.put("username", userID);
        else
            body.put("username", user);
        body.put("password", pass); // Garena doesn't actually care about
                                    // password here
        body.put("authToken", authToken);
        body.put("clientVersion", clientVersion);
        body.put("ipAddress", ipAddress);
        body.put("locale", locale);
        body.put("domain", "lolclient.lol.riotgames.com");
        body.put("operatingSystem", "LoLRTMPSClient");
        body.put("securityAnswer", null);
        body.put("oldPassword", null);
        if (useGarena)
            body.put("partnerCredentials", "8393 " + garenaToken);
        else
            body.put("partnerCredentials", null);
        int id = invoke("loginService", "login", new Object[] { body });

        // Read relevant data
        result = getResult(id);
        if (result.get("result").equals("_error"))
            throw new IOException(getErrorMessage(result));

        body = result.getTO("data").getTO("body");
        sessionToken = body.getString("token");
        accountID = body.getTO("accountSummary").getInt("accountId");

        // Login 2
        byte[] encbuff = null;
        if (useGarena)
            encbuff = (userID + ":" + sessionToken).getBytes("UTF-8");
        else
            encbuff = (user.toLowerCase() + ":" + sessionToken).getBytes("UTF-8");

        body = wrapBody(Base64.encodeBytes(encbuff), "auth", 8);
        body.type = "flex.messaging.messages.CommandMessage";

        id = invoke(body);
        result = getResult(id); // Read result (and discard)

        // Subscribe to the necessary items
        body = wrapBody(new Object[] { new TypedObject() }, "messagingDestination", 0);
        body.type = "flex.messaging.messages.CommandMessage";
        TypedObject headers = body.getTO("headers");
        // headers.put("DSRemoteCredentialsCharset", null); // unneeded
        // headers.put("DSRemoteCredentials", "");

        // bc
        headers.put("DSSubtopic", "bc");
        body.put("clientId", "bc-" + accountID);
        id = invoke(body);
        result = getResult(id); // Read result and discard

        // cn
        headers.put("DSSubtopic", "cn-" + accountID);
        body.put("clientId", "cn-" + accountID);
        id = invoke(body);
        result = getResult(id); // Read result and discard

        // gn
        headers.put("DSSubtopic", "gn-" + accountID);
        body.put("clientId", "gn-" + accountID);
        id = invoke(body);
        result = getResult(id); // Read result and discard

        // Start the heartbeat
        new LCDSHeartbeat(this);

        loggedIn = true;

        System.out.println("Connected to " + region);
    }

    /**
     * Closes the connection
     */
    public void close() {
        loggedIn = false;

        if (out != null) {
            // And attempt to logout, but don't care if we fail
            try {
                int id = invoke("loginService", "logout", new Object[] { authToken });
                join(id);
            }
            catch (IOException e) {
                // Ignored
            }
        }

        super.close();
    }

    /**
     * Additional reconnect steps for logging in after a reconnect
     */
    public void reconnect() {
        // Socket/RTMP reconnect
        super.reconnect();

        // Then login
        while (!isLoggedIn()) {
            try {
                login();
            }
            catch (IOException e) {
                System.err.println("Error when reconnecting: ");
                e.printStackTrace(); // For debug purposes

                sleep(5000);
                super.reconnect(); // Need to reconnect again here
            }
        }
    }

    /**
     * Returns the login state
     *
     * @return True if passed login queue and commands
     */
    public boolean isLoggedIn() {
        return loggedIn;
    }

    /**
     * Extracts the rootCause from an error message
     *
     * @param message The packet result
     * @return The error message
     */
    public String getErrorMessage(TypedObject message) {
        // Works for clientVersion
        return message.getTO("data").getTO("rootCause").getString("message");
    }

    /**
     * Calls Riot's IP address informer
     *
     * @throws IOException
     */
    private void getIPAddress() throws IOException {
        // Don't need to retrieve IP address on reconnect (probably)
        if (ipAddress != null)
            return;

        String response = readURL("http://ll.leagueoflegends.com/services/connection_info");

        // If we can't get an IP address for whatever reason (site's down, etc.)
        // use localhost
        if (response == null) {
            ipAddress = "127.0.0.1";
            return;
        }

        ObjectMap result = (ObjectMap)JSON.parse(response);
        ipAddress = result.getString("ip_address");
    }

    /**
     * Gets an authentication token from Garena to log in
     *
     * @throws IOException
     */
    private void getGarenaToken() throws IOException {
        try {
            // This is sloppy reverse engineered (via Wireshark) code
            byte[] md5 = MessageDigest.getInstance("MD5").digest(pass.getBytes("UTF-8"));
            int[] junk;
            Socket sock;
            OutputStream out;
            InputStream in;
            int c;

            // Find our user ID
            sock = new Socket("203.117.158.170", 9100);
            out = sock.getOutputStream();
            junk = new int[] { 0x49, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x79, 0x2f };
            for (int j : junk)
                out.write(j);

            out.write(user.getBytes());
            for (int i = 0; i < 16 - user.length(); i++)
                out.write(0x00);

            for (byte b : md5)
                out.write(String.format("%02x", b).getBytes());
            out.write(0x00);

            out.write(0x01);
            junk = new int[] { 0xD4, 0xAE, 0x52, 0xC0, 0x2E, 0xBA, 0x72, 0x03 };
            for (int j : junk)
                out.write(j);
            int timestamp = (int)(System.currentTimeMillis() / 1000);
            for (int i = 0; i < 4; i++)
                out.write((timestamp >> (8 * i)) & 0xFF);
            out.write(0x00);

            out.write("intl".getBytes());
            out.write(0x00);

            out.flush();

            // Read the result
            in = sock.getInputStream();

            // Skip the first 5 bytes
            for (int i = 0; i < 5; i++)
                in.read();

            // Get our ID
            int id = 0;
            for (int i = 0; i < 4; i++)
                id += in.read() * (1 << (8 * i));
            userID = String.valueOf(id);

            // Don't care about the rest
            sock.close();

            // Get our token
            sock = new Socket("lol.auth.garenanow.com", 12000);

            // Write our login info
            out = sock.getOutputStream();
            junk = new int[] { 0x32, 0x00, 0x00, 0x00, 0x01, 0x03, 0x80, 0x00, 0x00 };
            for (int j : junk)
                out.write(j);

            out.write(user.getBytes());
            out.write(0x00);

            md5 = MessageDigest.getInstance("MD5").digest(pass.getBytes("UTF-8"));
            for (byte b : md5)
                out.write(String.format("%02x", b).getBytes());
            out.write(0x00);

            out.write(0x00);
            out.write(0x00);

            out.flush();

            // Read our token
            in = sock.getInputStream();
            StringBuilder buff = new StringBuilder();

            // Skip the first 5 bytes
            for (int i = 0; i < 5; i++)
                in.read();

            // Read the result
            while ((c = in.read()) != 0)
                buff.append((char)c);

            garenaToken = buff.toString();

            sock.close();
        }
        catch (NoSuchAlgorithmException e) {
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Gets an authentication token for logging into Riot's servers
     *
     * @throws IOException
     */
    private void getAuthToken() throws IOException {
        // login-queue/rest/queue/authenticate
        // {"rate":60,"token":"d9a18f08-8159-4c27-9f3a-7927462b5150","reason":"login_rate","status":"LOGIN","delay":10000,"user":"USERHERE"}
        // --- OR ---
        // {"node":388,"vcap":20000,"rate":30,
        // "tickers":[
        // {"id":267284,"node":388,"champ":"Soraka","current":248118}, CHAMP
        // MATTERS
        // {"id":266782,"node":389,"champ":"Soraka","current":247595},
        // {"id":269287,"node":390,"champ":"Soraka","current":249444},
        // {"id":270005,"node":387,"champ":"Soraka","current":249735},
        // {"id":267732,"node":391,"champ":"Soraka","current":248190}
        // ],
        // "backlog":4,"reason":"login_rate","status":"QUEUE","champ":"Soraka","delay":10000,"user":"USERHERE"}

        // IF QUEUE
        // login-queue/rest/queue/ticker/CHAMPHERE
        // {"backlog":"8","387":"3d23b","388":"3cba5","389":"3c9ac","390":"3d10a","391":"3cc67"}

        // THEN
        // login-queue/rest/queue/authToken/USERHERE

        // Then optionally
        // login-queue/rest/queue/cancelQueue/USERHERE

        // Initial authToken request
        String payload;
        if (useGarena)
            payload = garenaToken;
        else
            payload = "user=" + user + ",password=" + pass;
        String query = "payload=" + URLEncoder.encode(payload, "ISO-8859-1");

        URL url = new URL(loginQueue + "login-queue/rest/queue/authenticate");

        HttpURLConnection connection;
        if (loginQueue.startsWith("https:")) {
            // Need to ignore certs (or use the one retrieved by RTMPSClient?)
            HttpsURLConnection.setDefaultSSLSocketFactory((SSLSocketFactory)DummySSLSocketFactory.getDefault());
            connection = (HttpsURLConnection)url.openConnection();
        }
        else {
            connection = (HttpURLConnection)url.openConnection();
        }

        connection.setDoOutput(true);
        connection.setRequestMethod("POST");

        // Open up the output stream of the connection
        DataOutputStream output = new DataOutputStream(connection.getOutputStream());

        // Write the POST data
        output.writeBytes(query);
        output.close();

        // Read the response
        String response;
        ObjectMap result;
        try {
            response = readAll(connection.getInputStream());
            result = (ObjectMap)JSON.parse(response);
        }
        catch (IOException e) {
            System.err.println("Incorrect username or password");
            throw e;
        }

        // Check for banned or other failures
        // {"rate":0,"reason":"account_banned","status":"FAILED","delay":10000,"banned":7647952951000}
        if (result.get("status").equals("FAILED"))
            throw new IOException("Error logging in: " + result.get("reason"));

        // Handle login queue
        if (!result.containsKey("token")) {
            int node = result.getInt("node"); // Our login queue ID
            String nodeStr = "" + node;
            String champ = result.getString("champ"); // The name of our login
                                                      // queue
            int rate = result.getInt("rate"); // How many tickets are processed
                                              // every queue update
            int delay = result.getInt("delay"); // How often the queue status
                                                // updates

            int id = 0;
            int cur = 0;
            Object[] tickers = result.getArray("tickers");
            for (Object o : tickers) {
                ObjectMap to = (ObjectMap)o;

                // Find our queue
                int tnode = to.getInt("node");
                if (tnode != node)
                    continue;

                id = to.getInt("id"); // Our ticket in line
                cur = to.getInt("current"); // The current ticket being
                                            // processed
                break;
            }

            // Let the user know
            System.out.println("In login queue for " + region + ", #" + (id - cur) + " in line");

            // Request the queue status until there's only 'rate' left to go
            while (id - cur > rate) {
                sleep(delay); // Sleep until the queue updates
                response = readURL(loginQueue + "login-queue/rest/queue/ticker/" + champ);
                result = (ObjectMap)JSON.parse(response);
                if (result == null)
                    continue;

                cur = hexToInt(result.getString(nodeStr));
                System.out.println("In login queue for " + region + ", #" + (int)Math.max(1, id - cur) + " in line");
            }

            // Then try getting our token repeatedly
            response = readURL(loginQueue + "login-queue/rest/queue/authToken/" + user.toLowerCase());
            result = (ObjectMap)JSON.parse(response);
            while (response == null || !result.containsKey("token")) {
                sleep(delay / 10);
                response = readURL(loginQueue + "login-queue/rest/queue/authToken/" + user.toLowerCase());
                result = (ObjectMap)JSON.parse(response);
            }
        }

        // Read the auth token
        authToken = result.getString("token");
    }

    /**
     * Reads all data available at a given URL
     *
     * @param url The URL to read
     * @return All data present at the given URL
     * @throws IOException
     */
    private String readURL(String url) {
        try {
            return readAll(new URL(url).openStream());
        }
        catch (MalformedURLException e) {
            // Should never happen
            e.printStackTrace();
            return null;
        }
        catch (IOException e) {
            // Only happens when we try to get our token too fast
            return null;
        }
    }

    /**
     * Reads all data from the given InputStream
     *
     * @param in The InputStream to read from
     * @return All data from the given InputStream
     * @throws IOException
     */
    private String readAll(InputStream in) throws IOException {
        StringBuilder ret = new StringBuilder();

        // Read in each character until end-of-stream is detected
        int c;
        while ((c = in.read()) != -1)
            ret.append((char)c);

        return ret.toString();
    }

    /**
     * Converts a hex string to an integer
     *
     * @param hex The hex string
     * @return The equivalent integer
     */
    private int hexToInt(String hex) {
        int total = 0;
        for (int i = 0; i < hex.length(); i++) {
            char c = hex.charAt(i);
            if (c >= '0' && c <= '9')
                total = total * 16 + c - '0';
            else
                total = total * 16 + c - 'a' + 10;
        }

        return total;
    }

    /**
     * Returns the account ID for this connection
     *
     * @return The account ID
     */
    public int getAccountID() {
        return accountID;
    }

    /**
     * Returns the session token for this connection
     *
     * @return The session token
     */
    public String getSessionToken() {
        return sessionToken;
    }
}
TOP

Related Classes of com.gvaneyck.rtmp.LoLRTMPSClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.