diff --git a/AssetEntry.cs b/AssetEntry.cs
index 091f075..b85fc1b 100644
--- a/AssetEntry.cs
+++ b/AssetEntry.cs
@@ -37,11 +37,6 @@ namespace HashCalculator.GUI
 
         public static AssetEntry? TryParse(XElement element)
         {
-            if (element == null)
-            {
-                return null;
-            }
-
             if (element.Name.Namespace != ModXml.EalaAsset)
             {
                 TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}");
@@ -59,6 +54,27 @@ namespace HashCalculator.GUI
             return null;
         }
 
+        public static AssetEntry? TryParseXsd(XElement element)
+        {
+            if (element.Name.Namespace != ModXsd.Schema)
+            {
+                TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}");
+            }
+
+            try
+            {
+                var type = element.Attribute("name")?.Value
+                    ?? throw new NotSupportedException();
+                return new AssetEntry(element.Name.LocalName, type);
+            }
+            catch (Exception e)
+            {
+                TracerListener.WriteLine($"Failed to parse element: {e}");
+            }
+
+            return null;
+        }
+
         public AssetEntry(XElement element)
         {
             if (element == null)
@@ -107,6 +123,18 @@ namespace HashCalculator.GUI
             TypeIdString = $"{TypeId:x8} ({TypeId,10})";
         }
 
+        private AssetEntry(string kind, string type)
+        {
+            Type = type;
+            Name = string.Empty;
+            NameString = $"<{kind} {Type}>";
+            DisplayLabels = Array.Empty<string>();
+            InstanceId = 0;
+            TypeId = SageHash.CalculateBinaryHash(Type);
+            InstanceIdString = string.Empty;
+            TypeIdString = $"{TypeId:x8} ({TypeId,10})";
+        }
+
         public bool Equals(AssetEntry? entry)
         {
             return this == entry;
diff --git a/Controller.cs b/Controller.cs
index 37d427c..54c57e4 100644
--- a/Controller.cs
+++ b/Controller.cs
@@ -46,14 +46,14 @@ namespace HashCalculator.GUI
                         ViewModel.StatusText = string.Empty;
                         return;
                     case InputEntryType.BigFile:
-                        await LoadBig(selected.Value).ConfigureAwait(true);
+                        await LoadBig(selected.Value);
                         return;
                     case InputEntryType.ManifestFile:
-                        await LoadManifestFromFile(selected.Value).ConfigureAwait(true);
+                        await LoadManifestFromFile(selected.Value);
                         return;
                     case InputEntryType.XmlFile:
-                        ViewModel.IsXml = true;
-                        await LoadXml(selected.Value).ConfigureAwait(true);
+                    case InputEntryType.XsdFile:
+                        await LoadXml(selected);
                         return;
                     default:
                         throw new NotSupportedException();
@@ -125,9 +125,8 @@ namespace HashCalculator.GUI
             var first = bigViewModel.AllManifests.FirstOrDefault();
             if (first != null)
             {
-                var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value);
-                if (name.StartsWith("mod", StringComparison.OrdinalIgnoreCase)
-                    || name.StartsWith("mapmetadata", StringComparison.OrdinalIgnoreCase))
+                var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value).ToLowerInvariant();
+                if (new[] { "mod", "mapmetadata_mod", "static", "global" }.Any(name.StartsWith))
                 {
                     bigViewModel.SelectedItem = first;
                     await bigViewModel.SelectCommand.ExecuteTask(ViewModel).ConfigureAwait(true);
@@ -189,7 +188,7 @@ namespace HashCalculator.GUI
             tokenSource.Cancel();
             try
             {
-                await task.ConfigureAwait(true);
+                await task;
             }
             catch (OperationCanceledException)
             {
@@ -197,11 +196,21 @@ namespace HashCalculator.GUI
             }
         }
 
