Skip to content

Instantly share code, notes, and snippets.

@AnirohDev
Last active August 21, 2020 02:02
Show Gist options
  • Save AnirohDev/e9e1c44b264cb055e43aa199bff384c2 to your computer and use it in GitHub Desktop.
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.
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