fixed & re-added valorant live

This commit is contained in:
Not Officer 2021-05-22 23:51:30 +02:00
parent d536df54ef
commit 9413e68b5b
2 changed files with 77 additions and 79 deletions

View File

@ -1,17 +1,22 @@
using System;
using CUE4Parse.UE4.Exceptions;
using CUE4Parse.UE4.Readers;
using FModel.Settings;
using Ionic.Zlib;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CUE4Parse.UE4.Exceptions;
using CUE4Parse.UE4.Readers;
using FModel.Settings;
using Ionic.Zlib;
using RestSharp;
namespace FModel.ViewModels.ApiEndpoints
{
@ -19,9 +24,7 @@ namespace FModel.ViewModels.ApiEndpoints
{
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
public ValorantApiEndpoint(IRestClient client) : base(client)
{
}
public ValorantApiEndpoint(IRestClient client) : base(client) { }
public async Task<VManifest> GetManifestAsync(CancellationToken token)
{
@ -30,10 +33,7 @@ namespace FModel.ViewModels.ApiEndpoints
return new VManifest(response.RawBytes);
}
public VManifest GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
}
public class VManifest
@ -43,9 +43,7 @@ namespace FModel.ViewModels.ApiEndpoints
public readonly VChunk[] Chunks;
public readonly VPak[] Paks;
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data))
{
}
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { }
private VManifest(FArchive Ar)
{
@ -55,13 +53,14 @@ namespace FModel.ViewModels.ApiEndpoints
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
if (uncompressedBuffer.Length != Header.UncompressedSize)
{
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
}
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifest.ReadArray(Header.ChunkCount, manifest.Read<VChunk>);
Paks = manifest.ReadArray(Header.PakCount, () => new VPak(manifest));
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
if (manifest.Position != manifest.Length)
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
}
_client = new HttpClient(new HttpClientHandler
@ -77,42 +76,44 @@ namespace FModel.ViewModels.ApiEndpoints
});
}
public async Task PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
if (File.Exists(chunkPath))
{
return;
}
if (File.Exists(chunkPath)) return;
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.GetUrl());
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
var chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await fileStream.WriteAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
{
byte[] chunkBytes = null;
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
byte[] chunkBytes;
if (File.Exists(chunkPath))
{
chunkBytes = new byte[chunk.Size];
await using var fileStream = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
await fileStream.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
}
else
{
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.GetUrl());
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode != HttpStatusCode.OK) return chunkBytes;
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await fileStream.WriteAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
else
{
chunkBytes = null; // Maybe add logging?
}
}
return chunkBytes;
@ -130,25 +131,25 @@ namespace FModel.ViewModels.ApiEndpoints
public readonly ulong ManifestId;
public readonly uint UncompressedSize;
public readonly uint CompressedSize;
public readonly int ChunkCount;
public readonly int PakCount;
public readonly uint ChunkCount;
public readonly uint PakCount;
public readonly string GameVersion;
public VHeader(FArchive Ar)
{
Magic = Ar.Read<uint>();
if (Magic != _MAGIC)
{
throw new ParserException(Ar, "Invalid manifest magic");
}
HeaderSize = Ar.Read<uint>();
ManifestId = Ar.Read<ulong>();
UncompressedSize = Ar.Read<uint>();
CompressedSize = Ar.Read<uint>();
ChunkCount = Ar.Read<int>();
PakCount = Ar.Read<int>();
GameVersion = Ar.ReadString();
ChunkCount = Ar.Read<uint>();
PakCount = Ar.Read<uint>();
var gameVersionLength = Ar.ReadByte();
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
Ar.Position = HeaderSize;
}
}
@ -165,10 +166,10 @@ namespace FModel.ViewModels.ApiEndpoints
Id = Ar.Read<ulong>();
Size = Ar.Read<uint>();
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
Name = Ar.ReadString();
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
}
public string GetFullName() => $"E:/RiotGames/Valorant/ShooterGame/Content/Paks/{Name}";
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@ -180,14 +181,17 @@ namespace FModel.ViewModels.ApiEndpoints
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
}
public class VPakStream : Stream
public class VPakStream : Stream, ICloneable
{
private readonly VManifest _manifest;
private readonly int _pakIndex;
private readonly VChunk[] _chunks;
public VPakStream(VManifest manifest, int pakIndex)
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
{
_manifest = manifest;
_pakIndex = pakIndex;
_position = position;
var pak = manifest.Paks[pakIndex];
_chunks = new VChunk[pak.ChunkIndices.Length];
@ -199,36 +203,37 @@ namespace FModel.ViewModels.ApiEndpoints
Length = pak.Size;
}
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var (i, startPos) = GetChunkIndex(_position);
if (i < 0) return 0;
if (i == -1) return 0;
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
var bytesRead = 0;
while (true)
{
var chunk = _chunks[i];
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
var chunkBytes = chunk.Size - startPos;
var bytesLeft = count - bytesRead;
var chunkBytes = _chunks[i].Size - startPos;
var chunkData = await _manifest.GetChunkBytes(_chunks[i], cancellationToken).ConfigureAwait(false);
if (bytesLeft <= chunkBytes)
{
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint) bytesLeft);
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
bytesRead += bytesLeft;
break;
}
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
bytesRead += (int) chunkBytes;
bytesRead += (int)chunkBytes;
startPos = 0u;
if (++i == _chunks.Length)
{
break;
}
if (++i == _chunks.Length) break;
}
_position += bytesRead;
@ -238,25 +243,24 @@ namespace FModel.ViewModels.ApiEndpoints
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
using (var s = new SemaphoreSlim(concurrentDownloads))
var s = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
while (count > 0)
{
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
var chunk = _chunks[i++];
tasks.Add(PrefetchChunkAsync(chunk));
s.Release();
var chunk = _chunks[i++];
tasks.Add(PrefetchChunkAsync(chunk));
if (i == _chunks.Length) break;
count -= chunk.Size - startPos;
startPos = 0u;
}
if (i == _chunks.Length) break;
count -= chunk.Size - startPos;
startPos = 0u;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
s.Dispose();
async Task PrefetchChunkAsync(VChunk chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
s.Release(); // This is intended
}
}
@ -265,11 +269,7 @@ namespace FModel.ViewModels.ApiEndpoints
for (var i = 0; i < _chunks.Length; i++)
{
var size = _chunks[i].Size;
if (position < size)
{
return (i, (uint) position);
}
if (position < size) return (i, (uint) position);
position -= size;
}
@ -283,9 +283,7 @@ namespace FModel.ViewModels.ApiEndpoints
set
{
if (value >= Length || value < 0)
{
throw new ArgumentOutOfRangeException();
}
throw new ArgumentOutOfRangeException(nameof(value));
_position = value;
}
@ -298,7 +296,7 @@ namespace FModel.ViewModels.ApiEndpoints
SeekOrigin.Begin => offset,
SeekOrigin.Current => offset + _position,
SeekOrigin.End => Length + offset,
_ => throw new ArgumentOutOfRangeException()
_ => throw new ArgumentOutOfRangeException(nameof(offset))
};
return _position;
}

View File

@ -61,7 +61,7 @@ namespace FModel.ViewModels
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
// yield return new DetectedGame {GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER};
yield return new DetectedGame {GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER};
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
}