Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs
Joonas Rikkonen b48aa89004 bbc4a31...e6715d6
commit e6715d605db9bb1d608e4a4990ac41f6214f61d1
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 5 17:22:31 2019 +0200

    Fixed the "control" console command not being usable by clients, changed the way arguments are given to the "setclientcharacter" command (no semicolon to separate the names, quotation marks have to be used for multi-word names just like with any other command). Closes #1224

commit acb7c1e0dc05bf619e7ec4875196cc45647d3fb4
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 5 16:30:38 2019 +0200

    Steam Workshop fixes:
    - Install content packages for items that only contain a sub. Otherwise the system can't determine if the item has been updated (and might also be useful for toggling off incompatible subs when switching from mod to another).
    - Log an error instead of crashing the game if CheckWorkshopItemEnabled or CheckWorkshopItemUpToDate fails due to a missing filelist.xml.
    - Show a messagebox when a workshop item is updated succesfully.

commit b2e8ed565bc03f466930799268c13b2fca4bb9c9
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 5 14:43:42 2019 +0200

    Fixed nullref exception when disabling a workshop item that doesn't have an update button (or when enabling the item fails)

commit 26f1f285cd80ca6f023b12e6dd80dc71e87ee9c3
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 5 14:15:14 2019 +0200

    Fixed console command aliases not being taken into account in GameClient.HasConsoleCommandPermission (meaning that the client needed a permission for each name variant of a command, making it impossible to for example use "fixwalls" instead of "fixhulls"). Closes #1225

commit dee02de681a212efd0e0a82c14619f3fe4839cc4
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 5 13:35:48 2019 +0200

    Fixed traitor rounds failing to start if there's no owner client, fixed occasional "traitorCount somehow ended up less than 1" errors due to Rand.Int using 0 as the minimum value. Closes #1217
2019-03-18 22:30:27 +02:00

840 lines
36 KiB
C#

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<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> 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<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, bool includeUnresponsive)
{
IEnumerable<ServerList.Server> servers = includeUnresponsive ?
new List<ServerList.Server>(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<IList<Workshop.Item>> onItemsFound, List<string> 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<IList<Workshop.Item>> onItemsFound, int amount, List<string> 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<string, int> 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<IList<Workshop.Item>> onItemsFound, List<string> 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();
}
/// <summary>
/// Creates a new folder, copies the specified files there and creates a metadata file with install instructions.
/// </summary>
public static void CreateWorkshopItemStaging(List<ContentFile> 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<string> copiedFilePaths = new List<string>();
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));
}
/// <summary>
/// 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
/// </summary>
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<object> 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;
}
/// <summary>
/// Enables a workshop item by moving it to the game folder.
/// </summary>
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<string> nonContentFiles = new List<string>();
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;
}
/// <summary>
/// Disables a workshop item by removing the files from the game folder.
/// </summary>
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<string> nonContentFiles = new List<string>();
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<string> directories = new HashSet<string>();
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;
}
/// <summary>
/// Is the item compatible with this version of Barotrauma. Returns null if compatibility couldn't be determined (item not installed)
/// </summary>
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
}
}