Unstable 0.17.0.0
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
static partial class SteamManager
|
||||
{
|
||||
private static Steamworks.AuthTicket currentTicket = null;
|
||||
public static Steamworks.AuthTicket GetAuthSessionTicket()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
currentTicket?.Cancel();
|
||||
currentTicket = Steamworks.SteamUser.GetAuthSessionTicket();
|
||||
return currentTicket;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
static partial class SteamManager
|
||||
{
|
||||
public const int STEAMP2P_OWNER_PORT = 30000;
|
||||
|
||||
public const uint AppID = 602960;
|
||||
|
||||
private static readonly Dictionary<string, int> tagCommonness = new Dictionary<string, int>()
|
||||
{
|
||||
{ "submarine", 10 },
|
||||
{ "item", 10 },
|
||||
{ "monster", 8 },
|
||||
{ "art", 8 },
|
||||
{ "mission", 8 },
|
||||
{ "event set", 8 },
|
||||
{ "total conversion", 5 },
|
||||
{ "environment", 5 },
|
||||
{ "item assembly", 5 },
|
||||
{ "language", 5 }
|
||||
};
|
||||
|
||||
public static bool IsInitialized { get; private set; }
|
||||
|
||||
private static readonly List<string> popularTags = new List<string>();
|
||||
public static IEnumerable<string> PopularTags
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsInitialized) { return Enumerable.Empty<string>(); }
|
||||
return popularTags;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
InitializeProjectSpecific();
|
||||
}
|
||||
|
||||
public static ulong GetSteamID()
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Steamworks.SteamClient.SteamId;
|
||||
}
|
||||
|
||||
public static bool IsFamilyShared()
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
|
||||
return Steamworks.SteamApps.IsSubscribedFromFamilySharing;
|
||||
}
|
||||
|
||||
public static bool IsFreeWeekend()
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
|
||||
return Steamworks.SteamApps.IsSubscribedFromFamilySharing;
|
||||
}
|
||||
|
||||
public static string GetUsername()
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return Steamworks.SteamClient.Name;
|
||||
}
|
||||
|
||||
public static bool UnlockAchievement(string achievementIdentifier) =>
|
||||
UnlockAchievement(achievementIdentifier.ToIdentifier());
|
||||
|
||||
public static bool UnlockAchievement(Identifier achievementIdentifier)
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugConsole.Log("Unlocked achievement \"" + achievementIdentifier + "\"");
|
||||
|
||||
var achievements = Steamworks.SteamUserStats.Achievements.ToList();
|
||||
int achIndex = achievements.FindIndex(ach => ach.Identifier == achievementIdentifier);
|
||||
bool unlocked = achIndex >= 0 ? achievements[achIndex].Trigger() : false;
|
||||
if (!unlocked)
|
||||
{
|
||||
//can be caused by an incorrect identifier, but also happens during normal gameplay:
|
||||
//SteamAchievementManager tries to unlock achievements that may or may not exist
|
||||
//(discovered[whateverbiomewasentered], kill[withwhateveritem], kill[somemonster] etc) so that we can add
|
||||
//some types of new achievements without the need for client-side changes.
|
||||
DebugConsole.Log($"Failed to unlock achievement \"{achievementIdentifier}\".");
|
||||
}
|
||||
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
public static bool IncrementStat(Identifier statName, int increment)
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
DebugConsole.Log($"Incremented stat \"{statName}\" by " + increment);
|
||||
bool success = Steamworks.SteamUserStats.AddStat(statName.Value.ToLowerInvariant(), increment);
|
||||
if (!success)
|
||||
{
|
||||
DebugConsole.Log("Failed to increment stat \"" + statName + "\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreStats();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool IncrementStat(Identifier statName, float increment)
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
DebugConsole.Log($"Incremented stat \"{statName}\" by " + increment);
|
||||
bool success = Steamworks.SteamUserStats.AddStat(statName.Value.ToLowerInvariant(), increment);
|
||||
if (!success)
|
||||
{
|
||||
DebugConsole.Log("Failed to increment stat \"" + statName + "\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreStats();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static int GetStatInt(Identifier statName)
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return 0; }
|
||||
return Steamworks.SteamUserStats.GetStatInt(statName.Value.ToLowerInvariant());
|
||||
}
|
||||
|
||||
public static bool StoreStats()
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
DebugConsole.Log("Storing Steam stats...");
|
||||
bool success = Steamworks.SteamUserStats.StoreStats();
|
||||
if (!success)
|
||||
{
|
||||
DebugConsole.Log("Failed to store Steam stats.");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool TryGetUnlockedAchievements(out List<Steamworks.Data.Achievement> achievements)
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
achievements = null;
|
||||
return false;
|
||||
}
|
||||
achievements = Steamworks.SteamUserStats.Achievements.Where(a => a.State).ToList();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
{
|
||||
if (!IsInitialized) { return; }
|
||||
|
||||
if (Steamworks.SteamClient.IsValid) { Steamworks.SteamClient.RunCallbacks(); }
|
||||
if (Steamworks.SteamServer.IsValid) { Steamworks.SteamServer.RunCallbacks(); }
|
||||
|
||||
SteamAchievementManager.Update(deltaTime);
|
||||
}
|
||||
|
||||
public static void ShutDown()
|
||||
{
|
||||
if (!IsInitialized) { return; }
|
||||
|
||||
if (Steamworks.SteamClient.IsValid) { Steamworks.SteamClient.Shutdown(); }
|
||||
if (Steamworks.SteamServer.IsValid) { Steamworks.SteamServer.Shutdown(); }
|
||||
IsInitialized = false;
|
||||
}
|
||||
|
||||
public static IEnumerable<ulong> ParseWorkshopIds(string workshopIdData)
|
||||
{
|
||||
string[] workshopIds = workshopIdData.Split(',');
|
||||
foreach (string id in workshopIds)
|
||||
{
|
||||
if (ulong.TryParse(id, out ulong idCast))
|
||||
{
|
||||
yield return idCast;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<ulong> WorkshopUrlsToIds(IEnumerable<string> urls)
|
||||
{
|
||||
return urls.Select((u) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(u))
|
||||
{
|
||||
return (ulong)0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetWorkshopItemIDFromUrl(u);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static ulong GetWorkshopItemIDFromUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri uri = new Uri(url);
|
||||
string idStr = HttpUtility.ParseQueryString(uri.Query)["id".ToIdentifier()];
|
||||
if (ulong.TryParse(idStr, out ulong id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to get Workshop item ID from the url \"" + url + "\"!", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static UInt64 SteamIDStringToUInt64(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) { return 0; }
|
||||
UInt64 retVal;
|
||||
if (str.StartsWith("STEAM64_", StringComparison.InvariantCultureIgnoreCase)) { str = str.Substring(8); }
|
||||
if (UInt64.TryParse(str, out retVal) && retVal > (1 << 52)) { return retVal; }
|
||||
if (!str.StartsWith("STEAM_", StringComparison.InvariantCultureIgnoreCase)) { return 0; }
|
||||
string[] split = str.Substring(6).Split(':');
|
||||
if (split.Length != 3) { return 0; }
|
||||
|
||||
if (!UInt64.TryParse(split[0], out UInt64 universe)) { return 0; }
|
||||
if (!UInt64.TryParse(split[1], out UInt64 y)) { return 0; }
|
||||
if (!UInt64.TryParse(split[2], out UInt64 accountNumber)) { return 0; }
|
||||
|
||||
UInt64 accountInstance = 1; UInt64 accountType = 1;
|
||||
|
||||
return (universe << 56) | (accountType << 52) | (accountInstance << 32) | (accountNumber << 1) | y;
|
||||
}
|
||||
|
||||
public static string SteamIDUInt64ToString(UInt64 uint64)
|
||||
{
|
||||
UInt64 y = uint64 & 0x1;
|
||||
UInt64 accountNumber = (uint64 >> 1) & 0x7fffffff;
|
||||
UInt64 universe = (uint64 >> 56) & 0xff;
|
||||
|
||||
string retVal = "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
|
||||
|
||||
if (SteamIDStringToUInt64(retVal) != uint64) { return "STEAM64_" + uint64.ToString(); }
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
#nullable enable
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Steamworks.Data;
|
||||
using WorkshopItemSet = System.Collections.Generic.ISet<Steamworks.Ugc.Item>;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
static partial class SteamManager
|
||||
{
|
||||
public const string WorkshopItemPreviewImageFolder = "Workshop";
|
||||
public const string PreviewImageName = "PreviewImage.png";
|
||||
public const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png";
|
||||
|
||||
public static partial class Workshop
|
||||
{
|
||||
private struct ItemEqualityComparer : IEqualityComparer<Steamworks.Ugc.Item>
|
||||
{
|
||||
public static readonly ItemEqualityComparer Instance = new ItemEqualityComparer();
|
||||
|
||||
public bool Equals(Steamworks.Ugc.Item x, Steamworks.Ugc.Item y)
|
||||
=> x.Id == y.Id;
|
||||
|
||||
public int GetHashCode(Steamworks.Ugc.Item obj)
|
||||
=> (int)obj.Id.Value;
|
||||
}
|
||||
|
||||
private static async Task<WorkshopItemSet> GetWorkshopItems(Steamworks.Ugc.Query query, int? maxPages = null)
|
||||
{
|
||||
if (!IsInitialized) { return new HashSet<Steamworks.Ugc.Item>(); }
|
||||
|
||||
await Task.Yield();
|
||||
query = query.WithKeyValueTags(true).WithLongDescription(true);
|
||||
var set = new HashSet<Steamworks.Ugc.Item>(ItemEqualityComparer.Instance);
|
||||
int prevSize = 0;
|
||||
for (int i = 1; maxPages is null || i <= maxPages; i++)
|
||||
{
|
||||
Steamworks.Ugc.ResultPage? page = await query.GetPageAsync(i);
|
||||
if (page is null || !page.Value.Entries.Any()) { break; }
|
||||
set.UnionWith(page.Value.Entries);
|
||||
|
||||
if (set.Count == prevSize) { break; }
|
||||
prevSize = set.Count;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public static async Task<WorkshopItemSet> GetAllSubscribedItems()
|
||||
{
|
||||
if (!IsInitialized) { return new HashSet<Steamworks.Ugc.Item>(); }
|
||||
|
||||
return await GetWorkshopItems(
|
||||
Steamworks.Ugc.Query.Items
|
||||
.WhereUserSubscribed());
|
||||
}
|
||||
|
||||
public static async Task<WorkshopItemSet> GetPopularItems()
|
||||
{
|
||||
if (!IsInitialized) { return new HashSet<Steamworks.Ugc.Item>(); }
|
||||
|
||||
return await GetWorkshopItems(
|
||||
Steamworks.Ugc.Query.Items
|
||||
.WithTrendDays(7)
|
||||
.RankedByTrend(), maxPages: 1);
|
||||
}
|
||||
|
||||
public static async Task<WorkshopItemSet> GetPublishedItems()
|
||||
{
|
||||
if (!IsInitialized) { return new HashSet<Steamworks.Ugc.Item>(); }
|
||||
|
||||
return await GetWorkshopItems(
|
||||
Steamworks.Ugc.Query.All
|
||||
.WhereUserPublished());
|
||||
}
|
||||
|
||||
public static async Task<Steamworks.Ugc.Item?> GetItem(UInt64 itemId)
|
||||
{
|
||||
if (!IsInitialized) { return null; }
|
||||
|
||||
var items = await GetWorkshopItems(
|
||||
Steamworks.Ugc.Query.All
|
||||
.WithFileId(itemId));
|
||||
return items.Any() ? items.First() : (Steamworks.Ugc.Item?)null;
|
||||
}
|
||||
|
||||
public static async Task ForceRedownload(UInt64 itemId)
|
||||
=> await ForceRedownload(new Steamworks.Ugc.Item(itemId));
|
||||
|
||||
public static void NukeDownload(Steamworks.Ugc.Item item)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.Directory.Delete(item.Directory, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//don't care in the slightest about what happens here
|
||||
}
|
||||
}
|
||||
|
||||
public static void Uninstall(Steamworks.Ugc.Item workshopItem)
|
||||
{
|
||||
NukeDownload(workshopItem);
|
||||
var toUninstall
|
||||
= ContentPackageManager.WorkshopPackages.Where(p => p.SteamWorkshopId == workshopItem.Id)
|
||||
.ToHashSet();
|
||||
toUninstall.Select(p => p.Dir).ForEach(d => Directory.Delete(d));
|
||||
ContentPackageManager.WorkshopPackages.Refresh();
|
||||
ContentPackageManager.EnabledPackages.DisableRemovedMods();
|
||||
}
|
||||
|
||||
public static async Task ForceRedownload(Steamworks.Ugc.Item item)
|
||||
{
|
||||
NukeDownload(item);
|
||||
await item.DownloadAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class creates a file called ".copying" that
|
||||
/// serves to keep mod copy operations in the same
|
||||
/// directory from overlapping.
|
||||
/// </summary>
|
||||
private class CopyIndicator : IDisposable
|
||||
{
|
||||
private readonly string path;
|
||||
|
||||
public CopyIndicator(string path)
|
||||
{
|
||||
this.path = path;
|
||||
using (var f = File.Create(path))
|
||||
{
|
||||
if (f is null)
|
||||
{
|
||||
throw new Exception($"File.Create returned null");
|
||||
}
|
||||
f.WriteByte((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//don't care!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class serves the purpose of preventing
|
||||
/// more than 10 mod install tasks from proceeding
|
||||
/// at the same time.
|
||||
/// </summary>
|
||||
private class InstallTaskCounter : IDisposable
|
||||
{
|
||||
private static readonly HashSet<InstallTaskCounter> installers = new HashSet<InstallTaskCounter>();
|
||||
private readonly static object mutex = new object();
|
||||
private const int MaxTasks = 7;
|
||||
|
||||
private readonly UInt64 itemId;
|
||||
private InstallTaskCounter(UInt64 id) { itemId = id; }
|
||||
|
||||
public static bool IsInstalling(Steamworks.Ugc.Item item)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
return installers.Any(i => i.itemId == item.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
await Task.Yield();
|
||||
while (true)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (installers.Count < MaxTasks) { installers.Add(this); return; }
|
||||
}
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<InstallTaskCounter> Create(Steamworks.Ugc.Item item)
|
||||
{
|
||||
var retVal = new InstallTaskCounter(item.Id);
|
||||
await retVal.Init();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (mutex) { installers.Remove(this); }
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsItemDirectoryUpToDate(in Steamworks.Ugc.Item item)
|
||||
{
|
||||
string itemDirectory = item.Directory;
|
||||
return Directory.Exists(itemDirectory)
|
||||
&& File.GetLastWriteTime(itemDirectory).ToUniversalTime() >= item.LatestUpdateTime;
|
||||
}
|
||||
|
||||
public static bool CanBeInstalled(ulong itemId)
|
||||
=> CanBeInstalled(new Steamworks.Ugc.Item(itemId));
|
||||
|
||||
public static bool CanBeInstalled(in Steamworks.Ugc.Item item)
|
||||
{
|
||||
bool needsUpdate = item.NeedsUpdate;
|
||||
bool isDownloading = item.IsDownloading;
|
||||
bool isInstalled = item.IsInstalled;
|
||||
bool directoryIsUpToDate = IsItemDirectoryUpToDate(item);
|
||||
|
||||
return !needsUpdate
|
||||
&& !isDownloading
|
||||
&& isInstalled
|
||||
&& directoryIsUpToDate;
|
||||
}
|
||||
|
||||
public static async Task DownloadModThenEnqueueInstall(Steamworks.Ugc.Item item)
|
||||
{
|
||||
if (!CanBeInstalled(item))
|
||||
{
|
||||
if (!item.IsDownloading && !item.IsDownloadPending) { await ForceRedownload(item); }
|
||||
}
|
||||
#if CLIENT
|
||||
else
|
||||
{
|
||||
OnItemDownloadComplete(item.Id);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void DeleteFailedCopies()
|
||||
{
|
||||
foreach (var dir in Directory.EnumerateDirectories(ContentPackage.WorkshopModsDir, "**"))
|
||||
{
|
||||
string copyingIndicatorPath = Path.Combine(dir, ContentPackageManager.CopyIndicatorFileName);
|
||||
if (File.Exists(copyingIndicatorPath))
|
||||
{
|
||||
Directory.Delete(dir, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsInstallingToPath(string path)
|
||||
=> File.Exists(Path.Combine(Path.GetDirectoryName(path)!, ContentPackageManager.CopyIndicatorFileName));
|
||||
|
||||
public static bool IsInstalling(Steamworks.Ugc.Item item)
|
||||
=> InstallTaskCounter.IsInstalling(item);
|
||||
|
||||
private static async Task InstallMod(ulong id)
|
||||
{
|
||||
var item = await GetItem(id);
|
||||
if (item is null) { return; }
|
||||
await InstallMod(item.Value);
|
||||
}
|
||||
|
||||
private static async Task InstallMod(Steamworks.Ugc.Item item)
|
||||
{
|
||||
await Task.Yield();
|
||||
using var installCounter = await InstallTaskCounter.Create(item);
|
||||
|
||||
string itemTitle = item.Title.Trim();
|
||||
UInt64 itemId = item.Id;
|
||||
string itemDirectory = item.Directory;
|
||||
DateTime updateTime = item.LatestUpdateTime;
|
||||
|
||||
if (!CanBeInstalled(item))
|
||||
{
|
||||
ForceRedownload(item);
|
||||
throw new InvalidOperationException($"Item {itemTitle} (id {itemId}) is not available for copying");
|
||||
}
|
||||
|
||||
const string workshopModDirReadme =
|
||||
"DO NOT MODIFY THE CONTENTS OF THIS FOLDER, EVEN IF\n"
|
||||
+ "YOU ARE EDITING A MOD YOU PUBLISHED YOURSELF.\n"
|
||||
+ "\n"
|
||||
+ "If you do you may run into networking issues and\n"
|
||||
+ "unexpected deletion of your hard work.\n"
|
||||
+ "Instead, modify a copy of your mod in LocalMods.\n";
|
||||
|
||||
string workshopModDirReadmeLocation = Path.Combine(SaveUtil.SaveFolder, "WorkshopMods", "README.txt");
|
||||
if (!File.Exists(workshopModDirReadmeLocation))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(workshopModDirReadmeLocation)!);
|
||||
File.WriteAllText(
|
||||
path: workshopModDirReadmeLocation,
|
||||
contents: workshopModDirReadme);
|
||||
}
|
||||
|
||||
string installDir = Path.Combine(ContentPackage.WorkshopModsDir, itemId.ToString());
|
||||
Directory.CreateDirectory(installDir);
|
||||
|
||||
string copyIndicatorPath = Path.Combine(installDir, ContentPackageManager.CopyIndicatorFileName);
|
||||
|
||||
XDocument fileListSrc = XMLExtensions.TryLoadXml(Path.Combine(itemDirectory, ContentPackage.FileListFileName));
|
||||
string modName = fileListSrc.Root.GetAttributeString("name", item.Title).Trim();
|
||||
string modVersion = fileListSrc.Root.GetAttributeString("modversion", ContentPackage.DefaultModVersion);
|
||||
Version gameVersion = fileListSrc.Root.GetAttributeVersion("gameversion", GameMain.Version);
|
||||
bool isCorePackage = fileListSrc.Root.GetAttributeBool("corepackage", false);
|
||||
string expectedHash = fileListSrc.Root.GetAttributeString("expectedhash", "");
|
||||
|
||||
using (var copyIndicator = new CopyIndicator(copyIndicatorPath))
|
||||
{
|
||||
await CopyDirectory(itemDirectory, modName, itemDirectory, installDir);
|
||||
|
||||
string fileListDestPath = Path.Combine(installDir, ContentPackage.FileListFileName);
|
||||
XDocument fileListDest = XMLExtensions.TryLoadXml(fileListDestPath);
|
||||
XElement root = fileListDest.Root ?? throw new NullReferenceException("Unable to install mod: file list root is null.");
|
||||
root.Attributes().Remove();
|
||||
|
||||
root.Add(
|
||||
new XAttribute("name", itemTitle),
|
||||
new XAttribute("steamworkshopid", itemId),
|
||||
new XAttribute("corepackage", isCorePackage),
|
||||
new XAttribute("modversion", modVersion),
|
||||
new XAttribute("gameversion", gameVersion),
|
||||
new XAttribute("installtime", ToolBox.Epoch.FromDateTime(updateTime)));
|
||||
if (modName.ToIdentifier() != itemTitle)
|
||||
{
|
||||
root.Add(new XAttribute("altnames", modName));
|
||||
}
|
||||
if (!expectedHash.IsNullOrEmpty())
|
||||
{
|
||||
root.Add(new XAttribute("expectedhash", expectedHash));
|
||||
}
|
||||
fileListDest.SaveSafe(fileListDestPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CorrectPaths(string fileListDir, string modName, XElement element)
|
||||
{
|
||||
foreach (var attribute in element.Attributes())
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
string val = attribute.Value.CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
|
||||
//Handle really old mods (0.9.0.4-era) that might be structured as
|
||||
//%ModDir%/Mods/[NAME]/[RESOURCE]
|
||||
string fullSrcPath = Path.Combine(fileListDir, val).CleanUpPath();
|
||||
if (File.Exists(fullSrcPath))
|
||||
{
|
||||
val = $"{ContentPath.ModDirStr}/{val}";
|
||||
}
|
||||
|
||||
//Handle old mods that installed to the fixed Mods directory
|
||||
//that no longer exists
|
||||
string oldModDir = $"Mods/{modName}";
|
||||
if (val.StartsWith(oldModDir, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
val = $"{ContentPath.ModDirStr}{val.Remove(0, oldModDir.Length)}";
|
||||
}
|
||||
//Handle old mods that depend on other mods
|
||||
else if (val.StartsWith("Mods/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string otherModName = val.Substring(val.IndexOf('/')+1);
|
||||
otherModName = otherModName.Substring(0, otherModName.IndexOf('/'));
|
||||
val = $"{string.Format(ContentPath.OtherModDirFmt, otherModName)}{val.Remove(0, $"Mods/{otherModName}".Length)}";
|
||||
}
|
||||
//Handle really old mods that installed Submarines in the wrong place
|
||||
else if (val.StartsWith("Submarines/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
val = $"{ContentPath.ModDirStr}/{val}";
|
||||
}
|
||||
attribute.Value = val;
|
||||
}
|
||||
await Task.WhenAll(
|
||||
element.Elements()
|
||||
.Select(subElement => CorrectPaths(
|
||||
fileListDir: fileListDir,
|
||||
modName: modName,
|
||||
element: subElement)));
|
||||
}
|
||||
|
||||
private static async Task CopyFile(string fileListDir, string modName, string from, string to)
|
||||
{
|
||||
await Task.Yield();
|
||||
Identifier extension = Path.GetExtension(from).ToIdentifier();
|
||||
if (extension == ".xml")
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument? doc = XMLExtensions.TryLoadXml(from, out var exception);
|
||||
if (exception is { Message: string exceptionMsg })
|
||||
{
|
||||
throw new Exception($"Could not load \"{from}\": {exceptionMsg}");
|
||||
}
|
||||
if (doc is null)
|
||||
{
|
||||
throw new Exception($"Could not load \"{from}\": doc is null");
|
||||
}
|
||||
await CorrectPaths(
|
||||
fileListDir: fileListDir,
|
||||
modName: modName,
|
||||
element: doc.Root ?? throw new NullReferenceException());
|
||||
doc.SaveSafe(to);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"An exception was thrown when attempting to copy \"{from}\" to \"{to}\": {e.Message}\n{e.StackTrace}");
|
||||
}
|
||||
}
|
||||
File.Copy(from, to, overwrite: true);
|
||||
}
|
||||
|
||||
private static async Task CopyDirectory(string fileListDir, string modName, string from, string to)
|
||||
{
|
||||
from = Path.GetFullPath(from); to = Path.GetFullPath(to);
|
||||
Directory.CreateDirectory(to);
|
||||
|
||||
string convertFromTo(string from)
|
||||
=> Path.Combine(to, Path.GetFileName(from));
|
||||
|
||||
string[] files = Directory.GetFiles(from);
|
||||
string[] subDirs = Directory.GetDirectories(from);
|
||||
foreach (var file in files)
|
||||
{
|
||||
await CopyFile(fileListDir, modName, file, convertFromTo(file));
|
||||
}
|
||||
|
||||
foreach (var dir in subDirs) { await CopyDirectory(fileListDir, modName, dir, convertFromTo(dir)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user