using Barotrauma.Networking; using Facepunch.Steamworks; using RestSharp.Extensions.MonoHttp; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; namespace Barotrauma.Steam { partial class SteamManager { private SteamManager() { try { client = new Facepunch.Steamworks.Client(AppID); isInitialized = client.IsSubscribed && client.IsValid; if (isInitialized) { DebugConsole.Log("Logged in as " + client.Username + " (SteamID " + client.SteamId + ")"); } } catch (DllNotFoundException) { isInitialized = false; new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("SteamDllNotFound")); } catch (Exception) { isInitialized = false; new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("SteamClientInitFailed")); } if (!isInitialized) { try { Facepunch.Steamworks.Client.Instance.Dispose(); } catch (Exception e) { if (GameSettings.VerboseLogging) DebugConsole.ThrowError("Disposing Steam client failed.", e); } } } public static ulong GetSteamID() { if (instance == null || !instance.isInitialized) { return 0; } return instance.client.SteamId; } public static ulong GetWorkshopItemIDFromUrl(string url) { try { Uri uri = new Uri(url); string idStr = HttpUtility.ParseQueryString(uri.Query).Get("id"); 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; } #region Connecting to servers public static bool GetServers(Action onServerFound, Action onServerRulesReceived, Action onFinished) { if (instance == null || !instance.isInitialized) { return false; } var filter = new ServerList.Filter { { "appid", AppID.ToString() }, { "gamedir", "Barotrauma" }, { "secure", "1" } }; //include unresponsive servers in the server list //the response is queried using the server's query port, not the game port, //so it may be possible to play on the server even if it doesn't respond to server list queries var query = instance.client.ServerList.Internet(filter); query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; query.OnFinished = onFinished; var localQuery = instance.client.ServerList.Local(filter); localQuery.OnUpdate += () => { UpdateServerQuery(localQuery, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; localQuery.OnFinished = onFinished; return true; } private static void UpdateServerQuery(ServerList.Request query, Action onServerFound, Action onServerRulesReceived, bool includeUnresponsive) { IEnumerable servers = includeUnresponsive ? new List(query.Responded).Concat(query.Unresponsive) : query.Responded; foreach (ServerList.Server s in servers) { if (!ValidateServerInfo(s)) { continue; } bool responded = query.Responded.Contains(s); if (responded) { DebugConsole.Log(s.Name + " responded to server query."); } else { DebugConsole.Log(s.Name + " did not respond to server query."); } var serverInfo = new ServerInfo() { ServerName = s.Name, Port = s.ConnectionPort.ToString(), IP = s.Address.ToString(), PlayerCount = s.Players, MaxPlayers = s.MaxPlayers, HasPassword = s.Passworded, RespondedToSteamQuery = responded }; serverInfo.PingChecked = true; serverInfo.Ping = s.Ping; if (responded) { s.FetchRules(); } s.OnReceivedRules += (bool rulesReceived) => { if (!rulesReceived || s.Rules == null) { return; } if (s.Rules.ContainsKey("message")) serverInfo.ServerMessage = s.Rules["message"]; if (s.Rules.ContainsKey("version")) serverInfo.GameVersion = s.Rules["version"]; if (s.Rules.ContainsKey("contentpackage")) serverInfo.ContentPackageNames.AddRange(s.Rules["contentpackage"].Split(',')); if (s.Rules.ContainsKey("contentpackagehash")) serverInfo.ContentPackageHashes.AddRange(s.Rules["contentpackagehash"].Split(',')); if (s.Rules.ContainsKey("contentpackageurl")) serverInfo.ContentPackageWorkshopUrls.AddRange(s.Rules["contentpackageurl"].Split(',')); if (s.Rules.ContainsKey("usingwhitelist")) serverInfo.UsingWhiteList = s.Rules["usingwhitelist"] == "True"; if (s.Rules.ContainsKey("modeselectionmode")) { if (Enum.TryParse(s.Rules["modeselectionmode"], out SelectionMode selectionMode)) serverInfo.ModeSelectionMode = selectionMode; } if (s.Rules.ContainsKey("subselectionmode")) { if (Enum.TryParse(s.Rules["subselectionmode"], out SelectionMode selectionMode)) serverInfo.SubSelectionMode = selectionMode; } if (s.Rules.ContainsKey("allowspectating")) serverInfo.AllowSpectating = s.Rules["allowspectating"] == "True"; if (s.Rules.ContainsKey("allowrespawn")) serverInfo.AllowRespawn = s.Rules["allowrespawn"] == "True"; if (s.Rules.ContainsKey("traitors")) { if (Enum.TryParse(s.Rules["traitors"], out YesNoMaybe traitorsEnabled)) serverInfo.TraitorsEnabled = traitorsEnabled; } if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count || serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopUrls.Count) { //invalid contentpackage info serverInfo.ContentPackageNames.Clear(); serverInfo.ContentPackageHashes.Clear(); } onServerRulesReceived(serverInfo); }; onServerFound(serverInfo); } query.Responded.Clear(); } private static bool ValidateServerInfo(ServerList.Server server) { if (string.IsNullOrEmpty(server.Name)) { return false; } if (server.Address == null) { return false; } return true; } public static Auth.Ticket GetAuthSessionTicket() { if (instance == null || !instance.isInitialized) { return null; } return instance.client.Auth.GetAuthSessionTicket(); } #endregion #region Workshop public const string WorkshopItemStagingFolder = "NewWorkshopItem"; public const string WorkshopItemPreviewImageFolder = "Workshop"; public const string PreviewImageName = "PreviewImage.png"; private const string MetadataFileName = "filelist.xml"; private const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png"; private Sprite defaultPreviewImage; public Sprite DefaultPreviewImage { get { if (defaultPreviewImage == null) { defaultPreviewImage = new Sprite(DefaultPreviewImagePath, sourceRectangle: null); } return defaultPreviewImage; } } public static void GetSubscribedWorkshopItems(Action> onItemsFound, List requireTags = null) { if (instance == null || !instance.isInitialized) return; var query = instance.client.Workshop.CreateQuery(); query.Order = Workshop.Order.RankedByTrend; query.UserId = instance.client.SteamId; query.UserQueryType = Workshop.UserQueryType.Subscribed; query.UploaderAppId = AppID; if (requireTags != null) query.RequireTags = requireTags; query.Run(); query.OnResult += (Workshop.Query q) => { onItemsFound?.Invoke(q.Items); }; } public static void GetPopularWorkshopItems(Action> onItemsFound, int amount, List requireTags = null) { if (instance == null || !instance.isInitialized) return; var query = instance.client.Workshop.CreateQuery(); query.Order = Workshop.Order.RankedByTrend; query.RankedByTrendDays = 30; query.UploaderAppId = AppID; if (requireTags != null) query.RequireTags = requireTags; query.Run(); query.OnResult += (Workshop.Query q) => { //count the number of each unique tag foreach (var item in q.Items) { foreach (string tag in item.Tags) { if (string.IsNullOrEmpty(tag)) { continue; } string caseInvariantTag = tag.ToLowerInvariant(); if (!instance.tagCommonness.ContainsKey(caseInvariantTag)) { instance.tagCommonness[caseInvariantTag] = 1; } else { instance.tagCommonness[caseInvariantTag]++; } } } //populate the popularTags list with tags sorted by commonness instance.popularTags.Clear(); foreach (KeyValuePair tagCommonness in instance.tagCommonness) { int i = 0; while (i < instance.popularTags.Count && instance.tagCommonness[instance.popularTags[i]] > tagCommonness.Value) { i++; } instance.popularTags.Insert(i, tagCommonness.Key); } var nonSubscribedItems = q.Items.Where(it => !it.Subscribed && !it.Installed); if (nonSubscribedItems.Count() > amount) { nonSubscribedItems = nonSubscribedItems.Take(amount); } onItemsFound?.Invoke(nonSubscribedItems.ToList()); }; } public static void GetPublishedWorkshopItems(Action> onItemsFound, List requireTags = null) { if (instance == null || !instance.isInitialized) return; var query = instance.client.Workshop.CreateQuery(); query.Order = Workshop.Order.RankedByPublicationDate; query.UserId = instance.client.SteamId; query.UserQueryType = Workshop.UserQueryType.Published; query.UploaderAppId = AppID; if (requireTags != null) query.RequireTags = requireTags; query.Run(); query.OnResult += (Workshop.Query q) => { onItemsFound?.Invoke(q.Items); }; } public static void SubscribeToWorkshopItem(string itemUrl) { if (instance == null || !instance.isInitialized) return; ulong id = GetWorkshopItemIDFromUrl(itemUrl); if (id == 0) { return; } var item = instance.client.Workshop.GetItem(id); if (item == null) { DebugConsole.ThrowError("Failed to find a Steam Workshop item with the ID " + id + "."); return; } item.Subscribe(); item.Download(); } /// /// Creates a new folder, copies the specified files there and creates a metadata file with install instructions. /// public static void CreateWorkshopItemStaging(List contentFiles, out Workshop.Editor itemEditor, out ContentPackage contentPackage) { var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder); if (stagingFolder.Exists) { SaveUtil.ClearFolder(stagingFolder.FullName); } else { stagingFolder.Create(); } Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Submarines")); Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Mods")); Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Mods", "ModName")); itemEditor = instance.client.Workshop.CreateItem(Workshop.ItemType.Community); itemEditor.Visibility = Workshop.Editor.VisibilityType.Public; itemEditor.WorkshopUploadAppId = AppID; itemEditor.Folder = stagingFolder.FullName; string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName)); File.Copy("Content/DefaultWorkshopPreviewImage.png", previewImagePath); //copy content files to the staging folder List copiedFilePaths = new List(); foreach (ContentFile file in contentFiles) { string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file.Path), Environment.CurrentDirectory); string destinationPath = Path.Combine(stagingFolder.FullName, relativePath); //make sure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); File.Copy(file.Path, destinationPath); copiedFilePaths.Add(destinationPath); } System.Diagnostics.Debug.Assert(copiedFilePaths.Count == contentFiles.Count); //create a new content package and include the copied files in it contentPackage = ContentPackage.CreatePackage("ContentPackage", Path.Combine(itemEditor.Folder, MetadataFileName), false); for (int i = 0; i < copiedFilePaths.Count; i++) { contentPackage.AddFile(copiedFilePaths[i], contentFiles[i].Type); } contentPackage.Save(Path.Combine(stagingFolder.FullName, MetadataFileName)); } /// /// Creates a copy of the specified workshop item in the staging folder and an editor that can be used to edit and update the item /// public static void CreateWorkshopItemStaging(Workshop.Item existingItem, out Workshop.Editor itemEditor, out ContentPackage contentPackage) { if (!existingItem.Installed) { itemEditor = null; contentPackage = null; DebugConsole.ThrowError("Cannot edit the workshop item \"" + existingItem.Title + "\" because it has not been installed."); return; } var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder); if (stagingFolder.Exists) { SaveUtil.ClearFolder(stagingFolder.FullName); } else { stagingFolder.Create(); } itemEditor = instance.client.Workshop.EditItem(existingItem.Id); itemEditor.Visibility = Workshop.Editor.VisibilityType.Public; itemEditor.Title = existingItem.Title; itemEditor.Tags = existingItem.Tags.ToList(); itemEditor.Description = existingItem.Description; itemEditor.WorkshopUploadAppId = AppID; itemEditor.Folder = stagingFolder.FullName; string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName)); itemEditor.PreviewImage = previewImagePath; using (WebClient client = new WebClient()) { if (File.Exists(previewImagePath)) { File.Delete(previewImagePath); } client.DownloadFile(new Uri(existingItem.PreviewImageUrl), previewImagePath); } ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem.Directory.FullName, MetadataFileName)); //item already installed, copy it from the game folder if (existingItem != null && CheckWorkshopItemEnabled(existingItem, checkContentFiles: false)) { string installedItemPath = GetWorkshopItemContentPackagePath(tempContentPackage); if (File.Exists(installedItemPath)) { tempContentPackage = new ContentPackage(installedItemPath); } } if (File.Exists(tempContentPackage.Path)) { string newContentPackagePath = Path.Combine(WorkshopItemStagingFolder, MetadataFileName); File.Copy(tempContentPackage.Path, newContentPackagePath, overwrite: true); contentPackage = new ContentPackage(newContentPackagePath); foreach (ContentFile contentFile in tempContentPackage.Files) { string sourceFile; if (contentFile.Type == ContentType.Submarine && File.Exists(contentFile.Path)) { sourceFile = contentFile.Path; } else { sourceFile = Path.Combine(existingItem.Directory.FullName, contentFile.Path); } if (!File.Exists(sourceFile)) { continue; } //make sure the destination directory exists string destinationPath = Path.Combine(WorkshopItemStagingFolder, contentFile.Path); Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); File.Copy(sourceFile, destinationPath, overwrite: true); contentPackage.AddFile(contentFile.Path, contentFile.Type); } } else { contentPackage = ContentPackage.CreatePackage(existingItem.Title, Path.Combine(WorkshopItemStagingFolder, MetadataFileName), false); contentPackage.Save(contentPackage.Path); } } public static void StartPublishItem(ContentPackage contentPackage, Workshop.Editor item) { if (instance == null || !instance.isInitialized) return; if (string.IsNullOrEmpty(item.Title)) { DebugConsole.ThrowError("Cannot publish workshop item - title not set."); return; } if (string.IsNullOrEmpty(item.Folder)) { DebugConsole.ThrowError("Cannot publish workshop item \"" + item.Title + "\" - folder not set."); return; } contentPackage.Name = item.Title; contentPackage.GameVersion = GameMain.Version; contentPackage.Save(Path.Combine(WorkshopItemStagingFolder, MetadataFileName)); if (File.Exists(PreviewImageName)) File.Delete(PreviewImageName); //move the preview image out of the staging folder, it does not need to be included in the folder sent to Workshop File.Move(Path.GetFullPath(Path.Combine(item.Folder, PreviewImageName)), PreviewImageName); item.PreviewImage = Path.GetFullPath(PreviewImageName); CoroutineManager.StartCoroutine(PublishItem(item)); } private static IEnumerable PublishItem(Workshop.Editor item) { if (instance == null || !instance.isInitialized) { yield return CoroutineStatus.Success; } item.Publish(); while (item.Publishing) { yield return CoroutineStatus.Running; } if (string.IsNullOrEmpty(item.Error)) { DebugConsole.NewMessage("Published workshop item " + item.Title + " successfully.", Microsoft.Xna.Framework.Color.LightGreen); } else { DebugConsole.ThrowError("Publishing workshop item " + item.Title + " failed. " + item.Error); } SaveUtil.ClearFolder(WorkshopItemStagingFolder); Directory.Delete(WorkshopItemStagingFolder); File.Delete(PreviewImageName); yield return CoroutineStatus.Success; } /// /// Enables a workshop item by moving it to the game folder. /// public static bool EnableWorkShopItem(Workshop.Item item, bool allowFileOverwrite, out string errorMsg) { if (!item.Installed) { errorMsg = TextManager.Get("WorkshopErrorInstallRequiredToEnable").Replace("[itemname]", item.Title); DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); return false; } string metaDataFilePath = Path.Combine(item.Directory.FullName, MetadataFileName); ContentPackage contentPackage = new ContentPackage(metaDataFilePath); string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage); var allPackageFiles = Directory.GetFiles(item.Directory.FullName, "*", SearchOption.AllDirectories); List nonContentFiles = new List(); foreach (string file in allPackageFiles) { if (file == metaDataFilePath) { continue; } string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory.FullName); string fullPath = Path.GetFullPath(relativePath); if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; } if (ContentPackage.IsModFilePathAllowed(relativePath)) { nonContentFiles.Add(relativePath); } } if (!allowFileOverwrite) { if (File.Exists(newContentPackagePath)) { errorMsg = TextManager.Get("WorkshopErrorOverwriteOnEnable") .Replace("[itemname]", item.Title) .Replace("[filename]", newContentPackagePath); DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); return false; } foreach (ContentFile contentFile in contentPackage.Files) { string sourceFile = Path.Combine(item.Directory.FullName, contentFile.Path); if (File.Exists(sourceFile) && File.Exists(contentFile.Path)) { errorMsg = TextManager.Get("WorkshopErrorOverwriteOnEnable") .Replace("[itemname]", item.Title) .Replace("[filename]", contentFile.Path); DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); return false; } } } try { foreach (ContentFile contentFile in contentPackage.Files) { string sourceFile = Path.Combine(item.Directory.FullName, contentFile.Path); if (!File.Exists(sourceFile)) { continue; } if (!ContentPackage.IsModFilePathAllowed(contentFile)) { DebugConsole.ThrowError(TextManager.Get("WorkshopErrorIllegalPathOnEnable").Replace("[filename]", contentFile.Path)); continue; } //make sure the destination directory exists Directory.CreateDirectory(Path.GetDirectoryName(contentFile.Path)); File.Copy(sourceFile, contentFile.Path, overwrite: true); } foreach (string nonContentFile in nonContentFiles) { string sourceFile = Path.Combine(item.Directory.FullName, nonContentFile); if (!File.Exists(sourceFile)) { continue; } if (!ContentPackage.IsModFilePathAllowed(nonContentFile)) { DebugConsole.ThrowError(TextManager.Get("WorkshopErrorIllegalPathOnEnable").Replace("[filename]", nonContentFile)); continue; } Directory.CreateDirectory(Path.GetDirectoryName(nonContentFile)); File.Copy(sourceFile, nonContentFile, overwrite: true); } } catch (Exception e) { errorMsg = TextManager.Get("WorkshopErrorEnableFailed").Replace("[itemname]", item.Title) + " " + e.Message; DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); return false; } var newPackage = new ContentPackage(contentPackage.Path, newContentPackagePath) { SteamWorkshopUrl = item.Url, InstallTime = item.Modified > item.Created ? item.Modified : item.Created }; newPackage.Save(newContentPackagePath); ContentPackage.List.Add(newPackage); if (newPackage.CorePackage) { //if enabling a core package, disable all other core packages GameMain.Config.SelectedContentPackages.RemoveWhere(cp => cp.CorePackage); } GameMain.Config.SelectedContentPackages.Add(newPackage); GameMain.Config.SaveNewPlayerConfig(); if (newPackage.Files.Any(f => f.Type == ContentType.Submarine)) { Submarine.RefreshSavedSubs(); } errorMsg = ""; return true; } /// /// Disables a workshop item by removing the files from the game folder. /// public static bool DisableWorkShopItem(Workshop.Item item, out string errorMsg) { if (!item.Installed) { errorMsg = "Cannot disable workshop item \"" + item.Title + "\" because it has not been installed."; DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); return false; } ContentPackage contentPackage = new ContentPackage(Path.Combine(item.Directory.FullName, MetadataFileName)); string installedContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage); var allPackageFiles = Directory.GetFiles(item.Directory.FullName, "*", SearchOption.AllDirectories); List nonContentFiles = new List(); foreach (string file in allPackageFiles) { if (file == MetadataFileName) { continue; } string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory.FullName); string fullPath = Path.GetFullPath(relativePath); if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; } if (ContentPackage.IsModFilePathAllowed(relativePath)) { nonContentFiles.Add(relativePath); } } if (File.Exists(installedContentPackagePath)) { File.Delete(installedContentPackagePath); } bool wasSub = contentPackage.Files.Any(f => f.Type == ContentType.Submarine); HashSet directories = new HashSet(); try { foreach (ContentFile contentFile in contentPackage.Files) { if (!ContentPackage.IsModFilePathAllowed(contentFile)) { //Workshop items are not allowed to add or modify files in the Content or Data folders; continue; } if (!File.Exists(contentFile.Path)) { continue; } File.Delete(contentFile.Path); directories.Add(Path.GetDirectoryName(contentFile.Path)); } foreach (string nonContentFile in nonContentFiles) { if (!ContentPackage.IsModFilePathAllowed(nonContentFile)) { //Workshop items are not allowed to add or modify files in the Content or Data folders; continue; } if (!File.Exists(nonContentFile)) { continue; } File.Delete(nonContentFile); directories.Add(Path.GetDirectoryName(nonContentFile)); } foreach (string directory in directories) { if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) { continue; } if (Directory.GetFiles(directory, "*", SearchOption.AllDirectories).Count() == 0) { Directory.Delete(directory, recursive: true); } } ContentPackage.List.RemoveAll(cp => System.IO.Path.GetFullPath(cp.Path) == System.IO.Path.GetFullPath(installedContentPackagePath)); GameMain.Config.SelectedContentPackages.RemoveWhere(cp => !ContentPackage.List.Contains(cp)); GameMain.Config.SaveNewPlayerConfig(); } catch (Exception e) { errorMsg = "Disabling the workshop item \"" + item.Title + "\" failed. " + e.Message; DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); return false; } if (wasSub) { Submarine.RefreshSavedSubs(); } errorMsg = ""; return true; } /// /// Is the item compatible with this version of Barotrauma. Returns null if compatibility couldn't be determined (item not installed) /// public static bool? CheckWorkshopItemCompatibility(Workshop.Item item) { if (!item.Installed) { return null; } string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName); if (!File.Exists(metaDataPath)) { throw new FileNotFoundException("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted."); } ContentPackage contentPackage = new ContentPackage(metaDataPath); return contentPackage.IsCompatible(); } public static bool CheckWorkshopItemEnabled(Workshop.Item item, bool checkContentFiles = true) { if (!item.Installed) return false; string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName); if (!File.Exists(metaDataPath)) { DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted."); return false; } ContentPackage contentPackage = new ContentPackage(metaDataPath); //make sure the contentpackage file is present if (!File.Exists(GetWorkshopItemContentPackagePath(contentPackage)) && !ContentPackage.List.Any(cp => cp.Name == contentPackage.Name)) { return false; } if (checkContentFiles) { foreach (ContentFile contentFile in contentPackage.Files) { if (!File.Exists(contentFile.Path)) return false; } } return true; } public static bool CheckWorkshopItemUpToDate(Workshop.Item item) { if (!item.Installed) return false; string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName); if (!File.Exists(metaDataPath)) { DebugConsole.ThrowError("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted."); return false; } ContentPackage steamPackage = new ContentPackage(metaDataPath); ContentPackage myPackage = ContentPackage.List.Find(cp => cp.Name == steamPackage.Name); if (myPackage?.InstallTime == null) { return false; } return item.Modified <= myPackage.InstallTime.Value; } public static bool AutoUpdateWorkshopItems() { if (instance == null || !instance.isInitialized) { return false; } bool itemsUpdated = false; foreach (ulong subscribedItemId in instance.client.Workshop.GetSubscribedItemIds()) { //TODO: fix this, GetItem doesn't query item.Modified var item = instance.client.Workshop.GetItem(subscribedItemId); if (item.Installed && CheckWorkshopItemEnabled(item) && !CheckWorkshopItemUpToDate(item)) { if (!UpdateWorkshopItem(item, out string errorMsg)) { DebugConsole.ThrowError(errorMsg); new GUIMessageBox( TextManager.Get("Error"), TextManager.Get("WorkshopItemUpdateFailed").Replace("[itemname]", item.Title).Replace("[errormessage]", errorMsg)); } else { new GUIMessageBox("", TextManager.Get("WorkshopItemUpdated").Replace("[itemname]", item.Title)); itemsUpdated = true; } } } return itemsUpdated; } public static bool UpdateWorkshopItem(Workshop.Item item, out string errorMsg) { errorMsg = ""; if (!item.Installed) { return false; } if (!DisableWorkShopItem(item, out errorMsg)) { return false; } if (!EnableWorkShopItem(item, allowFileOverwrite: false, errorMsg: out errorMsg)) { return false; } return true; } public static string GetWorkshopItemContentPackagePath(ContentPackage contentPackage) { string fileName = contentPackage.Name + ".xml"; string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); foreach (char c in invalidChars) fileName = fileName.Replace(c.ToString(), ""); return Path.Combine("Data", "ContentPackages", fileName); } #endregion } }