Client LuaCsForBarotrauma
ModProject.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Globalization;
5 using System.Linq;
6 using System.Xml.Linq;
7 using Barotrauma.IO;
8 
9 namespace Barotrauma
10 {
11  public class ModProject
12  {
13  public class File
14  {
15  private File(string path, Type type)
16  {
17  Path = path.CleanUpPathCrossPlatform(correctFilenameCase: false);
18  Type = type switch
19  {
20  _ when !type.IsSubclassOf(typeof(ContentFile)) => throw new ArgumentException($"{type.Name} does not derive from {nameof(ContentFile)}"),
21  { IsAbstract: true } => throw new ArgumentException($"{type.Name} is abstract"),
22  _ => type
23  };
24  }
25 
26  private File(ContentFile f)
27  {
28  Path = f.Path.RawValue ?? "";
29  Type = f.GetType();
30  }
31 
32  public static File FromContentFile(ContentFile file)
33  => new File(file);
34 
35  public static File FromPath<T>(string path) where T : ContentFile
36  => new File(path, typeof(T));
37 
42  public static File FromPath(string path, Type type)
43  => new File(path, type);
44 
45  public readonly string Path;
46  public readonly Type Type;
47 
48  public XElement ToXElement()
49  {
50  if (Type is null) { throw new InvalidOperationException("Type must be set before calling ToXElement"); }
51  if (Path.IsNullOrEmpty()) { throw new InvalidOperationException("Path must be set before calling ToXElement"); }
52  return new XElement(Type.Name.RemoveFromEnd("File"), new XAttribute("file", Path));
53  }
54  }
55 
56  public ModProject() { }
57 
58  public ModProject(ContentPackage? contentPackage)
59  {
60  if (contentPackage is null) { return; }
61  Name = contentPackage.Name;
62  AltNames = contentPackage.AltNames.ToList();
63  files = contentPackage.Files.Select(File.FromContentFile).ToList();
64  ModVersion = IncrementModVersion(contentPackage.ModVersion);
65  IsCore = contentPackage is CorePackage;
66  UgcId = contentPackage.UgcId;
67  ExpectedHash = contentPackage.Hash;
68  InstallTime = contentPackage.InstallTime;
69  }
70 
71  private string name = "";
72  public string Name
73  {
74  get => name;
75  set
76  {
77  var charsToRemove = Path.GetInvalidFileNameCharsCrossPlatform();
78  name = string.Concat(value.Where(c => !charsToRemove.Contains(c)));
79  }
80  }
81 
82  public readonly List<string> AltNames = new List<string>();
83 
84  private readonly List<File> files = new List<File>();
85  public IReadOnlyList<File> Files => files;
86 
88 
89  public Md5Hash? ExpectedHash { get; set; }
90 
91  public bool IsCore = false;
92 
93  public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None();
94 
95  public Option<SerializableDateTime> InstallTime = Option<SerializableDateTime>.None();
96 
97  public bool HasFile(File file)
98  => Files.Any(f =>
99  string.Equals(f.Path, file.Path, StringComparison.OrdinalIgnoreCase)
100  && f.Type == file.Type);
101 
102  public void AddFile(File file)
103  {
104  if (!HasFile(file))
105  {
106  files.Add(file);
108  }
109  }
110 
111  public void RemoveFile(File file)
112  {
113  if (HasFile(file))
114  {
115  files.Remove(file);
117  }
118  }
119 
121  {
122  ExpectedHash = null;
123  InstallTime = Option<SerializableDateTime>.None();
124  }
125 
126  public static string IncrementModVersion(string modVersion)
127  {
128  if (string.IsNullOrWhiteSpace(modVersion)) { return string.Empty; }
129 
130  //look for an integer at the end of the string and increment it
131  int startIndex = modVersion.Length - 1;
132  while (startIndex > 0 && char.IsDigit(modVersion[startIndex])) { startIndex--; }
133  startIndex++;
134 
135  if (startIndex >= modVersion.Length
136  || !char.IsDigit(modVersion[startIndex])
137  || !int.TryParse(
138  modVersion[startIndex..],
139  NumberStyles.Any,
140  CultureInfo.InvariantCulture,
141  out int theFinalInteger))
142  {
143  return modVersion;
144  }
145 
146  return $"{modVersion[..startIndex]}{(theFinalInteger + 1).ToString(CultureInfo.InvariantCulture)}";
147  }
148 
149  public XDocument ToXDocument()
150  {
151  XDocument doc = new XDocument();
152  XElement rootElement = new XElement("contentpackage");
153 
154  void addRootAttribute<T>(string name, T value) where T : notnull
155  => rootElement.Add(new XAttribute(name, value.ToString() ?? ""));
156 
157  addRootAttribute("name", Name);
158  if (!ModVersion.IsNullOrEmpty()) { addRootAttribute("modversion", ModVersion); }
159  addRootAttribute("corepackage", IsCore);
160  if (UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId steamWorkshopId) { addRootAttribute("steamworkshopid", steamWorkshopId.Value); }
161  addRootAttribute("gameversion", GameMain.Version);
162  if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); }
163  if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); }
164  if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", installTime); }
165 
166  files.ForEach(f => rootElement.Add(f.ToXElement()));
167 
168  doc.Add(rootElement);
169  return doc;
170  }
171 
172  public void Save(string path, bool catchUnauthorizedAccessExceptions = true)
173  {
174  Directory.CreateDirectory(Path.GetDirectoryName(path)!, catchUnauthorizedAccessExceptions);
175  ToXDocument().SaveSafe(path);
176  }
177  }
178 }
Base class for content file types, which are loaded from filelist.xml via reflection....
Definition: ContentFile.cs:23
readonly ContentPath Path
Definition: ContentFile.cs:137
ImmutableArray< ContentFile > Files
const string DefaultModVersion
readonly ImmutableArray< string > AltNames
readonly Option< ContentPackageId > UgcId
readonly Option< SerializableDateTime > InstallTime
readonly string ModVersion
readonly? string RawValue
Definition: ContentPath.cs:19
static readonly Version Version
Definition: GameMain.cs:46
readonly string StringRepresentation
Definition: Md5Hash.cs:32
readonly string Path
Definition: ModProject.cs:45
static File FromPath< T >(string path)
static File FromPath(string path, Type type)
Prefer FromPath<T> when possible, this just exists for cases where the type can only be decided at ru...
static File FromContentFile(ContentFile file)
bool HasFile(File file)
void AddFile(File file)
Definition: ModProject.cs:102
static string IncrementModVersion(string modVersion)
Definition: ModProject.cs:126
void DiscardHashAndInstallTime()
Definition: ModProject.cs:120
IReadOnlyList< File > Files
Definition: ModProject.cs:85
Option< SerializableDateTime > InstallTime
Definition: ModProject.cs:95
Option< ContentPackageId > UgcId
Definition: ModProject.cs:93
readonly List< string > AltNames
Definition: ModProject.cs:82
ModProject(ContentPackage? contentPackage)
Definition: ModProject.cs:58
XDocument ToXDocument()
Definition: ModProject.cs:149
void RemoveFile(File file)
Definition: ModProject.cs:111
void Save(string path, bool catchUnauthorizedAccessExceptions=true)
Definition: ModProject.cs:172