mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-27 07:59:03 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
|
||||
public static class ImageSignatureRequest
|
||||
{
|
||||
|
||||
|
||||
|
||||
const int HeaderScanLimit = 0x100;
|
||||
|
||||
const int MinimumRecords = 32;
|
||||
|
||||
|
||||
|
||||
|
||||
const int PrefixProbe = 8;
|
||||
|
||||
const int PackingProbe = 1024;
|
||||
|
||||
const int MaxLiteralLength = 1 << 20;
|
||||
|
||||
static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, true);
|
||||
|
||||
|
||||
public static string? DetectKeyId(byte[]? metadata)
|
||||
{
|
||||
if (metadata == null || metadata.Length < 24 ||
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(metadata) != 0xFAB11BAF ||
|
||||
BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(4)) != 27)
|
||||
return null;
|
||||
int table = BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(8));
|
||||
int size = BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(12));
|
||||
int data = BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(16));
|
||||
int dataSize = BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(20));
|
||||
if (table < 24 || size < 0 || size % 8 != 0 || (long)table + size > metadata.Length ||
|
||||
data < 24 || dataSize < 0 || (long)data + dataSize > metadata.Length)
|
||||
return null;
|
||||
|
||||
return ScanKeyTails(metadata, table, size / 8, data, dataSize);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static string? DetectKeyIdFromGameMetadata(byte[]? metadata)
|
||||
{
|
||||
if (metadata == null || metadata.Length < 24) return null;
|
||||
if (BinaryPrimitives.ReadUInt32LittleEndian(metadata) == 0xFAB11BAF &&
|
||||
BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(4)) == 27)
|
||||
return DetectKeyId(metadata);
|
||||
return DetectKeyIdByHeaderScan(metadata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static string? ScanKeyTails(byte[] metadata, int tableOffset, int recordCount, int dataOffset, int dataSize)
|
||||
{
|
||||
string? key = null;
|
||||
for (int record = 0; record < recordCount; record++)
|
||||
{
|
||||
var (length, index) = Record(metadata, tableOffset, record);
|
||||
if (length < 0 || index < 0 || (long)index + length > dataSize) return null;
|
||||
if (length != 8 && length != 18) continue;
|
||||
var literal = Encoding.UTF8.GetString(metadata, dataOffset + index, length);
|
||||
if (literal != ".rec.net" && literal != ".radicade.internal") continue;
|
||||
var candidate = "KEY:RSA:p1" + literal;
|
||||
if (key != null && key != candidate) return null;
|
||||
key = candidate;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static (int Length, int Index) Record(byte[] metadata, int tableOffset, int record)
|
||||
{
|
||||
int offset = tableOffset + 8 * record;
|
||||
return (BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(offset)),
|
||||
BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(offset + 4)));
|
||||
}
|
||||
|
||||
public static string? DetectKeyIdByHeaderScan(byte[]? metadata)
|
||||
{
|
||||
if (metadata == null || metadata.Length < HeaderScanLimit + 8) return null;
|
||||
|
||||
var fields = new List<int>();
|
||||
for (int offset = 0; offset + 4 <= HeaderScanLimit; offset += 4)
|
||||
{
|
||||
int value = BinaryPrimitives.ReadInt32LittleEndian(metadata.AsSpan(offset));
|
||||
if (value > 0 && value <= metadata.Length && !fields.Contains(value)) fields.Add(value);
|
||||
}
|
||||
|
||||
var tables = new List<(int Offset, int Count)>();
|
||||
foreach (var tableOffset in fields)
|
||||
foreach (var tableSize in fields)
|
||||
{
|
||||
if (tableSize % 8 != 0 || tableSize / 8 < MinimumRecords) continue;
|
||||
if (tableOffset < HeaderScanLimit || (long)tableOffset + tableSize > metadata.Length) continue;
|
||||
tables.Add((tableOffset, tableSize / 8));
|
||||
}
|
||||
|
||||
var slices = new List<(int Offset, int Size)>();
|
||||
foreach (var dataOffset in fields)
|
||||
foreach (var dataSize in fields)
|
||||
{
|
||||
if (dataSize <= 0 || dataOffset < HeaderScanLimit) continue;
|
||||
if ((long)dataOffset + dataSize > metadata.Length) continue;
|
||||
slices.Add((dataOffset, dataSize));
|
||||
}
|
||||
|
||||
|
||||
|
||||
tables.Sort((a, b) => b.Count.CompareTo(a.Count));
|
||||
|
||||
foreach (var (tableOffset, count) in tables)
|
||||
foreach (var (dataOffset, dataSize) in slices)
|
||||
{
|
||||
if (!LooksLikeLiteralTable(metadata, tableOffset, count, dataOffset, dataSize, PrefixProbe)) continue;
|
||||
if (!SampleDecodesAsText(metadata, tableOffset, count, dataOffset, dataSize)) continue;
|
||||
if (!LooksLikeLiteralTable(metadata, tableOffset, count, dataOffset, dataSize, PackingProbe)) continue;
|
||||
var key = ScanKeyTails(metadata, tableOffset, count, dataOffset, dataSize);
|
||||
if (key != null) return key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static bool LooksLikeLiteralTable(byte[] metadata, int tableOffset, int count, int dataOffset, int dataSize, int probe)
|
||||
{
|
||||
int examined = Math.Min(count, probe);
|
||||
int contiguous = 0;
|
||||
long running = 0;
|
||||
for (int record = 0; record < examined; record++)
|
||||
{
|
||||
var (length, index) = Record(metadata, tableOffset, record);
|
||||
if (length < 0 || length > MaxLiteralLength || index < 0 || (long)index + length > dataSize) return false;
|
||||
if (index == running) contiguous++;
|
||||
running = index + length;
|
||||
}
|
||||
return contiguous >= examined - Math.Max(1, examined / 100);
|
||||
}
|
||||
|
||||
static bool SampleDecodesAsText(byte[] metadata, int tableOffset, int count, int dataOffset, int dataSize)
|
||||
{
|
||||
int step = Math.Max(1, count / 48);
|
||||
for (int record = 0; record < count; record += step)
|
||||
{
|
||||
var (length, index) = Record(metadata, tableOffset, record);
|
||||
if (length < 0 || index < 0 || (long)index + length > dataSize) return false;
|
||||
if (length == 0) continue;
|
||||
try
|
||||
{
|
||||
StrictUtf8.GetCharCount(metadata, dataOffset + index, length);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string AddKeyId(string url, string serverUrl, string? keyId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyId) ||
|
||||
!Uri.TryCreate(url, UriKind.Absolute, out var request) ||
|
||||
!Uri.TryCreate(serverUrl, UriKind.Absolute, out var server) ||
|
||||
(server.Scheme != "http" && server.Scheme != "https") ||
|
||||
request.Scheme != server.Scheme || request.Authority != server.Authority ||
|
||||
!request.AbsolutePath.StartsWith(server.AbsolutePath.TrimEnd('/') + "/images/", StringComparison.OrdinalIgnoreCase))
|
||||
return url;
|
||||
|
||||
|
||||
var parameters = request.Query.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(p => !Uri.UnescapeDataString(p.Split('=')[0]).Equals("rcsig", StringComparison.OrdinalIgnoreCase))
|
||||
.Append("rcsig=" + Uri.EscapeDataString(keyId));
|
||||
return request.GetLeftPart(UriPartial.Path) + "?" + string.Join("&", parameters) + request.Fragment;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user