3 using System.Collections.Generic;
4 using System.Globalization;
17 Path = path.CleanUpPathCrossPlatform(correctFilenameCase:
false);
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"),
36 =>
new File(path, typeof(T));
43 =>
new File(path, type);
45 public readonly
string Path;
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));
60 if (contentPackage is
null) {
return; }
71 private string name =
"";
77 var charsToRemove = Path.GetInvalidFileNameCharsCrossPlatform();
78 name =
string.Concat(value.Where(c => !charsToRemove.Contains(c)));
82 public readonly List<string>
AltNames =
new List<string>();
84 private readonly List<File> files =
new List<File>();
85 public IReadOnlyList<File>
Files => files;
93 public Option<ContentPackageId>
UgcId = Option<ContentPackageId>.None();
95 public Option<SerializableDateTime>
InstallTime = Option<SerializableDateTime>.None();
99 string.Equals(f.
Path, file.Path, StringComparison.OrdinalIgnoreCase)
100 && f.Type == file.Type);
128 if (
string.IsNullOrWhiteSpace(modVersion)) {
return string.Empty; }
131 int startIndex = modVersion.Length - 1;
132 while (startIndex > 0 &&
char.IsDigit(modVersion[startIndex])) { startIndex--; }
135 if (startIndex >= modVersion.Length
136 || !
char.IsDigit(modVersion[startIndex])
138 modVersion[startIndex..],
140 CultureInfo.InvariantCulture,
141 out
int theFinalInteger))
146 return $
"{modVersion[..startIndex]}{(theFinalInteger + 1).ToString(CultureInfo.InvariantCulture)}";
151 XDocument doc =
new XDocument();
152 XElement rootElement =
new XElement(
"contentpackage");
154 void addRootAttribute<T>(
string name, T value) where T : notnull
155 => rootElement.Add(
new XAttribute(name, value.ToString() ??
""));
157 addRootAttribute(
"name",
Name);
159 addRootAttribute(
"corepackage",
IsCore);
160 if (
UgcId.TryUnwrap(out var ugcId) && ugcId is
SteamWorkshopId steamWorkshopId) { addRootAttribute(
"steamworkshopid", steamWorkshopId.Value); }
162 if (
AltNames.Any()) { addRootAttribute(
"altnames",
string.Join(
",",
AltNames)); }
164 if (
InstallTime.TryUnwrap(out var installTime)) { addRootAttribute(
"installtime", installTime); }
166 files.ForEach(f => rootElement.Add(f.ToXElement()));
168 doc.Add(rootElement);
172 public void Save(
string path,
bool catchUnauthorizedAccessExceptions =
true)
174 Directory.CreateDirectory(Path.GetDirectoryName(path)!, catchUnauthorizedAccessExceptions);
Base class for content file types, which are loaded from filelist.xml via reflection....
readonly ContentPath Path
ImmutableArray< ContentFile > Files
const string DefaultModVersion
readonly ImmutableArray< string > AltNames
readonly Option< ContentPackageId > UgcId
readonly Option< SerializableDateTime > InstallTime
readonly string ModVersion
readonly? string RawValue
static readonly Version Version
readonly string StringRepresentation
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)
static string IncrementModVersion(string modVersion)
void DiscardHashAndInstallTime()
IReadOnlyList< File > Files
Option< SerializableDateTime > InstallTime
Option< ContentPackageId > UgcId
readonly List< string > AltNames
ModProject(ContentPackage? contentPackage)
void RemoveFile(File file)
void Save(string path, bool catchUnauthorizedAccessExceptions=true)