diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e6f3453
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,217 @@
+# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
+root = true
+
+# c# 文件
+[*.cs]
+
+#### Core EditorConfig 选项 ####
+
+# 缩进和间距
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# 新行首选项
+end_of_line = crlf
+insert_final_newline = false
+
+#### .NET 编码约定 ####
+
+# 组织 Using
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. 和 Me. 首选项
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# 语言关键字与 bcl 类型首选项
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# 括号首选项
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+
+# 修饰符首选项
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# 表达式级首选项
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# 字段首选项
+dotnet_style_readonly_field = true
+
+# 参数首选项
+dotnet_code_quality_unused_parameters = all
+
+# 禁止显示首选项
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# 新行首选项
+dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### c# 编码约定 ####
+
+# var 首选项
+csharp_style_var_elsewhere = true:suggestion
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = true:suggestion
+
+# Expression-bodied 成员
+csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
+csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
+csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
+csharp_style_expression_bodied_methods = when_on_single_line:suggestion
+csharp_style_expression_bodied_operators = when_on_single_line:suggestion
+csharp_style_expression_bodied_properties = when_on_single_line:suggestion
+
+# 模式匹配首选项
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true:suggestion
+csharp_style_prefer_switch_expression = true
+
+# Null 检查首选项
+csharp_style_conditional_delegate_call = true
+
+# 修饰符首选项
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
+
+# 代码块首选项
+csharp_prefer_braces = when_multiline:suggestion
+csharp_prefer_simple_using_statement = true
+
+# 表达式级首选项
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_pattern_local_over_anonymous_function = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_range_operator = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# "using" 指令首选项
+csharp_using_directive_placement = outside_namespace:suggestion
+
+# 新行首选项
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# 格式规则 ####
+
+# 新行首选项
+csharp_new_line_before_catch = false
+csharp_new_line_before_else = false
+csharp_new_line_before_finally = false
+csharp_new_line_before_members_in_anonymous_types = false
+csharp_new_line_before_members_in_object_initializers = false
+csharp_new_line_before_open_brace = none
+csharp_new_line_between_query_expression_clauses = true
+
+# 缩进首选项
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# 空格键首选项
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# 包装首选项
+csharp_preserve_single_line_blocks = false
+csharp_preserve_single_line_statements = false
+
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# 符号规范
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# 命名样式
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
diff --git a/L4d2AddonsMgr.sln b/L4d2AddonsMgr.sln
index 6075bda..58a68cf 100644
--- a/L4d2AddonsMgr.sln
+++ b/L4d2AddonsMgr.sln
@@ -9,6 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "L4d2AddonsMgrTest", "L4d2Ad
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{95174B3B-1FB5-4AE4-BFD6-A826054249B6}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
ReadMe.md = ReadMe.md
EndProjectSection
EndProject
diff --git a/L4d2AddonsMgr/AcfFile/AcfFile.cs b/L4d2AddonsMgr/AcfFile/AcfFile.cs
index e223b01..2d6fabd 100644
--- a/L4d2AddonsMgr/AcfFile/AcfFile.cs
+++ b/L4d2AddonsMgr/AcfFile/AcfFile.cs
@@ -50,7 +50,7 @@ public static Node GetNode(Node node, string key) {
public static Node GetNodeIgnoreCase(Node node, string key) {
if (!(node is CompoundNode cnode)) return null;
foreach (var child in cnode.Value) {
- if (key.ToLowerInvariant() == child.Key.ToLowerInvariant()) return child;
+ if (key.ToLowerInvariant() == child.Key?.ToLowerInvariant()) return child;
}
return null;
}
@@ -184,7 +184,7 @@ public Node GetNodeByPath(params string[] path) {
Node node = Root;
foreach (var seg in path) {
if (node == null) return null;
- node = GetNode(node, seg);
+ node = GetNodeIgnoreCase(node, seg);
}
return node;
}
diff --git a/L4d2AddonsMgr/AddonsCollection.cs b/L4d2AddonsMgr/AddonsCollection.cs
index c420a04..2499dbf 100644
--- a/L4d2AddonsMgr/AddonsCollection.cs
+++ b/L4d2AddonsMgr/AddonsCollection.cs
@@ -73,8 +73,12 @@ public AddonsCollection(AddonsLibrary addonsLibrary) {
public void ReloadFromLibrary() {
Application.Current.Dispatcher.Invoke(() => Clear());
- foreach (var item in Library)
- Application.Current.Dispatcher.Invoke(new Action(() => Add(item)));
+ try {
+ foreach (var item in Library)
+ Application.Current.Dispatcher.Invoke(new Action(() => Add(item)));
+ } catch (Exception) {
+ //
+ }
}
private void Add(VpkHolder vpkHolder) {
diff --git a/L4d2AddonsMgr/ExplorerInterop.cs b/L4d2AddonsMgr/ExplorerInterop.cs
new file mode 100644
index 0000000..e49d337
--- /dev/null
+++ b/L4d2AddonsMgr/ExplorerInterop.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace L4d2AddonsMgr {
+ class ExplorerInterop {
+ [DllImport("shell32.dll", SetLastError = true)]
+ public static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, uint dwFlags);
+
+ [DllImport("shell32.dll", SetLastError = true)]
+ public static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr bindingContext, [Out] out IntPtr pidl, uint sfgaoIn, [Out] out uint psfgaoOut);
+
+ public static void OpenFolderAndSelectItem(string folderPath, string file) {
+ IntPtr nativeFolder;
+ uint psfgaoOut;
+ SHParseDisplayName(folderPath, IntPtr.Zero, out nativeFolder, 0, out psfgaoOut);
+
+ if (nativeFolder == IntPtr.Zero) {
+ // Log error, can't find folder
+ return;
+ }
+
+ IntPtr nativeFile;
+ SHParseDisplayName(Path.Combine(folderPath, file), IntPtr.Zero, out nativeFile, 0, out psfgaoOut);
+
+ IntPtr[] fileArray;
+ if (nativeFile == IntPtr.Zero) {
+ // Open the folder without the file selected if we can't find the file
+ fileArray = new IntPtr[0];
+ } else {
+ fileArray = new IntPtr[] { nativeFile };
+ }
+
+ SHOpenFolderAndSelectItems(nativeFolder, (uint)fileArray.Length, fileArray, 0);
+
+ Marshal.FreeCoTaskMem(nativeFolder);
+ if (nativeFile != IntPtr.Zero) {
+ Marshal.FreeCoTaskMem(nativeFile);
+ }
+ }
+ }
+}
diff --git a/L4d2AddonsMgr/GameDirLocator.cs b/L4d2AddonsMgr/GameDirLocator.cs
index 3ee9a47..65fba6e 100644
--- a/L4d2AddonsMgr/GameDirLocator.cs
+++ b/L4d2AddonsMgr/GameDirLocator.cs
@@ -23,11 +23,18 @@ public static string FindGameInLibraries(string baseDir) {
var conf = AcfFile.ParseString(confTxt, true);
var infoNode = conf.GetNodeByPath(CommonConsts.SteamLibraryConfigVdfFileRootNode);
foreach (var node in (infoNode as AcfFile.CompoundNode).Value) {
- if (!(node is AcfFile.LeafNode)) continue;
+ //if (!(node is AcfFile.LeafNode)) continue;
try {
int.Parse(node.Key);
+ var libPath = (node as AcfFile.LeafNode)?.Value;
+ if (libPath == null) {
+ var comNode = node as AcfFile.CompoundNode;
+ if ((comNode.GetChild("apps") as AcfFile.CompoundNode)?.GetChild("550") == null)
+ continue;
+ libPath = (comNode.GetChild("path") as AcfFile.LeafNode).Value;
+ }
gamePath = FindGameInLibrary(
- Path.Combine((node as AcfFile.LeafNode).Value, CommonConsts.SteamAppsDirectoryName)
+ Path.Combine(libPath, CommonConsts.SteamAppsDirectoryName)
);
if (gamePath != null) {
break;
diff --git a/L4d2AddonsMgr/L4d2AddonsMgr.csproj b/L4d2AddonsMgr/L4d2AddonsMgr.csproj
index 91481ff..2ff3e57 100644
--- a/L4d2AddonsMgr/L4d2AddonsMgr.csproj
+++ b/L4d2AddonsMgr/L4d2AddonsMgr.csproj
@@ -116,6 +116,7 @@
+
CancelConfirmationDialog.xaml
diff --git a/L4d2AddonsMgr/MainWindow.xaml.cs b/L4d2AddonsMgr/MainWindow.xaml.cs
index 887d371..ee5f85a 100644
--- a/L4d2AddonsMgr/MainWindow.xaml.cs
+++ b/L4d2AddonsMgr/MainWindow.xaml.cs
@@ -83,7 +83,9 @@ public MainWindow(string libraryPath) {
public bool SupportsEnabledState => libraryPath == null;
- public AddonsCollection Addons { get; private set; }
+ public AddonsCollection Addons {
+ get; private set;
+ }
// Stay identical with Windows accent color.
// https://stackoverflow.com/questions/13660976/get-the-active-color-of-windows-8-automatic-color-theme
@@ -97,11 +99,11 @@ protected override void OnSourceInitialized(EventArgs e) {
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
switch (msg) {
- case WmDwmColorizationColorChanged:
- UpdateAccentColor();
- return IntPtr.Zero;
- default:
- return IntPtr.Zero;
+ case WmDwmColorizationColorChanged:
+ UpdateAccentColor();
+ return IntPtr.Zero;
+ default:
+ return IntPtr.Zero;
}
}
@@ -119,59 +121,60 @@ private async void DoReloadAddonsFromLibrary() {
}
private async void AddonsGrid_Loaded(object sender, RoutedEventArgs e) {
- await Task.Run(() => {
- string path;
- // 0: Find Steam install path from registry.
- try {
- path = GameDirLocator.LocateSteamDirFromRegistry();
- } catch (Exception ex) {
- // Just use "+"! It would ALWAYS be optimized away.
- // https://stackoverflow.com/questions/288794/does-c-sharp-optimize-the-concatenation-of-string-literals
-
- // LMAO Forget about answers by
- // https://stackoverflow.com/questions/1100260/multiline-string-literal-in-c-sharp
- // https://stackoverflow.com/questions/31764898/long-string-interpolation-lines-in-c6/31766560#31766560
- // Performance art they are doing.
- Debug.WriteLine(
- "ERROR: Failed locating installed Steam directory using registry.\n" +
- "Did anything restricted me from accesing the registry or what?\n" +
- "We were looking into 32-bit's view of the registry.\nDetails:");
- Debug.WriteLine(ex);
- ErrBox("尝试从您的计算机查找Steam安装信息时出错。");
- return;
- }
- if (path == null) {
- ErrBox("未能从您的计算机查找到Steam安装信息。");
- return;
- }
-
- // 1: Find game dir in all Steam libraries.
- try {
- path = GameDirLocator.FindGameInLibraries(path);
- if (path == null) {
- ErrBox("未能从您的Steam库中查找到左4死2。");
+ if (libraryPath == null)
+ await Task.Run(() => {
+ string path;
+ // 0: Find Steam install path from registry.
+ try {
+ path = GameDirLocator.LocateSteamDirFromRegistry();
+ } catch (Exception ex) {
+ // Just use "+"! It would ALWAYS be optimized away.
+ // https://stackoverflow.com/questions/288794/does-c-sharp-optimize-the-concatenation-of-string-literals
+
+ // LMAO Forget about answers by
+ // https://stackoverflow.com/questions/1100260/multiline-string-literal-in-c-sharp
+ // https://stackoverflow.com/questions/31764898/long-string-interpolation-lines-in-c6/31766560#31766560
+ // Performance art they are doing.
+ Debug.WriteLine(
+ "ERROR: Failed locating installed Steam directory using registry.\n" +
+ "Did anything restricted me from accesing the registry or what?\n" +
+ "We were looking into 32-bit's view of the registry.\nDetails:");
+ Debug.WriteLine(ex);
+ ErrBox("尝试从您的计算机查找Steam安装信息时出错。");
return;
}
- if (!GameDirLocator.ValidateGameDir(path)) {
- ErrBox("从您的Steam库中查找到的左4死2文件夹结构不完整。");
+ if (path == null) {
+ ErrBox("未能从您的计算机查找到Steam安装信息。");
return;
}
- } catch (Exception ex) {
- Debug.WriteLine(ex);
- ErrBox("尝试从您的Steam库中查找左4死2时出错。");
- return;
- }
- if (libraryPath == null) {
+
+ // 1: Find game dir in all Steam libraries.
try {
- addonsList = new AddonsListTxt(path);
+ path = GameDirLocator.FindGameInLibraries(path);
+ if (path == null) {
+ ErrBox("未能从您的Steam库中查找到左4死2。");
+ return;
+ }
+ if (!GameDirLocator.ValidateGameDir(path)) {
+ ErrBox("从您的Steam库中查找到的左4死2文件夹结构不完整。");
+ return;
+ }
} catch (Exception ex) {
Debug.WriteLine(ex);
- MsgBox("读取附加组件配置文件失败,为避免数据丢失,部分功能将不可用。" +
- "建议您检查并更正addonlist.txt中的格式错误,或在线寻求帮助。");
+ ErrBox("尝试从您的Steam库中查找左4死2时出错。");
+ return;
}
- }
- gameDir = path;
- });
+ if (libraryPath == null) {
+ try {
+ addonsList = new AddonsListTxt(path);
+ } catch (Exception ex) {
+ Debug.WriteLine(ex);
+ MsgBox("读取附加组件配置文件失败,为避免数据丢失,部分功能将不可用。" +
+ "建议您检查并更正addonlist.txt中的格式错误,或在线寻求帮助。");
+ }
+ }
+ gameDir = path;
+ });
var lib = libraryPath == null ? new GameDirAddonsLibrary(gameDir, addonsList) : (AddonsLibrary)new ExternalDirectoryAddonsLibrary(libraryPath);
Addons = new AddonsCollection(lib);
Addons.PropertyChanged +=
@@ -225,7 +228,7 @@ private void ShowItemInExplorerCommand_Invoke(object sender, ExecutedRoutedEvent
// Show in explorer:
// https://stackoverflow.com/questions/334630/opening-a-folder-in-explorer-and-selecting-a-file
if (e.Parameter is FileInfo fileInfo)
- Process.Start("explorer.exe", "/select, \"" + fileInfo.FullName + "\"");
+ ExplorerInterop.OpenFolderAndSelectItem(fileInfo.DirectoryName, fileInfo.Name);
}
private async void RefreshListCommand_Invoke(object sender, ExecutedRoutedEventArgs e) {
@@ -259,20 +262,20 @@ private void OpAutoRenameCommand_Invoke(object sender, ExecutedRoutedEventArgs e
if (res ?? false) {
List list;
switch (dialog.MyAppliedScope.MyScope) {
- case AppliedScope.Scope.All:
- list = new List(Addons.Files);
- break;
- case AppliedScope.Scope.SelectedItems:
- // synchronized and lock:
- // https://stackoverflow.com/questions/541194/c-sharp-version-of-javas-synchronized-keyword
- lock (AddonsListBox.SelectedItems) {
- list = new List(AddonsListBox.SelectedItems.Count);
- foreach (object o in AddonsListBox.SelectedItems)
- list.Add((VpkHolder)o);
- }
- break;
- default:
- return;
+ case AppliedScope.Scope.All:
+ list = new List(Addons.Files);
+ break;
+ case AppliedScope.Scope.SelectedItems:
+ // synchronized and lock:
+ // https://stackoverflow.com/questions/541194/c-sharp-version-of-javas-synchronized-keyword
+ lock (AddonsListBox.SelectedItems) {
+ list = new List(AddonsListBox.SelectedItems.Count);
+ foreach (object o in AddonsListBox.SelectedItems)
+ list.Add((VpkHolder)o);
+ }
+ break;
+ default:
+ return;
}
Addons.IsLoading = true;
var renameTask = new AutoRenameTask(list, dialog.Model, gameDir, addonsList);