FFXIVLauncher-Netmaui
203 строки · 6.8 Кб
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Net.Http;
6using System.Net.Http.Headers;
7
8using System.Security.Cryptography;
9using System.Text.Json;
10using System.Text.Json.Serialization;
11using System.Threading.Tasks;
12using XIVLauncher.Common.Util;
13
14namespace LibDalamud.Common.Dalamud
15{
16public class AssetManager
17{
18private const string ASSET_STORE_URL = "https://kamori.goats.dev/Dalamud/Asset/Meta";
19
20internal class AssetInfo
21{
22[JsonPropertyName("version")]
23public int Version { get; set; }
24
25[JsonPropertyName("assets")]
26public IReadOnlyList<Asset> Assets { get; set; }
27
28public class Asset
29{
30[JsonPropertyName("url")]
31public string Url { get; set; }
32
33[JsonPropertyName("fileName")]
34public string FileName { get; set; }
35
36[JsonPropertyName("hash")]
37public string Hash { get; set; }
38}
39}
40
41public static async Task<DirectoryInfo> EnsureAssets(DirectoryInfo baseDir, bool forceProxy)
42{
43using var client = new HttpClient
44{
45Timeout = TimeSpan.FromMinutes(4),
46};
47
48client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue
49{
50NoCache = true,
51};
52
53using var sha1 = SHA1.Create();
54
55Console.WriteLine("[DASSET] Starting asset download");
56
57var (isRefreshNeeded, info) = CheckAssetRefreshNeeded(baseDir);
58
59// NOTE(goat): We should use a junction instead of copying assets to a new folder. There is no C# API for junctions in .NET Framework.
60
61var assetsDir = new DirectoryInfo(Path.Combine(baseDir.FullName, info.Version.ToString()));
62var devDir = new DirectoryInfo(Path.Combine(baseDir.FullName, "dev"));
63
64foreach (var entry in info.Assets)
65{
66var filePath = Path.Combine(assetsDir.FullName, entry.FileName);
67var filePathDev = Path.Combine(devDir.FullName, entry.FileName);
68
69Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
70
71try
72{
73Directory.CreateDirectory(Path.GetDirectoryName(filePathDev)!);
74}
75catch
76{
77// ignored
78}
79
80var refreshFile = false;
81
82if (File.Exists(filePath) && !string.IsNullOrEmpty(entry.Hash))
83{
84try
85{
86using var file = File.OpenRead(filePath);
87var fileHash = sha1.ComputeHash(file);
88var stringHash = BitConverter.ToString(fileHash).Replace("-", "");
89refreshFile = stringHash != entry.Hash;
90Console.WriteLine("[DASSET] {0} has {1}, remote {2}", entry.FileName, stringHash, entry.Hash);
91}
92catch (Exception ex)
93{
94Console.WriteLine(ex.Message, "[DASSET] Could not read asset");
95}
96}
97
98if (!File.Exists(filePath) || isRefreshNeeded || refreshFile)
99{
100var url = entry.Url;
101
102if (forceProxy && url.Contains("/File/Get/"))
103{
104url = url.Replace("/File/Get/", "/File/GetProxy/");
105}
106
107Console.WriteLine("[DASSET] Downloading {0} to {1}...", url, entry.FileName);
108
109var request = await client.GetAsync(url).ConfigureAwait(true);
110request.EnsureSuccessStatusCode();
111File.WriteAllBytes(filePath, await request.Content.ReadAsByteArrayAsync().ConfigureAwait(true));
112
113try
114{
115File.Copy(filePath, filePathDev, true);
116}
117catch
118{
119// ignored
120}
121}
122}
123
124if (isRefreshNeeded)
125SetLocalAssetVer(baseDir, info.Version);
126
127Console.WriteLine("[DASSET] Assets OK at {0}", assetsDir.FullName);
128
129CleanUpOld(baseDir, info.Version - 1);
130
131return assetsDir;
132}
133
134private static string GetAssetVerPath(DirectoryInfo baseDir)
135{
136return Path.Combine(baseDir.FullName, "asset.ver");
137}
138
139/// <summary>
140/// Check if an asset update is needed. When this fails, just return false - the route to github
141/// might be bad, don't wanna just bail out in that case
142/// </summary>
143/// <param name="baseDir">Base directory for assets</param>
144/// <returns>Update state</returns>
145private static (bool isRefreshNeeded, AssetInfo info) CheckAssetRefreshNeeded(DirectoryInfo baseDir)
146{
147using var client = new WebClient();
148
149var localVerFile = GetAssetVerPath(baseDir);
150var localVer = 0;
151
152try
153{
154if (File.Exists(localVerFile))
155localVer = int.Parse(File.ReadAllText(localVerFile));
156}
157catch (Exception ex)
158{
159// This means it'll stay on 0, which will redownload all assets - good by me
160Console.WriteLine(ex.Message, "[DASSET] Could not read asset.ver");
161}
162
163var remoteVer = JsonSerializer.Deserialize<AssetInfo>(client.DownloadString(ASSET_STORE_URL));
164
165Console.WriteLine("[DASSET] Ver check - local:{0} remote:{1}", localVer, remoteVer.Version);
166
167var needsUpdate = remoteVer.Version > localVer;
168
169return (needsUpdate, remoteVer);
170}
171
172private static void SetLocalAssetVer(DirectoryInfo baseDir, int version)
173{
174try
175{
176var localVerFile = GetAssetVerPath(baseDir);
177File.WriteAllText(localVerFile, version.ToString());
178}
179catch (Exception e)
180{
181Console.WriteLine(e.Message, "[DASSET] Could not write local asset version");
182}
183}
184
185private static void CleanUpOld(DirectoryInfo baseDir, int version)
186{
187if (GameHelpers.CheckIsGameOpen())
188return;
189
190var toDelete = Path.Combine(baseDir.FullName, version.ToString());
191
192try
193{
194if (Directory.Exists(toDelete))
195Directory.Delete(toDelete, true);
196}
197catch (Exception ex)
198{
199Console.WriteLine(ex.Message, "Could not clean up old assets");
200}
201}
202}
203}