-        private async Task LoadXml(string path)
+        private async Task LoadXml(InputEntry entry)
         {
-            await CancelLoadingXml().ConfigureAwait(true);
+            await CancelLoadingXml();
+            var path = entry.Text;
+            var type = entry.Type;
             using var tokenSource = new CancellationTokenSource();
-            var task = LoadXmlInternal(path, tokenSource.Token);
+            var modXml = type switch
+            {
+                InputEntryType.XmlFile => LoadModXml(path, tokenSource.Token),
+                InputEntryType.XsdFile => LoadModXsd(path, tokenSource.Token),
+                _ => throw new NotSupportedException(type.ToString())
+            };
+            ViewModel.IsXml = true;
+            ViewModel.IsXsd = type == InputEntryType.XsdFile;
+            var task = LoadXmlInternal(await modXml, tokenSource.Token);
             _lastXmlTask = (tokenSource, task).ToTuple();
 
             await ExceptionWrapepr(async () =>
@@ -229,23 +238,32 @@ namespace HashCalculator.GUI
             }, "SAGE FastHash 计算器 - XML 加载失败…", "XML 文件加载失败,也许,你选择的 XML 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true);
         }
 
-        private async Task LoadXmlInternal(string path, CancellationToken token)
+        private async Task<IModXml> LoadModXml(string path, CancellationToken cancelToken)
         {
             if (ModXml.LocateSdkFromRegistry() is not { } sdkRoot)
             {
                 var studio = await RequestOpenFile("选择 Mod Studio", ("EALAModStudio.exe", null));
                 sdkRoot = Path.GetDirectoryName(studio);
             }
-            var modXml = new ModXml(path, sdkRoot, token);
-
+            var modXml = new ModXml(path, sdkRoot, cancelToken);
             try
             {
-                await FindCsf(modXml.BaseDirectory).ConfigureAwait(true);
+                await FindCsf(modXml.BaseDirectory, cancelToken);
             }
             catch (Exception exception)
             {
                 TracerListener.WriteLine($"[ModXml] Failed to automatically find and load CSF: {exception}");
             }
+            return modXml;
+        }
+
+        private Task<IModXml> LoadModXsd(string path, CancellationToken token)
+        {
+            return Task.FromResult<IModXml>(new ModXsd(path, token));
+        }
+
+        private async Task LoadXmlInternal(IModXml modXml, CancellationToken token)
+        {
             token.ThrowIfCancellationRequested();
 
             var start = DateTimeOffset.UtcNow;
@@ -333,9 +351,9 @@ namespace HashCalculator.GUI
             });
         }
 
-        private static Task FindCsf(DirectoryInfo baseDirectory)
+        private static Task FindCsf(DirectoryInfo baseDirectory, CancellationToken cancelToken)
         {
-            return Task.Run(() =>
+            return Task.Run(async () =>
             {
                 if (!baseDirectory.Exists)
                 {
@@ -355,6 +373,7 @@ namespace HashCalculator.GUI
                            from data in directories.GetDirectories("Data")
                            from csf in data.GetFiles("*.csf")
                            select csf;
+                csfs = csfs.AsParallel().WithCancellation(cancelToken);
                 var result =
                     csfs.FirstOrDefault(x => x.Name.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase))
                     ?? csfs.FirstOrDefault();
@@ -363,8 +382,13 @@ namespace HashCalculator.GUI
                     throw new FileNotFoundException();
                 }
 
-                using var fileStream = result.OpenRead();
-                Translator.Provider = new CsfTranslationProvider(fileStream);
+                using var memoryStream = new MemoryStream();
+                using (var fileStream = result.OpenRead())
+                {
+                    await fileStream.CopyToAsync(memoryStream, cancelToken);
+                    memoryStream.Position = 0;
+                }
+                Translator.Provider = new CsfTranslationProvider(memoryStream);
                 TracerListener.WriteLine($"[ModXml]: Automatically loaded csf from `{result.FullName}`");
             });
         }
