Last active
August 21, 2020 02:02
-
-
Save AnirohDev/e9e1c44b264cb055e43aa199bff384c2 to your computer and use it in GitHub Desktop.
A small console application that can connect to the Theater backend of BFBC2 and obtain information like the server list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Net.Sockets; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace TheaterBackendSniffer | |
{ | |
class Program | |
{ | |
/// <summary> | |
/// A small console application that can connect to the Theater backend of BFBC2 and obtain information like the server list. | |
/// Created by Nico Hellmund aka Heico. You are free to do whatever you like with the code as long as you give proper credit. | |
/// Note: A valid LKey of an EA account with activated BFBC2 license is required to successfully connect to the Theater backend. | |
/// The LKey can be received by monitoring the BFBC2 client traffic with Wireshark. The key must be stored in msgLoginAsUser. | |
/// </summary> | |
/* Messages are written as "escaped strings" to replicate/fake the messages sent by the BFBC2 client accurately */ | |
private static readonly string msgConnectAt = "\x43\x4f\x4e\x4e\x40\x00\x00\x00\x00\x00" + | |
"\x00\x5b\x50\x52\x4f\x54\x3d\x32\x0a\x50\x52\x4f\x44\x3d\x62\x66" + | |
"\x62\x63\x32\x2d\x70\x63\x0a\x56\x45\x52\x53\x3d\x31\x2e\x30\x0a" + | |
"\x50\x4c\x41\x54\x3d\x50\x43\x0a\x4c\x4f\x43\x41\x4c\x45\x3d\x65" + | |
"\x6e\x5f\x55\x53\x0a\x53\x44\x4b\x56\x45\x52\x53\x49\x4f\x4e\x3d" + | |
"\x35\x2e\x31\x2e\x32\x2e\x30\x2e\x30\x0a\x54\x49\x44\x3d\x31\x0a" + | |
"\x00"; | |
private static readonly string msgLoginAsUser = "\x55\x53\x45\x52\x40\x00\x00\x00\x00\x00" + | |
"\x00\x59\x43\x49\x44\x3d\x0a\x4d\x41\x43\x3d\x24\x30\x30\x30\x30" + | |
"\x30\x30\x30\x30\x30\x30\x30\x30\x0a\x53\x4b\x55\x3d\x50\x43\x0a" + | |
"\x4c\x4b\x45\x59\x3d\x57\x31\x2d\x47\x58\x53\x6b\x43\x73\x6a\x54" + | |
"\x37\x51\x58\x50\x47\x43\x6b\x69\x36\x48\x41\x41\x41\x4b\x44\x77" + | |
"\x2e\x0a\x4e\x41\x4d\x45\x3d\x0a\x54\x49\x44\x3d\x32\x0a\x00"; | |
private static readonly string msgGetMiscInfo = "\x4c\x4c\x53\x54\x40\x00\x00\x00\x00\x00" + | |
"\x00\xa6\x46\x49\x4c\x54\x45\x52\x2d\x46\x41\x56\x2d\x4f\x4e\x4c" + | |
"\x59\x3d\x30\x0a\x46\x49\x4c\x54\x45\x52\x2d\x4e\x4f\x54\x2d\x46" + | |
"\x55\x4c\x4c\x3d\x30\x0a\x46\x49\x4c\x54\x45\x52\x2d\x4e\x4f\x54" + | |
"\x2d\x50\x52\x49\x56\x41\x54\x45\x3d\x30\x0a\x46\x49\x4c\x54\x45" + | |
"\x52\x2d\x4e\x4f\x54\x2d\x43\x4c\x4f\x53\x45\x44\x3d\x30\x0a\x46" + | |
"\x49\x4c\x54\x45\x52\x2d\x4d\x49\x4e\x2d\x53\x49\x5a\x45\x3d\x30" + | |
"\x0a\x46\x41\x56\x2d\x50\x4c\x41\x59\x45\x52\x3d\x0a\x46\x41\x56" + | |
"\x2d\x47\x41\x4d\x45\x3d\x0a\x46\x41\x56\x2d\x50\x4c\x41\x59\x45" + | |
"\x52\x2d\x55\x49\x44\x3d\x0a\x46\x41\x56\x2d\x47\x41\x4d\x45\x2d" + | |
"\x55\x49\x44\x3d\x0a\x54\x49\x44\x3d\x33\x0a\x00"; | |
private static readonly string msgGetServerList = "\x47\x4c\x53\x54\x40\x00\x00\x00\x00\x00" + | |
"\x00\xda\x4c\x49\x44\x3d\x32\x35\x37\x0a\x54\x59\x50\x45\x3d\x47" + | |
"\x0a\x46\x49\x4c\x54\x45\x52\x2d\x46\x41\x56\x2d\x4f\x4e\x4c\x59" + | |
"\x3d\x30\x0a\x46\x49\x4c\x54\x45\x52\x2d\x4e\x4f\x54\x2d\x46\x55" + | |
"\x4c\x4c\x3d\x30\x0a\x46\x49\x4c\x54\x45\x52\x2d\x4e\x4f\x54\x2d" + | |
"\x50\x52\x49\x56\x41\x54\x45\x3d\x30\x0a\x46\x49\x4c\x54\x45\x52" + | |
"\x2d\x4e\x4f\x54\x2d\x43\x4c\x4f\x53\x45\x44\x3d\x30\x0a\x46\x49" + | |
"\x4c\x54\x45\x52\x2d\x4d\x49\x4e\x2d\x53\x49\x5a\x45\x3d\x30\x0a" + | |
"\x46\x49\x4c\x54\x45\x52\x2d\x41\x54\x54\x52\x2d\x55\x2d\x67\x61" + | |
"\x6d\x65\x4d\x6f\x64\x3d\x42\x43\x32\x0a\x46\x41\x56\x2d\x50\x4c" + | |
"\x41\x59\x45\x52\x3d\x0a\x46\x41\x56\x2d\x47\x41\x4d\x45\x3d\x0a" + | |
"\x43\x4f\x55\x4e\x54\x3d\x32\x30\x30\x30\x0a\x46\x41\x56\x2d\x50" + | |
"\x4c\x41\x59\x45\x52\x2d\x55\x49\x44\x3d\x0a\x46\x41\x56\x2d\x47" + | |
"\x41\x4d\x45\x2d\x55\x49\x44\x3d\x0a\x54\x49\x44\x3d\x34\x0a\x00"; | |
static void Main(string[] args) | |
{ | |
//Run main entry point async | |
MainAsync().GetAwaiter().GetResult(); | |
WriteToLog("Press any key to close..."); | |
Console.ReadKey(); | |
} | |
private static async Task MainAsync() | |
{ | |
//Delete old log from previous session | |
if (File.Exists("event.log")) | |
File.Delete("event.log"); | |
//Connect to Theater backend via TCP | |
var client = new TcpClient(); | |
string hostname = "bfbc2-pc.theater.ea.com"; | |
client.Connect(hostname, 18395); | |
//Get network stream so we can interact with the backend (write & read) | |
NetworkStream networkStream = client.GetStream(); | |
networkStream.ReadTimeout = 5000; | |
/* Simulate communication of BFBC2 client to Theater backend by sending the same messages in the correct order */ | |
//Send initial connection message to Theater backend | |
await SendMessage(networkStream, msgConnectAt, 120); | |
//Login with a valid LKey of an BFBC2/EA account | |
await SendMessage(networkStream, msgLoginAsUser, 100); | |
//Request basic info about server list and stuff | |
await SendMessage(networkStream, msgGetMiscInfo, 187); | |
//Request actual server list | |
await SendMessage(networkStream, msgGetServerList, 138); | |
} | |
private static async Task SendMessage(NetworkStream stream, string message, int receiveBuffer) | |
{ | |
//Send message | |
byte[] data = Encoding.GetEncoding(1252).GetBytes(message); | |
stream.Write(data, 0, data.Length); | |
WriteToLog("Sent:\n" + message); | |
//Get response | |
string response = GetResponse(stream, receiveBuffer); | |
WriteToLog("Response:\n" + response); | |
if (message.StartsWith("LLST")) | |
{ | |
//msgGetMiscInfo will result in two responses so we capture the second one here | |
response = GetResponse(stream, 188); | |
WriteToLog("Response:\n" + response); | |
} | |
else if (message.StartsWith("GLST")) | |
{ | |
await Task.Delay(1000); | |
//msgGetServerList will result in several responses of unknown size so we capture all of them here | |
response = GetResponse(stream, 2); | |
WriteToLog("Response:\n" + response); | |
} | |
await Task.Delay(1000); | |
} | |
private static string GetResponse(NetworkStream stream, int buffer) | |
{ | |
//Reading the full response of the server can be more tricky than I thought | |
//So this method might not be 100% accurate, but most responses look fine | |
byte[] data = new byte[buffer]; | |
using (MemoryStream memoryStream = new MemoryStream()) | |
{ | |
do | |
{ | |
stream.Read(data, 0, data.Length); | |
memoryStream.Write(data, 0, data.Length); | |
} while (stream.DataAvailable); | |
return Encoding.GetEncoding(1252).GetString(memoryStream.ToArray(), 0, (int)memoryStream.Length); | |
} | |
} | |
private static void WriteToLog(string message) | |
{ | |
Console.WriteLine("\n[" + DateTime.Now + "] " + message); | |
using (StreamWriter sw = new StreamWriter("event.log", true)) | |
{ | |
sw.WriteLine("\n[" + DateTime.Now + "] " + message); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment