-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
93639a2
commit 2237250
Showing
50 changed files
with
49,698 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
name: docker-image | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
branches: | ||
- 'master' | ||
paths: | ||
- 'spectator/**' | ||
|
||
env: | ||
IMAGE_TAG_OWNER: opensource-deadlock-tools | ||
IMAGE_TAG_NAME: devlock | ||
|
||
permissions: | ||
contents: read | ||
packages: write | ||
|
||
concurrency: | ||
cancel-in-progress: true | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
|
||
jobs: | ||
docker: | ||
strategy: | ||
matrix: | ||
PATH: [spectator/server, spectator/client] | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
- name: Set up QEMU | ||
uses: docker/setup-qemu-action@v3 | ||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
- name: Login to GitHub Container Registry | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: ghcr.io | ||
username: ${{ github.repository_owner }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Build and Push Compose | ||
uses: docker/build-push-action@v6 | ||
with: | ||
push: true | ||
tags: ghcr.io/${{ env.IMAGE_TAG_OWNER }}/${{ env.IMAGE_TAG_NAME }}/${{ matrix.PATH }}:latest | ||
context: ${{ matrix.PATH }} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
SPECTATOR_STEAM_ACCOUNTS='[ | ||
{"username": "user1", "password": "pass1"}, | ||
{"username": "user2", "password": "pass2"} | ||
]' |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
docker-compose.yaml |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="protobuf-net" Version="3.2.30" /> | ||
<PackageReference Include="Snappier" Version="1.1.6" /> | ||
<PackageReference Include="SteamKit2" Version="2.5.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
using DeadlockAPI.Enums; | ||
using ouwou.GC.Deadlock.Internal; | ||
using SteamKit2; | ||
using SteamKit2.Authentication; | ||
using SteamKit2.GC; | ||
using SteamKit2.Internal; | ||
|
||
namespace DeadlockAPI { | ||
public class DeadlockClient { | ||
SteamClient client; | ||
|
||
SteamUser user; | ||
SteamGameCoordinator gameCoordinator; | ||
|
||
CallbackManager callbackMgr; | ||
|
||
string userName; | ||
string password; | ||
string previouslyStoredGuardData; | ||
|
||
bool disconnecting = false; | ||
|
||
const int APPID = 1422450; | ||
|
||
uint clientVersion = 0; | ||
|
||
public DeadlockClient(string userName, string password) { | ||
this.userName = userName; | ||
this.password = password; | ||
|
||
client = new SteamClient(); | ||
|
||
user = client.GetHandler<SteamUser>(); | ||
gameCoordinator = client.GetHandler<SteamGameCoordinator>(); | ||
|
||
callbackMgr = new CallbackManager(client); | ||
callbackMgr.Subscribe<SteamClient.ConnectedCallback>(OnConnected); | ||
callbackMgr.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected); | ||
callbackMgr.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn); | ||
callbackMgr.Subscribe<SteamGameCoordinator.MessageCallback>(OnGCMessage); | ||
|
||
if (File.Exists("guard.txt")) { | ||
previouslyStoredGuardData = File.ReadAllText("guard.txt"); | ||
} | ||
} | ||
|
||
public bool IsConnected { get => client.IsConnected; } | ||
|
||
public void Connect() { | ||
Console.WriteLine("[*] Connecting to Steam"); | ||
disconnecting = false; | ||
client.Connect(); | ||
} | ||
|
||
public void Disconnect() { | ||
disconnecting = true; | ||
client.Disconnect(); | ||
} | ||
|
||
public void Wait() { | ||
while (true) { | ||
callbackMgr.RunWaitCallbacks(TimeSpan.FromSeconds(1)); | ||
} | ||
} | ||
|
||
public void RunCallbacks(TimeSpan t) { | ||
callbackMgr.RunWaitCallbacks(t); | ||
} | ||
|
||
public void Start() | ||
{ | ||
Connect(); | ||
|
||
// Run Wait() on a separate thread | ||
_ = Task.Run(() => Wait()); | ||
} | ||
|
||
async void OnConnected(SteamClient.ConnectedCallback callback) { | ||
Console.WriteLine("[*] Connected. Logging into Steam as {0}", userName); | ||
|
||
var authSession = await client.Authentication.BeginAuthSessionViaCredentialsAsync(new AuthSessionDetails { | ||
Username = userName, | ||
Password = password, | ||
IsPersistentSession = true, | ||
GuardData = previouslyStoredGuardData, | ||
Authenticator = new UserConsoleAuthenticator(), | ||
}); | ||
var pollResponse = await authSession.PollingWaitForResultAsync(); | ||
if (pollResponse.NewGuardData != null) { | ||
previouslyStoredGuardData = pollResponse.NewGuardData; | ||
File.WriteAllText("guard.txt", previouslyStoredGuardData); | ||
} | ||
user.LogOn(new SteamUser.LogOnDetails { | ||
Username = pollResponse.AccountName, | ||
AccessToken = pollResponse.RefreshToken, | ||
ShouldRememberPassword = true, | ||
}); | ||
} | ||
|
||
void OnDisconnected(SteamClient.DisconnectedCallback callback) { | ||
if (!disconnecting) { | ||
Console.WriteLine("[*] Could not connect.. Retrying.."); | ||
Thread.Sleep(5000); | ||
Connect(); | ||
} | ||
} | ||
|
||
void OnLoggedOn(SteamUser.LoggedOnCallback callback) { | ||
if (callback.Result != EResult.OK) { | ||
Console.Error.WriteLine("[*] Unable to log on to Steam: {0}", callback.Result); | ||
return; | ||
} | ||
|
||
Console.WriteLine("[*] Logged in. Launching Deadlock"); | ||
|
||
var playGame = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed); | ||
playGame.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { | ||
game_id = new GameID(APPID) | ||
}); | ||
|
||
client.Send(playGame); | ||
|
||
Thread.Sleep(5000); | ||
|
||
var clientHello = new ClientGCMsgProtobuf<CMsgCitadelClientHello>((uint)EGCBaseClientMsg.k_EMsgGCClientHello); | ||
clientHello.Body.region_mode = ECitadelRegionMode.k_ECitadelRegionMode_ROW; | ||
gameCoordinator.Send(clientHello, APPID); | ||
} | ||
|
||
void OnGCMessage(SteamGameCoordinator.MessageCallback callback) { | ||
var messageMap = new Dictionary<uint, Action<IPacketGCMsg>> | ||
{ | ||
{ (uint) EGCBaseClientMsg.k_EMsgGCClientWelcome, OnClientWelcome }, | ||
{ (uint) EGCCitadelClientMessages.k_EMsgGCToClientDevPlaytestStatus, OnDevPlaytestStatus }, | ||
}; | ||
|
||
Action<IPacketGCMsg> func; | ||
if (!messageMap.TryGetValue(callback.EMsg, out func)) { | ||
return; | ||
} | ||
|
||
func(callback.Message); | ||
} | ||
|
||
public async Task<U?> SendAndReceiveWithJob<T, U>(ClientGCMsgProtobuf<T> msg) | ||
where T : ProtoBuf.IExtensible, new() | ||
where U : ProtoBuf.IExtensible, new() { | ||
msg.SourceJobID = client.GetNextJobID(); | ||
gameCoordinator.Send(msg, APPID); | ||
try { | ||
var cb = await new AsyncJob<SteamGameCoordinator.MessageCallback>(client, msg.SourceJobID); | ||
var response = new ClientGCMsgProtobuf<U>(cb.Message); | ||
|
||
return response.Body; | ||
} catch (Exception e) { | ||
Console.Write(e.ToString()); | ||
return default; | ||
} | ||
} | ||
|
||
public class MatchMetaData { | ||
public required CMsgClientToGCGetMatchMetaDataResponse Data; | ||
public required string ReplayURL; | ||
public required string MetadataURL; | ||
} | ||
|
||
public async Task<MatchMetaData?> GetMatchMetaData(uint matchId) { | ||
var msg = new ClientGCMsgProtobuf<CMsgClientToGCGetMatchMetaData>((uint)EGCCitadelClientMessages.k_EMsgClientToGCGetMatchMetaData); | ||
msg.Body.match_id = matchId; | ||
var r = await SendAndReceiveWithJob<CMsgClientToGCGetMatchMetaData, CMsgClientToGCGetMatchMetaDataResponse>(msg); | ||
if (r == null) return null; | ||
return new MatchMetaData() { | ||
Data = r, | ||
ReplayURL = $"http://replay{r.cluster_id}.valve.net/{APPID}/{matchId}_{r.replay_salt}.dem.bz2", | ||
MetadataURL = $"http://replay{r.cluster_id}.valve.net/{APPID}/{matchId}_{r.metadata_salt}.meta.bz2" | ||
}; | ||
} | ||
|
||
public async Task<CMsgClientToGCSpectateLobbyResponse?> SpectateLobby(uint matchId) { | ||
var msg = new ClientGCMsgProtobuf<CMsgClientToGCSpectateLobby>((uint)EGCCitadelClientMessages.k_EMsgClientToGCSpectateLobby); | ||
msg.Body.match_id = matchId; | ||
msg.Body.client_version = clientVersion; | ||
msg.Body.client_platform = EGCPlatform.k_eGCPlatform_PC; | ||
return await SendAndReceiveWithJob<CMsgClientToGCSpectateLobby, CMsgClientToGCSpectateLobbyResponse>(msg); | ||
} | ||
|
||
public async Task<CMsgClientToGCFindHeroBuildsResponse?> FindHeroBuilds(Heroes hero) { | ||
var msg = new ClientGCMsgProtobuf<CMsgClientToGCFindHeroBuilds>((uint)EGCCitadelClientMessages.k_EMsgClientToGCFindHeroBuilds); | ||
msg.Body.hero_id = (uint)hero; | ||
return await SendAndReceiveWithJob<CMsgClientToGCFindHeroBuilds, CMsgClientToGCFindHeroBuildsResponse>(msg); | ||
} | ||
|
||
public async Task<CMsgClientToGCGetActiveMatchesResponse?> GetActiveMatches() { | ||
var msg = new ClientGCMsgProtobuf<CMsgClientToGCGetActiveMatches>((uint)EGCCitadelClientMessages.k_EMsgClientToGCGetActiveMatches); | ||
msg.SourceJobID = client.GetNextJobID(); | ||
gameCoordinator.Send(msg, APPID); | ||
var cb = await new AsyncJob<SteamGameCoordinator.MessageCallback>(client, msg.SourceJobID); | ||
var decompressed = Snappier.Snappy.DecompressToArray(new ReadOnlySpan<byte>(cb.Message.GetData(), 24, cb.Message.GetData().Length - 24)); | ||
return ProtoBuf.Serializer.Deserialize<CMsgClientToGCGetActiveMatchesResponse>(new ReadOnlySpan<byte>(decompressed)); | ||
} | ||
|
||
public class ClientWelcomeEventArgs : EventArgs { | ||
public required CMsgClientWelcome Data; | ||
} | ||
public event EventHandler<ClientWelcomeEventArgs> ClientWelcomeEvent; | ||
void OnClientWelcome(IPacketGCMsg packetMsg) { | ||
var msg = new ClientGCMsgProtobuf<CMsgClientWelcome>(packetMsg); | ||
clientVersion = msg.Body.version; | ||
Console.WriteLine($"[*] Deadlock Client v{clientVersion}"); | ||
ClientWelcomeEvent?.Invoke(this, new ClientWelcomeEventArgs() { Data = msg.Body }); | ||
} | ||
|
||
public class DevPlaytestStatusEventArgs : EventArgs { | ||
public required CMsgGCToClientDevPlaytestStatus Data; | ||
} | ||
public event EventHandler<DevPlaytestStatusEventArgs> DevPlaytestStatusEvent; | ||
void OnDevPlaytestStatus(IPacketGCMsg packetMsg) { | ||
var msg = new ClientGCMsgProtobuf<CMsgGCToClientDevPlaytestStatus>(packetMsg); | ||
DevPlaytestStatusEvent?.Invoke(this, new DevPlaytestStatusEventArgs() { Data = msg.Body }); | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace DeadlockAPI.Enums | ||
{ | ||
public enum Heroes | ||
{ | ||
Abrams = 6, | ||
Bebop = 15, | ||
Dynamo = 11, | ||
GreyTalon = 17, | ||
Haze = 13, | ||
Infernus = 1, | ||
Ivy = 20, | ||
Kelvin = 12, | ||
LadyGeist = 4, | ||
Lash = 31, | ||
McGinnis = 8, | ||
MoAndKrill = 18, | ||
Paradox = 10, | ||
Pocket = 50, | ||
Seven = 2, | ||
Shiv = 19, | ||
Vindicta = 3, | ||
Viscous = 35, | ||
Warden = 25, | ||
Wraith = 7, | ||
Yamato = 27, | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace DeadlockAPI.Enums | ||
{ | ||
public enum MatchResult | ||
{ | ||
Win = 1, | ||
Loss = 0, | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace DeadlockAPI.Enums | ||
{ | ||
public enum Teams | ||
{ | ||
Team0 = 0, | ||
Team1 = 1 | ||
} | ||
} |
Oops, something went wrong.