diff --git a/Files/XmlFile.Deserializer.cs b/Files/XmlFile.Deserializer.cs
new file mode 100644
index 00000000..f13df26d
--- /dev/null
+++ b/Files/XmlFile.Deserializer.cs
@@ -0,0 +1,22 @@
+namespace SabreTools.Serialization.Files
+{
+ ///
+ /// Base class for other XML serializers
+ ///
+ ///
+ public partial class XmlFile : IFileSerializer
+ {
+ ///
+#if NET48
+ public T Deserialize(string path)
+#else
+ public T? Deserialize(string? path)
+#endif
+ {
+ using (var data = PathProcessor.OpenStream(path))
+ {
+ return new Streams.XmlFile().Deserialize(data);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PathProcessor.cs b/PathProcessor.cs
new file mode 100644
index 00000000..dee59422
--- /dev/null
+++ b/PathProcessor.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+
+namespace SabreTools.Serialization
+{
+ internal class PathProcessor
+ {
+ ///
+ /// Opens a path as a stream in a safe manner, decompressing if needed
+ ///
+ /// Path to open as a stream
+ /// Stream representing the file, null on error
+#if NET48
+ public static Stream OpenStream(string path)
+#else
+ public static Stream? OpenStream(string path)
+#endif
+ {
+ try
+ {
+ // If we don't have a file
+ if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
+ return null;
+
+ // Open the file for deserialization
+ var stream = File.OpenRead(path);
+
+ // Get the extension to determine if additional handling is needed
+ string ext = Path.GetExtension(path).TrimStart('.');
+
+ // Determine what we do based on the extension
+ if (string.Equals(ext, "gz", StringComparison.OrdinalIgnoreCase))
+ {
+ return new GZipStream(stream, CompressionMode.Decompress);
+ }
+ else if (string.Equals(ext, "zip", StringComparison.OrdinalIgnoreCase))
+ {
+ // TODO: Support zip-compressed files
+ return null;
+ }
+ else
+ {
+ return stream;
+ }
+ }
+ catch
+ {
+ // TODO: Handle logging the exception
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Streams/XmlFile.Deserializer.cs b/Streams/XmlFile.Deserializer.cs
new file mode 100644
index 00000000..24087d1a
--- /dev/null
+++ b/Streams/XmlFile.Deserializer.cs
@@ -0,0 +1,45 @@
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.Serialization;
+
+namespace SabreTools.Serialization.Streams
+{
+ ///
+ /// Base class for other XML serializers
+ ///
+ ///
+ public partial class XmlFile : IStreamSerializer
+ {
+ ///
+#if NET48
+ public T Deserialize(Stream data)
+#else
+ public T? Deserialize(Stream? data)
+#endif
+ {
+ // If the stream is null
+ if (data == null)
+ return default;
+
+ // Setup the serializer and the reader
+ var serializer = new XmlSerializer(typeof(T));
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ DtdProcessing = DtdProcessing.Ignore,
+ ValidationFlags = XmlSchemaValidationFlags.None,
+ ValidationType = ValidationType.None,
+ };
+ var streamReader = new StreamReader(data);
+ var xmlReader = XmlReader.Create(streamReader, settings);
+
+ // Perform the deserialization and return
+#if NET48
+ return (T)serializer.Deserialize(xmlReader);
+#else
+ return (T?)serializer.Deserialize(xmlReader);
+#endif
+ }
+ }
+}
\ No newline at end of file