FFXIVLauncher-Netmaui
123 строки · 3.9 Кб
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.IO;
5using System.Linq;
6using System.Text;
7using System.Threading.Tasks;
8using Serilog;
9using XIVLauncher.Common.Encryption.BlockCipher;
10using XIVLauncher.Common.PlatformAbstractions;
11using XIVLauncher.Common.Util;
12
13namespace XIVLauncher.Common.Encryption;
14
15public class Ticket
16{
17public string Text { get; }
18public int Length { get; }
19
20private const string FUCKED_GARBAGE_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
21
22private Ticket(string text, int length)
23{
24this.Text = text;
25this.Length = length;
26}
27
28public static async Task<Ticket?> Get(ISteam steam)
29{
30var ticketBytes = await steam.GetAuthSessionTicketAsync().ConfigureAwait(true);
31
32if (ticketBytes == null)
33return null;
34
35return EncryptAuthSessionTicket(ticketBytes, steam.GetServerRealTime());
36}
37
38public static Ticket EncryptAuthSessionTicket(byte[] ticket, uint time)
39{
40time -= 5;
41time -= time % 60; // Time should be rounded to nearest minute.
42
43var ticketString = BitConverter.ToString(ticket).Replace("-", "").ToLower();
44var rawTicketBytes = Encoding.ASCII.GetBytes(ticketString);
45
46var rawTicket = new byte[rawTicketBytes.Length + 1];
47Array.Copy(rawTicketBytes, rawTicket, rawTicketBytes.Length);
48rawTicket[rawTicket.Length - 1] = 0;
49
50var blowfishKey = $"{time:x08}#un@e=x>";
51
52using var memorySteam = new MemoryStream();
53using var binaryWriter = new BinaryWriter(memorySteam);
54
55/* REGULAR SUM + TICKET */
56ushort ticketSum = 0;
57
58foreach (byte b in rawTicket)
59{
60ticketSum += b;
61}
62
63binaryWriter.Write(ticketSum);
64binaryWriter.Write(rawTicket);
65
66/* GARBAGE */
67int castTicketSum = unchecked((short)ticketSum);
68Log.Information($"{castTicketSum:X}");
69var seed = time ^ castTicketSum;
70var rand = new CrtRand((uint)seed);
71
72var numRandomBytes = ((ulong)(rawTicket.Length + 9) & 0xFFFFFFFFFFFFFFF8) - 2 - (ulong)rawTicket.Length;
73var garbage = new byte[numRandomBytes];
74
75uint fuckedSum = BitConverter.ToUInt32(memorySteam.ToArray(), 0);
76
77for (var i = 0u; i < numRandomBytes; i++)
78{
79var randChar = FUCKED_GARBAGE_ALPHABET[(int)(fuckedSum + rand.Next()) & 0x3F];
80garbage[i] = (byte)randChar;
81fuckedSum += randChar;
82}
83
84binaryWriter.Write(garbage);
85
86memorySteam.Seek(0, SeekOrigin.Begin);
87binaryWriter.Write(fuckedSum);
88
89Log.Information("[STEAM] time: {Time}, bfKey: {FishKey}, rawTicket.Length: {TicketLen}, ticketSum: {TicketSum}, fuckedSum: {FuckedSum}, seed: {Seed}, numRandomBytes: {NumRandomBytes}", time,
90blowfishKey, rawTicket.Length, ticketSum, fuckedSum, seed, numRandomBytes);
91
92/* ENC + SPLIT */
93var finalBytes = memorySteam.ToArray();
94
95var t = finalBytes[0];
96finalBytes[0] = finalBytes[1];
97finalBytes[1] = t;
98
99var keyBytes = Encoding.ASCII.GetBytes(blowfishKey);
100
101var blowfish = new Blowfish(keyBytes);
102var ecb = new Ecb<Blowfish>(blowfish);
103
104var encBytes = new byte[finalBytes.Length];
105Debug.Assert(encBytes.Length % 8 == 0);
106
107ecb.Encrypt(finalBytes, encBytes);
108var encString = GameHelpers.ToMangledSeBase64(encBytes);
109
110const int SPLIT_SIZE = 300;
111var parts = ChunksUpto(encString, SPLIT_SIZE).ToArray();
112
113var finalString = string.Join(",", parts);
114
115return new Ticket(finalString, finalString.Length - (parts.Length - 1));
116}
117
118private static IEnumerable<string> ChunksUpto(string str, int maxChunkSize)
119{
120for (var i = 0; i < str.Length; i += maxChunkSize)
121yield return str.Substring(i, Math.Min(maxChunkSize, str.Length - i));
122}
123}