diff --git a/Converters/MultiBooleanConjunctionConverter.cs b/Converters/MultiBooleanConjunctionConverter.cs
new file mode 100644
index 0000000..2e6d3a2
--- /dev/null
+++ b/Converters/MultiBooleanConjunctionConverter.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Windows.Data;
+
+namespace HashCalculator.GUI.Converters
+{
+    class MultiBooleanConjunctionConverter : IMultiValueConverter
+    {
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            return values.Cast<bool>().Aggregate((a, b) => a && b);
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Converters/MultiValueAggregateConverter.cs b/Converters/MultiValueAggregateConverter.cs
new file mode 100644
index 0000000..78f79a7
--- /dev/null
+++ b/Converters/MultiValueAggregateConverter.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Windows.Data;
+
+namespace HashCalculator.GUI.Converters
+{
+    class MultiValueAggregateConverter : List<IValueConverter?>, IMultiValueConverter
+    {
+        public IMultiValueConverter? Converter { get; set; }
+        public IValueConverter? PostProcess { get; set; }
+
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (Converter is not { } converter)
+            {
+                throw new InvalidOperationException($"{nameof(Converter)} of {nameof(MultiValueAggregateConverter)} is null");
+            }
+            if (Count > 0)
+            {
+                values = values.ToArray(); // 复制一份
+                var length = Math.Min(Count, values.Length);
+                for (var i = 0; i < length; ++i)
+                {
+                    if (this[i] is IValueConverter c)
+                    {
+                        values[i] = c.Convert(values[i], targetType, parameter, culture);
+                    }
+                }
+            }
+            var result = converter.Convert(values, targetType, parameter, culture);
+            if (PostProcess is not { } postConverter)
+            {
+                return result;
+            }
+            return postConverter.Convert(result, targetType, parameter, culture);
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/FileSystemSuggestions.cs b/FileSystemSuggestions.cs
index 4801518..6e343c9 100644
--- a/FileSystemSuggestions.cs
+++ b/FileSystemSuggestions.cs
@@ -8,17 +8,17 @@ namespace HashCalculator.GUI
 {
     internal class FileSystemSuggestions
     {
-        private static readonly Dictionary<string, InputEntryType> Mapping = new Dictionary<string, InputEntryType>(StringComparer.OrdinalIgnoreCase)
+        private static readonly Dictionary<string, InputEntryType> Mapping = new(StringComparer.OrdinalIgnoreCase)
         {
             { ".big", InputEntryType.BigFile },
             { ".xml", InputEntryType.XmlFile },
             { ".w3x", InputEntryType.XmlFile },
             { ".manifest", InputEntryType.ManifestFile },
+            { ".xsd", InputEntryType.XsdFile },
         };
 
         private Search _search = new Search();
 
-        [SuppressMessage("Microsoft.Performance", "CA1031")]
         public IEnumerable<InputEntry> ProvideFileSystemSuggestions(string? path)
         {
             var empty = Enumerable.Empty<InputEntry>();
diff --git a/IModXml.cs b/IModXml.cs
new file mode 100644
index 0000000..dab48e6
--- /dev/null
+++ b/IModXml.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HashCalculator.GUI
+{
+    interface IModXml
+    {
+        public int TotalFilesProcessed { get; }
+        public int TotalAssets { get; }
+        IAsyncEnumerable<AssetEntry> ProcessDocument();
+    }
+}
diff --git a/InputEntry.cs b/InputEntry.cs
index 0fb2335..056680e 100644
--- a/InputEntry.cs
+++ b/InputEntry.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-
-using System.Text;
-using System.Threading.Tasks;
-using TechnologyAssembler.Core.IO;
+using System.Collections.Generic;
 
 namespace HashCalculator.GUI
 {
@@ -13,18 +8,20 @@ namespace HashCalculator.GUI
         BigFile,
         ManifestFile,
         XmlFile,
+        XsdFile,
         BinaryFile,
         Path,
     }
 
     internal sealed class InputEntry
     {
-        public static IReadOnlyDictionary<InputEntryType, string> Descriptions = new Dictionary<InputEntryType, string> 
+        public static IReadOnlyDictionary<InputEntryType, string> Descriptions = new Dictionary<InputEntryType, string>
         {
             { InputEntryType.BigFile, "可以尝试读取这个 big 文件里的 manifest 文件" },
             { InputEntryType.BinaryFile, "可以计算这个文件的哈希值" },
             { InputEntryType.ManifestFile, "可以尝试读取这个 manifest 文件,显示各个素材的哈希值" },
-            { InputEntryType.XmlFile, "可以尝试读取这个XML,显示 XML 里定义的各个素材的哈希值" },
+            { InputEntryType.XmlFile, "可以尝试读取这个 XML,显示 XML 里定义的各个素材的哈希值" },
+            { InputEntryType.XsdFile, "可以尝试读取 XSD,显示 XSD 里定义的各个素材的哈希值" },
             { InputEntryType.Path, string.Empty },
         };
 
@@ -33,13 +30,13 @@ namespace HashCalculator.GUI
         public string Text { get; }
         public string Details
         {
-            get 
+            get
             {
-                if(Type == InputEntryType.Text)
+                if (Type == InputEntryType.Text)
                 {
                     var hash = SageHash.CalculateLowercaseHash(Value);
                     var binaryHash = SageHash.CalculateBinaryHash(Value);
-                    return hash == binaryHash 
+                    return hash == binaryHash
                         ? $"这段文字的哈希值:{hash:x8} ({hash})"
                         : $"这段文字的哈希值:{hash:x8};大小写敏感哈希值 {binaryHash:x8}";
                 }
diff --git a/MainWindow.xaml b/MainWindow.xaml
index 78e9d30..d2338d9 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -6,6 +6,8 @@
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:l="clr-namespace:HashCalculator.GUI"
+    xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
+    xmlns:sys="clr-namespace:System;assembly=System.Runtime"
     mc:Ignorable="d"
     Title="SAGE FastHash 哈希计算器" 
     Height="600" 
@@ -152,47 +154,54 @@
                 Height="25"
                 Margin="0,0,0,5"
             >
-                <Grid
+                <StackPanel
                     DockPanel.Dock="Right"
-                    Width="190"
+                    Orientation="Horizontal"
+                    d:Visibility="Visible"
                     Visibility="{Binding IsXml, 
                                          Converter={StaticResource BooleanToVisibilityConverter}}"
                 >
                     <Button 
-                        Content="{Binding LoadCsfText}"
-                        Command="{Binding LoadCsf}"
-                        CommandParameter="{Binding ElementName=Self}"
-                        Width="92"
-                        HorizontalAlignment="Left"
-                    />
-                    <Button 
-                        Content="取消加载" 
+                        Content="取消加载 XML" 
                         Command="{Binding CancelXml}"
-                        Width="92"
-                        HorizontalAlignment="Right"
+                        Padding="8,0"
+                        Margin="0,0,8,0"
                         Visibility="{Binding IsLoadingXml, 
                                              Converter={StaticResource BooleanToVisibilityConverter}}"
                     />
-                </Grid>
-                <CheckBox 
+                    <Button
+                        Content="{Binding LoadCsfText}"
+                        Command="{Binding LoadCsf}"
+                        CommandParameter="{Binding ElementName=Self}"
+                        Padding="16,0"
+                        Visibility="{Binding IsXsd,
+                                             Converter={StaticResource FalseToVisibilityConverter}}"
+                    />
+                </StackPanel>
+                <StackPanel
                     DockPanel.Dock="Right"
-                    Content="只显示 GameObject"
-                    IsChecked="{Binding GameObjectOnly}"
-                    Command="{Binding FilterDisplayEntries}"
-                    Padding="4,0"
-                    Margin="8,0"
-                    VerticalAlignment="Center"
-                />
-                <CheckBox 
-                    DockPanel.Dock="Right"
-                    Content="搜索类型 ID"
-                    IsChecked="{Binding ConsiderTypeId}"
-                    Command="{Binding FilterDisplayEntries}"
-                    Padding="4,0"
-                    Margin="8,0"
-                    VerticalAlignment="Center"
-                    Visibility="{Binding GameObjectOnly, Converter={StaticResource FalseToVisibilityConverter}}"
-                />
+                    Orientation="Horizontal"
+                    Visibility="{Binding IsXsd,
+                                         Converter={StaticResource FalseToVisibilityConverter}}"
+                >
+                    <CheckBox 
+                        Content="只显示 GameObject"
+                        IsChecked="{Binding GameObjectOnly}"
+                        Command="{Binding FilterDisplayEntries}"
+                        Padding="4,0"
+                        Margin="8,0"
+                        VerticalAlignment="Center"
+                    />
+                    <CheckBox 
+                        Content="搜索类型 ID"
+                        IsChecked="{Binding ConsiderTypeId}"
+                        Command="{Binding FilterDisplayEntries}"
+                        Padding="4,0"
+                        Margin="8,0"
+                        VerticalAlignment="Center"
+                    />
+                </StackPanel>
+                
                 <TextBlock
                     Text="{Binding DisplayEntries.Count, StringFormat=列表里显示了{0}个素材}"
                     Margin="5,0,0,0"
@@ -232,6 +241,18 @@
                 EnableRowVirtualization="True"
                 VirtualizingPanel.VirtualizationMode="Recycling"
             >
+                <DataGrid.Resources>
+                    <c:MultiValueAggregateConverter x:Key="IsXmlNotXsd">
+                        <c:MultiValueAggregateConverter.Converter>
+                            <c:MultiBooleanConjunctionConverter />
+                        </c:MultiValueAggregateConverter.Converter>
+                        <c:MultiValueAggregateConverter.PostProcess>
+                            <BooleanToVisibilityConverter />
+                        </c:MultiValueAggregateConverter.PostProcess>
+                        <x:Null />
+                        <c:BooleanInvertConverter />
+                    </c:MultiValueAggregateConverter>
+                </DataGrid.Resources>
                 <DataGrid.Columns>
                     <DataGridTextColumn 
                         Header="类型/素材名称"
@@ -241,14 +262,27 @@
                         Header="哈希(InstanceId)"
                         Binding="{Binding InstanceIdString}"
                         FontFamily="Courier New"
+                        Visibility="{Binding Source={x:Reference Name=ReferenceProvider},
+                                             Path=DataContext.IsXsd,
+                                             Converter={StaticResource FalseToVisibilityConverter}}"
                     />
                     <DataGridTextColumn 
                         Header="名称"
                         Binding="{Binding LocalizedNames}"
-                        Visibility="{Binding Source={x:Reference Name=ReferenceProvider},
-                                             Path=DataContext.IsXml,
-                                             Converter={StaticResource BooleanToVisibilityConverter}}"
-                    />
+                    >
+                        <DataGridTextColumn.Visibility>
+                            <MultiBinding Converter="{StaticResource IsXmlNotXsd}">
+                                <Binding
+                                    Source="{x:Reference Name=ReferenceProvider}"
+                                    Path="DataContext.IsXml"
+                                />
+                                <Binding
+                                    Source="{x:Reference Name=ReferenceProvider}"
+                                    Path="DataContext.IsXsd"
+                                />
+                            </MultiBinding>
+                        </DataGridTextColumn.Visibility>
+                    </DataGridTextColumn>
                     <DataGridTextColumn 
                         Header="类型 ID"
                         Binding="{Binding TypeIdString}"
diff --git a/ModXml.cs b/ModXml.cs
index 34b6447..c7518df 100644
--- a/ModXml.cs
+++ b/ModXml.cs
@@ -1,7 +1,8 @@
-using System;
-using System.Collections.Generic;
+using Microsoft.Win32;
+using Mvp.Xml.XInclude;
+using System;
 using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading;
@@ -9,12 +10,10 @@ using System.Threading.Tasks;
 using System.Threading.Tasks.Dataflow;
 using System.Xml;
 using System.Xml.Linq;
-using Microsoft.Win32;
-using Mvp.Xml.XInclude;
 
 namespace HashCalculator.GUI
 {
-    public class ModXml
+    public class ModXml : IModXml
     {
         protected enum DocumentOption
         {
diff --git a/ModXsd.cs b/ModXsd.cs
new file mode 100644
index 0000000..d247f5c
--- /dev/null
+++ b/ModXsd.cs
@@ -0,0 +1,164 @@
+using Mvp.Xml.XInclude;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using System.Xml.Linq;
+
+namespace HashCalculator.GUI
+{
+    public class ModXsd : IModXml
+    {
+        protected enum DocumentOption
+        {
+            Normal,
+            IsEntryPoint
+        }
+
+        public static readonly IEnumerable<string> NotSupportedAttributes = new[]
+        {
+            "fragid", "set-xml-id", "encoding", "accept", "accept-language"
+        };
+        public static XNamespace Schema = "http://www.w3.org/2001/XMLSchema";
+        public int TotalFilesProcessed => _processed.Count;
+        public int TotalAssets => _assets.Count;
+        private readonly ConcurrentDictionary<string, byte> _processed = new();
+        private readonly ConcurrentDictionary<AssetEntry, string> _assets = new();
+        private readonly BufferBlock<AssetEntry> _entries = new();
+        private readonly string _xmlFullPath;
+        private readonly CancellationToken _token;
+
+        public ModXsd(string xmlPath, CancellationToken token)
+        {
+            _xmlFullPath = Path.GetFullPath(xmlPath);
+            _token = token;
+        }
+
+        public async IAsyncEnumerable<AssetEntry> ProcessDocument()
+        {
+            if (_processed.Any() || _assets.Any())
+            {
+                throw new InvalidOperationException();
+            }
+
+            var task = Task.Run(async () =>
+            {
+                try
+                {
+                    await ProcessDocumentInternal(_xmlFullPath);
+                }
+                catch
+                {
+                    if (!_token.IsCancellationRequested)
+                    {
+                        throw;
+                    }
+                }
+                finally
+                {
+                    _entries.Complete();
+                }
+            });
+
+            while (await _entries.OutputAvailableAsync())
+            {
+                yield return _entries.Receive();
+            }
+
+            await task;
+        }
+
+        private async Task ProcessDocumentInternal(string fullPath)
+        {
+            _token.ThrowIfCancellationRequested();
+            if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
+            {
+                return;
+            }
+
+            var document = await GetFileAsync(fullPath);
+            var rootName = document.Root?.Name
+                ?? throw new InvalidDataException("Document doesn't have a root");
+            if (rootName != Schema + "schema")
+            {
+                throw new NotSupportedException();
+            }
+
+            var includes = from include in document.Root.Elements(Schema + "include")
+                           let includePath = GetIncludePath(include, fullPath)
+                           where includePath != null
+                           select Task.Run(() => ProcessDocumentInternal(includePath), _token);
+            var includesTasks = includes.ToArray();
+
+            var items = from element in document.Root.Elements()
+                        let name = element.Name
+                        where name == Schema + "simpleType" || name == Schema + "complexType"
+                        let entry = AssetEntry.TryParseXsd(element)
+                        where entry != null
+                        select entry;
+            foreach (var item in items)
+            {
+                if (_assets.TryAdd(item, fullPath))
+                {
+                    _entries.Post(item);
+                }
+                else
+                {
+                    var previousPath = _assets[item];
+                    TracerListener.WriteLine($"[ModXsd] `{fullPath}`: Attempted to add item `{item.Type}:{item.Name}` multiple times - already processed in `{previousPath}`");
+                }
+            }
+
+            await Task.WhenAll(includesTasks);
+        }
+
+        private string? GetIncludePath(XElement include, string includerPath)
+        {
+            if (Path.GetDirectoryName(includerPath) is not { } currentDirectory)
+            {
+                throw new InvalidOperationException();
+            }
+
+            if (include.Name != Schema + "include")
+            {
+                throw new InvalidDataException();
+            }
+
+            var source = include.Attribute("schemaLocation")?.Value;
+            if (source is null)
+            {
+                throw new InvalidDataException();
+            }
+
+            var includedSource = Path.GetFullPath(source, currentDirectory);
+            if (!File.Exists(includedSource))
+            {
+                TracerListener.WriteLine($"[ModXsd]: Warning, include path does not exist! It is: {includedSource}");
+                return null;
+            }
+
+            return includedSource;
+        }
+
+        private async Task<XDocument> GetFileAsync(string normalizedPath)
+        {
+            try
+            {
+                var xml = await File.ReadAllBytesAsync(normalizedPath, _token);
+                using var xmlStream = new MemoryStream(xml);
+                using var reader = new XIncludingReader(normalizedPath, xmlStream);
+
+                reader.MoveToContent();
+                return XDocument.Load(reader);
+            }
+            catch (Exception e)
+            {
+                throw new InvalidDataException($"Failed to process XML file: {normalizedPath} - {e.Message}", e);
+            }
+        }
+    }
+}
diff --git a/ViewModel.cs b/ViewModel.cs
index 9537a0c..49f38e4 100644
--- a/ViewModel.cs
+++ b/ViewModel.cs
@@ -37,6 +37,23 @@ namespace HashCalculator.GUI
 
             Notify(propertyName);
         }
+
+        protected void SetField<X>(ref X field, X value, Action onChange, [CallerMemberName] string? propertyName = null)
+        {
+            if (propertyName == null)
+            {
+                throw new InvalidOperationException();
+            }
+
+            if (EqualityComparer<X>.Default.Equals(field, value))
+            {
+                return;
+            }
+
+            field = value;
+            onChange();
+            Notify(propertyName);
+        }
         #endregion
     }
 
@@ -53,25 +70,21 @@ namespace HashCalculator.GUI
         public string? Filter
         {
             get => _filter;
-            set
-            {
-                SetField(ref _filter, value);
-                FilterDisplayEntries.Execute(null);
-            }
+            set => SetField(ref _filter, value, () => FilterDisplayEntries.Execute(null));
         }
 
         private bool _gameObjectOnly;
         public bool GameObjectOnly
         {
             get => _gameObjectOnly;
-            set => SetField(ref _gameObjectOnly, value);
+            set => SetField(ref _gameObjectOnly, value, () => ConsiderTypeId &= !value);
         }
 
         private bool _considerTypeId;
         public bool ConsiderTypeId
         {
             get => _considerTypeId;
-            set => SetField(ref _considerTypeId, value);
+            set => SetField(ref _considerTypeId, value, () => GameObjectOnly &= !value);
         }
 
         public Command FilterDisplayEntries { get; }
@@ -95,7 +108,7 @@ namespace HashCalculator.GUI
         public bool IsXml
         {
             get => _isXml;
-            set => SetField(ref _isXml, value);
+            set => SetField(ref _isXml, value, () => IsXsd &= value);
         }
         private bool _isLoadingXml;
         public bool IsLoadingXml
@@ -103,6 +116,16 @@ namespace HashCalculator.GUI
             get => _isLoadingXml;
             set => SetField(ref _isLoadingXml, value);
         }
+        private bool _isXsd;
+        public bool IsXsd
+        {
+            get => _isXsd;
+            set => SetField(ref _isXsd, value, () => 
+            { 
+                IsXml |= value;
+                ConsiderTypeId |= value;
+            });
+        }
         public Command CancelXml { get; }
         [SuppressMessage("Microsoft.Performance", "CA1822")]
         public string LoadCsfText => $"{(Translator.HasProvider ? "更换" : "加载")} CSF";