mirror of
https://github.com/myawired/2025-RecRoom-Server-E12354.git
synced 2026-07-17 16:11:15 +10:00
199 lines
8.6 KiB
C#
199 lines
8.6 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Newtonsoft.Json;
|
|
using Rec_rewild_live_rewrite.api.server;
|
|
|
|
namespace Rec_rewild_live_rewrite.Controllers
|
|
{
|
|
[ApiController]
|
|
public class CDNController : Controller
|
|
{
|
|
[HttpGet("/cdn/config/LoadingScreenTipData")]
|
|
public IActionResult LoadingScreenTipData()
|
|
{
|
|
string filePath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "LoadingScreenTipData.json");
|
|
|
|
if (!System.IO.File.Exists(filePath))
|
|
return Content("[]", "application/json");
|
|
|
|
return PhysicalFile(filePath, "application/json");
|
|
}
|
|
|
|
[HttpGet("/iam/me/channels/{type}")]
|
|
public IActionResult GetIamChannel(string type)
|
|
{
|
|
var (account, error) = AuthHelper.ValidateToken(HttpContext);
|
|
if (error != null)
|
|
return error;
|
|
|
|
string basePath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS");
|
|
|
|
switch (type.ToLower())
|
|
{
|
|
case "announcements":
|
|
string announcementsPath = Path.Combine(basePath, "IamAnnouncements.json");
|
|
if (System.IO.File.Exists(announcementsPath))
|
|
return PhysicalFile(announcementsPath, "application/json");
|
|
break;
|
|
|
|
case "justintimetutorials":
|
|
return Ok(ConfigRewild.Bracket);
|
|
}
|
|
|
|
return Ok(ConfigRewild.Bracket);
|
|
}
|
|
|
|
[HttpGet("/sections/pagesource/{type}")]
|
|
public IActionResult GetWatchPages(string type)
|
|
{
|
|
var (account, error) = AuthHelper.ValidateToken(HttpContext);
|
|
if (error != null)
|
|
return error;
|
|
|
|
string path = Path.Combine(
|
|
Environment.CurrentDirectory,
|
|
"Data",
|
|
"APIS",
|
|
"WatchPages",
|
|
$"{type}.json"
|
|
);
|
|
|
|
if (!System.IO.File.Exists(path))
|
|
return Ok(ConfigRewild.Bracket);
|
|
|
|
return PhysicalFile(path, "application/json");
|
|
}
|
|
|
|
[Route("/cdn/room/{*room_path}")]
|
|
public async Task<IActionResult> CDNRoom(string room_path)
|
|
{
|
|
// 1. SANITIZE: Prevent path traversal
|
|
string fileName = Path.GetFileName(room_path);
|
|
|
|
string localPath = Path.Combine(Environment.CurrentDirectory, "Data", "CDN", "RoomBlobs", room_path);
|
|
string directory = Path.GetDirectoryName(localPath)!;
|
|
|
|
try
|
|
{
|
|
if (!Directory.Exists(directory))
|
|
Directory.CreateDirectory(directory);
|
|
|
|
// 2. DOWNLOAD IF MISSING
|
|
if (!System.IO.File.Exists(localPath))
|
|
{
|
|
using var client = new HttpClient();
|
|
var response = await client.GetAsync($"https://cdn.rec.net/room/{room_path}");
|
|
if (!response.IsSuccessStatusCode) return NotFound();
|
|
|
|
await using var remoteStream = await response.Content.ReadAsStreamAsync();
|
|
await using var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
|
await remoteStream.CopyToAsync(fileStream);
|
|
}
|
|
|
|
// 3. GATHER FILE METADATA & DYNAMIC DATA
|
|
var fileInfo = new FileInfo(localPath);
|
|
var now = DateTimeOffset.UtcNow;
|
|
|
|
// Compute actual MD5 hash dynamically
|
|
string md5Base64;
|
|
using (var md5 = System.Security.Cryptography.MD5.Create())
|
|
{
|
|
using var readStream = System.IO.File.OpenRead(localPath);
|
|
byte[] hash = await md5.ComputeHashAsync(readStream);
|
|
md5Base64 = Convert.ToBase64String(hash);
|
|
}
|
|
|
|
// Generate fake Azure/Cloudflare IDs
|
|
string requestId = Guid.NewGuid().ToString();
|
|
// Mimic Azure Ref format: YYYYMMDDTHHMMSSZ-[randomhex]
|
|
string azureRef = $"{now:yyyyMMddTHHmmssZ}-{Guid.NewGuid().ToString("N").Substring(0, 32)}";
|
|
// Generate ETag based on the file's LastWriteTime to act as a unique version identifier
|
|
string eTag = $"\"0x{fileInfo.LastWriteTimeUtc.Ticks:X}\"";
|
|
|
|
// 4. APPLY DYNAMIC HEADERS
|
|
// Note: Content-Length, Content-Type, and Accept-Ranges are handled automatically by the File() return method.
|
|
Response.Headers["Date"] = now.ToString("R"); // RFC 1123 format (e.g., Thu, 12 Mar 2026 16:06:27 GMT)
|
|
Response.Headers["Cache-Control"] = "public, max-age=31536000";
|
|
Response.Headers["Last-Modified"] = fileInfo.LastWriteTimeUtc.ToString("R");
|
|
Response.Headers["ETag"] = eTag;
|
|
|
|
// Mock Azure Blob Storage Headers
|
|
Response.Headers["x-ms-request-id"] = requestId;
|
|
Response.Headers["x-ms-version"] = "2021-04-10";
|
|
Response.Headers["x-ms-version-id"] = fileInfo.LastWriteTimeUtc.ToString("o"); // ISO 8601 format
|
|
Response.Headers["x-ms-is-current-version"] = "true";
|
|
Response.Headers["x-ms-creation-time"] = fileInfo.CreationTimeUtc.ToString("R");
|
|
Response.Headers["x-ms-blob-content-md5"] = md5Base64;
|
|
Response.Headers["x-ms-lease-status"] = "unlocked";
|
|
Response.Headers["x-ms-lease-state"] = "available";
|
|
Response.Headers["x-ms-blob-type"] = "BlockBlob";
|
|
Response.Headers["x-ms-server-encrypted"] = "true";
|
|
Response.Headers["x-ms-last-access-time"] = now.ToString("R");
|
|
|
|
// CORS and CDN Mock Headers
|
|
Response.Headers["Access-Control-Expose-Headers"] = "x-ms-request-id,Server,x-ms-version,x-ms-version-id,x-ms-is-current-version,Content-Type,Cache-Control,ETag,Last-Modified,x-ms-creation-time,x-ms-blob-content-md5,x-ms-lease-status,x-ms-lease-state,x-ms-blob-type,x-ms-server-encrypted,Accept-Ranges,x-ms-last-access-time,Content-Length,Date,Transfer-Encoding";
|
|
Response.Headers["Access-Control-Allow-Origin"] = "*";
|
|
Response.Headers["x-azure-ref"] = azureRef;
|
|
Response.Headers["x-fd-int-roxy-purgeid"] = "0";
|
|
Response.Headers["X-Cache-Info"] = "L2_T2";
|
|
Response.Headers["X-Cache"] = "TCP_REMOTE_HIT"; // Tells the client it successfully hit the CDN cache
|
|
|
|
// 5. RETURN AS STREAM
|
|
var fs = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
|
|
// enableRangeProcessing allows standard 206 Partial Content responses (crucial for large room files)
|
|
return File(fs, "application/octet-stream", enableRangeProcessing: true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"CDN Error: {ex.Message}");
|
|
return NotFound();
|
|
}
|
|
}
|
|
|
|
[Route("/cdn/data/{*room_path}")]
|
|
public async Task<IActionResult> CDNData(string room_path)
|
|
{
|
|
try
|
|
{
|
|
string localPath = Path.Combine(Environment.CurrentDirectory, "Data", "CDN", "DataBlobs", room_path);
|
|
|
|
string directory = Path.GetDirectoryName(localPath);
|
|
|
|
if (!Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
if (System.IO.File.Exists(localPath))
|
|
{
|
|
return File(await System.IO.File.ReadAllBytesAsync(localPath), "application/octet-stream");
|
|
}
|
|
else
|
|
{
|
|
using (HttpClient client = new HttpClient())
|
|
{
|
|
try
|
|
{
|
|
byte[] data = await client.GetByteArrayAsync($"https://cdn.rec.net/data/{room_path}", CancellationToken.None);
|
|
|
|
await System.IO.File.WriteAllBytesAsync(localPath, data);
|
|
|
|
return File(data, "application/octet-stream");
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
Console.WriteLine(ex);
|
|
return NotFound("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex.ToString());
|
|
return NotFound();
|
|
}
|
|
}
|
|
}
|
|
}
|