├── .gitignore ├── App.xaml ├── App.xaml.cs ├── Assets ├── arrow-left-dark.png ├── arrow-left.png ├── arrow-right-dark.png ├── arrow-right.png ├── file.png ├── folder.png ├── font.png ├── grid.png ├── image.png ├── list.png ├── lua.png └── video.png ├── DieselBundleViewer.csproj ├── DieselBundleViewer.sln ├── LICENSE ├── Models ├── FileEntry.cs ├── FolderEntry.cs ├── IEntry.cs └── Script.cs ├── Objects ├── PageData.cs └── VirtualFileDataObject.cs ├── README.md ├── Services ├── Definitions.cs ├── DragDropController.cs ├── FileManager.cs ├── FixListView.cs ├── FormatConverter.cs ├── HashlistUpdater.cs ├── JSONNode.cs ├── ScriptActions.cs ├── Settings.cs └── Utils.cs ├── ViewModels ├── AboutDialogViewModel.cs ├── BundleSelectorDialogViewModel.cs ├── ConvertFileDialogViewModel.cs ├── DialogBase.cs ├── EntryViewModel.cs ├── FindDialogViewModel.cs ├── MainWindowViewModel.cs ├── ProgressDialogViewModel.cs ├── PropertiesViewModel.cs ├── SettingsDialogViewModel.cs ├── TreeEntryViewModel.cs └── UpdateHashlistDialogViewModel.cs ├── Views ├── AboutDialog.xaml ├── AboutDialog.xaml.cs ├── BundleSelectorDialog.xaml ├── BundleSelectorDialog.xaml.cs ├── ConvertFileDialog.xaml ├── ConvertFileDialog.xaml.cs ├── DialogWindow.xaml ├── DialogWindow.xaml.cs ├── EntryGridView.xaml ├── EntryGridView.xaml.cs ├── EntryListView.xaml ├── EntryListView.xaml.cs ├── FindDialog.xaml ├── FindDialog.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ProgressDialog.xaml ├── ProgressDialog.xaml.cs ├── PropertiesDialog.xaml ├── PropertiesDialog.xaml.cs ├── SettingsDialog.xaml ├── SettingsDialog.xaml.cs ├── SmallStuff.xaml ├── TreeEntry.xaml ├── TreeEntry.xaml.cs ├── UpdateHashlistDialog.xaml ├── UpdateHashlistDialog.xaml.cs └── VirtualizingWrapPanel.cs ├── dlls ├── DieselEngineFormats.dll └── Wwise Sound Library.dll └── favicon.ico /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*[.json, .xml, .info] 146 | 147 | # Visual Studio code coverage results 148 | *.coverage 149 | *.coveragexml 150 | 151 | # NCrunch 152 | _NCrunch_* 153 | .*crunch*.local.xml 154 | nCrunchTemp_* 155 | 156 | # MightyMoose 157 | *.mm.* 158 | AutoTest.Net/ 159 | 160 | # Web workbench (sass) 161 | .sass-cache/ 162 | 163 | # Installshield output folder 164 | [Ee]xpress/ 165 | 166 | # DocProject is a documentation generator add-in 167 | DocProject/buildhelp/ 168 | DocProject/Help/*.HxT 169 | DocProject/Help/*.HxC 170 | DocProject/Help/*.hhc 171 | DocProject/Help/*.hhk 172 | DocProject/Help/*.hhp 173 | DocProject/Help/Html2 174 | DocProject/Help/html 175 | 176 | # Click-Once directory 177 | publish/ 178 | 179 | # Publish Web Output 180 | *.[Pp]ublish.xml 181 | *.azurePubxml 182 | # Note: Comment the next line if you want to checkin your web deploy settings, 183 | # but database connection strings (with potential passwords) will be unencrypted 184 | *.pubxml 185 | *.publishproj 186 | 187 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 188 | # checkin your Azure Web App publish settings, but sensitive information contained 189 | # in these scripts will be unencrypted 190 | PublishScripts/ 191 | 192 | # NuGet Packages 193 | *.nupkg 194 | # NuGet Symbol Packages 195 | *.snupkg 196 | # The packages folder can be ignored because of Package Restore 197 | **/[Pp]ackages/* 198 | # except build/, which is used as an MSBuild target. 199 | !**/[Pp]ackages/build/ 200 | # Uncomment if necessary however generally it will be regenerated when needed 201 | #!**/[Pp]ackages/repositories.config 202 | # NuGet v3's project.json files produces more ignorable files 203 | *.nuget.props 204 | *.nuget.targets 205 | 206 | # Microsoft Azure Build Output 207 | csx/ 208 | *.build.csdef 209 | 210 | # Microsoft Azure Emulator 211 | ecf/ 212 | rcf/ 213 | 214 | # Windows Store app package directories and files 215 | AppPackages/ 216 | BundleArtifacts/ 217 | Package.StoreAssociation.xml 218 | _pkginfo.txt 219 | *.appx 220 | *.appxbundle 221 | *.appxupload 222 | 223 | # Visual Studio cache files 224 | # files ending in .cache can be ignored 225 | *.[Cc]ache 226 | # but keep track of directories ending in .cache 227 | !?*.[Cc]ache/ 228 | 229 | # Others 230 | ClientBin/ 231 | ~$* 232 | *~ 233 | *.dbmdl 234 | *.dbproj.schemaview 235 | *.jfm 236 | *.pfx 237 | *.publishsettings 238 | orleans.codegen.cs 239 | 240 | # Including strong name files can present a security risk 241 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 242 | #*.snk 243 | 244 | # Since there are multiple workflows, uncomment next line to ignore bower_components 245 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 246 | #bower_components/ 247 | 248 | # RIA/Silverlight projects 249 | Generated_Code/ 250 | 251 | # Backup & report files from converting an old project file 252 | # to a newer Visual Studio version. Backup files are not needed, 253 | # because we have git ;-) 254 | _UpgradeReport_Files/ 255 | Backup*/ 256 | UpgradeLog*.XML 257 | UpgradeLog*.htm 258 | ServiceFabricBackup/ 259 | *.rptproj.bak 260 | 261 | # SQL Server files 262 | *.mdf 263 | *.ldf 264 | *.ndf 265 | 266 | # Business Intelligence projects 267 | *.rdl.data 268 | *.bim.layout 269 | *.bim_*.settings 270 | *.rptproj.rsuser 271 | *- [Bb]ackup.rdl 272 | *- [Bb]ackup ([0-9]).rdl 273 | *- [Bb]ackup ([0-9][0-9]).rdl 274 | 275 | # Microsoft Fakes 276 | FakesAssemblies/ 277 | 278 | # GhostDoc plugin setting file 279 | *.GhostDoc.xml 280 | 281 | # Node.js Tools for Visual Studio 282 | .ntvs_analysis.dat 283 | node_modules/ 284 | 285 | # Visual Studio 6 build log 286 | *.plg 287 | 288 | # Visual Studio 6 workspace options file 289 | *.opt 290 | 291 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 292 | *.vbw 293 | 294 | # Visual Studio LightSwitch build output 295 | **/*.HTMLClient/GeneratedArtifacts 296 | **/*.DesktopClient/GeneratedArtifacts 297 | **/*.DesktopClient/ModelManifest.xml 298 | **/*.Server/GeneratedArtifacts 299 | **/*.Server/ModelManifest.xml 300 | _Pvt_Extensions 301 | 302 | # Paket dependency manager 303 | .paket/paket.exe 304 | paket-files/ 305 | 306 | # FAKE - F# Make 307 | .fake/ 308 | 309 | # CodeRush personal settings 310 | .cr/personal 311 | 312 | # Python Tools for Visual Studio (PTVS) 313 | __pycache__/ 314 | *.pyc 315 | 316 | # Cake - Uncomment if you are using it 317 | # tools/** 318 | # !tools/packages.config 319 | 320 | # Tabs Studio 321 | *.tss 322 | 323 | # Telerik's JustMock configuration file 324 | *.jmconfig 325 | 326 | # BizTalk build output 327 | *.btp.cs 328 | *.btm.cs 329 | *.odx.cs 330 | *.xsd.cs 331 | 332 | # OpenCover UI analysis results 333 | OpenCover/ 334 | 335 | # Azure Stream Analytics local run output 336 | ASALocalRun/ 337 | 338 | # MSBuild Binary and Structured Log 339 | *.binlog 340 | 341 | # NVidia Nsight GPU debugger configuration file 342 | *.nvuser 343 | 344 | # MFractors (Xamarin productivity tool) working folder 345 | .mfractor/ 346 | 347 | # Local History for Visual Studio 348 | .localhistory/ 349 | 350 | # BeatPulse healthcheck temp database 351 | healthchecksdb 352 | 353 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 354 | MigrationBackup/ 355 | 356 | # Ionide (cross platform F# VS Code tools) working folder 357 | .ionide/ 358 | 359 | # Fody - auto-generated XML schema 360 | FodyWeavers.xsd 361 | *.zip 362 | *.7z 363 | -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | #0458de 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Prism.Ioc; 2 | using DieselBundleViewer.Views; 3 | using System.Windows; 4 | using System.Runtime.InteropServices; 5 | using DieselEngineFormats.Bundle; 6 | using DieselBundleViewer.ViewModels; 7 | using DieselBundleViewer.Services; 8 | using System.IO; 9 | using System; 10 | using DieselEngineFormats; 11 | using DieselEngineFormats.ScriptData; 12 | using System.Text; 13 | using System.Collections.Generic; 14 | using AdonisUI; 15 | using WwiseSoundLib; 16 | using Orangelynx.Multimedia; 17 | using System.Windows.Threading; 18 | 19 | namespace DieselBundleViewer 20 | { 21 | /// 22 | /// Interaction logic for App.xaml 23 | /// 24 | public partial class App 25 | { 26 | [DllImport("Kernel32")] 27 | public static extern void AllocConsole(); 28 | 29 | [DllImport("Kernel32")] 30 | public static extern void FreeConsole(); 31 | 32 | public App() 33 | { 34 | #if !DEBUG 35 | Dispatcher.UnhandledException += OnException; 36 | if(File.Exists("debug")) 37 | #endif 38 | AllocConsole(); 39 | 40 | 41 | Console.WriteLine("Loading local hashlist"); 42 | if (File.Exists("Data/hashlist")) 43 | HashIndex.LoadParallel("Data/hashlist"); 44 | else 45 | Console.WriteLine("Local hashlist is missing!"); 46 | 47 | LoadConverters(); 48 | } 49 | 50 | void OnException(object sender, DispatcherUnhandledExceptionEventArgs e) 51 | { 52 | MessageBox.Show($"An error has occurred: \n {e.Exception.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 53 | } 54 | 55 | protected override void RegisterTypes(IContainerRegistry containerRegistry) 56 | { 57 | containerRegistry.RegisterDialog(); 58 | containerRegistry.RegisterDialog(); 59 | containerRegistry.RegisterDialog(); 60 | containerRegistry.RegisterDialog(); 61 | containerRegistry.RegisterDialog(); 62 | containerRegistry.RegisterDialog(); 63 | containerRegistry.RegisterDialog(); 64 | containerRegistry.RegisterDialog(); 65 | 66 | containerRegistry.RegisterDialogWindow(); 67 | } 68 | 69 | private void LoadConverters() 70 | { 71 | ScriptActions.AddConverter(new FormatConverter 72 | { 73 | Key = "script_cxml", 74 | Title = "Custom XML", 75 | Extension = "xml", 76 | ExportEvent = (MemoryStream ms, bool escape) => 77 | { 78 | try 79 | { 80 | Dictionary root = new ScriptData(new BinaryReader(ms), Utils.IsRaid()).Root; 81 | return new CustomXMLNode("table", root, "").ToString(0, escape); 82 | } catch (Exception e) 83 | { 84 | MessageBox.Show($"Failed to read scriptdata: \n {e.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 85 | return null; 86 | } 87 | }, 88 | Type = "scriptdata" 89 | }); 90 | 91 | //Temporary until I get source code of this DLL, hopefully. 92 | ScriptActions.AddConverter(new FormatConverter 93 | { 94 | Key = "stream", 95 | Title = "Stream to Wav", 96 | RequiresAttention = false, 97 | Extension = "wav", 98 | Type = "stream", 99 | SaveEvent = (Stream stream, string toPath) => 100 | { 101 | WavFile file = new WavFile(stream); 102 | WavProcessor.ConvertToPCM(file); 103 | file.WriteFile(toPath); 104 | }, 105 | }); 106 | 107 | ScriptActions.AddConverter(new FormatConverter 108 | { 109 | Key = "diesel_strings", 110 | Title = "Diesel", 111 | Extension = "strings", 112 | ImportEvent = (path) => new StringsFile(path), 113 | Type = "strings" 114 | }); 115 | 116 | ScriptActions.AddConverter(new FormatConverter 117 | { 118 | Key = "movie", 119 | Title = "Bink Video", 120 | Extension = "bik", 121 | Type = "movie", 122 | RequiresAttention = false 123 | }); 124 | 125 | //Loop each XML format to have it automatically get .xml suffix 126 | 127 | ScriptActions.AddConverter(new FormatConverter 128 | { 129 | Key = "xmL_conversion", 130 | Type = "text", 131 | Extension = "xml", 132 | RequiresAttention = false 133 | }); 134 | 135 | ScriptActions.AddConverter(new FormatConverter 136 | { 137 | Key = "texture_dds", 138 | Title = "DDS", 139 | Extension = "dds", 140 | Type = "texture", 141 | RequiresAttention = false 142 | }); 143 | 144 | ScriptActions.AddConverter(new FormatConverter 145 | { 146 | Key = "strings_csv", 147 | Title = "CSV", 148 | Extension = "csv", 149 | ExportEvent = (MemoryStream ms, bool arg0) => 150 | { 151 | //Excel doesn't seem to like it? 152 | StringsFile str = new StringsFile(ms); 153 | StringBuilder builder = new StringBuilder(); 154 | builder.Append("ID,String\n"); 155 | foreach (var entry in str.LocalizationStrings) 156 | builder.Append("\"" + entry.ID.ToString() + "\",\"" + entry.Text + "\"\n"); 157 | Console.WriteLine(builder.ToString()); 158 | return builder.ToString(); 159 | }, 160 | Type = "strings" 161 | }); 162 | 163 | ScriptActions.AddConverter(new FormatConverter 164 | { 165 | Key = "script_json", 166 | Title = "JSON", 167 | Extension = "json", 168 | ExportEvent = (MemoryStream ms, bool arg0) => 169 | { 170 | try 171 | { 172 | ScriptData sdata = new ScriptData(new BinaryReader(ms), Utils.IsRaid()); 173 | return (new JSONNode("table", sdata.Root, "")).ToString(); 174 | } 175 | catch (Exception e) 176 | { 177 | MessageBox.Show($"Failed to read scriptdata: \n {e.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 178 | return null; 179 | } 180 | }, 181 | Type = "scriptdata" 182 | }); 183 | 184 | ScriptActions.AddConverter(new FormatConverter 185 | { 186 | Key = "strings_json", 187 | Title = "JSON", 188 | Extension = "json", 189 | ExportEvent = (MemoryStream ms, bool arg0) => 190 | { 191 | StringsFile str = new StringsFile(ms); 192 | StringBuilder builder = new StringBuilder(); 193 | builder.Append("{\n"); 194 | for (int i = 0; i < str.LocalizationStrings.Count; i++) 195 | { 196 | StringEntry entry = str.LocalizationStrings[i]; 197 | builder.Append('\t'); 198 | builder.Append("\"" + entry.ID + "\" : \"" + entry.Text + "\""); 199 | if (i < str.LocalizationStrings.Count - 1) 200 | builder.Append(','); 201 | builder.Append('\n'); 202 | } 203 | builder.Append('}'); 204 | Console.WriteLine(builder.ToString()); 205 | return builder.ToString(); 206 | }, 207 | Type = "strings" 208 | }); 209 | } 210 | 211 | protected override Window CreateShell() 212 | { 213 | return Container.Resolve(); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Assets/arrow-left-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/arrow-left-dark.png -------------------------------------------------------------------------------- /Assets/arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/arrow-left.png -------------------------------------------------------------------------------- /Assets/arrow-right-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/arrow-right-dark.png -------------------------------------------------------------------------------- /Assets/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/arrow-right.png -------------------------------------------------------------------------------- /Assets/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/file.png -------------------------------------------------------------------------------- /Assets/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/folder.png -------------------------------------------------------------------------------- /Assets/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/font.png -------------------------------------------------------------------------------- /Assets/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/grid.png -------------------------------------------------------------------------------- /Assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/image.png -------------------------------------------------------------------------------- /Assets/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/list.png -------------------------------------------------------------------------------- /Assets/lua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/lua.png -------------------------------------------------------------------------------- /Assets/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luffyyy/DieselBundleViewer/34ca5a0da1a52283105add4ed01d641ede5a69f1/Assets/video.png -------------------------------------------------------------------------------- /DieselBundleViewer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0-windows7.0 5 | true 6 | DieselBundleViewer 7 | favicon.ico 8 | DieselBundleViewer.App 9 | 1.2 10 | ModWorkshop 11 | https://github.com/Luffyyy 12 | true 13 | en 14 | Major 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | dlls\DieselEngineFormats.dll 67 | 68 | 69 | dlls\Wwise Sound Library.dll 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /DieselBundleViewer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35222.181 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DieselBundleViewer", "DieselBundleViewer.csproj", "{7EB10C1B-314D-459E-A8B5-C41DD70019B7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {7EB10C1B-314D-459E-A8B5-C41DD70019B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {7EB10C1B-314D-459E-A8B5-C41DD70019B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {7EB10C1B-314D-459E-A8B5-C41DD70019B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {7EB10C1B-314D-459E-A8B5-C41DD70019B7}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {412FFC09-3C21-4632-8619-320838E0646B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Luffy 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. 22 | -------------------------------------------------------------------------------- /Models/FileEntry.cs: -------------------------------------------------------------------------------- 1 | using DieselBundleViewer.Services; 2 | using DieselBundleViewer.ViewModels; 3 | using DieselEngineFormats.Bundle; 4 | using DieselEngineFormats.Utils; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.Text; 10 | 11 | namespace DieselBundleViewer.Models 12 | { 13 | public class FileEntry : IEntry 14 | { 15 | private string _name, _fullpath; 16 | private PackageFileEntry _max_entry = null; 17 | 18 | public Idstring PathIds; 19 | public Idstring LanguageIds; 20 | public Idstring ExtensionIds; 21 | 22 | private uint _size; 23 | public uint Size { 24 | get { 25 | if (_size == 0 && BundleEntries.Count > 0) 26 | { 27 | foreach (var be in BundleEntries) 28 | { 29 | _size += (uint)(be.Value).Length; 30 | } 31 | 32 | _size = (uint)(_size / Math.Max(BundleEntries.Count, 1)); 33 | } 34 | return _size; 35 | } 36 | } 37 | 38 | public string EntryPath 39 | { 40 | get 41 | { 42 | if (_fullpath == null) 43 | { 44 | _fullpath = PathIds.ToString(); 45 | 46 | if (LanguageIds != null) 47 | _fullpath += "." + LanguageIds.ToString(); 48 | 49 | _fullpath += "." + ExtensionIds.ToString(); 50 | } 51 | return _fullpath; 52 | } 53 | set => _fullpath = value; 54 | } 55 | 56 | public string Name 57 | { 58 | get 59 | { 60 | if (_name == null) 61 | _name = Path.GetFileName(EntryPath); 62 | 63 | return _name; 64 | } 65 | 66 | set => _name = value; 67 | } 68 | 69 | public Dictionary BundleEntries { get; set; } 70 | 71 | public DatabaseEntry DBEntry { get; set; } 72 | 73 | public string Type => ExtensionIds?.ToString(); 74 | 75 | public FolderEntry Parent { get; set; } 76 | 77 | public FileEntry() { 78 | BundleEntries = new Dictionary(); 79 | } 80 | 81 | public FileEntry(DatabaseEntry dbEntry) : this() { 82 | DBEntry = dbEntry; 83 | } 84 | 85 | public void LoadPath() 86 | { 87 | if(DBEntry != null) 88 | General.GetFilepath(DBEntry, out PathIds, out LanguageIds, out ExtensionIds, DBEntry.Parent); 89 | } 90 | 91 | public void AddBundleEntry(PackageFileEntry entry) 92 | { 93 | if (!BundleEntries.ContainsKey(entry.PackageName)) 94 | { 95 | BundleEntries.Add(entry.PackageName, entry); 96 | _max_entry = null; 97 | } 98 | } 99 | 100 | /// 101 | /// Checks if the file is in a bundle. 102 | /// 103 | /// The name (idstring) of the bundle 104 | /// true if it's in the bundle 105 | public bool InBundle(Idstring name) => BundleEntries.ContainsKey(name); 106 | 107 | /// 108 | /// Returns whether or not the file is in one of the bundles provided in the arguments 109 | /// 110 | /// Names (idstring) of packages to test with 111 | public bool InBundles(List names) 112 | { 113 | foreach (var bundle in names) 114 | { 115 | if (BundleEntries.ContainsKey(bundle)) 116 | return true; 117 | } 118 | return false; 119 | } 120 | 121 | /// 122 | /// Determines whether or not a file has any data to extract. Cooked physics are ignored since they often are 0 bytes but still exist. 123 | /// 124 | /// true if the file has data 125 | public bool HasData() 126 | { 127 | return Settings.Data.DisplayEmptyFiles || Type == "cooked_physics" || Size > 0; 128 | } 129 | 130 | public object FileData(PackageFileEntry be = null, FormatConverter exporter = null) 131 | { 132 | if (exporter == null) 133 | return FileStream(be); 134 | else 135 | { 136 | MemoryStream stream = FileStream(be); 137 | return stream == null ? null : exporter.Export(FileStream(be)); 138 | } 139 | } 140 | 141 | /// 142 | /// Returns the bytes[] of the file 143 | /// 144 | /// A package entry to use for the data. Defaults to what MaxBundleEntry returns. 145 | private byte[] FileEntryBytes(PackageFileEntry entry) 146 | { 147 | if (entry == null) 148 | { 149 | Console.WriteLine("Entry null?"); 150 | return null; 151 | } 152 | 153 | string bundle_path = Path.Combine(Utils.CurrentWindow.AssetsDir, entry.Parent.BundleName + ".bundle"); 154 | if (!File.Exists(bundle_path)) 155 | { 156 | Console.WriteLine("Bundle: {0}, does not exist", bundle_path); 157 | return null; 158 | } 159 | 160 | try 161 | { 162 | using FileStream fs = new FileStream(bundle_path, FileMode.Open, FileAccess.Read); 163 | using BinaryReader br = new BinaryReader(fs); 164 | if (entry.Length != 0) 165 | { 166 | fs.Position = entry.Address; 167 | return br.ReadBytes((int)(entry.Length == -1 ? fs.Length - fs.Position : entry.Length)); 168 | } 169 | else 170 | return new byte[0]; 171 | } 172 | catch (Exception exc) 173 | { 174 | Console.WriteLine("FAIL"); 175 | Console.WriteLine(exc.Message); 176 | Console.WriteLine(exc.StackTrace); 177 | } 178 | 179 | return null; 180 | } 181 | 182 | /// 183 | /// Returns a MemoryStream of the file. 184 | /// 185 | /// A package entry to use for the data. Defaults to what MaxBundleEntry returns. 186 | public MemoryStream FileStream(PackageFileEntry entry = null) 187 | { 188 | entry ??= MaxBundleEntry(); 189 | 190 | byte[] bytes = FileEntryBytes(entry); 191 | if (bytes == null) 192 | return null; 193 | 194 | MemoryStream stream = new MemoryStream(bytes) { Position = 0 }; 195 | return stream; 196 | } 197 | 198 | public byte[] FileBytes(PackageFileEntry entry = null) 199 | { 200 | entry ??= MaxBundleEntry(); 201 | 202 | return FileEntryBytes(entry); 203 | } 204 | 205 | /// 206 | /// Returns the bundle that has the largest version of the file. 207 | /// 208 | public PackageFileEntry MaxBundleEntry() 209 | { 210 | if (BundleEntries.Count == 0) 211 | return null; 212 | 213 | if (_max_entry == null) 214 | { 215 | _max_entry = null; 216 | foreach (var pair in BundleEntries) 217 | { 218 | PackageFileEntry entry = pair.Value; 219 | if (_max_entry == null) 220 | { 221 | _max_entry = entry; 222 | continue; 223 | } 224 | 225 | if (entry.Length > _max_entry.Length) 226 | _max_entry = entry; 227 | } 228 | 229 | } 230 | 231 | return _max_entry; 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /Models/FolderEntry.cs: -------------------------------------------------------------------------------- 1 | using DieselBundleViewer.Services; 2 | using DieselBundleViewer.ViewModels; 3 | using DieselEngineFormats.Bundle; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Text; 11 | 12 | namespace DieselBundleViewer.Models 13 | { 14 | public class FolderEntry(uint level = 0) : IEntry 15 | { 16 | public SortedDictionary Children { get; set; } = new SortedDictionary(); 17 | 18 | public FolderEntry Parent { get; set; } 19 | public string EntryPath { get; set; } 20 | public string Name { get; set; } 21 | public uint Size => 0; 22 | 23 | private ulong? totalSize; 24 | public ulong TotalSize { 25 | get 26 | { 27 | if (totalSize != null) 28 | return (ulong)totalSize; 29 | 30 | ulong size = 0; 31 | var children = GetAllChildren(); 32 | foreach (var entry in children) 33 | { 34 | if (entry is FileEntry) 35 | size += entry.Size; 36 | } 37 | totalSize = size; 38 | return size; 39 | } 40 | } 41 | 42 | public string Type => "File folder"; 43 | 44 | private uint folderLevel = level; 45 | 46 | public FolderEntry(FileEntry entry, uint level = 0) : this(level) 47 | { 48 | AddFileEntry(entry); 49 | } 50 | 51 | public FolderEntry(Dictionary ents, uint level = 0) : this(level) 52 | { 53 | foreach (KeyValuePair entry in ents) 54 | { 55 | AddFileEntry(entry.Value); 56 | } 57 | } 58 | 59 | public bool InBundle(Idstring name) 60 | { 61 | foreach(var child in Children) 62 | { 63 | if (child.Value.InBundle(name)) 64 | return true; 65 | } 66 | return false; 67 | } 68 | 69 | public bool InBundles(List names) 70 | { 71 | foreach (var child in Children) 72 | { 73 | if (child.Value.InBundles(names)) 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | public bool HasVisibleFiles() 80 | { 81 | if (Settings.Data.DisplayEmptyFiles) 82 | return true; 83 | 84 | foreach (var child in Children.Values) 85 | { 86 | if ((child is FolderEntry folder && folder.HasVisibleFiles()) || child.Size > 0) 87 | return true; 88 | } 89 | return false; 90 | } 91 | 92 | public List ChildObjects(Idstring pck = null) 93 | { 94 | List objs = new List(); 95 | var children = Children.Values; 96 | 97 | foreach (var child in children) 98 | { 99 | if (child is FolderEntry entry && (pck == null || entry.ContainsAnyBundleEntries(pck))) 100 | objs.Add(child); 101 | } 102 | 103 | foreach (var child in children) 104 | { 105 | if ((!(child is FileEntry entry) || pck == null || entry.BundleEntries.ContainsKey(pck)) && !(child is FolderEntry)) 106 | objs.Add(child); 107 | } 108 | 109 | return objs; 110 | } 111 | 112 | public void AddFileEntry(FileEntry entry) 113 | { 114 | string[] splt = entry.PathIds.HasUnHashed ? entry.PathIds.UnHashed.Split("/") : null; 115 | if (splt != null && splt.Length > (folderLevel + 1)) 116 | { 117 | string initial_folder = splt[folderLevel]; 118 | if (!Children.ContainsKey(initial_folder)) 119 | { 120 | FolderEntry folder = new FolderEntry(entry, this.folderLevel + 1) { Parent = this, EntryPath = "" }; 121 | for (int i = 0; i <= this.folderLevel; i++) 122 | { 123 | folder.EntryPath = Utils.CombineDir(folder.EntryPath, splt[i]); 124 | } 125 | //Debug.Print(string.Format("Folder: {0}", folder.Path)); 126 | 127 | folder.Name = initial_folder; 128 | Children.Add(initial_folder, folder); 129 | } 130 | else 131 | { 132 | ((FolderEntry)Children[initial_folder]).AddFileEntry(entry); 133 | } 134 | } 135 | else 136 | { 137 | entry.Parent = this; 138 | Children.Add(entry.Name, entry); 139 | } 140 | } 141 | 142 | public void AddToTree(FolderEntry item, Idstring pck = null) 143 | { 144 | foreach (KeyValuePair entry in Children) 145 | { 146 | if (entry.Value is FolderEntry) 147 | { 148 | FolderEntry _entry = entry.Value as FolderEntry; 149 | 150 | if (pck != null && !_entry.ContainsAnyBundleEntries(pck)) 151 | continue; 152 | 153 | // item.Children.Add(item); 154 | _entry.AddToTree(item, pck); 155 | } 156 | } 157 | } 158 | 159 | public bool ContainsAnyBundleEntries(Idstring package = null) 160 | { 161 | foreach (KeyValuePair entry in Children) 162 | { 163 | if (entry.Value is FolderEntry) 164 | { 165 | FolderEntry _entry = entry.Value as FolderEntry; 166 | if (_entry.ContainsAnyBundleEntries(package)) 167 | { 168 | return true; 169 | } 170 | } 171 | else if (entry.Value is FileEntry) 172 | { 173 | FileEntry _entry = entry.Value as FileEntry; 174 | if (_entry.BundleEntries.Count != 0 && (package != null ? _entry.BundleEntries.ContainsKey(package) : true)) 175 | { 176 | return true; 177 | } 178 | } 179 | } 180 | 181 | return false; 182 | } 183 | 184 | public List GetAllChildren(bool ignoreSelectedBundles = false, List list = null) 185 | { 186 | if (list == null) 187 | list = new List(); 188 | 189 | var selectedBundles = Utils.CurrentWindow.SelectedBundles; 190 | 191 | foreach (KeyValuePair pairEntry in Children) 192 | { 193 | IEntry entry = pairEntry.Value; 194 | if (ignoreSelectedBundles || selectedBundles.Count == 0 || entry.InBundles(selectedBundles)) 195 | { 196 | list.Add(entry); 197 | if (entry is FolderEntry) 198 | (entry as FolderEntry).GetAllChildren(ignoreSelectedBundles, list); 199 | } 200 | } 201 | 202 | return list; 203 | } 204 | 205 | public void ForEachEntryInDirectory(string dir, Action func = null) 206 | { 207 | foreach (KeyValuePair pairEntry in Children) 208 | { 209 | IEntry entry = pairEntry.Value; 210 | 211 | if (Utils.GetDirectory(entry.EntryPath) == dir) 212 | func(entry); 213 | 214 | if (entry is FolderEntry && dir.StartsWith(entry.EntryPath)) 215 | (entry as FolderEntry).ForEachEntryInDirectory(dir, func); 216 | } 217 | } 218 | 219 | 220 | public void ForEachEntry(Action func) 221 | { 222 | foreach (KeyValuePair pairEntry in Children) 223 | { 224 | IEntry entry = pairEntry.Value; 225 | func(entry); 226 | if (entry is FolderEntry) 227 | (entry as FolderEntry).ForEachEntry(func); 228 | } 229 | } 230 | 231 | public void ResetTotalSize() 232 | { 233 | totalSize = null; 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /Models/IEntry.cs: -------------------------------------------------------------------------------- 1 | using DieselBundleViewer.ViewModels; 2 | using DieselEngineFormats.Bundle; 3 | using System.Collections.Generic; 4 | 5 | namespace DieselBundleViewer.Models 6 | { 7 | public interface IEntry 8 | { 9 | string Name { get; } 10 | string Type { get; } 11 | uint Size { get; } 12 | string EntryPath { get; } 13 | FolderEntry Parent { get; set; } 14 | 15 | bool InBundle(Idstring name); 16 | bool InBundles(List names); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Models/Script.cs: -------------------------------------------------------------------------------- 1 | using Prism.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace DieselBundleViewer.Models 7 | { 8 | public class Script 9 | { 10 | public string Name { get; set; } 11 | public DelegateCommand OpenScript { get; set; } 12 | 13 | public dynamic Object; 14 | 15 | public Script(string name, dynamic obj) 16 | { 17 | Name = name; 18 | Object = obj; 19 | OpenScript = new DelegateCommand(Execute); 20 | } 21 | 22 | public void Execute() 23 | { 24 | Object.execute(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Objects/PageData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace DieselBundleViewer.Objects 6 | { 7 | public enum Sorting 8 | { 9 | Name, 10 | Type, 11 | Size 12 | } 13 | 14 | public class PageData(string path) 15 | { 16 | public string Path { get; set; } = path; 17 | public string Search { get; set; } 18 | public bool MatchWord { get; set; } 19 | public bool UseRegex { get; set; } 20 | public bool IsSearch { get; set; } 21 | public bool FullPath { get; set; } 22 | public Sorting SortBy { get; set; } = Sorting.Name; 23 | public bool Ascending { get; set; } = true; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DieselBundleViewer 2 | 3 | DieselBundleViewer is a program that allows you to view the files of all diesel games (PDTH, PD2, and RWW2) 4 | The program is mostly based of DieselToolBox with some code taken from there. 5 | 6 | ## What's the difference between DieselToolBox and this? 7 | * This version hopes to eliminate most of the bugs that the previous had and make it a viable option to DieselBundleModder. 8 | * Hopefully more user friendly. 9 | * New features! 10 | * Pressing the list headers actually sorts the items! 11 | * grid view for items. 12 | * Save files via dialog. 13 | * Powerful finder that let's you search with whole word and regex. 14 | * Select multiple bundles with a dialog and not a slow as fuck context menu. 15 | * Light/dark mode switch (defaults to dark 😎). 16 | * Option to hide 0 byte files (defaults to true). 17 | * Play/convert stream (wem) to wav on the fly. 18 | 19 | * This version also moves to WPF / .NET Core; I've tried using a bunch of cross-platform .NET GUIs, but in the end I decided it's best to just go full WPF and so this means this program is only for Windows. 20 | 21 | ## Installation 22 | Simply download from releases and unzip it somewhere. 23 | In case you don't have .NET Core 3.1, the program will prompt you to install it. However, you can install it easily from here: https://dotnet.microsoft.com/download/dotnet-core/current/runtime 24 | 25 | ## Builiding 26 | The program is built using Visual Studio 2019 and .NET Core 3.1. Make sure you have both. 27 | 28 | Most icons are from https://icons8.com/ 29 | 30 | Source code of the DieselEngineFormats library https://github.com/Luffyyy/DieselEngineFormats 31 | -------------------------------------------------------------------------------- /Services/Definitions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace DieselBundleViewer.Services 7 | { 8 | public class Definitions 9 | { 10 | public static string DataDir = "Data"; 11 | public static string[] ScriptDataExtensions = 12 | { 13 | "sequence_manager", 14 | "environment", 15 | "menu", 16 | "continent", 17 | "continents", 18 | "mission", 19 | "nav_data", 20 | "cover_data", 21 | "world", 22 | "world_cameras", 23 | "prefhud", 24 | "objective", 25 | "credits", 26 | "hint", 27 | "comment", 28 | "dialog", 29 | "dialog_index", 30 | "timeline", 31 | "action_message", 32 | "achievment", 33 | "controller_settings", 34 | "world_sounds", 35 | "blacklist", 36 | "drama_index", 37 | "drama" 38 | }; 39 | 40 | public static string[] RawTextExtension = 41 | { 42 | "unit", 43 | "material_config", 44 | "object", 45 | "animation_def", 46 | "animation_states", 47 | "animation_state_machine", 48 | "animation_subset", 49 | "merged_font", 50 | "physic_effect", 51 | "post_processor", 52 | "scene", 53 | "gui", 54 | "effect", 55 | "render_template_database", 56 | "xml", 57 | "network_settings", 58 | "xbox_live", 59 | "atom_batcher_settings", 60 | "camera_shakes", 61 | "cameras", 62 | "decals", 63 | "physics_settings", 64 | "scenes", 65 | "texture_channels", 66 | "diesel_layers", 67 | "light_intensities" 68 | }; 69 | 70 | public static string TypeFromExtension(string ext) 71 | { 72 | if (RawTextExtension.Contains(ext)) 73 | return "text"; 74 | else if (ScriptDataExtensions.Contains(ext)) 75 | return "scriptdata"; 76 | 77 | return ext; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Services/DragDropController.cs: -------------------------------------------------------------------------------- 1 | using DieselBundleViewer.Models; 2 | using DieselBundleViewer.ViewModels; 3 | using DieselEngineFormats.Bundle; 4 | using Prism.Events; 5 | using Prism.Dialogs; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Runtime.InteropServices; 9 | using System.Threading; 10 | using System.Windows; 11 | using System.Windows.Controls; 12 | 13 | namespace DieselBundleViewer.Services 14 | { public class StartProgress : PubSubEvent { } 15 | 16 | class DragDropController(bool outputFullPaths) 17 | { 18 | public bool OutputFullPaths = outputFullPaths; 19 | private TextBox Control { get; set; } = new TextBox(); 20 | 21 | private ProgressDialogViewModel Progress; 22 | 23 | private EventAggregator Aggregator; 24 | 25 | public void DoDragDrop(List entries) 26 | { 27 | VirtualFileDataObject virtualFileDataObject = new VirtualFileDataObject(StartDragDrop, EndDragDrop); 28 | var files = new List(); 29 | 30 | string currentDir = Utils.CurrentWindow.CurrentDir; 31 | foreach (var entry in entries) 32 | { 33 | if (entry is FileEntry file) 34 | PopulateFile(files, file, currentDir); 35 | else if(entry is FolderEntry folder) 36 | PopulateFiles(files, folder, currentDir); 37 | } 38 | 39 | //Show dialog only when there are more than 5 items moved. 40 | Console.WriteLine("Beginning with {0} files", files.Count); 41 | virtualFileDataObject.SetData(files); 42 | 43 | if(files.Count > 10) 44 | { 45 | Aggregator = new EventAggregator(); 46 | Aggregator.GetEvent().Subscribe(() => 47 | { 48 | var pms = new DialogParameters(); 49 | pms.Add("ProgressAction", new Action(dialog => { 50 | Progress = dialog; 51 | })); 52 | 53 | Utils.ShowDialog("ProgressDialog", pms); 54 | }, ThreadOption.UIThread); 55 | } 56 | 57 | try 58 | { 59 | VirtualFileDataObject.DoDragDrop(Control, virtualFileDataObject, DragDropEffects.Copy); 60 | } 61 | catch (COMException) 62 | { 63 | Console.WriteLine("Failed Drag-Drop."); 64 | } 65 | Console.WriteLine("Finished Drag-Drop operation setup"); 66 | } 67 | 68 | public void StartDragDrop(VirtualFileDataObject data) 69 | { 70 | if(Aggregator != null) 71 | { 72 | Thread t = new Thread(() => Aggregator.GetEvent().Publish()); 73 | t.IsBackground = true; 74 | t.Start(); 75 | } 76 | } 77 | public void EndDragDrop(VirtualFileDataObject data) 78 | { 79 | 80 | } 81 | 82 | int i = 0; 83 | 84 | public void PopulateFile(List files, FileEntry parent, string removeDirectory) 85 | { 86 | if (parent.BundleEntries.Count == 0) 87 | return; 88 | 89 | string name = parent.EntryPath; 90 | if (!OutputFullPaths && !string.IsNullOrEmpty(removeDirectory)) 91 | name = name.Replace(removeDirectory, ""); 92 | 93 | files.Add(new VirtualFileDataObject.FileDescriptor() 94 | { 95 | Name = name, 96 | StreamContents = (stream) => 97 | { 98 | i++; 99 | int total = files.Count; 100 | 101 | PackageFileEntry maxBundleEntry = parent.MaxBundleEntry(); 102 | 103 | byte[] bytes = parent.FileBytes(maxBundleEntry); 104 | if (bytes != null) 105 | stream.Write(bytes, 0, bytes.Length); 106 | else 107 | Console.WriteLine("Failed to extract {0} from package: {1}", name, maxBundleEntry.PackageName.ToString()); 108 | 109 | if(Progress != null) 110 | { 111 | if (Progress.IsClosed) 112 | throw new Exception(); //No clue how to really stop that other than exceptions lol. 113 | else 114 | Progress.SetProgress($"Copying {parent.EntryPath}", i, files.Count); 115 | } 116 | } 117 | }); 118 | } 119 | 120 | public void PopulateFiles(List files, FolderEntry parent, string removeDirectory) 121 | { 122 | foreach (var entry in parent.GetAllChildren()) 123 | { 124 | if (entry is FileEntry file) 125 | PopulateFile(files, file, removeDirectory); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Services/FileManager.cs: -------------------------------------------------------------------------------- 1 | using DieselBundleViewer.Models; 2 | using DieselBundleViewer.ViewModels; 3 | using DieselEngineFormats.Bundle; 4 | using Prism.Dialogs; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Windows.Forms; 13 | using DialogResult = System.Windows.Forms.DialogResult; 14 | 15 | namespace DieselBundleViewer.Services 16 | { 17 | public static class FileManager 18 | { 19 | public class TempFile : IDisposable 20 | { 21 | 22 | public string FilePath { get; set; } 23 | public Process RunProcess { get; set; } 24 | public PackageFileEntry Entry { get; set; } 25 | public string ExporterKey { get; set; } 26 | public bool Disposed { get; set; } 27 | 28 | public TempFile(FileEntry entry, PackageFileEntry be = null, FormatConverter converter = null, string filePath = null, bool includeInnerPath=true) 29 | { 30 | string path; 31 | if (includeInnerPath) 32 | path = entry.EntryPath.Replace("/", "\\"); 33 | else 34 | path = entry.Name; 35 | 36 | if(filePath == null) 37 | { 38 | FilePath = Path.Combine(Path.GetTempPath(), "DBV", path); 39 | if (converter != null && converter.Extension != null) 40 | FilePath += "." + converter.Extension; 41 | } else 42 | FilePath = filePath; 43 | 44 | SaveFile(entry, FilePath, converter, be, true); 45 | 46 | Entry = be; 47 | if (converter != null) 48 | ExporterKey = converter.Key; 49 | } 50 | 51 | ~TempFile() 52 | { 53 | Dispose(); 54 | } 55 | 56 | public void Dispose() 57 | { 58 | GC.SuppressFinalize(this); 59 | 60 | if (Disposed) 61 | return; 62 | 63 | try 64 | { 65 | if (!(RunProcess?.HasExited ?? true)) 66 | RunProcess?.Kill(); 67 | 68 | if (File.Exists(FilePath)) 69 | File.Delete(FilePath); 70 | 71 | Console.WriteLine("Deleted temp file {0}", FilePath); 72 | 73 | } 74 | catch (Exception exc) 75 | { 76 | Console.WriteLine(exc.Message); 77 | } 78 | 79 | Disposed = true; 80 | } 81 | } 82 | 83 | private static Dictionary TempFiles = new Dictionary(); 84 | 85 | public static bool Disposed { get; set; } 86 | 87 | public static void UpdateTempDirectory() 88 | { 89 | string temp_path; 90 | 91 | try 92 | { 93 | if (!Directory.Exists(temp_path = Path.Combine(Path.GetTempPath(), "DBV"))) 94 | Directory.CreateDirectory(temp_path); 95 | else 96 | { 97 | foreach (string file in Directory.GetFiles(temp_path)) 98 | { 99 | File.Delete(file); 100 | } 101 | } 102 | } 103 | catch (Exception exc) 104 | { 105 | Console.WriteLine(exc.Message); 106 | } 107 | } 108 | 109 | private static bool IsFileAvailable(string path) 110 | { 111 | try 112 | { 113 | using FileStream str = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 114 | return str.Length > 0; 115 | } 116 | catch 117 | { 118 | return false; 119 | } 120 | } 121 | 122 | private static bool ShouldDeleteFile(TempFile file) 123 | { 124 | return file.RunProcess != null && file.RunProcess.HasExited && IsFileAvailable(file.FilePath); 125 | } 126 | 127 | private static void Update(object sender, EventArgs e) 128 | { 129 | if (TempFiles.Count == 0) 130 | return; 131 | 132 | List to_delete = new List(); 133 | foreach (var temp in TempFiles) 134 | { 135 | if (IsFileAvailable(temp.Value.FilePath)) 136 | to_delete.Add(temp.Key); 137 | } 138 | 139 | foreach (FileEntry ent in to_delete) 140 | { 141 | DeleteTempFile(ent); 142 | } 143 | } 144 | 145 | public static void ConvertFile(FileEntry entry, Action done) 146 | { 147 | if (entry.BundleEntries.Count == 0) 148 | { 149 | Console.WriteLine("No bundle entries."); 150 | return; 151 | } 152 | 153 | string typ = Definitions.TypeFromExtension(entry.ExtensionIds.ToString()); 154 | 155 | if (ScriptActions.Converters.TryGetValue(typ, out Dictionary value)) 156 | { 157 | var convs = value; 158 | 159 | //Don't open the dialog for things that are just an extension change 160 | if(convs.Count == 1) 161 | { 162 | FormatConverter format = convs.First().Value; 163 | if (!format.RequiresAttention) 164 | { 165 | done(format); 166 | return; 167 | } 168 | } 169 | 170 | var formats = convs.Values.ToList(); 171 | formats.Add(new FormatConverter { Title = "None" }); 172 | 173 | DialogParameters pms = new DialogParameters 174 | { 175 | { "Formats", formats } 176 | }; 177 | Utils.CurrentDialogService.ShowDialog("ConvertFileDialog", pms, r => 178 | { 179 | if (r.Result == ButtonResult.OK) 180 | { 181 | FormatConverter selected = pms.GetValue("Format"); 182 | done(selected.Title == "None" ? null : selected); 183 | } 184 | }); 185 | } else 186 | { 187 | done(null); 188 | } 189 | } 190 | 191 | public static void SaveFileConvert(FileEntry file, string removeDirectory) 192 | { 193 | SaveFileDialog sfd = new SaveFileDialog { FileName = file.Name }; 194 | 195 | string typ = Definitions.TypeFromExtension(file.ExtensionIds.ToString()); 196 | if (ScriptActions.Converters.TryGetValue(typ, out Dictionary value)) 197 | { 198 | var convs = value; 199 | string filter = ""; 200 | var conerters = new List(); 201 | foreach (var pair in convs) 202 | { 203 | FormatConverter conv = pair.Value; 204 | filter += $"{conv.Title} (*.{conv.Extension})|*.{conv.Extension}|"; 205 | conerters.Add(conv); 206 | } 207 | sfd.Filter = filter.Remove(filter.Length - 1); // + "|All files (*.*)|*.*"; 208 | if (sfd.ShowDialog() == DialogResult.OK) 209 | { 210 | string filePath = Path.Combine(Utils.GetDirectory(file.EntryPath), sfd.FileName); 211 | if (!Settings.Data.ExtractFullDir && !string.IsNullOrEmpty(removeDirectory)) 212 | filePath = filePath.Replace(removeDirectory.Replace("/", "\\") + "\\", ""); 213 | SaveFile(file, filePath, conerters[sfd.FilterIndex - 1]); 214 | } 215 | } 216 | } 217 | 218 | public static void SaveFileAs(FileEntry file, string removeDirectory) 219 | { 220 | SaveFileDialog sfd = new SaveFileDialog { FileName = file.Name, Filter = "All files (*.*)|*.*" }; 221 | if (sfd.ShowDialog() == DialogResult.OK) 222 | { 223 | string filePath = Path.Combine(Utils.GetDirectory(file.EntryPath), sfd.FileName); 224 | if (!Settings.Data.ExtractFullDir && !string.IsNullOrEmpty(removeDirectory)) 225 | filePath = filePath.Replace(removeDirectory.Replace("/", "\\") + "\\", ""); 226 | SaveFile(file, filePath); 227 | } 228 | } 229 | 230 | public static void SaveFile(FileEntry file, string path, FormatConverter converter=null, PackageFileEntry be = null, bool checkIfExists=false) 231 | { 232 | if (checkIfExists && File.Exists(path)) 233 | return; 234 | 235 | object file_data = file.FileData(be, converter); 236 | 237 | string dir = Path.GetDirectoryName(path); 238 | if (!Directory.Exists(dir)) 239 | Directory.CreateDirectory(dir); 240 | 241 | if (file_data is byte[] v) 242 | File.WriteAllBytes(path, v); 243 | else if (file_data is Stream fs) 244 | { 245 | if (converter != null && converter.SaveEvent != null) 246 | converter.SaveEvent(fs, path); 247 | else 248 | { 249 | using FileStream file_stream = File.Create(path); 250 | fs.CopyTo(file_stream); 251 | fs.Close(); 252 | } 253 | } 254 | else if (file_data is string dataStr) 255 | File.WriteAllText(path, dataStr); 256 | else if (file_data is string[] dataArr) 257 | File.WriteAllLines(path, dataArr); 258 | } 259 | 260 | public static void SaveMultiple(List entries, string removeDirectory) 261 | { 262 | FolderBrowserDialog fbd = new FolderBrowserDialog(); 263 | if (fbd.ShowDialog() == DialogResult.OK) 264 | { 265 | var pms = new DialogParameters(); 266 | pms.Add("ProgressAction", new Action(dialog => { 267 | Thread t = new Thread(o => 268 | { 269 | Thread.Sleep(100); //Give dialog time to show up 270 | int total = entries.Count; 271 | for (int i = 0; i < total; i++) 272 | { 273 | if (dialog.IsClosed) 274 | return; 275 | 276 | IEntry entry = entries[i]; 277 | string entryPath = entry.EntryPath.Replace("/", "\\"); 278 | if (!Settings.Data.ExtractFullDir && !string.IsNullOrEmpty(removeDirectory)) 279 | entryPath = entryPath.Replace(removeDirectory.Replace("/", "\\") + "\\", ""); 280 | 281 | string path = Path.Combine(fbd.SelectedPath, entryPath); 282 | 283 | if (entry is FileEntry childFile) 284 | { 285 | int current = i + 1; 286 | dialog.SetProgress($"Copying {entry.EntryPath}", current, total); 287 | SaveFile(childFile, path); 288 | } 289 | else if (entry is FolderEntry childFolder) 290 | { 291 | Directory.CreateDirectory(path); 292 | } 293 | } 294 | }); 295 | 296 | t.IsBackground = true; 297 | t.Start(); 298 | })); 299 | 300 | Utils.ShowDialog("ProgressDialog", pms); 301 | } 302 | } 303 | 304 | public static async Task ExtractAll(string outputDir, List files, string removeDirectory, IProgress progress, CancellationToken ct) 305 | { 306 | progress.Report(new ProgressRecord("Determining working order", 0, 0)); 307 | 308 | var filesToProcess = files.OfType() 309 | .Select(fe => 310 | { 311 | var bh = fe.MaxBundleEntry(); 312 | return (fe, bh, bh.Parent.BundleName); 313 | }) 314 | .GroupBy(i => i.BundleName) 315 | .ToDictionary(t => t.Key, t => t.OrderBy(e => e.pfe.Address).ToList()); 316 | 317 | var dirsToCreate = files.OfType().OrderBy(fe => fe.EntryPath).ToList(); 318 | int total = dirsToCreate.Count; 319 | int currNum = 0; 320 | foreach(var entry in dirsToCreate) 321 | { 322 | progress.Report(new ProgressRecord("Creating directory tree", total, currNum)); 323 | 324 | string entryPath = entry.EntryPath.Replace("/", "\\"); 325 | if (!Settings.Data.ExtractFullDir && !string.IsNullOrEmpty(removeDirectory)) 326 | entryPath = entryPath.Replace(removeDirectory.Replace("/", "\\") + "\\", ""); 327 | string path = Path.Combine(outputDir, entryPath); 328 | Directory.CreateDirectory(path); 329 | 330 | currNum++; 331 | } 332 | 333 | total = filesToProcess.Select(kv => kv.Value.Count).Sum(); 334 | currNum = 0; 335 | foreach (var (bundleName, fileList) in filesToProcess) 336 | { 337 | if (ct.IsCancellationRequested) { return; } 338 | 339 | string bundle_path = Path.Combine(Utils.CurrentWindow.AssetsDir, bundleName + ".bundle"); 340 | if (!File.Exists(bundle_path)) 341 | { 342 | Console.WriteLine("Bundle: {0}, does not exist!", bundle_path); 343 | continue; 344 | } 345 | 346 | if (fileList.Count == 0) { continue; } 347 | 348 | using var fs = new FileStream(bundle_path, FileMode.Open, FileAccess.Read, FileShare.Read, 16834, true); 349 | 350 | if (fileList.Select(i => i.pfe.Length).Sum() > (fs.Length / 2)) 351 | { 352 | progress.Report(new ProgressRecord("Reading bundle", total, currNum)); 353 | var entireBundle = new byte[fs.Length]; 354 | await fs.ReadAsync(entireBundle.AsMemory(0, (int)fs.Length), ct); 355 | 356 | foreach (var (entry, pfe, bn) in fileList) 357 | { 358 | progress.Report(new ProgressRecord($"Copying {entry.EntryPath}", total, currNum)); 359 | if (ct.IsCancellationRequested) { return; } 360 | 361 | string entryPath = entry.EntryPath.Replace("/", "\\"); 362 | if (!Settings.Data.ExtractFullDir && !string.IsNullOrEmpty(removeDirectory)) 363 | entryPath = entryPath.Replace(removeDirectory.Replace("/", "\\") + "\\", ""); 364 | string path = Path.Combine(outputDir, entryPath); 365 | 366 | using var outfile = File.Create(path); 367 | await outfile.WriteAsync(entireBundle.AsMemory((int)pfe.Address, (int)pfe.Length), ct); 368 | 369 | currNum++; 370 | } 371 | } 372 | else 373 | { 374 | List writes = new List(fileList.Count); 375 | foreach (var (entry, pfe, bn) in fileList) 376 | { 377 | progress.Report(new ProgressRecord($"Copying {entry.EntryPath}", total, currNum)); 378 | 379 | string entryPath = entry.EntryPath.Replace("/", "\\"); 380 | if (!Settings.Data.ExtractFullDir && !string.IsNullOrEmpty(removeDirectory)) 381 | entryPath = entryPath.Replace(removeDirectory.Replace("/", "\\") + "\\", ""); 382 | string path = Path.Combine(outputDir, entryPath); 383 | 384 | using var outfile = File.Create(path); 385 | var buf = new byte[pfe.Length]; 386 | fs.Seek(pfe.Address, SeekOrigin.Begin); 387 | await fs.ReadAsync(buf.AsMemory(0, pfe.Length), ct); 388 | await outfile.WriteAsync(buf.AsMemory(0, pfe.Length), ct); 389 | 390 | currNum++; 391 | } 392 | } 393 | } 394 | 395 | progress.Report(new ProgressRecord($"Done", total, total)); 396 | } 397 | 398 | public static void ViewFile(FileEntry entry, PackageFileEntry be = null) 399 | { 400 | ConvertFile(entry, converter => DoViewFile(entry, be, converter)); 401 | } 402 | 403 | public static void DoViewFile(FileEntry entry, PackageFileEntry be = null, FormatConverter exporter=null) 404 | { 405 | try 406 | { 407 | if (entry.BundleEntries.Count == 0) 408 | return; 409 | 410 | TempFile temp = GetTempFile(entry, be, exporter); 411 | GC.Collect(); 412 | Process proc = new Process(); 413 | proc.StartInfo.FileName = "explorer"; 414 | proc.StartInfo.Arguments = $"\"{temp.FilePath}\""; 415 | proc.Start(); 416 | TempFiles.TryAdd(entry, temp); 417 | } 418 | catch (Exception exc) 419 | { 420 | Console.WriteLine(exc.Message); 421 | Console.WriteLine(exc.StackTrace); 422 | } 423 | } 424 | 425 | public static TempFile CreateTempFile(FileEntry entry, PackageFileEntry be = null, FormatConverter exporter = null) 426 | { 427 | if (TempFiles.ContainsKey(entry)) 428 | DeleteTempFile(entry); 429 | 430 | TempFile temp = new TempFile(entry, be, exporter); 431 | 432 | return temp; 433 | } 434 | 435 | public static TempFile GetTempFile(FileEntry file, PackageFileEntry entry = null, FormatConverter exporter = null) 436 | { 437 | TempFile path; 438 | if (!TempFiles.TryGetValue(file, out TempFile value) || value.Disposed || !File.Exists(value.FilePath) || (exporter != null && value.ExporterKey != exporter.Key) || value.Entry != entry) 439 | { 440 | if (TempFiles.ContainsKey(file)) 441 | DeleteTempFile(file); 442 | 443 | path = CreateTempFile(file, entry, exporter); 444 | } 445 | else 446 | path = value; 447 | 448 | return path; 449 | } 450 | 451 | public static void DeleteTempFile(FileEntry entry) 452 | { 453 | if (!TempFiles.TryGetValue(entry, out TempFile value)) 454 | return; 455 | 456 | TempFile temp_file = value; 457 | 458 | temp_file.Dispose(); 459 | 460 | TempFiles.Remove(entry); 461 | } 462 | 463 | public static void DeleteAllTempFiles() 464 | { 465 | Console.WriteLine("Deleting temporary files."); 466 | List key_list = TempFiles.Values.ToList(); 467 | for (int i = 0; i < TempFiles.Count; i++) 468 | { 469 | key_list[i].Dispose(); 470 | } 471 | 472 | TempFiles.Clear(); 473 | } 474 | } 475 | 476 | } 477 | -------------------------------------------------------------------------------- /Services/FixListView.cs: -------------------------------------------------------------------------------- 1 | using DieselBundleViewer.Services; 2 | using Prism.Mvvm; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Windows.Controls; 7 | using System.Windows.Input; 8 | 9 | /// 10 | /// This is generally needed because the extended selection mode in lists doesn't really work as one would expect. 11 | /// Instead of selecting everything via Ctrl+A, it selects only what is visible. This is fine in terms of virtualization, 12 | /// but awful in pratice and so we obviously need to fix. 13 | /// 14 | namespace DieselBundleViewer.ViewModels 15 | { 16 | /// 17 | /// A class to have a base for list item based items so we can deal with them without being too specific 18 | /// 19 | public class ListItemViewModelBase : BindableBase 20 | { 21 | protected bool isSelected; 22 | public virtual bool IsSelected { get => isSelected; set => SetProperty(ref isSelected, value); } 23 | } 24 | 25 | public class FixListView 26 | { 27 | ListBox lastList; 28 | 29 | public void ListPreviewKeydown(object sender, KeyEventArgs e) 30 | { 31 | if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.A)) 32 | { 33 | ListBox list = (ListBox)sender; 34 | foreach (ListItemViewModelBase item in list.Items) 35 | { 36 | item.IsSelected = true; 37 | } 38 | } 39 | } 40 | 41 | public void ListSelectionChanged(object sender, SelectionChangedEventArgs e) 42 | { 43 | if (Keyboard.IsKeyDown(Key.LeftShift)) 44 | { 45 | ListBox list = (ListBox)sender; 46 | 47 | var items = list.SelectedItems; 48 | if (items.Count == 0) 49 | return; 50 | 51 | var firstIndex = list.SelectedIndex; 52 | var lastItem = (ListItemViewModelBase)items[items.Count - 1]; 53 | 54 | if (lastItem != null) 55 | { 56 | int lastIndex = list.Items.IndexOf(lastItem); 57 | 58 | if (firstIndex > lastIndex) 59 | { 60 | int temp = firstIndex; 61 | firstIndex = list.Items.IndexOf(items[1]); 62 | lastIndex = temp; 63 | } 64 | 65 | for (int i = firstIndex; i < lastIndex + 1; i++) 66 | { 67 | var item = (list.Items[i] as ListItemViewModelBase); 68 | if (!item.IsSelected) 69 | item.IsSelected = true; 70 | } 71 | } 72 | } 73 | } 74 | 75 | public void ListItemPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 76 | { 77 | if (lastList != null) 78 | { 79 | if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Utils.CurrentWindow.Dragging) 80 | { 81 | foreach (ListItemViewModelBase item in lastList.Items) 82 | { 83 | item.IsSelected = false; 84 | } 85 | } 86 | } 87 | } 88 | 89 | public void ListPreviewMouseDown(object sender, MouseButtonEventArgs e) 90 | { 91 | if (lastList == null) 92 | lastList = (ListBox)sender; 93 | } 94 | 95 | public void ListItemPreviewMouseMouseMove(object sender, MouseEventArgs e) 96 | { 97 | //Fixes a bug where drag and drop would take the file near instead. 98 | if (Utils.CurrentWindow.Dragging) 99 | e.Handled = true; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Services/FormatConverter.cs: -------------------------------------------------------------------------------- 1 | using IronPython.Hosting; 2 | using Microsoft.Scripting.Hosting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | 11 | namespace DieselBundleViewer.Services 12 | { 13 | public class FormatConverter 14 | { 15 | public string Key { get; set; } 16 | public string Title { get; set; } 17 | public string Extension { get; set; } 18 | public bool RequiresAttention { get; set; } = true; 19 | public string Type { get; set; } 20 | 21 | public delegate object ExportEventDel(MemoryStream ms, bool arg0); 22 | public ExportEventDel ExportEvent { get; set; } 23 | 24 | public object Export(MemoryStream ms, bool arg0 = false) 25 | { 26 | if (ExportEvent != null) 27 | return ExportEvent(ms, arg0); 28 | return ms; 29 | } 30 | 31 | //TEMPORARY 32 | public delegate void SaveEventDel(Stream ms, string toPath); 33 | public SaveEventDel SaveEvent { get; set; } 34 | 35 | public delegate object ImportEventDel(string path); 36 | public ImportEventDel ImportEvent { get; set; } 37 | 38 | public object Import(string path) 39 | { 40 | if (ImportEvent != null) 41 | return ImportEvent(path); 42 | return null; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Services/HashlistUpdater.cs: -------------------------------------------------------------------------------- 1 | using DieselEngineFormats.Bundle; 2 | using System; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Text.Json.Nodes; 6 | using System.Threading.Tasks; 7 | 8 | namespace DieselBundleViewer.Services 9 | { 10 | public static class HashlistUpdater 11 | { 12 | private const string _localHashlistPath = "Data/hashlist"; 13 | private const string _remoteHashlistInfoUrl = "https://api.github.com/repos/Luffyyy/PAYDAY-2-Hashlist/branches/master"; 14 | private const string _remoteHashlistLatestUrl = "https://raw.githubusercontent.com/Luffyyy/PAYDAY-2-Hashlist/master/hashlist"; 15 | private const string _firefoxUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0"; 16 | 17 | private static DateTime? _localHashlistLatestUpdate = null; 18 | private static DateTime? _remoteHashlistLatestUpdate = null; 19 | private static HttpClient _httpClient = null; 20 | 21 | public static async Task DownloadLatestHashlist() 22 | { 23 | var client = GetOrInitHttpClient(); 24 | var response = await client.GetStringAsync(_remoteHashlistLatestUrl); 25 | 26 | using var streamWriter = new StreamWriter(_localHashlistPath, false); 27 | streamWriter.Write(response.ToString()); 28 | streamWriter.Flush(); 29 | streamWriter.Close(); 30 | 31 | ReloadHashIndex(); 32 | } 33 | 34 | public static bool? IsHashlistUpToDate() 35 | { 36 | if (!File.Exists(_localHashlistPath)) 37 | return null; 38 | 39 | if (!_localHashlistLatestUpdate.HasValue) 40 | _localHashlistLatestUpdate = File.GetLastWriteTimeUtc(_localHashlistPath); 41 | 42 | if (!_remoteHashlistLatestUpdate.HasValue) 43 | { 44 | var client = GetOrInitHttpClient(); 45 | 46 | var response = client.GetStringAsync(_remoteHashlistInfoUrl); 47 | var branchInfo = JsonNode.Parse(response.Result.ToString()); 48 | 49 | // The path down the JSON here is a bit annoying but I don't need any of this data 50 | var latestDate = branchInfo?["commit"]["commit"]["author"]["date"]?.ToString(); 51 | 52 | if (latestDate != null) 53 | _remoteHashlistLatestUpdate = DateTime.Parse(latestDate); 54 | } 55 | 56 | return _localHashlistLatestUpdate >= _remoteHashlistLatestUpdate; 57 | } 58 | 59 | private static HttpClient GetOrInitHttpClient() 60 | { 61 | if (_httpClient == null) 62 | { 63 | HttpClient client = new HttpClient(); 64 | // GitHub API has a free limit but not having a User-Agent excludes you from even that 65 | client.DefaultRequestHeaders.Add( 66 | "User-Agent", 67 | _firefoxUserAgent 68 | ); 69 | 70 | _httpClient = client; 71 | } 72 | 73 | return _httpClient; 74 | } 75 | private static void ReloadHashIndex() 76 | { 77 | HashIndex.Clear(); 78 | HashIndex.LoadParallel(_localHashlistPath); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Services/JSONNode.cs: -------------------------------------------------------------------------------- 1 | using DieselEngineFormats.ScriptData; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace DieselBundleViewer.Services 9 | { 10 | public class JSONNode : ScriptDataNode 11 | { 12 | public JSONNode() : base() { } 13 | 14 | public JSONNode(string meta, string data, object index, int indent = 0) : base(meta, data, index, indent) { } 15 | public JSONNode(string meta, float[] data, object index, int indent = 0) : base(meta, data, index, indent) { } 16 | public JSONNode(string meta, float data, object index, int indent = 0) : base(meta, data, index, indent) { } 17 | public JSONNode(string meta, bool data, object index, int indent = 0) : base(meta, data, index, indent) { } 18 | public JSONNode(string meta, Dictionary data, object index, int indent = 0) : base(meta, data, index, indent) { } 19 | public JSONNode(StreamReader data) : base(data) { } 20 | 21 | public override string ToString() 22 | { 23 | if (this.meta != "value_node") 24 | { 25 | StringBuilder indentation = new StringBuilder(); 26 | for (int i = 0; i < this.indent; i++) 27 | indentation.Append('\t'); 28 | 29 | StringBuilder sb = new StringBuilder(); 30 | 31 | sb.Append("{\n"); 32 | 33 | if (this.meta != "table" && (string)this.index != this.meta) 34 | sb.Append(indentation.ToString() + "\t\"_meta\" : \"" + this.meta + "\"" + (this.attributes.Count == 0 && this.children.Count == 0 ? "" : ",") + "\n"); 35 | 36 | //foreach (ScriptDataNode child in this.children) 37 | for (int i = 0; i < this.children.Count; i++) 38 | { 39 | ScriptDataNode child = this.children[i]; 40 | 41 | sb.Append(indentation.ToString() + "\t"); 42 | 43 | if (this.index != null) 44 | sb.Append("\"" + child.index.ToString() + "\" : "); 45 | 46 | sb.Append(child); 47 | sb.Append((this.attributes.Count == 0 && i == this.children.Count - 1 ? "" : ",") + "\n"); 48 | } 49 | 50 | //foreach (KeyValuePair kvp in this.attributes) 51 | List> attributeList = this.attributes.ToList(); 52 | 53 | for (int i = 0; i < this.attributes.Count; i++) 54 | { 55 | KeyValuePair kvp = attributeList[i]; 56 | 57 | sb.Append(indentation.ToString() + "\t\"" + kvp.Key + "\" : "); 58 | 59 | if (kvp.Value is float[]) 60 | { 61 | sb.Append('"'); 62 | float[] vals = kvp.Value as float[]; 63 | 64 | if (vals.Length == 3) 65 | { 66 | sb.Append("Vector3("); 67 | for (int j = 0; j < 3; j++) 68 | sb.Append(vals[j] + (j == 2 ? ")" : ", ")); 69 | } 70 | else if (vals.Length == 4) 71 | { 72 | for (int j = 0; j < 3; j++) 73 | sb.Append(vals[j] + (j == 2 ? "" : ", ")); 74 | //Quaternion quat = new Quaternion(vals[1], vals[2], vals[3], vals[0]); 75 | //Vector3 vec = quat.Xyz; 76 | //Quaternion quat = new Quaternion(vals[3], vals[2], vals[0], vals[1]); 77 | //Vector3 vec = quat.ToEulerAnglesInDegrees(); 78 | 79 | //sb.Append("Rotation(" + (-vec.x).ToString() + ", " + vec.y.ToString() + ", " + (-vec.z).ToString() + ")"); 80 | } 81 | sb.Append('"'); 82 | } 83 | else if (kvp.Value is bool) 84 | sb.Append(((bool)kvp.Value ? "true" : "false")); 85 | else if (kvp.Value is float) 86 | sb.Append(kvp.Value); 87 | else 88 | sb.Append("\"" + kvp.Value + "\""); 89 | 90 | sb.Append((i == this.attributes.Count - 1 ? "" : ",") + "\n"); 91 | } 92 | 93 | sb.Append(indentation.ToString() + "}"); 94 | 95 | return sb.ToString(); 96 | } 97 | else 98 | { 99 | return this.data.ToString(); 100 | } 101 | 102 | 103 | } 104 | 105 | public override void FromString(StreamReader data) 106 | { 107 | bool closed = false; 108 | 109 | string line = data.ReadLine(); 110 | int line_pos = 0; 111 | 112 | if (String.IsNullOrWhiteSpace(line)) 113 | return; 114 | 115 | for (; line_pos < line.Length; line_pos++) 116 | { 117 | if (line[line_pos] == '<') 118 | { 119 | line_pos++; 120 | break; 121 | } 122 | } 123 | 124 | if (!closed && line_pos < line.Length && line[line_pos] == '/') 125 | { 126 | closed = true; 127 | line_pos++; 128 | } 129 | 130 | StringBuilder meta = new StringBuilder(); 131 | 132 | for (; line_pos < line.Length; line_pos++) 133 | { 134 | if (line[line_pos] != ' ' && line[line_pos] != '=' && line[line_pos] != '/' && line[line_pos] != '>') 135 | { 136 | meta.Append(line[line_pos]); 137 | } 138 | else 139 | { 140 | line_pos++; 141 | break; 142 | } 143 | } 144 | 145 | this.meta = meta.ToString(); 146 | 147 | if (line_pos < line.Length) 148 | { 149 | StringBuilder attrib_name = new StringBuilder(); 150 | StringBuilder attrib_data = new StringBuilder(); 151 | 152 | for (; line_pos < line.Length; line_pos++) 153 | { 154 | if (line[line_pos] == '/' || line[line_pos] == '>') 155 | { 156 | break; 157 | } 158 | 159 | if (line[line_pos] == ' ') 160 | { 161 | continue; 162 | } 163 | 164 | for (; line_pos < line.Length; line_pos++) 165 | { 166 | if (line[line_pos] != '=') 167 | { 168 | attrib_name.Append(line[line_pos]); 169 | } 170 | else 171 | { 172 | line_pos++; 173 | break; 174 | } 175 | } 176 | 177 | if (line[line_pos] == '"') 178 | line_pos++; 179 | 180 | for (; line_pos < line.Length; line_pos++) 181 | { 182 | if (line[line_pos] != '"') 183 | { 184 | attrib_data.Append(line[line_pos]); 185 | } 186 | else 187 | { 188 | break; 189 | } 190 | } 191 | 192 | Boolean data_bool; 193 | if (Boolean.TryParse(attrib_data.ToString(), out data_bool)) 194 | { 195 | this.attributes.Add(attrib_name.ToString(), data_bool); 196 | attrib_name.Clear(); 197 | attrib_data.Clear(); 198 | continue; 199 | } 200 | 201 | float data_float; 202 | if (float.TryParse(attrib_data.ToString(), out data_float)) 203 | { 204 | this.attributes.Add(attrib_name.ToString(), data_float); 205 | attrib_name.Clear(); 206 | attrib_data.Clear(); 207 | continue; 208 | } 209 | 210 | List data_floats = new List(); 211 | bool isFloatArray = true; 212 | if (attrib_data.ToString().Split(' ').Length > 1) 213 | { 214 | string[] splits = attrib_data.ToString().Split(' '); 215 | 216 | foreach (String spl in splits) 217 | { 218 | float test_out; 219 | if (float.TryParse(spl, out test_out)) 220 | { 221 | data_floats.Add(test_out); 222 | } 223 | else 224 | { 225 | isFloatArray = false; 226 | } 227 | 228 | } 229 | 230 | if (isFloatArray) 231 | { 232 | this.attributes.Add(attrib_name.ToString(), data_floats.ToArray()); 233 | attrib_name.Clear(); 234 | attrib_data.Clear(); 235 | continue; 236 | } 237 | } 238 | 239 | this.attributes.Add(attrib_name.ToString(), attrib_data.ToString()); 240 | attrib_name.Clear(); 241 | attrib_data.Clear(); 242 | 243 | } 244 | } 245 | 246 | if (!closed && line_pos < line.Length && line[line_pos] == '/') 247 | { 248 | closed = true; 249 | line_pos++; 250 | } 251 | 252 | if (!closed) 253 | { 254 | JSONNode child; 255 | while (!((child = new JSONNode(data)).meta).Equals(this.meta)) 256 | this.children.Add(child); 257 | } 258 | } 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /Services/ScriptActions.cs: -------------------------------------------------------------------------------- 1 | using CSScripting; 2 | using CSScriptLib; 3 | using DieselBundleViewer.Models; 4 | using IronPython.Hosting; 5 | using Microsoft.Scripting.Hosting; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Text; 13 | using System.Web; 14 | 15 | namespace DieselBundleViewer.Services 16 | { 17 | public static class ScriptActions 18 | { 19 | public static Dictionary> Converters = []; 20 | public static List