├── .editorconfig
├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug-report.yaml
│ └── config.yml
├── .gitignore
├── .gitmodules
├── BG3ModManager.sln
├── BuildRelease.py
├── LICENSE
├── README.md
├── Screenshots
└── PreferencesWindow_GameDataPath.png
├── Update.xml
└── src
├── Core
├── AppServices
│ ├── FileWatcherService.cs
│ ├── ModRegistryService.cs
│ └── ScreenReaderService.cs
├── Attributes
│ ├── MenuSettingsAttribute.cs
│ ├── ScreenReaderAttribute.cs
│ └── SettingsEntryAttribute.cs
├── Directory.build.props
├── DivinityApp.cs
├── DivinityInteractions.cs
├── DivinityModManagerCore.csproj
├── Enums
│ ├── AlertType.cs
│ ├── DivinityExtenderModStatus.cs
│ ├── DivinityGameLaunchWindowAction.cs
│ ├── DivinityOsirisModStatus.cs
│ ├── Extender
│ │ └── ExtenderUpdateChannel.cs
│ ├── LaunchGameType.cs
│ ├── ModSourceType.cs
│ ├── ScriptExtenderIconType.cs
│ └── Steam
│ │ └── EPublishedFileQueryType.cs
├── Extensions
│ ├── DictionaryExtensions.cs
│ ├── EnumExtensions.cs
│ ├── KeyExtensions.cs
│ ├── ModelExtensions.cs
│ ├── ReactiveExtensions.cs
│ ├── ResourceExtensions.cs
│ └── StringExtensions.cs
├── FodyWeavers.xml
├── FodyWeavers.xsd
├── ModUpdater
│ ├── Cache
│ │ ├── GithubModsCacheHandler.cs
│ │ ├── IExternalModCacheHandler.cs
│ │ ├── NexusModsCacheHandler.cs
│ │ └── SteamWorkshopCacheHandler.cs
│ └── ModUpdateHandler.cs
├── Models
│ ├── App
│ │ ├── AppSettings.cs
│ │ ├── ConfirmationSettings.cs
│ │ ├── DefaultPathwayData.cs
│ │ ├── Hotkey.cs
│ │ ├── IgnoredModsData.cs
│ │ ├── ImportOperationResults.cs
│ │ ├── MissingModResults.cs
│ │ ├── ModLoadingResults.cs
│ │ ├── ModSettingsParseResults.cs
│ │ └── MultiReactiveCommandHotkey.cs
│ ├── BaseHistoryObject.cs
│ ├── Cache
│ │ ├── BaseModCacheData.cs
│ │ ├── GithubModsCachedData.cs
│ │ ├── NexusModsCachedData.cs
│ │ └── SteamWorkshopCachedData.cs
│ ├── Conflicts
│ │ ├── DivinityConflictEntryData.cs
│ │ └── DivinityConflictGroup.cs
│ ├── DivinityBaseModData.cs
│ ├── DivinityGameMasterCampaign.cs
│ ├── DivinityLoadOrder.cs
│ ├── DivinityMissingModData.cs
│ ├── DivinityModData.cs
│ ├── DivinityModFilterData.cs
│ ├── DivinityModManagerSettings.cs
│ ├── DivinityModScriptExtenderConfig.cs
│ ├── DivinityModVersion.cs
│ ├── DivinityModVersion2.cs
│ ├── DivinityModWorkshopCachedData.cs
│ ├── DivinityModWorkshopData.cs
│ ├── DivinityPathwayData.cs
│ ├── DivinityProfileActiveModData.cs
│ ├── DivinityProfileData.cs
│ ├── DivinitySerializedModData.cs
│ ├── Extender
│ │ ├── ScriptExtenderSettings.cs
│ │ ├── ScriptExtenderUpdateConfig.cs
│ │ └── ScriptExtenderUpdateData.cs
│ ├── Github
│ │ └── GithubModData.cs
│ ├── ISelectable.cs
│ ├── ModFileDeletionData.cs
│ ├── ModuleShortDesc.cs
│ ├── NexusMods
│ │ ├── NexusModFileVersionData.cs
│ │ ├── NexusModsModData.cs
│ │ └── NexusModsModDownloadLink.cs
│ ├── Steam
│ │ ├── IWorkshopPublishFileDetails.cs
│ │ ├── PublishedFileDetails.cs
│ │ └── QueryFilesResponseData.cs
│ ├── Updates
│ │ ├── DivinityModUpdateData.cs
│ │ └── UpdateResult.cs
│ ├── View
│ │ └── EnumEntry.cs
│ └── WindowSettings.cs
├── Services.cs
├── Usings.cs
├── Util
│ ├── DateUtils.cs
│ ├── DivinityFileUtils.cs
│ ├── DivinityGlobalCommands.cs
│ ├── DivinityGlobalEvents.cs
│ ├── DivinityJsonUtils.cs
│ ├── DivinityModDataLoader.cs
│ ├── DivinityModSorter.cs
│ ├── DivinityRegistryHelper.cs
│ ├── DivinitySaveTools.cs
│ ├── DivinityStreamUtils.cs
│ ├── GithubHelper.cs
│ ├── JunctionPoint.cs
│ ├── NativeLibraryHelper.cs
│ ├── NexusModsDataLoader.cs
│ ├── ProcessHelper.cs
│ ├── PropertyConverters.cs
│ ├── RecycleBinHelper.cs
│ ├── TempFile.cs
│ └── WebHelper.cs
└── ViewModels
│ ├── BaseHistoryViewModel.cs
│ ├── BaseViewModel.cs
│ └── IDivinityAppViewModel.cs
├── GUI
├── App.xaml
├── App.xaml.cs
├── AppCommands.cs
├── BG3ModManager.ico
├── Controls
│ ├── AlertBar.xaml
│ ├── AlertBar.xaml.cs
│ ├── AutoGrayableImage.cs
│ ├── AutoGrid.cs
│ ├── AutomationTooltip.cs
│ ├── Behavior
│ │ ├── GridViewAutosizeColumnsBehavior.cs
│ │ ├── ScreenReaderHelperBehavior.cs
│ │ ├── TextBlockSettingsEntryAttributeBehavior.cs
│ │ └── ToolTipHelperBehavior.cs
│ ├── BusyIndicator.xaml
│ ├── BusyIndicator.xaml.cs
│ ├── CircleDecorator.cs
│ ├── Extensions
│ │ ├── DependencyExtensions.cs
│ │ ├── EnumExtension.cs
│ │ └── HyperlinkExtensions.cs
│ ├── HotkeyEditorControl.xaml
│ ├── HotkeyEditorControl.xaml.cs
│ ├── HyperlinkText.xaml
│ ├── HyperlinkText.xaml.cs
│ ├── Markdown.cs
│ ├── ModEntryGrid.cs
│ ├── ModListView.cs
│ ├── SelectableTextBlock.cs
│ ├── TemplateSelectors
│ │ └── TagTemplateSelector.cs
│ └── UnfocusableTextBox.cs
├── Converters
│ ├── BoolToVisibilityConverter.cs
│ ├── EnumToStringConverter.cs
│ ├── IntToVisibilityConverter.cs
│ ├── ModExistsConverter.cs
│ ├── ModIsActiveConverter.cs
│ ├── ModToDisplayNameConverter.cs
│ ├── StringNotEmptyToVisibilityConverter.cs
│ ├── StringToLinearBrushConverter.cs
│ ├── StringToSolidBrushConverter.cs
│ ├── StringToUriConverter.cs
│ └── UriToBitmapImageConverter.cs
├── Directory.build.props
├── Extensions
│ └── DependencyObjectExtensions.cs
├── FodyWeavers.xml
├── FodyWeavers.xsd
├── GUI.csproj
├── Program.cs
├── Resources
│ ├── AppFeatures.json
│ ├── BG3MMSplashScreen.png
│ ├── DefaultPathways.json
│ ├── Icons
│ │ ├── AddItem_16x.png
│ │ ├── AlertBar_Close.png
│ │ ├── AlertBar_Close_Hover.png
│ │ ├── AlertBar_Danger_16x.png
│ │ ├── AlertBar_Information_16x.png
│ │ ├── AlertBar_Success_16x.png
│ │ ├── AlertBar_Warning_16x.png
│ │ ├── BG3_64x.png
│ │ ├── BrowserLink_16x.png
│ │ ├── Builder_16x.png
│ │ ├── CopyToClipboard_16x.png
│ │ ├── DefaultIcon_16x.png
│ │ ├── DeleteAzureResource_16x.png
│ │ ├── DivinityEngine2_64x.png
│ │ ├── DivinityEngineGlasses_64x.png
│ │ ├── ExclamationPoint_16x.png
│ │ ├── ExpandChevronDown_16x.png
│ │ ├── ExpandChevronDown_lightGray_16x.png
│ │ ├── ExpandChevronUp_16x.png
│ │ ├── ExpandChevronUp_lightGrey_16x.png
│ │ ├── ExportData_16x.png
│ │ ├── ExportScript_16x.png
│ │ ├── FileMissing_16x.png
│ │ ├── Folder_16x.png
│ │ ├── Github_16x.png
│ │ ├── HighPriority_16x.png
│ │ ├── Kofi_16x.png
│ │ ├── LoadCampaignOrder_16x.png
│ │ ├── Log_Normal_32x.png
│ │ ├── OpenFile_16x.png
│ │ ├── OpenFolder_16x.png
│ │ ├── Osiris_16x.png
│ │ ├── Osiris_ModFixer_16x.png
│ │ ├── Refresh_16x.png
│ │ ├── Rename_16x.png
│ │ ├── SaveAs_16x.png
│ │ ├── SaveGrey_16x.png
│ │ ├── Save_16x.png
│ │ ├── StatusCriticalError_16x.png
│ │ ├── StatusWarning_16x.png
│ │ ├── Steam_16x.png
│ │ ├── XMLSchemaError_16x.png
│ │ ├── ZipFileAs_16x.png
│ │ └── ZipFile_16x.png
│ └── IgnoredMods.json
├── Themes
│ ├── Dark.xaml
│ ├── DefaultStyles
│ │ ├── ComboBox.xaml
│ │ └── MarkdownStyle.xaml
│ ├── DivinityColors.cs
│ ├── Light.xaml
│ ├── MainResourceDictionary.xaml
│ └── ResourceAliasHelper.cs
├── Usings.cs
├── Util
│ ├── AutomationTooltipPeer.cs
│ ├── BindingHelper.cs
│ ├── CustomPropertyResolver.cs
│ ├── ElementHelper.cs
│ ├── FocusHelper.cs
│ ├── LogTraceListener.cs
│ ├── RxExceptionHandler.cs
│ └── ScreenReader
│ │ ├── AlertBarAutomationPeer.cs
│ │ ├── CachedAutomationPeer.cs
│ │ ├── MainWindowAutomationPeer.cs
│ │ ├── ModEntryGridAutomationPeer.cs
│ │ └── ModListViewAutomationPeer.cs
├── ViewModels
│ ├── AppKeys.cs
│ ├── AppUpdateWindowViewModel.cs
│ ├── BaseProgressViewModel.cs
│ ├── DeleteFilesViewData.cs
│ ├── ExportOrderToArchiveViewModel.cs
│ ├── MainWindowExceptionHandler.cs
│ ├── MainWindowViewModel.cs
│ ├── ModListDragHandler.cs
│ ├── ModListDropHandler.cs
│ ├── ModUpdatesViewData.cs
│ └── SettingsWindowViewModel.cs
├── Views
│ ├── AboutWindow.xaml
│ ├── AboutWindow.xaml.cs
│ ├── AppUpdateWindow.xaml
│ ├── AppUpdateWindow.xaml.cs
│ ├── DeleteFilesConfirmationView.xaml
│ ├── DeleteFilesConfirmationView.xaml.cs
│ ├── ExportOrderToArchiveView.xaml
│ ├── ExportOrderToArchiveView.xaml.cs
│ ├── HelpWindow.xaml
│ ├── HelpWindow.xaml.cs
│ ├── HideWindowBase.cs
│ ├── HorizontalModLayout.xaml
│ ├── HorizontalModLayout.xaml.cs
│ ├── LeaderLibSettingsWindow.xaml
│ ├── LeaderLibSettingsWindow.xaml.cs
│ ├── MainViewControl.xaml
│ ├── MainViewControl.xaml.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── ModUpdatesLayout.xaml
│ ├── ModUpdatesLayout.xaml.cs
│ ├── SettingsWindow.xaml
│ ├── SettingsWindow.xaml.cs
│ ├── VersionGeneratorWindow.xaml
│ ├── VersionGeneratorWindow.xaml.cs
│ ├── VerticalModLayout.xaml
│ └── VerticalModLayout.xaml.cs
└── app.manifest
└── Toolbox
├── Args
├── MainArgs.cs
└── ScriptExtenderUpdaterArgs.cs
├── Directory.build.props
├── NativeMethods.cs
├── Program.cs
├── ScriptExtender
└── Updater.cs
└── Toolbox.csproj
/.gitattributes:
--------------------------------------------------------------------------------
1 | .gitignore merge=ours
2 | AppFeatures.json merge=ours
3 | AssemblyInfo.cs merge=ours
4 | BuildRelease.py merge=ours
5 | DefaultPathways.json merge=ours
6 | DivinityApp.cs merge=ours
7 | DivinityBaseModData.cs merge=ours
8 | DivinityModDataLoader.cs merge=ours
9 | DivinityModManagerSettings.cs merge=ours
10 | DivinityModOsiExtenderConfig.cs merge=ours
11 | GUI.csproj merge=ours
12 | IgnoredMods.json merge=ours
13 | MainWindow.xaml merge=ours
14 | MainWindow.xaml.cs merge=ours
15 | MainWindowViewModel.cs merge=ours
16 | ScriptExtenderSettings.cs merge=ours
17 | SettingsWindow.xaml merge=ours
18 | SettingsWindow.xaml.cs merge=ours
19 | Update.xml merge=ours
20 | VersionGeneratorWindow.xaml merge=ours
21 | packages.config merge=ours
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 | - type: input
11 | id: os
12 | attributes:
13 | label: Operating System
14 | description: The operating system you're using (Windows 10 / 11, Linux with Wine, Mac etc). Note that only Windows is officially supported.
15 | placeholder: Windows 10
16 | validations:
17 | required: true
18 | - type: input
19 | id: version
20 | attributes:
21 | label: BG3 Mod Manager Version
22 | description: The version of the mod manager when you encountered this issue (visible on the window title bar).
23 | placeholder: 1.0.9.10
24 | validations:
25 | required: true
26 | - type: input
27 | id: game-version
28 | attributes:
29 | label: BG3 Game Version
30 | description: The version of Baldur's Gate 3 when you encountered this issue.
31 | placeholder: Full Release Patch 3 Hotfix 2
32 | validations:
33 | required: true
34 | - type: textarea
35 | id: what-happened
36 | attributes:
37 | label: Bug Summary
38 | description: Describe what the bug is, and how to reproduce it.
39 | placeholder: Describe the bug here.
40 | value:
41 | validations:
42 | required: true
43 | - type: textarea
44 | id: links
45 | attributes:
46 | label: Links
47 | description: Links to any screenshots, pastes for files etc that are too big to be attached.
48 | placeholder: Debug log https://gist.github.com/MyUserName/SomeLink
49 | value:
50 | validations:
51 | required: false
52 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Ask a Question
4 | url: https://github.com/LaughingLeader/BG3ModManager/discussions/new?category=q-a
5 | about: Ask a question in the Discussions tab.
6 | - name: Request a Feature
7 | url: https://github.com/LaughingLeader/BG3ModManager/discussions/new?category=feature-requests
8 | about: Request a new feature in the Discussions tab.
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "External/CrossSpeak"]
2 | path = External/CrossSpeak
3 | url = https://github.com/LaughingLeader/CrossSpeak
4 | [submodule "External/lslib"]
5 | path = External/lslib
6 | url = https://github.com/LaughingLeader/lslib
7 |
--------------------------------------------------------------------------------
/BuildRelease.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from pathlib import Path
4 | import shutil
5 | import zipfile
6 | from zipfile import ZipFile
7 |
8 | import contextlib
9 |
10 | def get_arg(arg, fallback):
11 | if len(sys.argv) > arg:
12 | val = sys.argv[arg]
13 | if val != None:
14 | return val
15 | return fallback
16 |
17 | script_dir = Path(os.path.dirname(os.path.abspath(__file__)))
18 | os.chdir(script_dir)
19 |
20 | version = get_arg(1, None)
21 |
22 | file_name = f"BG3ModManager_v{version}.zip"
23 | export_file = "BG3ModManager_Latest.zip"
24 | print(f"Writing release zip:{file_name}")
25 |
26 | def zipdir(src, zip_name):
27 | ziph = zipfile.ZipFile(zip_name, 'w')
28 | for root, dirs, files in os.walk(src):
29 | for file in files:
30 | ziph.write(os.path.join(root, file), arcname=os.path.join(root.replace(src, ""), file), compress_type=zipfile.ZIP_DEFLATED)
31 | ziph.close()
32 |
33 | def SilentRemove(f):
34 | try:
35 | if Path(f).is_dir():
36 | shutil.rmtree(f, ignore_errors=True)
37 | else:
38 | os.remove(f)
39 | print(f"Removed {f}")
40 | except Exception:
41 | pass
42 |
43 | def SilentCopyAndRemove(source, dest):
44 | with contextlib.suppress(FileNotFoundError, PermissionError):
45 | shutil.copy(source, dest)
46 | if Path(source).is_dir():
47 | shutil.rmtree(source, ignore_errors=True)
48 | else:
49 | os.remove(source)
50 | print(f"Removed {source}")
51 |
52 | import time
53 | time.sleep(3)
54 |
55 | SilentRemove("bin/Publish/Data")
56 | SilentRemove("bin/Publish/Orders")
57 | SilentRemove("bin/Publish/_Logs")
58 | SilentRemove(file_name)
59 | zipdir("bin/Publish", file_name)
60 | shutil.copy(file_name, export_file)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 LaughingLeader
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Screenshots/PreferencesWindow_GameDataPath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LaughingLeader/BG3ModManager/4b2fa9c04e98784a2e0ef73589f5efb3e75b69d5/Screenshots/PreferencesWindow_GameDataPath.png
--------------------------------------------------------------------------------
/Update.xml:
--------------------------------------------------------------------------------
1 |
2 | -
3 | 1.0.12.9
4 | https://github.com/LaughingLeader/BG3ModManager/releases/latest/download/BG3ModManager_Latest.zip
5 | https://raw.githubusercontent.com/wiki/LaughingLeader/BG3ModManager/Changelog.md
6 | false
7 |
--------------------------------------------------------------------------------
/src/Core/AppServices/ModRegistryService.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Models;
2 |
3 | using DynamicData;
4 |
5 | namespace DivinityModManager
6 | {
7 | public interface IModRegistryService
8 | {
9 | bool TryGetDisplayName(string uuid, out string name);
10 | bool ModExists(string uuid);
11 | bool ModIsActive(string uuid);
12 | }
13 | }
14 |
15 | namespace DivinityModManager.AppServices
16 | {
17 | public class ModRegistryService : IModRegistryService
18 | {
19 | private readonly SourceCache _mods;
20 |
21 | public bool TryGetDisplayName(string uuid, out string name)
22 | {
23 | name = "";
24 | var mod = _mods.Lookup(uuid);
25 | if (mod.HasValue)
26 | {
27 | name = mod.Value.DisplayName;
28 | return true;
29 | }
30 | return false;
31 | }
32 |
33 | public bool ModExists(string uuid)
34 | {
35 | var mod = _mods.Lookup(uuid);
36 | if (mod.HasValue)
37 | {
38 | return true;
39 | }
40 | return false;
41 | }
42 |
43 | public bool ModIsActive(string uuid)
44 | {
45 | var mod = _mods.Lookup(uuid);
46 | if (mod.HasValue && mod.Value.IsActive)
47 | {
48 | return true;
49 | }
50 | return false;
51 | }
52 |
53 | public ModRegistryService(SourceCache mods)
54 | {
55 | _mods = mods;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Core/AppServices/ScreenReaderService.cs:
--------------------------------------------------------------------------------
1 | using CrossSpeak;
2 |
3 | using DivinityModManager.Util;
4 |
5 | namespace DivinityModManager
6 | {
7 | public interface IScreenReaderService
8 | {
9 | bool IsScreenReaderActive();
10 | void Output(string text, bool interrupt = false);
11 | void Speak(string text, bool interrupt = false);
12 | void Close();
13 | void Silence();
14 | }
15 | }
16 |
17 | namespace DivinityModManager.AppServices
18 | {
19 | public class ScreenReaderService : IScreenReaderService
20 | {
21 | private static readonly string[] _dlls = ["nvdaControllerClient64.dll", "SAAPI64.dll", "Tolk.dll"];
22 | private static bool _loadedDlls = false;
23 |
24 | public bool IsScreenReaderActive()
25 | {
26 | if (EnsureInit(false))
27 | {
28 | return !String.IsNullOrWhiteSpace(CrossSpeakManager.Instance.DetectScreenReader());
29 | }
30 | return false;
31 | }
32 |
33 | public void Close()
34 | {
35 | if (CrossSpeakManager.Instance.IsLoaded())
36 | {
37 | CrossSpeakManager.Instance.Close();
38 | }
39 | }
40 |
41 | public void Silence()
42 | {
43 | if (CrossSpeakManager.Instance.IsLoaded())
44 | {
45 | CrossSpeakManager.Instance.Silence();
46 | }
47 | }
48 |
49 | private bool EnsureInit(bool trySAPI = false)
50 | {
51 | if (!_loadedDlls)
52 | {
53 | var libPath = Path.Combine(DivinityApp.GetAppDirectory(), "_Lib");
54 | foreach (var dll in _dlls)
55 | {
56 | var filePath = Path.Combine(libPath, dll);
57 | try
58 | {
59 | if (File.Exists(filePath))
60 | {
61 | NativeLibraryHelper.LoadLibrary(filePath);
62 | }
63 | }
64 | catch (Exception ex)
65 | {
66 | DivinityApp.Log($"Error loading '{dll}':\n{ex}");
67 | }
68 | }
69 | _loadedDlls = true;
70 | }
71 | if (!CrossSpeakManager.Instance.IsLoaded())
72 | {
73 | CrossSpeakManager.Instance.Initialize();
74 | if (trySAPI && !CrossSpeakManager.Instance.HasSpeech())
75 | {
76 | CrossSpeakManager.Instance.TrySAPI(true);
77 | }
78 | }
79 | return CrossSpeakManager.Instance.IsLoaded();
80 | }
81 |
82 | public void Output(string text, bool interrupt = true)
83 | {
84 | if (EnsureInit(true))
85 | {
86 | CrossSpeakManager.Instance.Output(text, interrupt);
87 | }
88 | }
89 |
90 | public void Speak(string text, bool interrupt = true)
91 | {
92 | if (EnsureInit(true))
93 | {
94 | CrossSpeakManager.Instance.Output(text, interrupt);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Core/Attributes/MenuSettingsAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | public class MenuSettingsAttribute : Attribute
4 | {
5 | public string DisplayName { get; set; }
6 | public string Parent { get; set; }
7 | public bool AddSeparator { get; set; }
8 | public string Tooltip { get; set; }
9 | public string Style { get; set; }
10 | public MenuSettingsAttribute(string parent = "", string displayName = "", bool addSeparatorAfter = false, string tooltip = "")
11 | {
12 | DisplayName = displayName;
13 | Parent = parent;
14 | AddSeparator = addSeparatorAfter;
15 | Tooltip = tooltip;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Core/Attributes/ScreenReaderAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | public class ScreenReaderHelperAttribute : Attribute
4 | {
5 | public string Name { get; set; }
6 | public string HelpText { get; set; }
7 |
8 | public ScreenReaderHelperAttribute(string name = "", string helpText = "")
9 | {
10 | Name = name;
11 | HelpText = HelpText;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Core/Attributes/SettingsEntryAttribute.cs:
--------------------------------------------------------------------------------
1 | using DynamicData.Binding;
2 |
3 | using System.Reflection;
4 |
5 | namespace DivinityModManager;
6 |
7 | public class SettingsEntryAttribute : Attribute
8 | {
9 | public string DisplayName { get; set; }
10 | public string Tooltip { get; set; }
11 | public bool IsDebug { get; set; }
12 | public bool HideFromUI { get; set; }
13 | public SettingsEntryAttribute(string displayName = "", string tooltip = "", bool isDebug = false)
14 | {
15 | DisplayName = displayName;
16 | Tooltip = tooltip;
17 | IsDebug = isDebug;
18 | }
19 | }
20 |
21 | public static class SettingsEntryAttributeExtensions
22 | {
23 | public static List GetSettingsAttributes(this ReactiveObject model)
24 | {
25 | var props = model.GetType().GetProperties()
26 | .Select(x => SettingsAttributeProperty.FromProperty(x))
27 | .Where(x => x.Attribute != null).ToList();
28 | return props;
29 | }
30 |
31 | public static IObservable WhenAnySettingsChange(this ReactiveObject model)
32 | {
33 | var props = model.GetType().GetProperties()
34 | .Select(x => SettingsAttributeProperty.FromProperty(x))
35 | .Where(x => x.Attribute != null).Select(x => x.Property.Name).ToArray();
36 | return model.WhenAnyPropertyChanged(props);
37 | }
38 | }
39 |
40 | public struct SettingsAttributeProperty
41 | {
42 | public PropertyInfo Property { get; set; }
43 | public SettingsEntryAttribute Attribute { get; set; }
44 |
45 | public static SettingsAttributeProperty FromProperty(PropertyInfo property)
46 | {
47 | return new SettingsAttributeProperty
48 | {
49 | Property = property,
50 | Attribute = property.GetCustomAttribute()
51 | };
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Core/Directory.build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(SolutionDir)obj\Core\
4 |
5 |
--------------------------------------------------------------------------------
/src/Core/DivinityInteractions.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | public struct DeleteFilesViewConfirmationData
4 | {
5 | public int Total;
6 | public bool PermanentlyDelete;
7 | public CancellationToken Token;
8 | }
9 |
10 | public static class DivinityInteractions
11 | {
12 | public static readonly Interaction ConfirmModDeletion = new();
13 | }
14 |
--------------------------------------------------------------------------------
/src/Core/DivinityModManagerCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-windows
4 | true
5 | true
6 | DivinityModManager
7 | x64
8 | x64
9 | Debug;Release;Publish;PublishTest
10 | false
11 | en-US
12 | none
13 | True
14 |
15 |
16 | full
17 | False
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/Core/Enums/AlertType.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | public enum AlertType
4 | {
5 | Info,
6 | Success,
7 | Warning,
8 | Danger
9 | }
10 |
--------------------------------------------------------------------------------
/src/Core/Enums/DivinityExtenderModStatus.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | [Flags]
4 | public enum DivinityExtenderModStatus
5 | {
6 | None,
7 | Supports,
8 | Fulfilled,
9 | DisabledFromConfig,
10 | MissingRequiredVersion,
11 | MissingAppData,
12 | MissingUpdater,
13 | }
14 |
--------------------------------------------------------------------------------
/src/Core/Enums/DivinityGameLaunchWindowAction.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Converters;
2 |
3 | using System.ComponentModel;
4 |
5 | namespace DivinityModManager;
6 |
7 | [JsonConverter(typeof(StringEnumConverter))]
8 | public enum DivinityGameLaunchWindowAction
9 | {
10 | [Description("None")]
11 | None,
12 | [Description("Minimize")]
13 | Minimize,
14 | [Description("Close")]
15 | Close
16 | }
17 |
--------------------------------------------------------------------------------
/src/Core/Enums/DivinityOsirisModStatus.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | public enum DivinityOsirisModStatus
4 | {
5 | NONE,
6 | SCRIPTS,
7 | MODFIXER
8 | }
9 |
--------------------------------------------------------------------------------
/src/Core/Enums/Extender/ExtenderUpdateChannel.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Converters;
2 |
3 | using System.ComponentModel;
4 |
5 | namespace DivinityModManager.Enums.Extender;
6 |
7 | [JsonConverter(typeof(StringEnumConverter))]
8 | public enum ExtenderUpdateChannel
9 | {
10 | [Description("Release")]
11 | Release,
12 | [Description("Devel")]
13 | Devel,
14 | [Description("Nightly")]
15 | Nightly
16 | }
17 |
--------------------------------------------------------------------------------
/src/Core/Enums/LaunchGameType.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace DivinityModManager;
4 | public enum LaunchGameType
5 | {
6 | [Display(Name = "Default (Exe)", Description = "Open the game exe directly,\nand create a steam_appid.txt in the bin folder if it doesn't exist,\nallowing you to bypassing the launcher")]
7 | Exe,
8 | [Display(Name = "Steam", Description = "Open the game by running the Steam launch protocol ('steam://run/1086940')")]
9 | Steam,
10 | [Display(Name = "Custom", Description = "Open the game by opening a different file or protocol (ex. a batch file or protocol handler like playnite://playnite/start/id)")]
11 | Custom
12 | }
--------------------------------------------------------------------------------
/src/Core/Enums/ModSourceType.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace DivinityModManager;
4 |
5 | public enum ModSourceType
6 | {
7 | [Description("None")]
8 | NONE,
9 | [Description("Steam Workshop")]
10 | STEAM,
11 | [Description("Nexus Mods")]
12 | NEXUSMODS,
13 | [Description("Github")]
14 | GITHUB
15 | }
16 |
--------------------------------------------------------------------------------
/src/Core/Enums/ScriptExtenderIconType.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 | public enum ScriptExtenderIconType
3 | {
4 | None,
5 | Missing,
6 | Warning,
7 | FulfilledSupports,
8 | FulfilledRequired
9 | }
10 |
--------------------------------------------------------------------------------
/src/Core/Enums/Steam/EPublishedFileQueryType.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Enums.Steam;
2 |
3 | public enum EPublishedFileQueryType
4 | {
5 | RankedByVote = 0,
6 | RankedByPublicationDate = 1,
7 | AcceptedForGameRankedByAcceptanceDate = 2,
8 | RankedByTrend = 3,
9 | FavoritedByFriendsRankedByPublicationDate = 4,
10 | CreatedByFriendsRankedByPublicationDate = 5,
11 | RankedByNumTimesReported = 6,
12 | CreatedByFollowedUsersRankedByPublicationDate = 7,
13 | NotYetRated = 8,
14 | RankedByTotalUniqueSubscriptions = 9,
15 | RankedByTotalVotesAsc = 10,
16 | RankedByVotesUp = 11,
17 | RankedByTextSearch = 12,
18 | RankedByPlaytimeTrend = 13,
19 | RankedByTotalPlaytime = 14,
20 | RankedByAveragePlaytimeTrend = 15,
21 | RankedByLifetimeAveragePlaytime = 16,
22 | RankedByPlaytimeSessionsTrend = 17,
23 | RankedByLifetimePlaytimeSessions = 18,
24 | RankedByInappropriateContentRating = 19,
25 | }
--------------------------------------------------------------------------------
/src/Core/Extensions/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Extensions;
2 |
3 | public static class DictionaryExtensions
4 | {
5 | public static object FindKeyValue(this Dictionary dict, string key, StringComparison stringComparison = StringComparison.OrdinalIgnoreCase)
6 | {
7 | foreach (KeyValuePair kvp in dict)
8 | {
9 | if (kvp.Key.Equals(key, stringComparison))
10 | {
11 | return kvp.Value;
12 | }
13 | else if (kvp.Value.GetType() == typeof(Dictionary))
14 | {
15 | var subDict = (Dictionary)kvp.Value;
16 | var val = subDict.FindKeyValue(key, stringComparison);
17 | if (val != null) return val;
18 | }
19 | }
20 | return null;
21 | }
22 |
23 | private static object FindKeyValue_Recursive(object obj, string key, StringComparison stringComparison = StringComparison.OrdinalIgnoreCase)
24 | {
25 | if (obj.GetType() == typeof(Dictionary))
26 | {
27 | var subDict = (Dictionary)obj;
28 | var val = subDict.FindKeyValue(key, stringComparison);
29 | if (val != null) return val;
30 | }
31 | else if (obj is IList list)
32 | {
33 | foreach (var childobj in list)
34 | {
35 | var val = FindKeyValue_Recursive(childobj, key, stringComparison);
36 | if (val != null) return val;
37 | }
38 | }
39 | else if (obj is IEnumerable enumerable)
40 | {
41 | foreach (var childobj in enumerable)
42 | {
43 | var val = FindKeyValue_Recursive(childobj, key, stringComparison);
44 | if (val != null) return val;
45 | }
46 | }
47 | return null;
48 | }
49 |
50 | public static bool TryFindKeyValue(this Dictionary dict, string key, out object valObj, StringComparison stringComparison = StringComparison.OrdinalIgnoreCase)
51 | {
52 | foreach (KeyValuePair kvp in dict)
53 | {
54 | if (kvp.Key.Equals(key, stringComparison))
55 | {
56 | valObj = kvp.Value;
57 | return true;
58 | }
59 | else
60 | {
61 | var val = FindKeyValue_Recursive(kvp.Value, key, stringComparison);
62 | if (val != null)
63 | {
64 | valObj = val;
65 | return true;
66 | }
67 | }
68 | }
69 | valObj = null;
70 | return false;
71 | }
72 |
73 | ///
74 | /// ToDictionary that allows duplicate key entries.
75 | /// Source: https://stackoverflow.com/a/22508992/2290477
76 | ///
77 | ///
78 | ///
79 | ///
80 | ///
81 | ///
82 | ///
83 | ///
84 | ///
85 | public static Dictionary SafeToDictionary(
86 | this IEnumerable source,
87 | Func keySelector,
88 | Func elementSelector,
89 | IEqualityComparer comparer = null)
90 | {
91 | var dictionary = new Dictionary(comparer);
92 |
93 | if (source == null)
94 | {
95 | return dictionary;
96 | }
97 |
98 | foreach (TSource element in source)
99 | {
100 | dictionary[keySelector(element)] = elementSelector(element);
101 | }
102 |
103 | return dictionary;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Core/Extensions/EnumExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.Reflection;
4 |
5 | namespace DivinityModManager;
6 |
7 | public static class EnumExtensions
8 | {
9 | ///
10 | /// Get an enum's Description attribute value.
11 | ///
12 | public static string GetDescription(this Enum enumValue)
13 | {
14 | var member = enumValue.GetType().GetMember(enumValue.ToString()).FirstOrDefault();
15 | if (member != null)
16 | {
17 | var descriptionAttribute = member.GetCustomAttribute(false);
18 |
19 | if (descriptionAttribute == null)
20 | {
21 | var displayAttribute = member.GetCustomAttribute(false);
22 | if (displayAttribute != null)
23 | {
24 | return displayAttribute.Name;
25 | }
26 | }
27 | else
28 | {
29 | return descriptionAttribute.Description;
30 | }
31 | return enumValue.ToString();
32 | }
33 | return "";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Core/Extensions/KeyExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Input;
2 |
3 | namespace DivinityModManager;
4 |
5 | public static class KeyExtensions
6 | {
7 | private static readonly Dictionary KeyToName = new()
8 | {
9 | {Key.Add, "+"},
10 | {Key.D0, "0"},
11 | {Key.D1, "1"},
12 | {Key.D2, "2"},
13 | {Key.D3, "3"},
14 | {Key.D4, "4"},
15 | {Key.D5, "5"},
16 | {Key.D6, "6"},
17 | {Key.D7, "7"},
18 | {Key.D8, "8"},
19 | {Key.D9, "9"},
20 | {Key.Decimal, "."},
21 | {Key.Divide, " / "},
22 | {Key.Multiply, "*"},
23 | {Key.Oem1, ";"},
24 | {Key.Oem5, "\\"},
25 | {Key.Oem6, "]"},
26 | {Key.Oem7, "'"},
27 | {Key.OemBackslash, "\\"},
28 | {Key.OemComma, ","},
29 | {Key.OemMinus, "-"},
30 | {Key.OemOpenBrackets, "["},
31 | {Key.OemPeriod, "."},
32 | {Key.OemPlus, "="},
33 | {Key.OemQuestion, "/"},
34 | {Key.OemTilde, "`"},
35 | {Key.Subtract, "-"}
36 | };
37 |
38 | public static string GetKeyName(this Key key)
39 | {
40 | if (KeyToName.TryGetValue(key, out string name))
41 | {
42 | return name;
43 | }
44 | return key.ToString();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Core/Extensions/ModelExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace DivinityModManager.Extensions;
4 |
5 | public static class ModelExtensions
6 | {
7 | public static void SetToDefault(this ReactiveObject model)
8 | {
9 | /*PropertyInfo[] props = model.GetType().GetProperties();
10 | foreach (PropertyInfo prop in props)
11 | {
12 | var d = prop.GetCustomAttribute();
13 | if (d != null && prop.GetValue(model) != d.Value)
14 | {
15 | prop.SetValue(model, d.Value);
16 | }
17 | }*/
18 | var props = TypeDescriptor.GetProperties(model.GetType());
19 | foreach (PropertyDescriptor pr in props)
20 | {
21 | if (pr.CanResetValue(model))
22 | {
23 | pr.ResetValue(model);
24 | }
25 | }
26 | }
27 |
28 | public static void SetFrom(this T target, T from) where T : ReactiveObject
29 | {
30 | var props = TypeDescriptor.GetProperties(target.GetType());
31 | foreach (PropertyDescriptor pr in props)
32 | {
33 | var value = pr.GetValue(from);
34 | if (value != null)
35 | {
36 | pr.SetValue(target, value);
37 | target.RaisePropertyChanged(pr.Name);
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Core/Extensions/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Extensions;
2 |
3 | public static class StringExtensions
4 | {
5 | public static bool IsExistingDirectory(this string path)
6 | {
7 | return !String.IsNullOrWhiteSpace(path) && Directory.Exists(path);
8 | }
9 |
10 | public static bool IsExistingFile(this string path)
11 | {
12 | return !String.IsNullOrWhiteSpace(path) && File.Exists(path);
13 | }
14 |
15 | /*
16 | * MaybeAddReplacement("%LOCALAPPDATA%", Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
17 | MaybeAddReplacement("%APPDATA%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
18 | MaybeAddReplacement("%USERPROFILE%", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
19 | * */
20 |
21 | private static Dictionary _specialPaths = new Dictionary()
22 | {
23 | { "%LOCALAPPDATA%", Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) },
24 | { "%APPDATA%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) },
25 | { "%USERPROFILE%", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) },
26 | };
27 |
28 | public static string ReplaceSpecialPaths(this string path)
29 | {
30 | if (string.IsNullOrEmpty(path)) return path;
31 |
32 | foreach (var kvp in _specialPaths)
33 | {
34 | if(!string.IsNullOrEmpty(kvp.Value))
35 | {
36 | path = path.Replace(kvp.Value, kvp.Key);
37 | }
38 | }
39 | return path;
40 | }
41 |
42 | ///
43 | /// Checks File.Exists after expanding environment variables.
44 | ///
45 | public static bool FileExists(this string path)
46 | {
47 | if (string.IsNullOrEmpty(path)) return false;
48 |
49 | return File.Exists(Environment.ExpandEnvironmentVariables(path));
50 | }
51 |
52 | ///
53 | /// Checks Directory.Exists after expanding environment variables.
54 | ///
55 | public static bool DirectoryExists(this string path)
56 | {
57 | if (string.IsNullOrEmpty(path)) return false;
58 |
59 | return Directory.Exists(Environment.ExpandEnvironmentVariables(path));
60 | }
61 |
62 | ///
63 | /// Expands environment variables and makes the path relative to the app directory if not rooted.
64 | ///
65 | public static string ToRealPath(this string path)
66 | {
67 | if (string.IsNullOrEmpty(path)) return path;
68 |
69 | var finalPath = Environment.ExpandEnvironmentVariables(path);
70 | if(!Path.IsPathRooted(finalPath))
71 | {
72 | finalPath = DivinityApp.GetAppDirectory(finalPath);
73 | }
74 | return finalPath;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Core/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/Core/FodyWeavers.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
12 |
13 |
14 |
15 |
16 | A comma-separated list of error codes that can be safely ignored in assembly verification.
17 |
18 |
19 |
20 |
21 | 'false' to turn off automatic generation of the XML Schema file.
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Core/ModUpdater/Cache/GithubModsCacheHandler.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Models;
2 | using DivinityModManager.Models.Cache;
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace DivinityModManager.ModUpdater.Cache;
7 |
8 | public class GithubModsCacheHandler : IExternalModCacheHandler
9 | {
10 | public ModSourceType SourceType => ModSourceType.GITHUB;
11 | public string FileName => "githubdata.json";
12 |
13 | //Format Github data so people can more easily edit/add mods manually.
14 | public JsonSerializerSettings SerializerSettings => new()
15 | {
16 | NullValueHandling = NullValueHandling.Ignore,
17 | Formatting = Formatting.Indented,
18 | };
19 |
20 | public bool IsEnabled { get; set; }
21 | public GithubModsCachedData CacheData { get; set; }
22 |
23 | public GithubModsCacheHandler() : base()
24 | {
25 | CacheData = new GithubModsCachedData();
26 | }
27 |
28 | public async Task Update(IEnumerable mods, CancellationToken cts)
29 | {
30 | return false;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Core/ModUpdater/Cache/IExternalModCacheHandler.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | using DivinityModManager.Models;
4 | using DivinityModManager.Models.Cache;
5 | using DivinityModManager.Util;
6 |
7 | using Newtonsoft.Json;
8 |
9 | using System.Text;
10 |
11 | namespace DivinityModManager.ModUpdater.Cache;
12 |
13 | public interface IExternalModCacheHandler where T : IModCacheData
14 | {
15 | ModSourceType SourceType { get; }
16 | string FileName { get; }
17 | JsonSerializerSettings SerializerSettings { get; }
18 |
19 | bool IsEnabled { get; set; }
20 | T CacheData { get; set; }
21 | Task Update(IEnumerable mods, CancellationToken cts);
22 | }
23 |
24 | public static class IExternalModCacheDataExtensions
25 | {
26 | public static async Task LoadCacheAsync(this IExternalModCacheHandler cacheData, string currentAppVersion, CancellationToken cts) where T : IModCacheData
27 | {
28 | var filePath = DivinityApp.GetAppDirectory("Data", cacheData.FileName);
29 |
30 | if (File.Exists(filePath))
31 | {
32 | var cachedData = await DivinityJsonUtils.DeserializeFromPathAsync(filePath, cts);
33 | if (cachedData != null)
34 | {
35 | if (string.IsNullOrEmpty(cachedData.LastVersion) || cachedData.LastVersion != currentAppVersion)
36 | {
37 | cachedData.LastUpdated = -1;
38 | }
39 | cachedData.CacheUpdated = true;
40 | return cachedData;
41 | }
42 | }
43 | return default;
44 | }
45 |
46 | public static async Task SaveCacheAsync(this IExternalModCacheHandler handler, bool updateLastTimestamp, string currentAppVersion, CancellationToken cts) where T : IModCacheData
47 | {
48 | try
49 | {
50 | var parentDir = DivinityApp.GetAppDirectory("Data");
51 | var filePath = Path.Combine(parentDir, handler.FileName);
52 | if (!Directory.Exists(parentDir)) Directory.CreateDirectory(parentDir);
53 |
54 | if (updateLastTimestamp)
55 | {
56 | handler.CacheData.LastUpdated = DateTimeOffset.Now.ToUnixTimeSeconds();
57 | }
58 | handler.CacheData.LastVersion = currentAppVersion;
59 |
60 | string contents = JsonConvert.SerializeObject(handler.CacheData, handler.SerializerSettings);
61 |
62 | var buffer = Encoding.UTF8.GetBytes(contents);
63 | using var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create,
64 | System.IO.FileAccess.Write, System.IO.FileShare.None, buffer.Length, true);
65 | await fs.WriteAsync(buffer, 0, buffer.Length, cts);
66 |
67 | return true;
68 | }
69 | catch (Exception ex)
70 | {
71 | DivinityApp.Log($"Error saving cache:\n{ex}");
72 | }
73 | return false;
74 | }
75 |
76 | public static bool DeleteCache(this IExternalModCacheHandler handler, bool permanent = false) where T : IModCacheData
77 | {
78 | try
79 | {
80 | var parentDir = DivinityApp.GetAppDirectory("Data");
81 | var filePath = Path.Combine(parentDir, handler.FileName);
82 | if (File.Exists(filePath))
83 | {
84 | RecycleBinHelper.DeleteFile(filePath, false, permanent);
85 | return true;
86 | }
87 | }
88 | catch (Exception ex)
89 | {
90 | DivinityApp.Log($"Error saving cache:\n{ex}");
91 | }
92 | return false;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Core/ModUpdater/Cache/NexusModsCacheHandler.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Models;
2 | using DivinityModManager.Models.Cache;
3 | using DivinityModManager.Util;
4 |
5 | using Newtonsoft.Json;
6 |
7 | namespace DivinityModManager.ModUpdater.Cache;
8 |
9 | public class NexusModsCacheHandler : IExternalModCacheHandler
10 | {
11 | public ModSourceType SourceType => ModSourceType.NEXUSMODS;
12 | public string FileName => "nexusmodsdata.json";
13 | public JsonSerializerSettings SerializerSettings => ModUpdateHandler.DefaultSerializerSettings;
14 | public bool IsEnabled { get; set; } = false;
15 | public NexusModsCachedData CacheData { get; set; }
16 |
17 | public string APIKey { get; set; }
18 | public string AppName { get; set; }
19 | public string AppVersion { get; set; }
20 |
21 | public NexusModsCacheHandler() : base()
22 | {
23 | CacheData = new NexusModsCachedData();
24 | }
25 |
26 | public async Task Update(IEnumerable mods, CancellationToken cts)
27 | {
28 | if (!NexusModsDataLoader.IsInitialized && !string.IsNullOrEmpty(APIKey))
29 | {
30 | NexusModsDataLoader.Init(APIKey, AppName, AppVersion);
31 | }
32 |
33 | if (NexusModsDataLoader.CanFetchData)
34 | {
35 | var result = await NexusModsDataLoader.LoadAllModsDataAsync(mods, cts);
36 |
37 | if (result.Success)
38 | {
39 | DivinityApp.Log($"Fetched NexusMods mod info for {result.UpdatedMods.Count} mod(s).");
40 |
41 | foreach (var mod in mods.Where(x => x.NexusModsData.ModId >= DivinityApp.NEXUSMODS_MOD_ID_START).Select(x => x.NexusModsData))
42 | {
43 | CacheData.Mods[mod.UUID] = mod;
44 | }
45 |
46 | return true;
47 | }
48 | else
49 | {
50 | DivinityApp.Log($"Failed to update NexusMods mod info:\n{result.FailureMessage}");
51 | }
52 | }
53 | else
54 | {
55 | DivinityApp.Log("NexusModsAPIKey not set, or daily/hourly limit reached. Skipping.");
56 | }
57 | return false;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Core/ModUpdater/Cache/SteamWorkshopCacheHandler.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Models;
2 | using DivinityModManager.Models.Cache;
3 | using DivinityModManager.Util;
4 |
5 | using Newtonsoft.Json;
6 |
7 | namespace DivinityModManager.ModUpdater.Cache;
8 |
9 | public class SteamWorkshopCacheHandler : IExternalModCacheHandler
10 | {
11 | public ModSourceType SourceType => ModSourceType.STEAM;
12 | public string FileName => "workshopdata.json";
13 | public JsonSerializerSettings SerializerSettings => ModUpdateHandler.DefaultSerializerSettings;
14 | public SteamWorkshopCachedData CacheData { get; set; }
15 | public bool IsEnabled { get; set; } = false;
16 |
17 | public string SteamAppID { get; set; }
18 |
19 | public SteamWorkshopCacheHandler() : base()
20 | {
21 | CacheData = new SteamWorkshopCachedData();
22 | }
23 |
24 | public async Task Update(IEnumerable mods, CancellationToken cts)
25 | {
26 | return false;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Core/Models/App/AppSettings.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.App;
2 |
3 | public class AppSettings
4 | {
5 | public DefaultPathwayData DefaultPathways { get; set; } = new DefaultPathwayData();
6 |
7 | public Dictionary Features { get; set; } = new Dictionary();
8 |
9 | public bool FeatureEnabled(string id)
10 | {
11 | if (Features.TryGetValue(id.ToLower(), out bool v))
12 | {
13 | return v == true;
14 | }
15 | return false;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Core/Models/App/ConfirmationSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.Serialization;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace DivinityModManager.Models.App;
9 |
10 | [DataContract]
11 | public class ConfirmationSettings : ReactiveObject
12 | {
13 | [Reactive, DataMember] public bool DisableAdminModeWarning { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Core/Models/App/DefaultPathwayData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.App;
2 |
3 | public class AppPathwayData
4 | {
5 | public string Registry_32 { get; set; }
6 | public string Registry_64 { get; set; }
7 | public string AppID { get; set; }
8 | public string RootFolderName { get; set; }
9 | public string ExePath { get; set; }
10 | }
11 | public class DefaultPathwayData
12 | {
13 | public AppPathwayData Steam { get; set; } = new AppPathwayData();
14 | public AppPathwayData GOG { get; set; } = new AppPathwayData();
15 |
16 | public string DocumentsGameFolder { get; set; }
17 | public string GameDataFolder { get; set; }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Core/Models/App/IgnoredModsData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models;
2 |
3 | public class IgnoredModsData
4 | {
5 | public List IgnoreDependencies { get; set; } = new List();
6 | public List> Mods { get; set; } = new List>();
7 | public List IgnoreBuiltinPath { get; set; } = new List();
8 | }
9 |
--------------------------------------------------------------------------------
/src/Core/Models/App/ImportOperationResults.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.App;
2 |
3 | public struct ImportOperationError
4 | {
5 | public Exception Exception { get; set; }
6 | public string File { get; set; }
7 | }
8 |
9 | public class ImportOperationResults
10 | {
11 | public bool Success => Mods.Count >= TotalPaks;
12 | public int TotalFiles { get; set; }
13 | public int TotalPaks { get; set; }
14 | public List Mods { get; set; } = new List();
15 | public List Orders { get; set; } = new List();
16 | public List Errors { get; set; } = new List();
17 |
18 | public void AddError(string path, Exception ex) => Errors.Add(new ImportOperationError { Exception = ex, File = path });
19 | }
20 |
--------------------------------------------------------------------------------
/src/Core/Models/App/ModLoadingResults.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.App;
2 |
3 | public class ModLoadingResults
4 | {
5 | public string DirectoryPath { get; set; }
6 | public List Mods { get; set; }
7 | public List Duplicates { get; set; }
8 |
9 | public ModLoadingResults()
10 | {
11 | Mods = new List();
12 | Duplicates = new List();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Core/Models/App/ModSettingsParseResults.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.App;
2 |
3 | public class ModSettingsParseResults
4 | {
5 | public List ActiveMods { get; set; }
6 |
7 | public int CountActive(bool includeIgnoredMods = false)
8 | {
9 | var i = 0;
10 | foreach (var mod in ActiveMods)
11 | {
12 | if (includeIgnoredMods || !DivinityApp.IgnoredMods.Lookup( mod.UUID).HasValue)
13 | {
14 | i++;
15 | }
16 | }
17 | return i;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Core/Models/App/MultiReactiveCommandHotkey.cs:
--------------------------------------------------------------------------------
1 | using DynamicData;
2 | using DynamicData.Binding;
3 |
4 | using System.Text;
5 | using System.Windows.Input;
6 |
7 | namespace DivinityModManager.Models.App;
8 |
9 | public class MultiReactiveCommandHotkey : ReactiveObject, IHotkey
10 | {
11 | [Reactive] public string DisplayName { get; set; }
12 | [Reactive] public Key Key { get; set; }
13 |
14 | [Reactive] public ModifierKeys Modifiers { get; set; }
15 |
16 | [Reactive] public ICommand Command { get; set; }
17 |
18 | [Reactive] public bool Enabled { get; set; }
19 |
20 | [Reactive] public IObservable CanExecute { get; private set; }
21 |
22 | private ObservableCollection commands = new();
23 |
24 | public ObservableCollection Commands
25 | {
26 | get => commands;
27 | }
28 |
29 | public void Add(RxCommandUnit command)
30 | {
31 | Commands.Add(command);
32 | }
33 |
34 | public void Invoke()
35 | {
36 | foreach (var cmd in Commands)
37 | {
38 | Observable.Start(() => { }).InvokeCommand(cmd);
39 | }
40 | }
41 |
42 | private void Init(Key key, ModifierKeys modifiers)
43 | {
44 | Key = key;
45 | Modifiers = modifiers;
46 |
47 | var canExecuteInitial = this.WhenAnyValue(x => x.Enabled, (b) => b == true);
48 | var anyCommandsCanExecute = Commands.ToObservableChangeSet().AutoRefreshOnObservable(c => c.CanExecute).ToCollection().Select(x => x.Any());
49 | CanExecute = Observable.Merge(new IObservable[2] { canExecuteInitial, anyCommandsCanExecute });
50 | Command = ReactiveCommand.Create(Invoke, CanExecute);
51 | }
52 |
53 |
54 | public MultiReactiveCommandHotkey(Key key)
55 | {
56 | Init(key, ModifierKeys.None);
57 | }
58 |
59 | public MultiReactiveCommandHotkey(Key key, ModifierKeys modifiers)
60 | {
61 | Init(key, modifiers);
62 | }
63 |
64 | public override string ToString()
65 | {
66 | var str = new StringBuilder();
67 |
68 | if (Modifiers.HasFlag(ModifierKeys.Control))
69 | str.Append("Ctrl + ");
70 | if (Modifiers.HasFlag(ModifierKeys.Shift))
71 | str.Append("Shift + ");
72 | if (Modifiers.HasFlag(ModifierKeys.Alt))
73 | str.Append("Alt + ");
74 | if (Modifiers.HasFlag(ModifierKeys.Windows))
75 | str.Append("Win + ");
76 |
77 | str.Append(Key);
78 |
79 | return str.ToString();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Core/Models/BaseHistoryObject.cs:
--------------------------------------------------------------------------------
1 | using ReactiveHistory;
2 |
3 | using System.Reflection;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace DivinityModManager.Models;
7 |
8 | public class BaseHistoryObject : ReactiveObject
9 | {
10 | public IHistory History { get; set; }
11 |
12 | public virtual void Snapshot(Action undo, Action redo)
13 | {
14 | History.Snapshot(undo, redo);
15 | }
16 |
17 | public bool UpdateWithHistory(ref T field, T value, [CallerMemberName] string propertyName = null)
18 | {
19 | if (!Equals(field, value))
20 | {
21 | if (History != null)
22 | {
23 | var undoValue = field;
24 | var redoValue = value;
25 |
26 | History.Snapshot(() =>
27 | {
28 | this.SetProperty(this, propertyName, undoValue);
29 | }, () =>
30 | {
31 | this.SetProperty(this, propertyName, redoValue);
32 | });
33 | }
34 |
35 | this.RaiseAndSetIfChanged(ref field, value, propertyName);
36 | return true;
37 | }
38 | return false;
39 | }
40 |
41 | public bool UpdateWithHistory(ref T field, T value, Action undo, Action redo, [CallerMemberName] string propertyName = null)
42 | {
43 | if (!Equals(field, value))
44 | {
45 | if (History != null)
46 | {
47 | History.Snapshot(undo, redo);
48 | }
49 |
50 | this.RaiseAndSetIfChanged(ref field, value, propertyName);
51 | return true;
52 | }
53 | return false;
54 | }
55 |
56 | private bool SetProperty(object targetObject, string propertyName, T value)
57 | {
58 | var prop = this.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);
59 | if (prop != null && prop.CanWrite)
60 | {
61 | prop.SetValue(this, value);
62 | return true;
63 | }
64 | return false;
65 | }
66 |
67 | private bool SetField(string fieldName, T value, string propertyName = null)
68 | {
69 | var field = this.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
70 | if (field != null)
71 | {
72 | field.SetValue(this, value);
73 | return true;
74 | }
75 | return false;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Core/Models/Cache/BaseModCacheData.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace DivinityModManager.Models.Cache;
4 |
5 | public interface IModCacheData
6 | {
7 | long LastUpdated { get; set; }
8 | string LastVersion { get; set; }
9 | bool CacheUpdated { get; set; }
10 | }
11 |
12 | [DataContract]
13 | public class BaseModCacheData : IModCacheData
14 | {
15 | [DataMember] public long LastUpdated { get; set; }
16 | [DataMember] public string LastVersion { get; set; }
17 | [DataMember] public Dictionary Mods { get; set; } = new Dictionary();
18 |
19 | public bool CacheUpdated { get; set; }
20 |
21 | public BaseModCacheData()
22 | {
23 | LastUpdated = -1;
24 | LastVersion = "";
25 |
26 | Mods = new Dictionary();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Core/Models/Cache/GithubModsCachedData.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Models.Github;
2 |
3 | namespace DivinityModManager.Models.Cache;
4 |
5 | public class GithubModsCachedData : BaseModCacheData
6 | {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/Core/Models/Cache/NexusModsCachedData.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Models.NexusMods;
2 |
3 | namespace DivinityModManager.Models.Cache;
4 |
5 | public class NexusModsCachedData : BaseModCacheData
6 | {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/Core/Models/Conflicts/DivinityConflictEntryData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.Conflicts;
2 |
3 | public class DivinityConflictEntryData : ReactiveObject
4 | {
5 | private string target;
6 |
7 | public string Target
8 | {
9 | get => target;
10 | set { this.RaiseAndSetIfChanged(ref target, value); }
11 | }
12 |
13 | private string name;
14 |
15 | public string Name
16 | {
17 | get => name;
18 | set { this.RaiseAndSetIfChanged(ref name, value); }
19 | }
20 |
21 | public List ConflictModDataList { get; set; } = new List();
22 | }
23 |
24 | public class DivinityConflictModData : ReactiveObject
25 | {
26 | private readonly DivinityModData modData;
27 | public DivinityModData Mod => modData;
28 |
29 | public string Value { get; set; }
30 |
31 | public DivinityConflictModData(DivinityModData mod, string val = "")
32 | {
33 | modData = mod;
34 | Value = val;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Core/Models/Conflicts/DivinityConflictGroup.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.Conflicts;
2 |
3 | public class DivinityConflictGroup : ReactiveObject
4 | {
5 | private string header;
6 |
7 | public string Header
8 | {
9 | get => header;
10 | set { this.RaiseAndSetIfChanged(ref header, value); }
11 | }
12 |
13 | private int totalConflicts = 0;
14 |
15 | public int TotalConflicts
16 | {
17 | get => totalConflicts;
18 | set { this.RaiseAndSetIfChanged(ref totalConflicts, value); }
19 | }
20 |
21 | public List Conflicts { get; set; } = new List();
22 |
23 | private int selectedConflictIndex = 0;
24 |
25 | public int SelectedConflictIndex
26 | {
27 | get => selectedConflictIndex;
28 | set { this.RaiseAndSetIfChanged(ref selectedConflictIndex, value); }
29 | }
30 |
31 | public void OnActivated(CompositeDisposable disposables)
32 | {
33 | this.WhenAnyValue(x => x.Conflicts.Count).Subscribe(c => TotalConflicts = c).DisposeWith(disposables);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityGameMasterCampaign.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | using DivinityModManager.Extensions;
4 |
5 | using LSLib.LS;
6 |
7 | namespace DivinityModManager.Models;
8 |
9 | [ScreenReaderHelper(Name = "DisplayName", HelpText = "HelpText")]
10 | public class DivinityGameMasterCampaign : DivinityBaseModData
11 | {
12 | public Resource MetaResource { get; set; }
13 |
14 | public List Dependencies = new();
15 |
16 | public bool Export(IEnumerable order)
17 | {
18 | try
19 | {
20 | var conversionParams = ResourceConversionParameters.FromGameVersion(DivinityApp.GAME);
21 | if (File.Exists(FilePath) && new FileInfo(FilePath).Length > 0)
22 | {
23 | var backupName = Path.Combine(Path.GetDirectoryName(FilePath), FileName + ".backup");
24 | File.Copy(FilePath, backupName, true);
25 | }
26 |
27 | if (MetaResource.TryFindNode("Dependencies", out var dependenciesNode))
28 | {
29 | if (dependenciesNode.Children.TryGetValue("ModuleShortDesc", out var nodeList))
30 | {
31 | nodeList.Clear();
32 | foreach (var m in order)
33 | {
34 | var attributes = new Dictionary()
35 | {
36 | { "UUID", new NodeAttribute(AttributeType.FixedString) {Value = m.UUID}},
37 | { "Name", new NodeAttribute(AttributeType.LSString) {Value = m.Name}},
38 | { "Version", new NodeAttribute(AttributeType.Int) {Value = m.Version.VersionInt}},
39 | { "MD5", new NodeAttribute(AttributeType.LSString) {Value = m.MD5}},
40 | { "Folder", new NodeAttribute(AttributeType.LSString) {Value = m.Folder}},
41 | };
42 | var modNode = new Node()
43 | {
44 | Name = "ModuleShortDesc",
45 | Parent = dependenciesNode,
46 | Attributes = attributes,
47 | Children = new Dictionary>()
48 | };
49 | dependenciesNode.AppendChild(modNode);
50 | //nodeList.Add(modNode);
51 | }
52 | }
53 | }
54 | ResourceUtils.SaveResource(MetaResource, FilePath, LSLib.LS.Enums.ResourceFormat.LSF, conversionParams);
55 | if (File.Exists(FilePath))
56 | {
57 | File.SetLastWriteTime(FilePath, DateTime.Now);
58 | File.SetLastAccessTime(FilePath, DateTime.Now);
59 | LastModified = DateTime.Now;
60 | DivinityApp.Log($"Wrote GM campaign metadata to {FilePath}");
61 | }
62 | return true;
63 | }
64 | catch (Exception ex)
65 | {
66 | DivinityApp.Log($"Error saving GM Campaign meta.lsf:\n{ex}");
67 | }
68 | return false;
69 | }
70 |
71 | public DivinityGameMasterCampaign() : base()
72 | {
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityMissingModData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models;
2 |
3 | public class DivinityMissingModData
4 | {
5 | public string Name { get; set; }
6 | public int Index { get; set; }
7 | public string UUID { get; set; }
8 | public string Author { get; set; }
9 | public bool IsDependency { get; set; }
10 | public List RequiredBy { get; } = [];
11 |
12 | public override string ToString()
13 | {
14 | List text = [];
15 | if (Index > 0)
16 | {
17 | text.Add($"{Index}. ");
18 | }
19 | if(!string.IsNullOrWhiteSpace(Name))
20 | {
21 | text.Add(Name);
22 | }
23 | else
24 | {
25 | text.Add(UUID);
26 | }
27 | if (!string.IsNullOrEmpty(Author))
28 | {
29 | text.Add(" by " + Author);
30 | }
31 | if (RequiredBy.Count > 0)
32 | {
33 | text.Add(", Required By " + string.Join(';', RequiredBy.Order().Distinct()));
34 | }
35 | return string.Join("", text);
36 | }
37 |
38 | public static DivinityMissingModData FromData(DivinityModData modData, bool isDependency = true, string[] requiredBy = null)
39 | {
40 | var data = new DivinityMissingModData
41 | {
42 | Name = modData.Name,
43 | UUID = modData.UUID,
44 | Author = modData.Author,
45 | Index = modData.Index,
46 | IsDependency = isDependency
47 | };
48 | if (requiredBy != null)
49 | {
50 | data.RequiredBy.AddRange(requiredBy);
51 | }
52 | return data;
53 | }
54 |
55 | public static DivinityMissingModData FromData(DivinityLoadOrderEntry modData, int index, bool isDependency = true, string[] requiredBy = null)
56 | {
57 | var data = new DivinityMissingModData
58 | {
59 | Name = modData.Name,
60 | UUID = modData.UUID,
61 | Index = index,
62 | IsDependency = isDependency
63 | };
64 | if (requiredBy != null)
65 | {
66 | data.RequiredBy.AddRange(requiredBy);
67 | }
68 | return data;
69 | }
70 |
71 | public static DivinityMissingModData FromData(ModuleShortDesc modData, int index, bool isDependency = true, string[] requiredBy = null)
72 | {
73 | var data = new DivinityMissingModData
74 | {
75 | Name = modData.Name,
76 | UUID = modData.UUID,
77 | Index = index,
78 | IsDependency = isDependency
79 | };
80 | if (requiredBy != null)
81 | {
82 | data.RequiredBy.AddRange(requiredBy);
83 | }
84 | return data;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityModFilterData.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | public struct DivinityModFilterData
6 | {
7 | public string FilterProperty { get; set; }
8 | public string FilterValue { get; set; }
9 |
10 | private static readonly char[] separators = new char[1] { ' ' };
11 |
12 | public bool ValueContains(string val, bool separateWhitespace = false)
13 | {
14 | if (separateWhitespace && val.IndexOf(" ") > 1)
15 | {
16 | var vals = val.Split(separators, StringSplitOptions.RemoveEmptyEntries);
17 | var findVals = FilterValue.Split(separators, StringSplitOptions.RemoveEmptyEntries);
18 | DivinityApp.Log($"Searching for '{String.Join("; ", findVals)}' in ({String.Join("; ", vals)}");
19 | return vals.Any(x => findVals.Any(x2 => CultureInfo.CurrentCulture.CompareInfo.IndexOf(x, x2, CompareOptions.IgnoreCase) >= 0));
20 | }
21 | else
22 | {
23 | return CultureInfo.CurrentCulture.CompareInfo.IndexOf(val, FilterValue, CompareOptions.IgnoreCase) >= 0;
24 | }
25 | }
26 |
27 | public bool PropertyContains(string val)
28 | {
29 | return CultureInfo.CurrentCulture.CompareInfo.IndexOf(FilterProperty, val, CompareOptions.IgnoreCase) >= 0;
30 | }
31 |
32 | public bool Match(DivinityModData mod)
33 | {
34 | if (String.IsNullOrWhiteSpace(FilterValue)) return true;
35 |
36 | if (PropertyContains("Author"))
37 | {
38 | if (ValueContains(mod.Author)) return true;
39 | }
40 |
41 | if (PropertyContains("Version"))
42 | {
43 | if (ValueContains(mod.Version.Version)) return true;
44 | }
45 |
46 | if (PropertyContains("Depend"))
47 | {
48 | foreach (var dependency in mod.Dependencies.Items)
49 | {
50 | if (ValueContains(dependency.Name) || FilterValue == dependency.UUID || ValueContains(dependency.Folder))
51 | {
52 | return true;
53 | }
54 | }
55 | }
56 |
57 | if (PropertyContains("Name"))
58 | {
59 | //DivinityApp.LogMessage($"Searching for '{FilterValue}' in '{mod.Name}' | {mod.Name.IndexOf(FilterValue)}");
60 | if (ValueContains(mod.Name)) return true;
61 | }
62 |
63 | if (PropertyContains("File"))
64 | {
65 | if (ValueContains(mod.FileName)) return true;
66 | }
67 |
68 | if (PropertyContains("Desc"))
69 | {
70 | if (ValueContains(mod.Description)) return true;
71 | }
72 |
73 | if (PropertyContains("Type"))
74 | {
75 | if (ValueContains(mod.ModType)) return true;
76 | }
77 |
78 | if (PropertyContains("UUID"))
79 | {
80 | if (ValueContains(mod.UUID)) return true;
81 | }
82 |
83 | if (PropertyContains("Selected"))
84 | {
85 | if (mod.IsSelected) return true;
86 | }
87 |
88 | if (PropertyContains("Editor"))
89 | {
90 | if (mod.IsEditorMod) return true;
91 | }
92 |
93 | if (PropertyContains("Modified") || PropertyContains("Updated"))
94 | {
95 | DateTime date = DateTime.Now;
96 | if (DateTime.TryParse(FilterValue, out date))
97 | {
98 | if (mod.LastModified >= date) return true;
99 | }
100 | }
101 |
102 | if (PropertyContains("Tag"))
103 | {
104 | if (mod.Tags != null && mod.Tags.Count > 0)
105 | {
106 | var f = this;
107 | if (mod.Tags.Any(x => f.ValueContains(x))) return true;
108 | }
109 | }
110 |
111 | return false;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityModScriptExtenderConfig.cs:
--------------------------------------------------------------------------------
1 | using DynamicData;
2 | using DynamicData.Binding;
3 |
4 | using System.Runtime.Serialization;
5 |
6 | namespace DivinityModManager.Models;
7 |
8 | [DataContract]
9 | public class DivinityModScriptExtenderConfig : ReactiveObject
10 | {
11 | [DataMember][Reactive] public int RequiredVersion { get; set; }
12 | [DataMember][Reactive] public string ModTable { get; set; }
13 |
14 | [DataMember] public ObservableCollectionExtended FeatureFlags { get; set; }
15 |
16 | private ObservableAsPropertyHelper _totalFeatureFlags;
17 | public int TotalFeatureFlags => _totalFeatureFlags.Value;
18 |
19 | private ObservableAsPropertyHelper _hasAnySettings;
20 | public bool HasAnySettings => _hasAnySettings.Value;
21 |
22 | public bool Lua => FeatureFlags.Contains("Lua");
23 |
24 | public DivinityModScriptExtenderConfig()
25 | {
26 | RequiredVersion = -1;
27 | FeatureFlags = new ObservableCollectionExtended();
28 | var featureFlagsConnection = FeatureFlags.ToObservableChangeSet();
29 | _totalFeatureFlags = featureFlagsConnection.Count().ToProperty(this, nameof(TotalFeatureFlags), 0);
30 | _hasAnySettings = this.WhenAnyValue(x => x.RequiredVersion, x => x.TotalFeatureFlags, x => x.ModTable)
31 | .Select(x => x.Item1 > -1 || x.Item2 > 0 || !String.IsNullOrEmpty(x.Item3)).ToProperty(this, nameof(HasAnySettings));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityModVersion.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | [JsonObject(MemberSerialization.OptIn)]
6 | public class DivinityModVersion : ReactiveObject
7 | {
8 | [Reactive] public int Major { get; set; }
9 | [Reactive] public int Minor { get; set; }
10 | [Reactive] public int Revision { get; set; }
11 | [Reactive] public int Build { get; set; }
12 |
13 | private readonly ObservableAsPropertyHelper _version;
14 | public string Version => _version.Value;
15 |
16 | private readonly ObservableAsPropertyHelper _versionInt;
17 | public int VersionInt => _versionInt.Value;
18 |
19 | public int ToInt()
20 | {
21 | return (Major << 28) + (Minor << 24) + (Revision << 16) + (Build);
22 | }
23 |
24 | public override string ToString()
25 | {
26 | return Version;
27 | }
28 |
29 | public void ParseInt(int vInt)
30 | {
31 | Major = (vInt >> 28);
32 | Minor = (vInt >> 24) & 0x0F;
33 | Revision = (vInt >> 16) & 0xFF;
34 | Build = (vInt & 0xFFFF);
35 | }
36 |
37 | public static string StringFromIndividual(int major, int minor, int revision, int build)
38 | {
39 | return $"{major}.{minor}.{revision}.{build}";
40 | }
41 |
42 | public static int IntFromIndividual(int major, int minor, int revision, int build)
43 | {
44 | return (major << 28) + (minor << 24) + (revision << 16) + (build);
45 | }
46 |
47 | public static DivinityModVersion FromInt(int vInt)
48 | {
49 | return new DivinityModVersion(vInt);
50 | }
51 |
52 | public DivinityModVersion()
53 | {
54 | var whenAnyNum = this.WhenAnyValue(x => x.Major, x => x.Minor, x => x.Revision, x => x.Build);
55 | _version = whenAnyNum.Select(v => StringFromIndividual(v.Item1, v.Item2, v.Item3, v.Item4)).ToProperty(this, nameof(Version));
56 | _versionInt = whenAnyNum.Select(v => IntFromIndividual(v.Item1, v.Item2, v.Item3, v.Item4)).ToProperty(this, nameof(VersionInt));
57 | }
58 |
59 | public DivinityModVersion(int vInt) : this()
60 | {
61 | ParseInt(vInt);
62 | }
63 |
64 | public static bool operator >(DivinityModVersion a, DivinityModVersion b)
65 | {
66 | return a.VersionInt > b.VersionInt;
67 | }
68 |
69 | public static bool operator <(DivinityModVersion a, DivinityModVersion b)
70 | {
71 | return a.VersionInt < b.VersionInt;
72 | }
73 |
74 | public static bool operator >=(DivinityModVersion a, DivinityModVersion b)
75 | {
76 | return a.VersionInt >= b.VersionInt;
77 | }
78 |
79 | public static bool operator <=(DivinityModVersion a, DivinityModVersion b)
80 | {
81 | return a.VersionInt <= b.VersionInt;
82 | }
83 |
84 | public DivinityModVersion(int headerMajor, int headerMinor, int headerRevision, int headerBuild) : this()
85 | {
86 | Major = headerMajor;
87 | Minor = headerMinor;
88 | Revision = headerRevision;
89 | Build = headerBuild;
90 | }
91 | }
--------------------------------------------------------------------------------
/src/Core/Models/DivinityModVersion2.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | [JsonObject(MemberSerialization.OptIn)]
6 | public class DivinityModVersion2 : ReactiveObject
7 | {
8 | [Reactive] public ulong Major { get; set; }
9 | [Reactive] public ulong Minor { get; set; }
10 | [Reactive] public ulong Revision { get; set; }
11 | [Reactive] public ulong Build { get; set; }
12 |
13 | [JsonProperty][Reactive] public string Version { get; set; }
14 |
15 | private ulong versionInt = 0;
16 |
17 | [JsonProperty]
18 | public ulong VersionInt
19 | {
20 | get { return versionInt; }
21 | set => ParseInt(value);
22 | }
23 |
24 | private void UpdateVersion()
25 | {
26 | Version = $"{Major}.{Minor}.{Revision}.{Build}";
27 | }
28 |
29 | public ulong ToInt()
30 | {
31 | return (Major << 55) + (Minor << 47) + (Revision << 31) + Build;
32 | }
33 |
34 | public override string ToString()
35 | {
36 | return String.Format("{0}.{1}.{2}.{3}", Major, Minor, Revision, Build);
37 | }
38 |
39 | public void ParseInt(ulong nextVersionInt)
40 | {
41 | nextVersionInt = Math.Max(ulong.MinValue, Math.Min(nextVersionInt, ulong.MaxValue));
42 | if (versionInt != nextVersionInt)
43 | {
44 | versionInt = nextVersionInt;
45 | if (versionInt != 0)
46 | {
47 | Major = versionInt >> 55;
48 | Minor = (versionInt >> 47) & 0xFF;
49 | Revision = (versionInt >> 31) & 0xFFFF;
50 | Build = versionInt & 0x7FFFFFFFUL;
51 | }
52 | else
53 | {
54 | Major = Minor = Revision = Build = 0;
55 | }
56 | this.RaisePropertyChanged("VersionInt");
57 | }
58 | }
59 |
60 | public static DivinityModVersion2 FromInt(ulong vInt)
61 | {
62 | if (vInt == 1 || vInt == 268435456)
63 | {
64 | // 1.0.0.0
65 | vInt = 36028797018963968;
66 | }
67 | return new DivinityModVersion2(vInt);
68 | }
69 |
70 | public static bool operator >(DivinityModVersion2 a, DivinityModVersion2 b)
71 | {
72 | return a.VersionInt > b.VersionInt;
73 | }
74 |
75 | public static bool operator <(DivinityModVersion2 a, DivinityModVersion2 b)
76 | {
77 | return a.VersionInt < b.VersionInt;
78 | }
79 |
80 | public static bool operator >=(DivinityModVersion2 a, DivinityModVersion2 b)
81 | {
82 | return a.VersionInt >= b.VersionInt;
83 | }
84 |
85 | public static bool operator <=(DivinityModVersion2 a, DivinityModVersion2 b)
86 | {
87 | return a.VersionInt <= b.VersionInt;
88 | }
89 |
90 | public DivinityModVersion2()
91 | {
92 | this.WhenAnyValue(x => x.VersionInt).Subscribe((x) =>
93 | {
94 | UpdateVersion();
95 | });
96 | }
97 |
98 | public DivinityModVersion2(ulong vInt) : this()
99 | {
100 | ParseInt(vInt);
101 | }
102 |
103 | public DivinityModVersion2(ulong headerMajor, ulong headerMinor, ulong headerRevision, ulong headerBuild) : this()
104 | {
105 | Major = headerMajor;
106 | Minor = headerMinor;
107 | Revision = headerRevision;
108 | Build = headerBuild;
109 | versionInt = ToInt();
110 | UpdateVersion();
111 | }
112 |
113 | public static readonly DivinityModVersion2 Empty = new(0);
114 | }
115 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityModWorkshopCachedData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models;
2 |
3 | public class DivinityModWorkshopCachedData
4 | {
5 | public string UUID { get; set; }
6 | public string WorkshopID { get; set; }
7 | public long Created { get; set; }
8 | public long LastUpdated { get; set; }
9 | public List Tags { get; set; } = new List();
10 | }
11 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityModWorkshopData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models;
2 |
3 | public class DivinityModWorkshopData : ReactiveObject
4 | {
5 | private string id = "";
6 |
7 | public string ID
8 | {
9 | get => id;
10 | set { this.RaiseAndSetIfChanged(ref id, value); }
11 | }
12 |
13 | private DateTime createdDate;
14 |
15 | public DateTime CreatedDate
16 | {
17 | get => createdDate;
18 | set { this.RaiseAndSetIfChanged(ref createdDate, value); }
19 | }
20 |
21 | private DateTime updatedDate;
22 |
23 | public DateTime UpdatedDate
24 | {
25 | get => updatedDate;
26 | set { this.RaiseAndSetIfChanged(ref updatedDate, value); }
27 | }
28 |
29 | private List tags;
30 |
31 | public List Tags
32 | {
33 | get => tags;
34 | set
35 | {
36 | this.RaiseAndSetIfChanged(ref tags, value);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityPathwayData.cs:
--------------------------------------------------------------------------------
1 |
2 | using DivinityModManager.Extensions;
3 |
4 | namespace DivinityModManager.Models;
5 |
6 | public class DivinityPathwayData : ReactiveObject
7 | {
8 | ///
9 | /// The path to the root game folder, i.e. SteamLibrary\steamapps\common\Baldur's Gate 3
10 | ///
11 | [Reactive] public string InstallPath { get; set; }
12 |
13 | ///
14 | /// The path to %LOCALAPPDATA%\Larian Studios\Baldur's Gate 3
15 | ///
16 | [Reactive] public string AppDataGameFolder { get; set; }
17 |
18 | ///
19 | /// The path to %LOCALAPPDATA%\Larian Studios\Baldur's Gate 3\Mods
20 | ///
21 | [Reactive] public string AppDataModsPath { get; set; }
22 |
23 | ///
24 | /// The path to %LOCALAPPDATA%\Larian Studios\Baldur's Gate 3\PlayerProfiles
25 | ///
26 | [Reactive] public string AppDataProfilesPath { get; set; }
27 |
28 | [Reactive] public string LastSaveFilePath { get; set; }
29 |
30 | [Reactive] public string ScriptExtenderLatestReleaseUrl { get; set; }
31 | [Reactive] public string ScriptExtenderLatestReleaseVersion { get; set; }
32 |
33 | public DivinityPathwayData()
34 | {
35 | InstallPath = "";
36 | AppDataGameFolder = "";
37 | AppDataModsPath = "";
38 | LastSaveFilePath = "";
39 | ScriptExtenderLatestReleaseUrl = "";
40 | ScriptExtenderLatestReleaseVersion = "";
41 | }
42 |
43 | public string ScriptExtenderSettingsFile(DivinityModManagerSettings settings)
44 | {
45 | if (settings.GameExecutablePath.IsExistingFile())
46 | {
47 | return Path.Combine(Path.GetDirectoryName(settings.GameExecutablePath), DivinityApp.EXTENDER_CONFIG_FILE);
48 | }
49 | return "";
50 | }
51 |
52 | public string ScriptExtenderUpdaterConfigFile(DivinityModManagerSettings settings)
53 | {
54 | if (settings.GameExecutablePath.IsExistingFile())
55 | {
56 | return Path.Combine(Path.GetDirectoryName(settings.GameExecutablePath), DivinityApp.EXTENDER_UPDATER_CONFIG_FILE);
57 | }
58 | return "";
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityProfileActiveModData.cs:
--------------------------------------------------------------------------------
1 | using LSLib.LS;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | public class DivinityProfileActiveModData
6 | {
7 | public string Folder { get; set; }
8 | public string MD5 { get; set; }
9 | public string Name { get; set; }
10 | public string UUID { get; set; }
11 | public ulong Version { get; set; }
12 |
13 | private static readonly NodeSerializationSettings _serializationSettings = new()
14 | {
15 | ByteSwapGuids = false,
16 | DefaultByteSwapGuids = false
17 | };
18 |
19 | private static string GetAttributeAsString(Dictionary attributes, string name, string fallBack)
20 | {
21 | if (attributes.TryGetValue(name, out var attribute))
22 | {
23 | return attribute.AsString(_serializationSettings);
24 | }
25 | return fallBack;
26 | }
27 |
28 | private static ulong GetULongAttribute(Dictionary attributes, string name, ulong fallBack)
29 | {
30 | if (attributes.TryGetValue(name, out var attribute))
31 | {
32 | if (attribute.Value is string att)
33 | {
34 | if (ulong.TryParse(att, out ulong val))
35 | {
36 | return val;
37 | }
38 | else
39 | {
40 | return fallBack;
41 | }
42 | }
43 | else if (attribute.Value is ulong val)
44 | {
45 | return val;
46 | }
47 | }
48 | return fallBack;
49 | }
50 |
51 | public void LoadFromAttributes(Dictionary attributes)
52 | {
53 | Folder = GetAttributeAsString(attributes, "Folder", "");
54 | MD5 = GetAttributeAsString(attributes, "MD5", "");
55 | Name = GetAttributeAsString(attributes, "Name", "");
56 | UUID = GetAttributeAsString(attributes, "UUID", "");
57 | Version = GetULongAttribute(attributes, "Version", 0UL);
58 |
59 | //DivinityApp.LogMessage($"[DivinityProfileActiveModData] Name({Name}) UUID({UUID})");
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinityProfileData.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | using DivinityModManager.Util;
4 |
5 | using DynamicData;
6 |
7 | namespace DivinityModManager.Models;
8 |
9 | public class DivinityProfileData : ReactiveObject
10 | {
11 | [Reactive] public string Name { get; set; }
12 |
13 | ///
14 | /// The stored name in the profile.lsb or profile5.lsb file.
15 | ///
16 | [Reactive] public string ProfileName { get; set; }
17 | [Reactive] public string UUID { get; set; }
18 | [Reactive] public string ModSettingsFile { get; set; }
19 |
20 | [Reactive] public string Folder { get; private set; }
21 |
22 | ///
23 | /// The mod data under the Mods node, from modsettings.lsx.
24 | ///
25 | public List ActiveMods { get; set; }
26 |
27 | public DivinityLoadOrder GetLoadOrder(SourceCache mods)
28 | {
29 | var order = new DivinityLoadOrder() { Name = "Current", FilePath = Path.Combine(Folder, "modsettings.lsx"), IsModSettings = true };
30 | var i = 0;
31 | foreach (var activeMod in ActiveMods)
32 | {
33 | var mod = mods.Items.FirstOrDefault(m => m.UUID.Equals(activeMod.UUID, StringComparison.OrdinalIgnoreCase));
34 | if (mod != null)
35 | {
36 | order.Add(mod);
37 | }
38 | i++;
39 | }
40 | return order;
41 | }
42 |
43 | public DivinityProfileData(string uuid, string modSettingsFile)
44 | {
45 | UUID = uuid;
46 | ModSettingsFile = modSettingsFile;
47 | ActiveMods = [];
48 |
49 | this.WhenAnyValue(x => x.ModSettingsFile).Select(DivinityFileUtils.GetParentOrEmpty).BindTo(this, x => x.Folder);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Core/Models/DivinitySerializedModData.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | [DataContract]
6 | public class DivinitySerializedModData : IDivinityModData
7 | {
8 | [DataMember] public int Index { get; set; }
9 | [DataMember] public string FileName { get; set; }
10 | [DataMember] public string UUID { get; set; }
11 | [DataMember] public string Folder { get; set; }
12 | [DataMember] public string Name { get; set; }
13 | [DataMember] public string Description { get; set; }
14 | [DataMember] public string Author { get; set; }
15 |
16 | [DataMember] public DivinityModVersion2 Version { get; set; }
17 |
18 | [DataMember] public string Type { get; set; }
19 | [DataMember] public List Modes { get; set; }
20 |
21 | [DataMember] public string Targets { get; set; }
22 |
23 | [DataMember] public DivinityModScriptExtenderConfig ScriptExtenderData { get; set; }
24 | [DataMember] public List Dependencies { get; set; }
25 |
26 | [DataMember] public string MD5 { get; set; }
27 |
28 | public static DivinitySerializedModData FromMod(DivinityModData mod)
29 | {
30 | return new DivinitySerializedModData
31 | {
32 | Author = mod.Author,
33 | Dependencies = mod.Dependencies.Items.ToList(),
34 | Description = mod.Description,
35 | FileName = mod.FileName,
36 | Folder = mod.Folder,
37 | Name = mod.Name,
38 | Version = mod.Version,
39 | Type = mod.ModType,
40 | Index = mod.Index,
41 | ScriptExtenderData = mod.ScriptExtenderData,
42 | UUID = mod.UUID,
43 | MD5 = mod.MD5
44 | };
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Core/Models/Extender/ScriptExtenderUpdateConfig.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Enums.Extender;
2 | using DivinityModManager.Extensions;
3 |
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Converters;
6 |
7 | using System.ComponentModel;
8 | using System.Runtime.Serialization;
9 |
10 | namespace DivinityModManager.Models.Extender;
11 |
12 | [DataContract]
13 | public class ScriptExtenderUpdateConfig : ReactiveObject
14 | {
15 | [Reactive] public bool UpdaterIsAvailable { get; set; }
16 | [Reactive] public int UpdaterVersion { get; set; }
17 |
18 | [DefaultValue(ExtenderUpdateChannel.Release)]
19 | [SettingsEntry("Update Channel", "Use a specific update channel", HideFromUI = true)]
20 | [DataMember, Reactive]
21 | public ExtenderUpdateChannel UpdateChannel { get; set; }
22 |
23 | [DefaultValue("")]
24 | [SettingsEntry("Target Version", "Update to a specific version of the script extender (ex. '5.0.0.0')")]
25 | [DataMember, Reactive]
26 | public string TargetVersion { get; set; }
27 |
28 | [DefaultValue("")]
29 | [SettingsEntry("Target Resource Digest", "Use a specific Digest for the target update", true)]
30 | [DataMember, Reactive]
31 | public string TargetResourceDigest { get; set; }
32 |
33 | [DefaultValue(false)]
34 | [SettingsEntry("Disable Updates", "Disable automatic updating to the latest extender version")]
35 | [DataMember, Reactive]
36 | public bool DisableUpdates { get; set; }
37 |
38 | [DefaultValue(false)]
39 | [SettingsEntry("IPv4Only", "Use only IPv4 when fetching the latest update")]
40 | [DataMember, Reactive]
41 | public bool IPv4Only { get; set; }
42 |
43 | [DefaultValue(false)]
44 | [SettingsEntry("Debug", "Enable debug mode in the extender updater, which prints more messages to the console window")]
45 | [DataMember, Reactive]
46 | public bool Debug { get; set; }
47 |
48 | [DefaultValue("")]
49 | [SettingsEntry("Manifest URL", "", true)]
50 | [DataMember, Reactive]
51 | public string ManifestURL { get; set; }
52 |
53 | [DefaultValue("")]
54 | [SettingsEntry("Manifest Name", "", true)]
55 | [DataMember, Reactive]
56 | public string ManifestName { get; set; }
57 |
58 | [DefaultValue("")]
59 | [SettingsEntry("CachePath", "", true)]
60 | [DataMember, Reactive]
61 | public string CachePath { get; set; }
62 |
63 | [DefaultValue(false)]
64 | [SettingsEntry("Validate Signature", "", true)]
65 | [DataMember, Reactive]
66 | public bool ValidateSignature { get; set; }
67 |
68 | public ScriptExtenderUpdateConfig()
69 | {
70 | this.SetToDefault();
71 | UpdaterVersion = -1;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Core/Models/Extender/ScriptExtenderUpdateData.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Runtime.Serialization;
3 |
4 | namespace DivinityModManager.Models.Extender;
5 |
6 | [DataContract]
7 | public class ScriptExtenderUpdateData
8 | {
9 | [DataMember] public int ManifestMinorVersion { get; set; }
10 | [DataMember] public int ManifestVersion { get; set; }
11 | [DataMember] public string NoMatchingVersionNotice { get; set; }
12 | [DataMember] public List Resources { get; set; }
13 | }
14 |
15 | [DataContract]
16 | public class ScriptExtenderUpdateResource
17 | {
18 | [DataMember] public string Name { get; set; }
19 | [DataMember] public List Versions { get; set; }
20 | }
21 |
22 | [DataContract]
23 | public class ScriptExtenderUpdateVersion : ReactiveObject
24 | {
25 | [DataMember][Reactive] public long BuildDate { get; set; }
26 | [DataMember][Reactive] public string Digest { get; set; }
27 | [DataMember][Reactive] public string MinGameVersion { get; set; }
28 | [DataMember][Reactive] public string Notice { get; set; }
29 | [DataMember][Reactive] public string URL { get; set; }
30 | [DataMember][Reactive] public string Version { get; set; }
31 | [DataMember][Reactive] public string Signature { get; set; }
32 |
33 | private readonly ObservableAsPropertyHelper _displayName;
34 | public string DisplayName => _displayName.Value;
35 |
36 | private readonly ObservableAsPropertyHelper _buildDateDate;
37 | public string BuildDateDisplayString => _buildDateDate.Value;
38 |
39 | private readonly ObservableAsPropertyHelper _isEmpty;
40 | public bool IsEmpty => _isEmpty.Value;
41 |
42 | private string TimestampToReadableString(long timestamp)
43 | {
44 | var date = DateTime.FromFileTime(timestamp);
45 | return date.ToString(DivinityApp.DateTimeExtenderBuildFormat, CultureInfo.InstalledUICulture);
46 | }
47 |
48 | private string ToDisplayName(ValueTuple data)
49 | {
50 | if (String.IsNullOrEmpty(data.Item1)) return "Latest";
51 | var result = data.Item1;
52 | if (!String.IsNullOrEmpty(data.Item2))
53 | {
54 | result += $" ({data.Item2})";
55 | }
56 | if (!String.IsNullOrEmpty(data.Item3))
57 | {
58 | result += $" - {data.Item3}";
59 | }
60 | return result;
61 | }
62 |
63 | public ScriptExtenderUpdateVersion()
64 | {
65 | _isEmpty = this.WhenAnyValue(x => x.Version).Select(x => String.IsNullOrEmpty(x)).ToProperty(this, nameof(IsEmpty), true, RxApp.MainThreadScheduler);
66 | _buildDateDate = this.WhenAnyValue(x => x.BuildDate).Select(TimestampToReadableString).ToProperty(this, nameof(BuildDateDisplayString), true, RxApp.MainThreadScheduler);
67 | _displayName = this.WhenAnyValue(x => x.Version, x => x.MinGameVersion, x => x.BuildDateDisplayString).Select(ToDisplayName).ToProperty(this, nameof(DisplayName), true, RxApp.MainThreadScheduler);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Core/Models/Github/GithubModData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.Github;
2 |
3 | public class GithubModData : ReactiveObject
4 | {
5 | [Reactive] public string Author { get; set; }
6 | [Reactive] public string Repository { get; set; }
7 | [Reactive] public Uri LatestRelease { get; set; }
8 |
9 | public void Update(GithubModData data)
10 | {
11 | //TODO
12 | Author = data.Author;
13 | Repository = data.Repository;
14 | LatestRelease = data.LatestRelease;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Core/Models/ISelectable.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | public interface ISelectable
6 | {
7 | bool IsSelected { get; set; }
8 | Visibility Visibility { get; set; }
9 | bool CanDrag { get; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Core/Models/ModFileDeletionData.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models;
2 |
3 | public class ModFileDeletionData : ReactiveObject
4 | {
5 | [Reactive] public bool IsSelected { get; set; }
6 | [Reactive] public string FilePath { get; set; }
7 | [Reactive] public string DisplayName { get; set; }
8 | [Reactive] public string UUID { get; set; }
9 | [Reactive] public string Duplicates { get; set; }
10 |
11 | public static ModFileDeletionData FromMod(DivinityModData mod, bool isDeletingDuplicates = false, List loadedMods = null)
12 | {
13 | var data = new ModFileDeletionData { FilePath = mod.FilePath, DisplayName = mod.DisplayName, IsSelected = true, UUID = mod.UUID};
14 | if (isDeletingDuplicates && loadedMods != null)
15 | {
16 | var duplicatesStr = loadedMods.FirstOrDefault(x => x.UUID == mod.UUID)?.FilePath;
17 | if (!String.IsNullOrEmpty(duplicatesStr))
18 | {
19 | data.Duplicates = duplicatesStr;
20 | }
21 | }
22 | return data;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Core/Models/ModuleShortDesc.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | [DataContract]
6 | public class ModuleShortDesc : ReactiveObject, IDivinityModData
7 | {
8 | [DataMember, Reactive] public string UUID { get; set; }
9 | [DataMember, Reactive] public string Name { get; set; }
10 | [DataMember, Reactive] public string Folder { get; set; }
11 | [DataMember, Reactive] public string MD5 { get; set; }
12 | [DataMember, Reactive] public ulong PublishHandle { get; set; }
13 | [DataMember, Reactive] public DivinityModVersion2 Version { get; set; }
14 | public DateTimeOffset? LastModified { get; set; }
15 |
16 | public override string ToString() => $"[ModuleShortDesc] Name({Name}) UUID({UUID}) Version({Version?.Version})";
17 |
18 | public static ModuleShortDesc FromModData(DivinityModData m)
19 | {
20 | return new ModuleShortDesc
21 | {
22 | Folder = m.Folder,
23 | Name = m.Name,
24 | UUID = m.UUID,
25 | MD5 = m.MD5,
26 | PublishHandle = m.PublishHandle,
27 | Version = m.Version,
28 | LastModified = m.LastModified
29 | };
30 | }
31 |
32 | public ModuleShortDesc()
33 | {
34 | UUID = "";
35 | Name = "";
36 | Folder = "";
37 | MD5 = "";
38 | PublishHandle = 0ul;
39 | Version = new DivinityModVersion2();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Core/Models/NexusMods/NexusModFileVersionData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace DivinityModManager.Models.NexusMods;
4 |
5 | public struct NexusModFileVersionData
6 | {
7 | public long ModId { get; set; }
8 | public long FileId { get; set; }
9 | public bool Success { get; set; }
10 |
11 | static readonly Regex _filePattern = new(@"^.*?-(\d+)-(.*?)(\d+)");
12 |
13 | public static NexusModFileVersionData FromFilePath(string path)
14 | {
15 | var name = Path.GetFileNameWithoutExtension(path);
16 | var match = _filePattern.Match(name);
17 |
18 | long modId = -1;
19 | long fileId = -1;
20 |
21 | if (match.Success)
22 | {
23 | if (long.TryParse(match.Groups[1]?.Value, out var mid))
24 | {
25 | modId = mid;
26 | }
27 | if (long.TryParse(match.Groups[3]?.Value, out var fid))
28 | {
29 | fileId = fid;
30 | }
31 | }
32 |
33 | return new NexusModFileVersionData()
34 | {
35 | ModId = modId,
36 | FileId = fileId,
37 | Success = match.Success
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Core/Models/NexusMods/NexusModsModDownloadLink.cs:
--------------------------------------------------------------------------------
1 | using NexusModsNET.DataModels;
2 |
3 | namespace DivinityModManager.Models.NexusMods;
4 |
5 | public struct NexusModsModDownloadLink
6 | {
7 | public DivinityModData Mod { get; set; }
8 | public NexusModFileDownloadLink DownloadLink { get; set; }
9 |
10 | public NexusModsModDownloadLink(DivinityModData mod, NexusModFileDownloadLink link)
11 | {
12 | Mod = mod;
13 | DownloadLink = link;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Core/Models/Steam/IWorkshopPublishFileDetails.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.Steam;
2 |
3 | public interface IWorkshopPublishFileDetails
4 | {
5 | string publishedfileid { get; set; }
6 | long time_created { get; set; }
7 | long time_updated { get; set; }
8 |
9 | List tags { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Core/Models/Steam/PublishedFileDetails.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.Steam;
2 |
3 | public struct WorkshopTag
4 | {
5 | public string tag { get; set; }
6 | }
7 |
8 | public class PublishedFileDetailsResponse
9 | {
10 | public PublishedFileDetailsResponseData response { get; set; }
11 | }
12 |
13 | public class PublishedFileDetailsResponseData
14 | {
15 | public int result { get; set; }
16 | public int resultcount { get; set; }
17 |
18 | public List publishedfiledetails { get; set; }
19 | }
20 |
21 |
22 | public class PublishedFileDetails : IWorkshopPublishFileDetails
23 | {
24 | public string publishedfileid { get; set; }
25 | public int result { get; set; }
26 | public string creator { get; set; }
27 | public int creator_app_id { get; set; }
28 | public int consumer_app_id { get; set; }
29 | public string filename { get; set; }
30 | public string file_size { get; set; }
31 | public string file_url { get; set; }
32 | public string hcontent_file { get; set; }
33 | public string preview_url { get; set; }
34 | public string hcontent_preview { get; set; }
35 | public string title { get; set; }
36 | public string description { get; set; }
37 | public long time_created { get; set; }
38 | public long time_updated { get; set; }
39 | public int visibility { get; set; }
40 | public bool banned { get; set; }
41 | public string ban_reason { get; set; }
42 | public int subscriptions { get; set; }
43 | public int favorited { get; set; }
44 | public int lifetime_subscriptions { get; set; }
45 | public int lifetime_favorited { get; set; }
46 | public int views { get; set; }
47 | public List tags { get; set; }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Core/Models/Steam/QueryFilesResponseData.cs:
--------------------------------------------------------------------------------
1 | using DivinityModManager.Util;
2 |
3 | using Newtonsoft.Json;
4 |
5 | namespace DivinityModManager.Models.Steam;
6 |
7 | public class QueryFilesResponse
8 | {
9 | public QueryFilesResponseData response { get; set; }
10 | }
11 |
12 | public class QueryFilesResponseData
13 | {
14 | public int total { get; set; }
15 |
16 | public List publishedfiledetails { get; set; }
17 | }
18 |
19 | public class QueryFilesPublishedFileDetails : IWorkshopPublishFileDetails
20 | {
21 | public int result { get; set; }
22 | public string publishedfileid { get; set; }
23 | public string creator { get; set; }
24 | public string filename { get; set; }
25 | public string file_size { get; set; }
26 | public string file_url { get; set; }
27 | public string preview_url { get; set; }
28 | public string url { get; set; }
29 | public string title { get; set; }
30 | public string description { get; set; }
31 | public long time_created { get; set; }
32 | public long time_updated { get; set; }
33 | public int visibility { get; set; }
34 | public int flags { get; set; }
35 | public List tags { get; set; }
36 | public string metadata { get; set; }
37 | [JsonIgnore] public QueryFilesPublishedFileDivinityMetadataMain MetaData { get; set; }
38 | public int language { get; set; }
39 | public string revision_change_number { get; set; }
40 | public int revision { get; set; }
41 |
42 | public string GetGuid()
43 | {
44 | if (this.MetaData != null)
45 | {
46 | try
47 | {
48 | return this.MetaData.root.regions.MetaData.Guid.Value;
49 | }
50 | catch
51 | {
52 | }
53 | }
54 | return null;
55 | }
56 |
57 | public void DeserializeMetadata()
58 | {
59 | if (!String.IsNullOrEmpty(metadata))
60 | {
61 | MetaData = DivinityJsonUtils.SafeDeserialize(metadata);
62 | }
63 | }
64 | }
65 |
66 | public class QueryFilesPublishedFileDivinityMetadataMain
67 | {
68 | public QueryFilesPublishedFileDivinityMetadataRoot root { get; set; }
69 | }
70 |
71 | public class QueryFilesPublishedFileDivinityMetadataRoot
72 | {
73 | public QueryFilesPublishedFileDivinityMetadataHeader header { get; set; }
74 | public QueryFilesPublishedFileDivinityMetadataRegions regions { get; set; }
75 | }
76 |
77 | public class QueryFilesPublishedFileDivinityMetadataHeader
78 | {
79 | public int time { get; set; }
80 | public string version { get; set; }
81 | }
82 |
83 | public class QueryFilesPublishedFileDivinityMetadataRegions
84 | {
85 | public QueryFilesPublishedFileDivinityMetadataEntry MetaData { get; set; }
86 | }
87 |
88 | public class QueryFilesPublishedFileDivinityMetadataEntry
89 | {
90 | public QueryFilesPublishedFileDivinityMetadataEntryAttribute Guid { get; set; }
91 | public QueryFilesPublishedFileDivinityMetadataEntryAttribute Type { get; set; }
92 | public QueryFilesPublishedFileDivinityMetadataEntryAttribute Version { get; set; }
93 | }
94 | public class QueryFilesPublishedFileDivinityMetadataEntryAttribute
95 | {
96 | public int type { get; set; }
97 | public object value { get; set; }
98 |
99 | [JsonIgnore]
100 | public T Value
101 | {
102 | get
103 | {
104 | return (T)value;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Core/Models/Updates/UpdateResult.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager.Models.Updates;
2 |
3 | public class UpdateResult
4 | {
5 | public List UpdatedMods { get; set; }
6 | public string FailureMessage { get; set; }
7 | public bool Success { get; set; }
8 |
9 | public UpdateResult()
10 | {
11 | UpdatedMods = new List();
12 | Success = true;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Core/Models/View/EnumEntry.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.Reflection;
4 |
5 | namespace DivinityModManager.Models.View;
6 |
7 | public class EnumEntry : ReactiveObject
8 | {
9 | [Reactive] public string Name { get; set; }
10 | [Reactive] public string Description { get; set; }
11 | [Reactive] public object Value { get; set; }
12 |
13 | public EnumEntry(string description, object value, string name = "")
14 | {
15 | Name = name;
16 | Description = description;
17 | Value = value;
18 | }
19 |
20 | public EnumEntry(Enum enumValue)
21 | {
22 | Value = enumValue;
23 | Name = "";
24 | Description = "";
25 | var member = enumValue.GetType().GetMember(enumValue.ToString()).FirstOrDefault();
26 | if (member != null)
27 | {
28 | var descriptionAttribute = member.GetCustomAttribute(false);
29 |
30 | if (descriptionAttribute == null)
31 | {
32 | var displayAttribute = member.GetCustomAttribute(false);
33 | if (displayAttribute != null)
34 | {
35 | Name = displayAttribute.Name;
36 | Description = displayAttribute.Description;
37 | }
38 | }
39 | else
40 | {
41 | Name = descriptionAttribute.Description;
42 | }
43 | }
44 | }
45 |
46 | public EnumEntry() { }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Core/Models/WindowSettings.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace DivinityModManager.Models;
4 |
5 | public class WindowSettings
6 | {
7 | public bool Maximized { get; set; }
8 |
9 | [DefaultValue(0)]
10 | public int Screen { get; set; }
11 |
12 | [DefaultValue(-1)]
13 | public double X { get; set; }
14 |
15 | [DefaultValue(-1)]
16 | public double Y { get; set; }
17 |
18 | [DefaultValue(-1)]
19 | public double Width { get; set; }
20 |
21 | [DefaultValue(-1)]
22 | public double Height { get; set; }
23 |
24 | public WindowSettings()
25 | {
26 | Maximized = false;
27 | X = -1;
28 | Y = -1;
29 | Width = -1;
30 | Height = -1;
31 | Screen = 0;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Core/Services.cs:
--------------------------------------------------------------------------------
1 | namespace DivinityModManager;
2 |
3 | public static class Services
4 | {
5 | public static IScreenReaderService ScreenReader => Get();
6 |
7 | public static T Get(string contract = null)
8 | {
9 | return Locator.Current.GetService(contract);
10 | }
11 |
12 | public static void Register(Func