├── Artwork
├── icon1.ico
├── icon1.png
├── icon2.ico
└── icon2.png
├── readmeStuff
├── gui.png
└── contextMenu.png
├── Repackinator
├── bass.dll
├── libbass.so
├── libbass.dylib
├── Assets
│ ├── repackinator.ico
│ ├── teamresurgent.jpg
│ └── Stuart Wilson - Not Another Comic Bakery Remix.mp3
├── ViewModels
│ ├── ViewModelBase.cs
│ ├── AttachUpdateViewModel.cs
│ ├── ProcessViewModel.cs
│ └── ScanOutputViewModel.cs
├── Models
│ ├── GameDataSection.cs
│ ├── LogItem.cs
│ ├── ScrubOption.cs
│ ├── CompressOption.cs
│ ├── GameDataFilter.cs
│ └── GroupingOption.cs
├── Utils
│ ├── WindowLocator.cs
│ ├── StringToBoolConverter.cs
│ ├── WarningColorConverter.cs
│ └── LogLevelColorConverter.cs
├── Views
│ ├── AboutWindow.axaml.cs
│ ├── AboutWindow.axaml
│ ├── AttachUpdateWindow.axaml.cs
│ ├── ProcessWindow.axaml.cs
│ ├── ScanOutputWindow.axaml.cs
│ ├── MessageWindow.axaml
│ ├── MessageWindow.axaml.cs
│ ├── MainWindow.axaml.cs
│ ├── ProcessWindow.axaml
│ ├── ScanOutputWindow.axaml
│ └── AttachUpdateWindow.axaml
├── ViewLocator.cs
├── Program.cs
├── app.manifest
├── App.axaml.cs
├── App.axaml
└── Repackinator.UI.csproj
├── Repackinator.Shell
├── repackinator.ico
├── Program.cs
├── Repackinator.Shell.csproj
├── Console
│ ├── ConsoleRegister.cs
│ ├── ConsoleUnregister.cs
│ ├── ConsoleStartup.cs
│ ├── ConsoleExtract.cs
│ ├── ConsoleUtil.cs
│ ├── ConsoleChecksum.cs
│ ├── ConsoleInfo.cs
│ ├── ConsoleXbeInfo.cs
│ ├── ConsoleCompare.cs
│ └── ConsolePack.cs
└── Shell
│ └── ContextMenu.cs
├── Repackinator.Core
├── Resources
│ └── attach.xbe
├── Exceptions
│ └── ExtractAbortException.cs
├── Version.cs
├── Models
│ ├── ProgressInfo.cs
│ ├── GameDataHelper.cs
│ ├── GameData.cs
│ └── Config.cs
├── Repackinator.Core.csproj
├── Extensions
│ └── StreamExtension.cs
├── Logging
│ └── LogMessage.cs
├── Streams
│ ├── ProgressStream.cs
│ └── ExtractSplitStream.cs
├── Helpers
│ ├── ResourceLoader.cs
│ └── Utility.cs
└── Actions
│ ├── AttachUpdater.cs
│ └── Scanner.cs
├── Linux
└── share
│ ├── icons
│ ├── hicolor
│ │ ├── 256x256
│ │ │ └── apps
│ │ │ │ ├── cerbios.png
│ │ │ │ └── repackinator.png
│ │ ├── 48x48
│ │ │ └── apps
│ │ │ │ ├── cerbios.png
│ │ │ │ └── repackinator.png
│ │ ├── 1024x1024
│ │ │ └── apps
│ │ │ │ └── cerbios.png
│ │ ├── 128x128
│ │ │ └── mimetypes
│ │ │ │ ├── application-x-cci.png
│ │ │ │ └── gnome-mime-application-x-cci.png
│ │ └── scalable
│ │ │ └── apps
│ │ │ └── cerbios.svg
│ └── pixmaps
│ │ ├── cerbios.xpm
│ │ └── repackinator.xpm
│ ├── mime
│ └── packages
│ │ └── application-x-cci.xml
│ ├── applications
│ ├── cci.desktop
│ └── repackinator.desktop
│ ├── kservices5
│ └── ServiceMenus
│ │ └── repackinator_menu.desktop
│ └── kde4
│ └── services
│ └── ServiceMenus
│ └── repackinator_menu.desktop
├── editorconfig
├── .gitattributes
├── Nuget.config
├── Repackinator.sln
├── .github
└── workflows
│ └── dotnet.yml
└── .gitignore
/Artwork/icon1.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Artwork/icon1.ico
--------------------------------------------------------------------------------
/Artwork/icon1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Artwork/icon1.png
--------------------------------------------------------------------------------
/Artwork/icon2.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Artwork/icon2.ico
--------------------------------------------------------------------------------
/Artwork/icon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Artwork/icon2.png
--------------------------------------------------------------------------------
/readmeStuff/gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/readmeStuff/gui.png
--------------------------------------------------------------------------------
/Repackinator/bass.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator/bass.dll
--------------------------------------------------------------------------------
/Repackinator/libbass.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator/libbass.so
--------------------------------------------------------------------------------
/Repackinator/libbass.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator/libbass.dylib
--------------------------------------------------------------------------------
/readmeStuff/contextMenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/readmeStuff/contextMenu.png
--------------------------------------------------------------------------------
/Repackinator.Shell/repackinator.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator.Shell/repackinator.ico
--------------------------------------------------------------------------------
/Repackinator/Assets/repackinator.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator/Assets/repackinator.ico
--------------------------------------------------------------------------------
/Repackinator/Assets/teamresurgent.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator/Assets/teamresurgent.jpg
--------------------------------------------------------------------------------
/Repackinator.Core/Resources/attach.xbe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator.Core/Resources/attach.xbe
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/256x256/apps/cerbios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/256x256/apps/cerbios.png
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/48x48/apps/cerbios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/48x48/apps/cerbios.png
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/1024x1024/apps/cerbios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/1024x1024/apps/cerbios.png
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/256x256/apps/repackinator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/256x256/apps/repackinator.png
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/48x48/apps/repackinator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/48x48/apps/repackinator.png
--------------------------------------------------------------------------------
/editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = crlf
5 | indent_style = space
6 | indent_size = 4
7 | trim_trailing_whitespace = true
8 | insert_fianl_newline = true
--------------------------------------------------------------------------------
/Repackinator.Core/Exceptions/ExtractAbortException.cs:
--------------------------------------------------------------------------------
1 | namespace Repackinator.Core.Exceptions
2 | {
3 | public class ExtractAbortException : Exception
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/128x128/mimetypes/application-x-cci.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/128x128/mimetypes/application-x-cci.png
--------------------------------------------------------------------------------
/Repackinator/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace Repackinator.UI.ViewModels
4 | {
5 | public class ViewModelBase : ReactiveObject
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
--------------------------------------------------------------------------------
/Repackinator/Assets/Stuart Wilson - Not Another Comic Bakery Remix.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Repackinator/Assets/Stuart Wilson - Not Another Comic Bakery Remix.mp3
--------------------------------------------------------------------------------
/Repackinator/Models/GameDataSection.cs:
--------------------------------------------------------------------------------
1 | namespace Repackinator.UI.Models
2 | {
3 | public class GameDataSection(string name)
4 | {
5 | public string Name { get; set; } = name;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/128x128/mimetypes/gnome-mime-application-x-cci.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-Resurgent/Repackinator/HEAD/Linux/share/icons/hicolor/128x128/mimetypes/gnome-mime-application-x-cci.png
--------------------------------------------------------------------------------
/Nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Repackinator/Utils/WindowLocator.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Repackinator.UI.Utils
4 | {
5 | public static class WindowLocator
6 | {
7 | public static Window? MainWindow { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Repackinator/Views/AboutWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Repackinator.UI;
4 |
5 | public partial class AboutWindow : Window
6 | {
7 | public AboutWindow()
8 | {
9 | InitializeComponent();
10 | }
11 | }
--------------------------------------------------------------------------------
/Repackinator/Models/LogItem.cs:
--------------------------------------------------------------------------------
1 | namespace Repackinator.UI.Models
2 | {
3 | public class LogItem
4 | {
5 | public string Time { get; set; } = "00:00:00";
6 | public string Level { get; set; } = string.Empty;
7 | public string Message { get; set; } = string.Empty;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Repackinator.Core/Version.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Repackinator.Core
8 | {
9 | public static class Version
10 | {
11 | public static string Value { get; } = "2.1.0";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Program.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Shell.Console;
2 |
3 | namespace Repackinator.Shell
4 | {
5 | internal sealed class Program
6 | {
7 | public static void Main(string[] args)
8 | {
9 | ConsoleStartup.Process(Repackinator.Core.Version.Value, args);
10 | }
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/Linux/share/mime/packages/application-x-cci.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cerbios Compressed Image
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Linux/share/applications/cci.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Categories=Game;
3 | Comment[en_US]=Cerbios Compressed Image
4 | Comment=Cerbios Compressed Image
5 | Exec=repackinator
6 | GenericName[en_US]=Cerbios Compressed Image
7 | GenericName=Cerbios Compressed Image
8 | Icon=application-x-cci
9 | MimeType=application/x-cci;
10 | Name[en_US]=Cerbios Compressed Image
11 | Name=Cerbios Compressed Image
12 | StartupNotify=true
13 | Terminal=false
14 | Type=Application
--------------------------------------------------------------------------------
/Repackinator/Models/ScrubOption.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Helpers;
2 | using Repackinator.Core.Models;
3 |
4 | namespace Repackinator.UI.Models
5 | {
6 | public class ScrubOption(ScrubOptionType type)
7 | {
8 | public ScrubOptionType Type { get; set; } = type;
9 |
10 | public string Name
11 | {
12 | get
13 | {
14 | return Utility.EnumValueToString(Type);
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Repackinator/Models/CompressOption.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Helpers;
2 | using Repackinator.Core.Models;
3 |
4 | namespace Repackinator.UI.Models
5 | {
6 | public class CompressOption(CompressOptionType type)
7 | {
8 | public CompressOptionType Type { get; set; } = type;
9 |
10 | public string Name
11 | {
12 | get
13 | {
14 | return Utility.EnumValueToString(Type);
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Repackinator/Models/GameDataFilter.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Helpers;
2 | using Repackinator.Core.Models;
3 |
4 | namespace Repackinator.UI.Models
5 | {
6 | public class GameDataFilter(GameDataFilterType type)
7 | {
8 | public GameDataFilterType Type { get; set; } = type;
9 |
10 | public string Name
11 | {
12 | get
13 | {
14 | return Utility.EnumValueToString(Type);
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Repackinator/Models/GroupingOption.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Helpers;
2 | using Repackinator.Core.Models;
3 |
4 | namespace Repackinator.UI.Models
5 | {
6 | public class GroupingOption(GroupingOptionType type)
7 | {
8 | public GroupingOptionType Type { get; set; } = type;
9 |
10 | public string Name
11 | {
12 | get
13 | {
14 | return Utility.EnumValueToString(Type);
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Linux/share/applications/repackinator.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Categories=Game;
3 | Comment[en_US]=OG Xbox XISO and CCI image creation tool.
4 | Comment=OG Xbox XISO and CCI image creation tool.
5 | Exec=repackinator
6 | GenericName[en_US]=Repackinator
7 | GenericName=Repackinator
8 | Icon=repackinator
9 | Keywords=Utility;Games;Xbox;XISO;
10 | MimeType=
11 | Name[en_US]=Repackinator
12 | Name=Repackinator
13 | Type=Application
14 | PrefersNonDefaultGPU=true
15 | X-DBUS-ServiceName=
16 | X-DBUS-StartupType=
17 | X-KDE-SubstituteUID=false
18 | X-KDE-Username=
19 |
--------------------------------------------------------------------------------
/Repackinator.Core/Models/ProgressInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Repackinator.Core.Models
2 | {
3 | public struct ProgressInfo
4 | {
5 | public float Progress1 { get; set; }
6 |
7 | public string Progress1Text { get; set; }
8 |
9 | public float Progress2 { get; set; }
10 |
11 | public string Progress2Text { get; set; }
12 |
13 | public ProgressInfo()
14 | {
15 | Progress1 = 0;
16 | Progress1Text = string.Empty;
17 | Progress2 = 0;
18 | Progress2Text = string.Empty;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Repackinator/Views/AboutWindow.axaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Repackinator/Views/AttachUpdateWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Repackinator.Core.Models;
3 | using Repackinator.UI.ViewModels;
4 |
5 | namespace Repackinator.UI;
6 |
7 | public partial class AttachUpdateWindow : Window
8 | {
9 | public AttachUpdateWindow()
10 | {
11 | InitializeComponent();
12 |
13 | DataContext = new AttachUpdateViewModel(this, [], new Config());
14 | }
15 |
16 | public AttachUpdateWindow(GameData[] gameDataArray, Config config)
17 | {
18 | InitializeComponent();
19 |
20 | var logViewModel = new AttachUpdateViewModel(this, gameDataArray, config);
21 | DataContext = logViewModel;
22 |
23 | Opened += async (_, _) =>
24 | {
25 | await logViewModel.StartAsync();
26 | };
27 | }
28 | }
--------------------------------------------------------------------------------
/Repackinator/Views/ProcessWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Repackinator.Core.Models;
3 | using Repackinator.UI.ViewModels;
4 |
5 | namespace Repackinator.UI;
6 |
7 | public partial class ProcessWindow : Window
8 | {
9 | public GameData[]? GameDataList;
10 |
11 | public ProcessWindow()
12 | {
13 | InitializeComponent();
14 |
15 | DataContext = new AttachUpdateViewModel(this, [], new Config());
16 | }
17 |
18 | public ProcessWindow(GameData[] gameDataArray, Config config)
19 | {
20 | InitializeComponent();
21 |
22 | var logViewModel = new ProcessViewModel(this, gameDataArray, config);
23 | DataContext = logViewModel;
24 |
25 | Opened += async (_, _) =>
26 | {
27 | GameDataList = await logViewModel.StartAsync();
28 | };
29 | }
30 | }
--------------------------------------------------------------------------------
/Repackinator/Views/ScanOutputWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Repackinator.Core.Models;
3 | using Repackinator.UI.ViewModels;
4 |
5 | namespace Repackinator.UI;
6 |
7 | public partial class ScanOutputWindow : Window
8 | {
9 | public GameData[]? GameDataList;
10 |
11 | public ScanOutputWindow()
12 | {
13 | InitializeComponent();
14 |
15 | DataContext = new AttachUpdateViewModel(this, [], new Config());
16 | }
17 |
18 | public ScanOutputWindow(GameData[] gameDataArray, Config config)
19 | {
20 | InitializeComponent();
21 |
22 | var logViewModel = new ScanOutputViewModel(this, gameDataArray, config);
23 | DataContext = logViewModel;
24 |
25 | Opened += async (_, _) =>
26 | {
27 | GameDataList = await logViewModel.StartAsync();
28 | };
29 | }
30 | }
--------------------------------------------------------------------------------
/Repackinator/Utils/StringToBoolConverter.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Data.Converters;
2 | using System;
3 | using System.Globalization;
4 |
5 | namespace Repackinator.UI.Utils
6 | {
7 | public class StringToBoolConverter : IValueConverter
8 | {
9 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
10 | {
11 | if (value is string stringValue)
12 | {
13 | return stringValue.Equals("Y", StringComparison.OrdinalIgnoreCase);
14 | }
15 | return false;
16 | }
17 |
18 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
19 | {
20 | if (value is bool boolValue)
21 | {
22 | return boolValue ? "Y" : "N";
23 | }
24 | return "N";
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Repackinator/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Templates;
3 | using Repackinator.UI.ViewModels;
4 | using System;
5 |
6 | namespace Repackinator.UI
7 | {
8 | public class ViewLocator : IDataTemplate
9 | {
10 |
11 | public Control? Build(object? param)
12 | {
13 | if (param is null)
14 | return null;
15 |
16 | var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
17 | var type = Type.GetType(name);
18 |
19 | if (type != null)
20 | {
21 | return (Control)Activator.CreateInstance(type)!;
22 | }
23 |
24 | return new TextBlock { Text = "Not Found: " + name };
25 | }
26 |
27 | public bool Match(object? data)
28 | {
29 | return data is ViewModelBase;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Repackinator/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 | using System;
4 |
5 | namespace Repackinator.UI
6 | {
7 | internal sealed class Program
8 | {
9 | // Initialization code. Don't use any Avalonia, third-party APIs or any
10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
11 | // yet and stuff might break.
12 | [STAThread]
13 | public static void Main(string[] args)
14 | {
15 | BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
16 | }
17 |
18 | // Avalonia configuration, don't remove; also used by visual designer.
19 | public static AppBuilder BuildAvaloniaApp()
20 | => AppBuilder.Configure()
21 | .UsePlatformDetect()
22 | .WithInterFont()
23 | .LogToTrace()
24 | .UseReactiveUI();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Repackinator/Views/MessageWindow.axaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Repackinator/Views/MessageWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using ReactiveUI;
3 | using System.Windows.Input;
4 |
5 | namespace Repackinator.UI;
6 |
7 | public partial class MessageWindow : Window
8 | {
9 | public ICommand OkCommand { get; }
10 |
11 |
12 | public MessageWindow()
13 | {
14 | InitializeComponent();
15 |
16 | Title = string.Empty;
17 | Message.Content = string.Empty;
18 |
19 | OkCommand = ReactiveCommand.Create(() =>
20 | {
21 | Close();
22 | });
23 |
24 | OkButton.Command = OkCommand;
25 | }
26 |
27 | public MessageWindow(string title, string message)
28 | {
29 | InitializeComponent();
30 |
31 | Title = title;
32 | Message.Content = message;
33 |
34 | OkCommand = ReactiveCommand.Create(() =>
35 | {
36 | Close();
37 | });
38 |
39 | OkButton.Command = OkCommand;
40 | }
41 | }
--------------------------------------------------------------------------------
/Repackinator.Shell/Repackinator.Shell.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | repackinator.shell
9 | repackinator.ico
10 | repackinator.shell
11 | true
12 | false
13 | true
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Repackinator/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Repackinator.Core/Repackinator.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Repackinator/Utils/WarningColorConverter.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Data.Converters;
3 | using Avalonia.Media;
4 | using System;
5 | using System.Globalization;
6 |
7 | namespace Repackinator.UI.Utils
8 | {
9 | public class WarningColorConverter : IValueConverter
10 | {
11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
12 | {
13 | var isDarkMode = (Application.Current?.ActualThemeVariant.Key ?? "").Equals("Dark");
14 | var defaultForeground = isDarkMode ? Brushes.White : Brushes.Black;
15 | if (value is bool boolValue)
16 | {
17 | return boolValue ? defaultForeground : Brushes.Red;
18 | }
19 | return defaultForeground;
20 | }
21 |
22 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
23 | {
24 | if (value is SolidColorBrush brush)
25 | {
26 | return brush.Color == Colors.Red;
27 | }
28 | return false;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Repackinator.Core/Extensions/StreamExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Repackinator.Core.Extensions
2 | {
3 | public static class StreamExtension
4 | {
5 | public static ushort ReadUInt16(this Stream stream)
6 | {
7 | byte[] buffer = new byte[sizeof(ushort)];
8 | if (stream.Read(buffer, 0, sizeof(ushort)) != sizeof(ushort))
9 | {
10 | throw new Exception();
11 | }
12 | return BitConverter.ToUInt16(buffer, 0);
13 | }
14 |
15 | public static uint ReadUInt32(this Stream stream)
16 | {
17 | byte[] buffer = new byte[sizeof(uint)];
18 | if (stream.Read(buffer, 0, sizeof(uint)) != sizeof(uint))
19 | {
20 | throw new Exception();
21 | }
22 | return BitConverter.ToUInt32(buffer, 0);
23 | }
24 |
25 | public static ulong ReadUInt64(this Stream stream)
26 | {
27 | byte[] buffer = new byte[sizeof(ulong)];
28 | if (stream.Read(buffer, 0, sizeof(ulong)) != sizeof(ulong))
29 | {
30 | throw new Exception();
31 | }
32 | return BitConverter.ToUInt64(buffer, 0);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Repackinator.Core/Logging/LogMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Repackinator.Core.Logging
2 | {
3 | public enum LogMessageLevel
4 | {
5 | None,
6 | Info,
7 | Completed,
8 | Skipped,
9 | Warning,
10 | NotFound,
11 | Error,
12 | Done
13 | }
14 |
15 | public class LogMessage
16 | {
17 | public DateTime Time { get; set; }
18 |
19 | public string LogLevel => Level switch
20 | {
21 | LogMessageLevel.None => "None",
22 | LogMessageLevel.Info => "Info",
23 | LogMessageLevel.Completed => "Completed",
24 | LogMessageLevel.Skipped => "Skipped",
25 | LogMessageLevel.Warning => "Warning",
26 | LogMessageLevel.NotFound => "NotFound",
27 | LogMessageLevel.Error => "Error",
28 | LogMessageLevel.Done => "Done",
29 | _ => $"UNKNOWN({Level.ToString()})"
30 | };
31 |
32 | public LogMessageLevel Level { get; set; }
33 |
34 | public string Message { get; set; } = string.Empty;
35 |
36 | public string ToLogFormat(string timeFormat = "HH:mm:ss")
37 | {
38 | if (Level == LogMessageLevel.None)
39 | {
40 | return "\n";
41 | }
42 | return $"{Time.ToString(timeFormat)} {LogLevel} - {Message}\n";
43 | }
44 |
45 | public LogMessage(LogMessageLevel level, string message)
46 | {
47 | Time = DateTime.Now;
48 | Level = level;
49 | Message = message;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Repackinator/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Animation;
2 | using Avalonia.Controls;
3 | using Repackinator.UI.Utils;
4 | using Repackinator.UI.ViewModels;
5 | using System.Threading.Tasks;
6 |
7 | namespace Repackinator.UI.Views
8 | {
9 |
10 | public partial class MainWindow : Window
11 | {
12 | private void GameDataListCellEditEnding(object? sender, DataGridCellEditEndingEventArgs e)
13 | {
14 | if (DataContext is MainWindowViewModel vm)
15 | {
16 | vm.GameDataListCellEditEnding(sender, e);
17 | }
18 | }
19 |
20 | public MainWindow()
21 | {
22 | InitializeComponent();
23 | WindowLocator.MainWindow = this;
24 | //this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control | KeyModifiers.Alt));
25 |
26 | Title = $"Repackinator V{Repackinator.Core.Version.Value}";
27 |
28 | Opened += async (_, _) =>
29 | {
30 | await Task.Delay(2000);
31 |
32 | var animationFadeIn = Resources["FadeInAnimation"] as Animation;
33 | var animationFadeOut = Resources["FadeOutAnimation"] as Animation;
34 | if (animationFadeIn != null && animationFadeOut != null)
35 | {
36 | await Task.WhenAll(
37 | animationFadeIn.RunAsync(MainContent),
38 | animationFadeOut.RunAsync(SplashView)
39 | );
40 | }
41 | SplashView.IsVisible = false;
42 | };
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/Repackinator/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Data.Core.Plugins;
4 | using Avalonia.Markup.Xaml;
5 | using Repackinator.UI.ViewModels;
6 | using Repackinator.UI.Views;
7 | using System.Linq;
8 |
9 | namespace Repackinator.UI
10 | {
11 | public partial class App : Application
12 | {
13 | public override void Initialize()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 |
18 | public override void OnFrameworkInitializationCompleted()
19 | {
20 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
21 | {
22 | // Avoid duplicate validations from both Avalonia and the CommunityToolkit.
23 | // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
24 | DisableAvaloniaDataAnnotationValidation();
25 | desktop.MainWindow = new MainWindow
26 | {
27 | DataContext = new MainWindowViewModel(),
28 | };
29 | }
30 |
31 | base.OnFrameworkInitializationCompleted();
32 | }
33 |
34 | private void DisableAvaloniaDataAnnotationValidation()
35 | {
36 | // Get an array of plugins to remove
37 | var dataValidationPluginsToRemove =
38 | BindingPlugins.DataValidators.OfType().ToArray();
39 |
40 | // remove each entry found
41 | foreach (var plugin in dataValidationPluginsToRemove)
42 | {
43 | BindingPlugins.DataValidators.Remove(plugin);
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/Repackinator/Utils/LogLevelColorConverter.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Data.Converters;
3 | using Avalonia.Media;
4 | using Repackinator.Core.Logging;
5 | using System;
6 | using System.Globalization;
7 |
8 | namespace Repackinator.UI.Utils
9 | {
10 | public class LogLevelColorConverter : IValueConverter
11 | {
12 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
13 | {
14 | var isDarkMode = (Application.Current?.ActualThemeVariant.Key ?? "").Equals("Dark");
15 | var defaultForeground = isDarkMode ? Brushes.White : Brushes.Black;
16 |
17 | if (value is LogMessageLevel logMessageLevel)
18 | {
19 | if (logMessageLevel == LogMessageLevel.Warning)
20 | {
21 | return new SolidColorBrush(Color.FromArgb(255, 255, 192, 0));
22 | }
23 | else if (logMessageLevel == LogMessageLevel.Error)
24 | {
25 | return new SolidColorBrush(Color.FromArgb(255, 255, 64, 64));
26 | }
27 | else if (logMessageLevel == LogMessageLevel.Skipped)
28 | {
29 | return new SolidColorBrush(Color.FromArgb(255, 255, 255, 0));
30 | }
31 | else if (logMessageLevel == LogMessageLevel.NotFound)
32 | {
33 | return new SolidColorBrush(Color.FromArgb(255, 64, 64, 255));
34 | }
35 | else if (logMessageLevel == LogMessageLevel.Completed || logMessageLevel == LogMessageLevel.Done)
36 | {
37 | return new SolidColorBrush(Color.FromArgb(255, 64, 255, 64));
38 | }
39 | }
40 |
41 | return defaultForeground;
42 | }
43 |
44 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
45 | {
46 | throw new NotImplementedException();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Repackinator.Core/Streams/ProgressStream.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Exceptions;
2 |
3 | namespace Repackinator.Core.Streams
4 | {
5 | public class ProgressStream : Stream
6 | {
7 | private Stream m_stream;
8 | private long m_length;
9 | private Action m_progress;
10 | private CancellationToken m_cancellationToken;
11 |
12 | public ProgressStream(Stream stream, long length, Action progress, CancellationToken cancellationToken)
13 | {
14 | m_stream = stream;
15 | m_length = length;
16 | m_progress = progress;
17 | m_cancellationToken = cancellationToken;
18 | }
19 |
20 | public override int Read(byte[] buffer, int offset, int count)
21 | {
22 | var result = m_stream.Read(buffer, offset, count);
23 | m_progress(m_stream.Position / (float)m_length);
24 | return result;
25 | }
26 |
27 | public override void Write(byte[] buffer, int offset, int count)
28 | {
29 | if (m_cancellationToken.IsCancellationRequested)
30 | {
31 | throw new ExtractAbortException();
32 | }
33 |
34 | m_stream.Write(buffer, offset, count);
35 | m_progress(m_stream.Position / (float)m_length);
36 | }
37 |
38 | public override void Flush()
39 | {
40 | m_stream.Flush();
41 | }
42 |
43 | public override long Seek(long offset, SeekOrigin origin)
44 | {
45 | return m_stream.Seek(offset, origin);
46 | }
47 | public override void SetLength(long value)
48 | {
49 | m_stream.SetLength(Length);
50 | }
51 |
52 | public override bool CanRead => m_stream.CanRead;
53 |
54 | public override bool CanWrite => m_stream.CanWrite;
55 |
56 | public override bool CanSeek => m_stream.CanSeek;
57 |
58 | public override long Length => m_stream.Length;
59 |
60 | public override long Position
61 | {
62 | get { return m_stream.Position; }
63 | set { m_stream.Position = value; }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Repackinator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 18
4 | VisualStudioVersion = 18.0.11222.15
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repackinator.UI", "Repackinator\Repackinator.UI.csproj", "{DF56920E-1432-4F34-B948-573A2163F19E}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repackinator.Core", "Repackinator.Core\Repackinator.Core.csproj", "{C66D6B85-EC0F-4E6F-BA9F-A9E321C8E31A}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repackinator.Shell", "Repackinator.Shell\Repackinator.Shell.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {DF56920E-1432-4F34-B948-573A2163F19E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {DF56920E-1432-4F34-B948-573A2163F19E}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {DF56920E-1432-4F34-B948-573A2163F19E}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {DF56920E-1432-4F34-B948-573A2163F19E}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {C66D6B85-EC0F-4E6F-BA9F-A9E321C8E31A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {C66D6B85-EC0F-4E6F-BA9F-A9E321C8E31A}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {C66D6B85-EC0F-4E6F-BA9F-A9E321C8E31A}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {C66D6B85-EC0F-4E6F-BA9F-A9E321C8E31A}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {C9E86BE9-FAA3-4B80-BACE-01C71C1136AD}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/Repackinator.Core/Helpers/ResourceLoader.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace Repackinator.Core.Helpers
4 | {
5 | public static class ResourceLoader
6 | {
7 | private static string GetResourceFullName(string resourceFileName, ref Assembly? assembly)
8 | {
9 | if (assembly == null)
10 | {
11 | assembly = typeof(ResourceLoader).GetTypeInfo().Assembly;
12 | }
13 | string[] resourceNames = assembly.GetManifestResourceNames();
14 | string[] resourcePaths = resourceNames.Where(x => x.EndsWith(resourceFileName, StringComparison.CurrentCultureIgnoreCase)).ToArray();
15 | if (resourcePaths.Length > 1)
16 | {
17 | throw new Exception($"Multiple resources ending with {resourceFileName} found: {Environment.NewLine}{string.Join(Environment.NewLine, resourcePaths)}");
18 | }
19 | if (resourcePaths.Length == 0)
20 | {
21 | throw new Exception($"Resource ending with {resourceFileName} not found.");
22 | }
23 | return resourcePaths.First();
24 | }
25 |
26 | public static Stream? GetEmbeddedResourceStream(string resourceFileName, Assembly? assembly = null)
27 | {
28 | string resourcePath = GetResourceFullName(resourceFileName, ref assembly);
29 | return assembly?.GetManifestResourceStream(resourcePath);
30 | }
31 |
32 | public static string GetEmbeddedResourceString(string resourceFileName, Assembly? assembly = null)
33 | {
34 | Stream? stream = GetEmbeddedResourceStream(resourceFileName, assembly);
35 | if (stream == null)
36 | {
37 | return string.Empty;
38 | }
39 | using var streamReader = new StreamReader(stream);
40 | return streamReader.ReadToEnd();
41 | }
42 |
43 | public static byte[] GetEmbeddedResourceBytes(string resourceFileName, Assembly? assembly = null)
44 | {
45 | Stream? stream = GetEmbeddedResourceStream(resourceFileName, assembly);
46 | if (stream == null)
47 | {
48 | return Array.Empty();
49 | }
50 | using var streamReader = new MemoryStream();
51 | stream.CopyTo(streamReader);
52 | return streamReader.ToArray();
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/Repackinator/App.axaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Repackinator.Core/Models/GameDataHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Text.Json;
3 | using Repackinator.Core.Helpers;
4 |
5 | namespace Repackinator.Core.Models
6 | {
7 | public static class GameDataHelper
8 | {
9 | public static string fix(string tofix, string region)
10 | {
11 | var textinfo = new CultureInfo("en-US", false).TextInfo;
12 | tofix = textinfo.ToTitleCase(tofix.ToLower());
13 | tofix = tofix.Replace("FC", "FC", StringComparison.CurrentCultureIgnoreCase);
14 | tofix = tofix.Replace("II", "II", StringComparison.CurrentCultureIgnoreCase);
15 | tofix = tofix.Replace($"({region})", $"({region})", StringComparison.CurrentCultureIgnoreCase);
16 | return tofix;
17 | }
18 |
19 | public static GameData[] LoadGameData(string path)
20 | {
21 | var gameDataJson = File.ReadAllText(path);
22 | var result = JsonSerializer.Deserialize(gameDataJson) ?? [];
23 | for (int i = 0; i < result.Length; i++)
24 | {
25 | result[i].Index = i;
26 | }
27 | return result;
28 | }
29 |
30 | public static GameData[]? LoadGameData()
31 | {
32 | var applicationPath = Utility.GetApplicationPath();
33 | if (applicationPath == null)
34 | {
35 | return null;
36 | }
37 |
38 | var repackListPath = Path.Combine(applicationPath, "RepackList.json");
39 | if (!File.Exists(repackListPath))
40 | {
41 | var repackList = ResourceLoader.GetEmbeddedResourceBytes("RepackList.json");
42 | File.WriteAllBytes(repackListPath, repackList);
43 | }
44 |
45 | return LoadGameData(repackListPath);
46 | }
47 |
48 | public static void SaveGameData(string path, GameData[]? gameData)
49 | {
50 | if (gameData == null)
51 | {
52 | return;
53 | }
54 |
55 | var result = JsonSerializer.Serialize(gameData, new JsonSerializerOptions { WriteIndented = true });
56 | File.WriteAllText(path, result);
57 | }
58 |
59 | public static void SaveGameData(GameData[]? gameData)
60 | {
61 | var applicationPath = Utility.GetApplicationPath();
62 | if (applicationPath == null)
63 | {
64 | return;
65 | }
66 |
67 | var repackListPath = Path.Combine(applicationPath, "RepackList.json");
68 | SaveGameData(repackListPath, gameData);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Repackinator.Core/Models/GameData.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Helpers;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Repackinator.Core.Models
5 | {
6 | public struct GameData
7 | {
8 | [JsonIgnore]
9 | public bool Selected { get; set; }
10 |
11 | [JsonIgnore]
12 | public int Index { get; set; }
13 |
14 | [JsonPropertyName("List")]
15 | public string Section { get; set; }
16 |
17 | [JsonPropertyName("Title ID")]
18 | public string TitleID { get; set; }
19 |
20 | [JsonPropertyName("Title Name")]
21 | public string TitleName { get; set; }
22 |
23 | [JsonPropertyName("Version")]
24 | public string Version { get; set; }
25 |
26 | [JsonPropertyName("Region")]
27 | public string Region { get; set; }
28 |
29 | [JsonPropertyName("Letter")]
30 | public string Letter { get; set; }
31 |
32 | [JsonPropertyName("XBE Title")]
33 | public string XBETitle { get; set; }
34 |
35 | [JsonPropertyName("Folder Name")]
36 | public string FolderName { get; set; }
37 |
38 | [JsonPropertyName("ISO Name")]
39 | public string ISOName { get; set; }
40 |
41 | [JsonPropertyName("ISO Checksum")]
42 | public string ISOChecksum { get; set; }
43 |
44 | [JsonPropertyName("Process")]
45 | public string Process { get; set; }
46 |
47 | [JsonPropertyName("Category")]
48 | public string Category { get; set; }
49 |
50 | [JsonPropertyName("Link")]
51 | public string Link { get; set; }
52 |
53 | [JsonPropertyName("Info")]
54 | public string Info { get; set; }
55 |
56 | [JsonIgnore]
57 | public bool IsValidXBETitle
58 | {
59 | get
60 | {
61 | if (XBETitle.Length > 40 && Utility.ValidateFatX(XBETitle))
62 | {
63 | return false;
64 | }
65 | return true;
66 | }
67 | }
68 |
69 | [JsonIgnore]
70 | public bool IsValidFolderName
71 | {
72 | get
73 | {
74 | if (FolderName.Length > 42 && Utility.ValidateFatX(FolderName))
75 | {
76 | return false;
77 | }
78 | return true;
79 | }
80 | }
81 |
82 | [JsonIgnore]
83 | public bool IsValidISOName
84 | {
85 | get
86 | {
87 | if (ISOName.Length > 36 && Utility.ValidateFatX(ISOName))
88 | {
89 | return false;
90 | }
91 | return true;
92 | }
93 | }
94 |
95 | [JsonIgnore]
96 | public bool IsValid => IsValidXBETitle && IsValidFolderName && IsValidISOName;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Repackinator/Views/ProcessWindow.axaml:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/Repackinator/Views/ScanOutputWindow.axaml:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/Repackinator/Views/AttachUpdateWindow.axaml:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/Linux/share/kservices5/ServiceMenus/repackinator_menu.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Actions=ConvertToISO;ConvertToISOScrub;ConvertToISOTrimmedScrub;ConvertToCCI;ConvertToCCIScrub;ConvertToCCITrimmedScrub;CompareISO1;CompareISO2;CompareCCI1;CompareCCI2;InfoCCIorISO;ChecksumSectorData;ExtractCCIorISO;
3 | Comment[en_US]=OG Xbox XISO and CCI image creation tool context menu
4 | Comment=OG Xbox XISO and CCI image creation tool context menu
5 | GenericName[en_US]=Repackinator
6 | GenericName=Repackinator
7 | Icon=repackinator
8 | Keywords=Utility;Games;Xbox;XISO
9 | MimeType=application/x-nintendo-3ds-rom;application/x-cd-image;application/x-cci;application/x-compressed-iso;
10 | Name[en_US]=Repackinator
11 | Name=Repackinator
12 | Type=Service
13 | X-DBUS-ServiceName=
14 | X-DBUS-StartupType=none
15 | X-KDE-Icon=repackinator
16 | X-KDE-Priority=TopLevel
17 | X-KDE-ServiceTypes=KonqPopupMenu/Plugin
18 | X-KDE-Submenu=Repackinator
19 | X-KDE-SubstituteUID=false
20 | X-KDE-Username=
21 |
22 | [Desktop Action ConvertToISO]
23 | Exec=gnome-terminal -- repackinator -a convert -w -i %U
24 | Icon=repackinator
25 | Name=Convert To ISO
26 |
27 | [Desktop Action ConvertToISOScrub]
28 | Exec=gnome-terminal -- repackinator -a convert -s Scrub -w -i %U
29 | Icon=repackinator
30 | Name=Convert To ISO (Scrub)
31 |
32 | [Desktop Action ConvertToISOTrimmedScrub]
33 | Exec=gnome-terminal -- repackinator -a convert -s TrimmedScrub -w -i %U
34 | Icon=repackinator
35 | Name=Convert To ISO (Scrub + Truncate)
36 |
37 | [Desktop Action ConvertToCCI]
38 | Exec=gnome-terminal -- repackinator -a convert -c -w -i %U
39 | Icon=repackinator
40 | Name=Convert To CCI
41 |
42 | [Desktop Action ConvertToCCIScrub]
43 | Exec=gnome-terminal -- repackinator -a convert -c -s Scrub -w -i %U
44 | Icon=repackinator
45 | Name=Convert To CCI (Scrub)
46 |
47 | [Desktop Action ConvertToCCITrimmedScrub]
48 | Exec=gnome-terminal -- repackinator -a convert -c -s TrimmedScrub -w -i %U
49 | Icon=repackinator
50 | Name=Convert To CCI (Scrub + Truncate)
51 |
52 | [Desktop Action CompareISO1]
53 | Exec=gnome-terminal -- repackinator -a compare -f %U
54 | Icon=repackinator
55 | Name=Compare ISO ↘ 1st
56 |
57 | [Desktop Action CompareISO2]
58 | Exec=gnome-terminal -- repackinator -a compare -c -w -s %U
59 | Icon=repackinator
60 | Name=Compare ISO ↗ 2nd
61 |
62 | [Desktop Action CompareCCI1]
63 | Exec=gnome-terminal -- repackinator -a compare -f %U
64 | Icon=repackinator
65 | Name=Compare CCI ↘ 1st
66 |
67 | [Desktop Action CompareCCI2]
68 | Exec=gnome-terminal -- repackinator -a compare -c -w -s %U
69 | Icon=repackinator
70 | Name=Compare CCI ↗ 2nd
71 |
72 | [Desktop Action InfoCCIorISO]
73 | Exec=gnome-terminal -- repackinator -a info -w -i %U
74 | Icon=repackinator
75 | Name=Information CCI or ISO
76 |
77 | [Desktop Action ChecksumSectorData]
78 | Exec=gnome-terminal -- repackinator -a checksum -w -i %U
79 | Icon=repackinator
80 | Name=Checksum Sector Data (SHA256)
81 |
82 | [Desktop Action ExtractCCIorISO]
83 | Exec=gnome-terminal -- repackinator -a extract -w -i %U
84 | Icon=repackinator
85 | Name=Extract CCI or ISO
--------------------------------------------------------------------------------
/Linux/share/kde4/services/ServiceMenus/repackinator_menu.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Actions=ConvertToISO;ConvertToISOScrub;ConvertToISOTrimmedScrub;ConvertToCCI;ConvertToCCIScrub;ConvertToCCITrimmedScrub;CompareISO1;CompareISO2;CompareCCI1;CompareCCI2;InfoCCIorISO;ChecksumSectorData;ExtractCCIorISO;
3 | Comment[en_US]=OG Xbox XISO and CCI image creation tool context menu
4 | Comment=OG Xbox XISO and CCI image creation tool context menu
5 | GenericName[en_US]=Repackinator
6 | GenericName=Repackinator
7 | Icon=repackinator
8 | Keywords=Utility;Games;Xbox;XISO
9 | MimeType=application/x-nintendo-3ds-rom;application/x-cd-image;application/x-cci;application/x-compressed-iso;
10 | Name[en_US]=Repackinator
11 | Name=Repackinator
12 | Type=Service
13 | X-DBUS-ServiceName=
14 | X-DBUS-StartupType=none
15 | X-KDE-Icon=repackinator
16 | X-KDE-Priority=TopLevel
17 | X-KDE-ServiceTypes=KonqPopupMenu/Plugin
18 | X-KDE-Submenu=Repackinator
19 | X-KDE-SubstituteUID=false
20 | X-KDE-Username=
21 |
22 | [Desktop Action ConvertToISO]
23 | Exec=gnome-terminal -- repackinator -a convert -w -i %U
24 | Icon=repackinator
25 | Name=Convert To ISO
26 |
27 | [Desktop Action ConvertToISOScrub]
28 | Exec=gnome-terminal -- repackinator -a convert -s Scrub -w -i %U
29 | Icon=repackinator
30 | Name=Convert To ISO (Scrub)
31 |
32 | [Desktop Action ConvertToISOTrimmedScrub]
33 | Exec=gnome-terminal -- repackinator -a convert -s TrimmedScrub -w -i %U
34 | Icon=repackinator
35 | Name=Convert To ISO (Scrub + Truncate)
36 |
37 | [Desktop Action ConvertToCCI]
38 | Exec=gnome-terminal -- repackinator -a convert -c -w -i %U
39 | Icon=repackinator
40 | Name=Convert To CCI
41 |
42 | [Desktop Action ConvertToCCIScrub]
43 | Exec=gnome-terminal -- repackinator -a convert -c -s Scrub -w -i %U
44 | Icon=repackinator
45 | Name=Convert To CCI (Scrub)
46 |
47 | [Desktop Action ConvertToCCITrimmedScrub]
48 | Exec=gnome-terminal -- repackinator -a convert -c -s TrimmedScrub -w -i %U
49 | Icon=repackinator
50 | Name=Convert To CCI (Scrub + Truncate)
51 |
52 | [Desktop Action CompareISO1]
53 | Exec=gnome-terminal -- repackinator -a compare -f %U
54 | Icon=repackinator
55 | Name=Compare ISO ↘ 1st
56 |
57 | [Desktop Action CompareISO2]
58 | Exec=gnome-terminal -- repackinator -a compare -c -w -s %U
59 | Icon=repackinator
60 | Name=Compare ISO ↗ 2nd
61 |
62 | [Desktop Action CompareCCI1]
63 | Exec=gnome-terminal -- repackinator -a compare -f %U
64 | Icon=repackinator
65 | Name=Compare CCI ↘ 1st
66 |
67 | [Desktop Action CompareCCI2]
68 | Exec=gnome-terminal -- repackinator -a compare -c -w -s %U
69 | Icon=repackinator
70 | Name=Compare CCI ↗ 2nd
71 |
72 | [Desktop Action InfoCCIorISO]
73 | Exec=gnome-terminal -- repackinator -a info -w -i %U
74 | Icon=repackinator
75 | Name=Information CCI or ISO
76 |
77 | [Desktop Action ChecksumSectorData]
78 | Exec=gnome-terminal -- repackinator -a checksum -w -i %U
79 | Icon=repackinator
80 | Name=Checksum Sector Data (SHA256)
81 |
82 | [Desktop Action ExtractCCIorISO]
83 | Exec=gnome-terminal -- repackinator -a extract -w -i %U
84 | Icon=repackinator
85 | Name=Extract CCI or ISO
--------------------------------------------------------------------------------
/Repackinator/Repackinator.UI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net8.0
5 | enable
6 | true
7 | app.manifest
8 | true
9 | Assets\repackinator.ico
10 | repackinator.ui
11 | true
12 | false
13 | true
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | PreserveNewest
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Always
41 | true
42 |
43 |
44 | Always
45 | true
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | None
63 | All
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | AboutWindow.axaml
76 |
77 |
78 | MessageWindow.axaml
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleRegister.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using Repackinator.Shell.Shell;
3 | using Repackinator.Core.Helpers;
4 |
5 | namespace Repackinator.Shell.Console
6 | {
7 | public static class ConsoleRegister
8 | {
9 | public const string Action = "Register";
10 | public static bool ShowHelp { get; set; } = false;
11 | public static bool Wait { get; set; } = false;
12 |
13 | public static OptionSet GetOptions()
14 | {
15 | return new OptionSet {
16 | { "h|help", "show help", h => ShowHelp = h != null },
17 | { "w|wait", "Wait on exit", w => Wait = w != null }
18 | };
19 | }
20 |
21 | public static void ShowOptionDescription()
22 | {
23 | System.Console.WriteLine();
24 | System.Console.WriteLine("Register Action...");
25 | System.Console.WriteLine();
26 | System.Console.WriteLine("This action adds or updates repackinator's context menu (needs admin privileges).");
27 | System.Console.WriteLine();
28 | GetOptions().WriteOptionDescriptions(System.Console.Out);
29 | }
30 |
31 | public static void Process(string version, string[] args)
32 | {
33 | try
34 | {
35 | var options = GetOptions();
36 | options.Parse(args);
37 | if (ShowHelp)
38 | {
39 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
40 | ConsoleUtil.ProcessWait(Wait);
41 | return;
42 | }
43 |
44 | if (!OperatingSystem.IsWindows())
45 | {
46 | System.Console.WriteLine("Register action is only available on Windows.");
47 | Environment.ExitCode = 1;
48 | ConsoleUtil.ProcessWait(Wait);
49 | return;
50 | }
51 |
52 | if (!Utility.IsAdmin())
53 | {
54 | System.Console.WriteLine("This action requires administrator privileges.");
55 | System.Console.WriteLine("Attempting to elevate permissions...");
56 |
57 | // Reconstruct original arguments for elevation
58 | var elevatedArgs = new List { "-a=register" };
59 | if (Wait)
60 | {
61 | elevatedArgs.Add("-w");
62 | }
63 |
64 | if (Utility.RestartAsAdmin(elevatedArgs.ToArray()))
65 | {
66 | // Successfully started elevated process, exit this one
67 | return;
68 | }
69 | else
70 | {
71 | System.Console.WriteLine("Error: Failed to elevate permissions. Please run this command as administrator.");
72 | Environment.ExitCode = 1;
73 | ConsoleUtil.ProcessWait(Wait);
74 | return;
75 | }
76 | }
77 |
78 | var result = ContextMenu.RegisterContext();
79 | if (result)
80 | {
81 | System.Console.WriteLine("Context menu added.");
82 | }
83 | else
84 | {
85 | System.Console.WriteLine("Failed to add context menu.");
86 | Environment.ExitCode = 1;
87 | }
88 | }
89 | catch (OptionException e)
90 | {
91 | ConsoleUtil.ShowOptionException(e, Action, version);
92 | Environment.ExitCode = 1;
93 | }
94 |
95 | ConsoleUtil.ProcessWait(Wait);
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleUnregister.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using Repackinator.Shell.Shell;
3 | using Repackinator.Core.Helpers;
4 |
5 | namespace Repackinator.Shell.Console
6 | {
7 | public static class ConsoleUnregister
8 | {
9 | public const string Action = "Unregister";
10 | public static bool ShowHelp { get; set; } = false;
11 | public static bool Wait { get; set; } = false;
12 |
13 | public static OptionSet GetOptions()
14 | {
15 | return new OptionSet {
16 | { "h|help", "show help", h => ShowHelp = h != null },
17 | { "w|wait", "Wait on exit", w => Wait = w != null }
18 | };
19 | }
20 |
21 | public static void ShowOptionDescription()
22 | {
23 | System.Console.WriteLine();
24 | System.Console.WriteLine("Unregister Action...");
25 | System.Console.WriteLine();
26 | System.Console.WriteLine("This action removes repackinator's context menu (needs admin privileges).");
27 | System.Console.WriteLine();
28 | GetOptions().WriteOptionDescriptions(System.Console.Out);
29 | }
30 |
31 | public static void Process(string version, string[] args)
32 | {
33 | try
34 | {
35 | var options = GetOptions();
36 | options.Parse(args);
37 | if (ShowHelp)
38 | {
39 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
40 | ConsoleUtil.ProcessWait(Wait);
41 | return;
42 | }
43 |
44 | if (!OperatingSystem.IsWindows())
45 | {
46 | System.Console.WriteLine("Unregister action is only available on Windows.");
47 | Environment.ExitCode = 1;
48 | ConsoleUtil.ProcessWait(Wait);
49 | return;
50 | }
51 |
52 | if (!Utility.IsAdmin())
53 | {
54 | System.Console.WriteLine("This action requires administrator privileges.");
55 | System.Console.WriteLine("Attempting to elevate permissions...");
56 |
57 | // Reconstruct original arguments for elevation
58 | var elevatedArgs = new List { "-a=unregister" };
59 | if (Wait)
60 | {
61 | elevatedArgs.Add("-w");
62 | }
63 |
64 | if (Utility.RestartAsAdmin(elevatedArgs.ToArray()))
65 | {
66 | // Successfully started elevated process, exit this one
67 | return;
68 | }
69 | else
70 | {
71 | System.Console.WriteLine("Error: Failed to elevate permissions. Please run this command as administrator.");
72 | Environment.ExitCode = 1;
73 | ConsoleUtil.ProcessWait(Wait);
74 | return;
75 | }
76 | }
77 |
78 | var result = ContextMenu.UnregisterContext();
79 | if (result)
80 | {
81 | System.Console.WriteLine("Context menu removed.");
82 | }
83 | else
84 | {
85 | System.Console.WriteLine("Failed to remove context menu.");
86 | Environment.ExitCode = 1;
87 | }
88 | }
89 | catch (OptionException e)
90 | {
91 | ConsoleUtil.ShowOptionException(e, Action, version);
92 | Environment.ExitCode = 1;
93 | }
94 |
95 | ConsoleUtil.ProcessWait(Wait);
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/Repackinator.Core/Streams/ExtractSplitStream.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Exceptions;
2 |
3 | namespace Repackinator.Core.Streams
4 | {
5 | public class ExtractSplitStream : Stream
6 | {
7 | private int m_currentPart;
8 | private Stream[] m_outputParts;
9 | private long m_bytesProcessed;
10 | private long m_isoLength;
11 | private Action m_progress;
12 | private CancellationToken m_cancellationToken;
13 |
14 | public ExtractSplitStream(Stream outputPart1, Stream outputPart2, long isoLength, Action progress, CancellationToken cancellationToken)
15 | {
16 | m_currentPart = 0;
17 | m_outputParts = new Stream[] { outputPart1, outputPart2 };
18 | m_cancellationToken = cancellationToken;
19 | m_isoLength = isoLength;
20 | m_progress = progress;
21 | }
22 |
23 | public override void Write(byte[] buffer, int offset, int count)
24 | {
25 | if (m_cancellationToken.IsCancellationRequested)
26 | {
27 | throw new ExtractAbortException();
28 | }
29 |
30 | const long VideoSectors = 0x30600;
31 | const long RedumpSectors = 0x3A4D50;
32 | long skipSize = m_isoLength == RedumpSectors << 11 ? VideoSectors << 11 : 0;
33 |
34 | // Ignore video partition sectors
35 | if (m_bytesProcessed < skipSize)
36 | {
37 | var remainder = m_bytesProcessed + count - skipSize;
38 | if (remainder > 0)
39 | {
40 | offset = count - (int)remainder + offset;
41 | m_bytesProcessed += count;
42 | count = (int)remainder;
43 | }
44 | else
45 | {
46 | m_bytesProcessed += count;
47 | count = 0;
48 | }
49 | }
50 |
51 | if (count == 0)
52 | {
53 | m_progress(m_bytesProcessed / (float)m_isoLength);
54 | return;
55 | }
56 |
57 | long sectorSplitPosition = (m_isoLength - skipSize) / 4096 * 2048 + skipSize;
58 |
59 | if (m_bytesProcessed < sectorSplitPosition)
60 | {
61 | var remainder = m_bytesProcessed + count - sectorSplitPosition;
62 | if (remainder > 0)
63 | {
64 | m_bytesProcessed += count;
65 | m_outputParts[0].Write(buffer, offset, count - (int)remainder);
66 |
67 | offset = count - (int)remainder + offset;
68 | count = (int)remainder;
69 | m_currentPart = 1;
70 | m_outputParts[m_currentPart].Write(buffer, offset, count);
71 | }
72 | else
73 | {
74 | m_bytesProcessed += count;
75 | m_outputParts[m_currentPart].Write(buffer, offset, count);
76 | }
77 | m_progress(m_bytesProcessed / (float)m_isoLength);
78 | return;
79 | }
80 |
81 | m_bytesProcessed += count;
82 | m_outputParts[m_currentPart].Write(buffer, offset, count);
83 | m_progress(m_bytesProcessed / (float)m_isoLength);
84 | }
85 |
86 | public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
87 | public override void Flush() => throw new NotImplementedException();
88 | public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
89 | public override void SetLength(long value) => throw new NotImplementedException();
90 |
91 | public override bool CanRead => throw new NotImplementedException();
92 | public override bool CanSeek => throw new NotImplementedException();
93 | public override bool CanWrite => throw new NotImplementedException();
94 | public override long Length => throw new NotImplementedException();
95 |
96 | public override long Position
97 | {
98 | get { throw new NotImplementedException(); }
99 | set { throw new NotImplementedException(); }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Repackinator.Core/Models/Config.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 | using System.Text.Json.Serialization.Metadata;
5 | using Repackinator.Core.Helpers;
6 |
7 | namespace Repackinator.Core.Models
8 | {
9 | public enum GameDataFilterType
10 | {
11 | Scrub,
12 | Process,
13 | [Description("Title ID")]
14 | TitleID,
15 | Region,
16 | Version,
17 | [Description("Title Name")]
18 | TitleName,
19 | Letter,
20 | [Description("XBE Title")]
21 | XBETitle,
22 | [Description("Folder Name")]
23 | FolderName,
24 | [Description("ISO Name")]
25 | ISOName,
26 | [Description("ISO Checksum")]
27 | ISOChecksum
28 | }
29 |
30 | public enum GroupingOptionType
31 | {
32 | None,
33 | Region,
34 | Letter,
35 | [Description("Region Letter")]
36 | RegionLetter,
37 | [Description("Letter Region")]
38 | LetterRegion
39 | }
40 |
41 | public enum CompressOptionType
42 | {
43 | None,
44 | CCI
45 | }
46 |
47 | public enum ScrubOptionType
48 | {
49 | None,
50 | Scrub,
51 | TrimScrub
52 | }
53 |
54 | public class Config
55 | {
56 | public string Section { get; set; }
57 |
58 | public GameDataFilterType FilterType { get; set; }
59 |
60 | [JsonIgnore]
61 | public int LeechType { get; set; }
62 |
63 | public string CompareFirst { get; set; }
64 |
65 | public string CompareSecond { get; set; }
66 |
67 | public GroupingOptionType GroupingOption { get; set; }
68 |
69 | public bool Uppercase { get; set; }
70 |
71 | public CompressOptionType CompressOption { get; set; }
72 |
73 | public ScrubOptionType ScrubOption { get; set; }
74 |
75 | public bool RecurseInput { get; set; }
76 |
77 | public bool NoSplit { get; set; }
78 |
79 | public string InputPath { get; set; }
80 |
81 | public string OutputPath { get; set; }
82 |
83 | public string UnpackPath { get; set; }
84 |
85 | public Config()
86 | {
87 | Section = "Main";
88 | FilterType = GameDataFilterType.TitleName;
89 | LeechType = 0;
90 | CompareFirst = string.Empty;
91 | CompareSecond = string.Empty;
92 | GroupingOption = GroupingOptionType.None;
93 | Uppercase = false;
94 | CompressOption = CompressOptionType.None;
95 | ScrubOption = ScrubOptionType.Scrub;
96 | RecurseInput = false;
97 | NoSplit = false;
98 | InputPath = string.Empty;
99 | OutputPath = string.Empty;
100 | UnpackPath = string.Empty;
101 | }
102 |
103 | public static Config LoadConfig(string path)
104 | {
105 | var configJson = File.ReadAllText(path);
106 | var result = JsonSerializer.Deserialize(configJson);
107 | return result ?? new();
108 | }
109 |
110 | public static Config LoadConfig()
111 | {
112 | var applicationPath = Utility.GetApplicationPath();
113 | if (applicationPath == null)
114 | {
115 | return new Config();
116 | }
117 |
118 | var configPath = Path.Combine(applicationPath, "config.json");
119 | if (!File.Exists(configPath))
120 | {
121 | return new Config();
122 | }
123 |
124 | return LoadConfig(configPath);
125 | }
126 |
127 | public static void SaveConfig(string path, Config? config)
128 | {
129 | if (config == null)
130 | {
131 | return;
132 | }
133 |
134 | var result = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
135 | File.WriteAllText(path, result);
136 | }
137 |
138 | public static void SaveConfig(Config? config)
139 | {
140 | var applicationPath = Utility.GetApplicationPath();
141 | if (applicationPath == null)
142 | {
143 | return;
144 | }
145 |
146 | var configPath = Path.Combine(applicationPath, "config.json");
147 | SaveConfig(configPath, config);
148 | }
149 |
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/Repackinator.Core/Helpers/Utility.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Diagnostics;
3 | using System.Reflection;
4 | using System.Security.Principal;
5 |
6 | namespace Repackinator.Core.Helpers
7 | {
8 | public static class Utility
9 | {
10 | public static bool IsAdmin()
11 | {
12 | if (!OperatingSystem.IsWindows())
13 | {
14 | return false;
15 | }
16 | bool isAdmin;
17 | try
18 | {
19 | var user = WindowsIdentity.GetCurrent();
20 | var principal = new WindowsPrincipal(user);
21 | isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
22 | }
23 | catch
24 | {
25 | isAdmin = false;
26 | }
27 | return isAdmin;
28 | }
29 |
30 | ///
31 | /// Restarts the current process with administrator privileges.
32 | /// Returns true if the process was restarted, false if elevation failed or is not needed.
33 | ///
34 | public static bool RestartAsAdmin(string[] args)
35 | {
36 | if (!OperatingSystem.IsWindows())
37 | {
38 | return false;
39 | }
40 |
41 | if (IsAdmin())
42 | {
43 | return false; // Already admin, no need to restart
44 | }
45 |
46 | try
47 | {
48 | var exePath = Environment.ProcessPath;
49 | if (string.IsNullOrEmpty(exePath))
50 | {
51 | return false;
52 | }
53 |
54 | var startInfo = new ProcessStartInfo
55 | {
56 | FileName = exePath,
57 | Arguments = string.Join(" ", args.Select(arg => arg.Contains(' ') ? $"\"{arg}\"" : arg)),
58 | UseShellExecute = true,
59 | Verb = "runas" // This triggers UAC elevation
60 | };
61 |
62 | Process.Start(startInfo);
63 | return true; // Successfully started elevated process
64 | }
65 | catch
66 | {
67 | // User likely cancelled UAC prompt or elevation failed
68 | return false;
69 | }
70 | }
71 |
72 | public static string GetNameFromSlice(string filename)
73 | {
74 | var nameWithoutExtension = Path.GetFileNameWithoutExtension(filename);
75 | var subExtension = Path.GetExtension(nameWithoutExtension);
76 | if (subExtension.Equals(".1") || subExtension.Equals(".2"))
77 | {
78 | nameWithoutExtension = Path.GetFileNameWithoutExtension(nameWithoutExtension);
79 | }
80 | return nameWithoutExtension;
81 | }
82 |
83 | public static string? GetApplicationPath()
84 | {
85 | var exePath = AppDomain.CurrentDomain.BaseDirectory;
86 | if (exePath == null)
87 | {
88 | return null;
89 | }
90 |
91 | var result = Path.GetDirectoryName(exePath);
92 | return result;
93 | }
94 |
95 | public static string EnumValueToString(Enum value)
96 | {
97 | var field = value.GetType().GetField(value.ToString());
98 | var attr = field?.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast().FirstOrDefault();
99 | return attr?.Description ?? value.ToString();
100 | }
101 |
102 | public static T StringValueToEnum(string value) where T : struct, Enum
103 | {
104 | foreach (T enumValue in Enum.GetValues(typeof(T)))
105 | {
106 | var enumString = enumValue.ToString() ?? "";
107 | var field = value.GetType().GetField(enumString);
108 | var attr = field?.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast().FirstOrDefault();
109 | if (attr?.Description.Equals(value) ?? false || enumString.Equals(value))
110 | {
111 | return enumValue;
112 | }
113 | }
114 | throw new ArgumentException($"The value '{value}' is not a valid value for enum type {typeof(T).Name}.", nameof(value));
115 | }
116 |
117 | public static bool ValidateFatX(string value)
118 | {
119 | const string validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 0123456789!#$%&'()-.@[]^_`{}~";
120 | foreach (var c in value)
121 | {
122 | if (!validChars.Contains(c))
123 | {
124 | return false;
125 | }
126 | }
127 | return true;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Shell/ContextMenu.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using Repackinator.Core.Helpers;
3 |
4 | namespace Repackinator.Shell.Shell
5 | {
6 | public class ContextMenu
7 | {
8 | private static void RegisterSubMenu(RegistryKey key, string name, string description, string command, string iconPath)
9 | {
10 | if (OperatingSystem.IsWindows() && Utility.IsAdmin())
11 | {
12 | var menu1Key = key.CreateSubKey($"shell\\{name}");
13 | menu1Key.SetValue("MUIVerb", description);
14 | menu1Key.SetValue("Icon", $"{iconPath},0");
15 | var commandMenu1Key = menu1Key.CreateSubKey("command");
16 | commandMenu1Key.SetValue(null, command);
17 | }
18 | }
19 |
20 | public static bool RegisterContext()
21 | {
22 | if (OperatingSystem.IsWindows() && Utility.IsAdmin())
23 | {
24 | // Always unregister first to clear old menus
25 | UnregisterContext();
26 |
27 | // Use shell executable for command-line actions
28 | var baseDir = AppDomain.CurrentDomain.BaseDirectory;
29 | var shellExePath = Path.Combine(baseDir, "repackinator.shell.exe");
30 |
31 | // Fallback to main exe if shell doesn't exist (for backwards compatibility)
32 | var exePath = File.Exists(shellExePath) ? shellExePath : Path.Combine(baseDir, $"{AppDomain.CurrentDomain.FriendlyName}.exe");
33 |
34 | using var key = Registry.ClassesRoot.CreateSubKey($"*\\shell\\Repackinator");
35 |
36 | key.SetValue("AppliesTo", ".iso OR .cci");
37 | key.SetValue("MUIVerb", "Repackinator");
38 | key.SetValue("SubCommands", string.Empty);
39 | key.SetValue("Icon", $"{exePath},0");
40 | key.SetValue("Version", Core.Version.Value);
41 |
42 | // Convert to ISO options
43 | RegisterSubMenu(key, "01ConvertToISO", "Convert to ISO", $"\"{exePath}\" -a=convert -i \"%L\" -w", exePath);
44 | RegisterSubMenu(key, "02ConvertToISOScrub", "Convert to ISO (Scrub)", $"\"{exePath}\" -a=convert -i \"%L\" -s -w", exePath);
45 | RegisterSubMenu(key, "03ConvertToISOTrimScrub", "Convert to ISO (TrimScrub)", $"\"{exePath}\" -a=convert -i \"%L\" -t -w", exePath);
46 |
47 | // Convert to CCI options
48 | RegisterSubMenu(key, "04ConvertToCCI", "Convert to CCI", $"\"{exePath}\" -a=convert -i \"%L\" -c -w", exePath);
49 | RegisterSubMenu(key, "05ConvertToCCIScrub", "Convert to CCI (Scrub)", $"\"{exePath}\" -a=convert -i \"%L\" -s -c -w", exePath);
50 | RegisterSubMenu(key, "06ConvertToCCITrimScrub", "Convert to CCI (TrimScrub)", $"\"{exePath}\" -a=convert -i \"%L\" -t -c -w", exePath);
51 |
52 | // Information and extraction options
53 | RegisterSubMenu(key, "07XbeInfo", "XBE Info", $"\"{exePath}\" -a=xbeinfo -i \"%L\" -w", exePath);
54 | RegisterSubMenu(key, "08Info", "Sector Info", $"\"{exePath}\" -a=info -i \"%L\" -w", exePath);
55 | RegisterSubMenu(key, "09Checksum", "Checksum (SHA256)", $"\"{exePath}\" -a=checksum -i \"%L\" -w", exePath);
56 | RegisterSubMenu(key, "10Extract", "Extract Files", $"\"{exePath}\" -a=extract -i \"%L\" -w", exePath);
57 |
58 | // Compare options
59 | RegisterSubMenu(key, "11CompareSetFirst", "Compare - Set First", $"\"{exePath}\" -a=compare -f \"%L\"", exePath);
60 | RegisterSubMenu(key, "12CompareFirstWith", "Compare - First With This", $"\"{exePath}\" -a=compare -s \"%L\" -c -w", exePath);
61 |
62 | // Register context menu for folders (Directory)
63 | // (UnregisterFolderContext was already called by UnregisterContext above)
64 | using var dirKey = Registry.ClassesRoot.CreateSubKey($"Directory\\shell\\Repackinator");
65 |
66 | dirKey.SetValue("MUIVerb", "Repackinator");
67 | dirKey.SetValue("SubCommands", string.Empty);
68 | dirKey.SetValue("Icon", $"{exePath},0");
69 | dirKey.SetValue("Version", Core.Version.Value);
70 |
71 | // Pack To ISO - output will be in parent directory with folder name
72 | RegisterSubMenu(dirKey, "01PackToISO", "Pack To ISO", $"\"{exePath}\" -a=pack -i \"%1\" -o \"%1.iso\" -w", exePath);
73 |
74 | // Pack To CCI - output will be in parent directory with folder name
75 | RegisterSubMenu(dirKey, "02PackToCCI", "Pack To CCI", $"\"{exePath}\" -a=pack -i \"%1\" -o \"%1.cci\" -c -w", exePath);
76 |
77 | return true;
78 | }
79 |
80 | return false;
81 | }
82 |
83 | public static bool UnregisterContext()
84 | {
85 | if (OperatingSystem.IsWindows() && Utility.IsAdmin())
86 | {
87 | Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\Repackinator", false);
88 | UnregisterFolderContext();
89 | return true;
90 | }
91 |
92 | return false;
93 | }
94 |
95 | private static bool UnregisterFolderContext()
96 | {
97 | if (OperatingSystem.IsWindows() && Utility.IsAdmin())
98 | {
99 | Registry.ClassesRoot.DeleteSubKeyTree("Directory\\shell\\Repackinator", false);
100 | return true;
101 | }
102 |
103 | return false;
104 | }
105 | }
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleStartup.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 |
3 | namespace Repackinator.Shell.Console
4 | {
5 | public static class ConsoleStartup
6 | {
7 | public static string Action { get; set; } = string.Empty;
8 | public static bool ShowHelp { get; set; } = false;
9 | public static bool Wait { get; set; } = false;
10 |
11 | private static Dictionary> ActionsRegister = BuildActionsRegister();
12 |
13 | private static Dictionary> BuildActionsRegister()
14 | {
15 | var dict = new Dictionary>(StringComparer.CurrentCultureIgnoreCase);
16 |
17 | if (OperatingSystem.IsWindows())
18 | {
19 | dict.Add(ConsoleRegister.Action, ConsoleRegister.Process);
20 | dict.Add(ConsoleUnregister.Action, ConsoleUnregister.Process);
21 | }
22 |
23 | dict.Add(ConsoleConvert.Action, ConsoleConvert.Process);
24 | dict.Add(ConsoleCompare.Action, ConsoleCompare.Process);
25 | dict.Add(ConsoleInfo.Action, ConsoleInfo.Process);
26 | dict.Add(ConsoleChecksum.Action, ConsoleChecksum.Process);
27 | dict.Add(ConsoleExtract.Action, ConsoleExtract.Process);
28 | dict.Add(ConsoleRepack.Action, ConsoleRepack.Process);
29 | dict.Add(ConsolePack.Action, ConsolePack.Process);
30 | dict.Add(ConsoleXbeInfo.Action, ConsoleXbeInfo.Process);
31 |
32 | return dict;
33 | }
34 |
35 | public static void Process(string version, string[] args)
36 | {
37 | var options = new OptionSet {
38 | { "a|action=", $"Action ({string.Join(", ", ActionsRegister.Keys)})", a => Action = a },
39 | { "h|help", "show help", h => ShowHelp = h != null },
40 | { "w|wait", "Wait on exit", w => Wait = w != null }
41 | };
42 |
43 | try
44 | {
45 | options.Parse(args);
46 | if (ShowHelp && string.IsNullOrEmpty(Action))
47 | {
48 | // Show concise main help with action summaries
49 | var actionDescriptions = new Dictionary
50 | {
51 | { "Pack", "Pack a folder into ISO or CCI format" },
52 | { "Repack", "Repackinate a collection of Xbox disk images" },
53 | { "Convert", "Convert one Xbox disk image format to another" },
54 | { "Extract", "Extract files from Xbox disk image" },
55 | { "Compare", "Compare two Xbox disk images" },
56 | { "Info", "Show Xbox disk data sector information" },
57 | { "Checksum", "Calculate checksum of Xbox disk image sectors" },
58 | { "XbeInfo", "Extract XBE certificate information from disk image" }
59 | };
60 |
61 | if (OperatingSystem.IsWindows())
62 | {
63 | actionDescriptions.Add("Register", "Register context menu (Windows, requires admin)");
64 | actionDescriptions.Add("Unregister", "Unregister context menu (Windows, requires admin)");
65 | }
66 |
67 | ConsoleUtil.ShowHelpHeader(version, options, actionDescriptions);
68 | ConsoleUtil.ProcessWait(Wait);
69 | return;
70 | }
71 |
72 | if (string.IsNullOrEmpty(Action))
73 | {
74 | // Show main help when no action is specified
75 | var actionDescriptions = new Dictionary
76 | {
77 | { "Pack", "Pack a folder into ISO or CCI format" },
78 | { "Repack", "Repackinate a collection of Xbox disk images" },
79 | { "Convert", "Convert one Xbox disk image format to another" },
80 | { "Extract", "Extract files from Xbox disk image" },
81 | { "Compare", "Compare two Xbox disk images" },
82 | { "Info", "Show Xbox disk data sector information" },
83 | { "Checksum", "Calculate checksum of Xbox disk image sectors" },
84 | { "XbeInfo", "Extract XBE certificate information from disk image" }
85 | };
86 |
87 | if (OperatingSystem.IsWindows())
88 | {
89 | actionDescriptions.Add("Register", "Register context menu (Windows, requires admin)");
90 | actionDescriptions.Add("Unregister", "Unregister context menu (Windows, requires admin)");
91 | }
92 |
93 | ConsoleUtil.ShowHelpHeader(version, options, actionDescriptions);
94 | ConsoleUtil.ProcessWait(Wait);
95 | return;
96 | }
97 |
98 | if (ActionsRegister.ContainsKey(Action))
99 | {
100 | ActionsRegister[Action](version, args);
101 | }
102 | else
103 | {
104 | throw new OptionException($"Action '{Action}' is not valid. Use -h to see available actions.", "action");
105 | }
106 | }
107 | catch (OptionException e)
108 | {
109 | ConsoleUtil.ShowOptionException(e, Action, version);
110 | }
111 | }
112 | }
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: BuildRepackinator
2 |
3 | on:
4 | push:
5 | branches: [ "main", "workflow" ]
6 | pull_request:
7 | branches: [ "main", "workflow" ]
8 | workflow_dispatch: {}
9 |
10 | permissions:
11 | contents: write
12 |
13 | jobs:
14 | build:
15 |
16 | runs-on: ubuntu-latest
17 |
18 | strategy:
19 | matrix:
20 | include:
21 | - os_flavor: win-x64
22 | arch: x64
23 | archive_ext: zip
24 | project: Repackinator.UI
25 | project_path: Repackinator/Repackinator.UI
26 | project_lowercase: repackinator.ui
27 | - os_flavor: linux-x64
28 | arch: x64
29 | archive_ext: tar
30 | project: Repackinator.UI
31 | project_path: Repackinator/Repackinator.UI
32 | project_lowercase: repackinator.ui
33 | - os_flavor: osx-x64
34 | arch: x64
35 | archive_ext: tar
36 | project: Repackinator.UI
37 | project_path: Repackinator/Repackinator.UI
38 | project_lowercase: repackinator.ui
39 | - os_flavor: osx-arm64
40 | arch: arm64
41 | archive_ext: tar
42 | project: Repackinator.UI
43 | project_path: Repackinator/Repackinator.UI
44 | project_lowercase: repackinator.ui
45 | - os_flavor: win-x64
46 | arch: x64
47 | archive_ext: zip
48 | project: Repackinator.Shell
49 | project_path: Repackinator.Shell/Repackinator.Shell
50 | project_lowercase: repackinator.shell
51 | - os_flavor: linux-x64
52 | arch: x64
53 | archive_ext: tar
54 | project: Repackinator.Shell
55 | project_path: Repackinator.Shell/Repackinator.Shell
56 | project_lowercase: repackinator.shell
57 | - os_flavor: osx-x64
58 | arch: x64
59 | archive_ext: tar
60 | project: Repackinator.Shell
61 | project_path: Repackinator.Shell/Repackinator.Shell
62 | project_lowercase: repackinator.shell
63 | - os_flavor: osx-arm64
64 | arch: arm64
65 | archive_ext: tar
66 | project: Repackinator.Shell
67 | project_path: Repackinator.Shell/Repackinator.Shell
68 | project_lowercase: repackinator.shell
69 |
70 | steps:
71 | - uses: actions/checkout@v4
72 | - name: Setup .NET
73 | uses: actions/setup-dotnet@v4
74 | with:
75 | dotnet-version: 8.0.x
76 |
77 | - name: Publish
78 | run: |
79 | if [ "${{ matrix.project }}" == "Repackinator.UI" ]; then
80 | dotnet publish ./${{ matrix.project_path }}.csproj -p:Platform=${{ matrix.arch }} -r ${{ matrix.os_flavor }} -c Release -p:SelfContained=true -p:PublishSingleFile=true -p:PublishReadyToRun=true -p:TrimUnusedDependencies=false -p:DebugType=None -p:DebugSymbols=false -p:AvaloniaUseCompiledBindingsByDefault=true -p:IncludeNativeLibrariesForSelfExtract=true --output ${{ github.workspace }}/artifacts/${{ matrix.project }}/${{ matrix.os_flavor }}-${{ matrix.arch }} -maxcpucount:1
81 | else
82 | dotnet publish ./${{ matrix.project_path }}.csproj -p:Platform=${{ matrix.arch }} -r ${{ matrix.os_flavor }} -c Release -p:SelfContained=true -p:PublishSingleFile=true -p:PublishReadyToRun=true -p:TrimUnusedDependencies=false -p:DebugType=None -p:DebugSymbols=false -p:IncludeNativeLibrariesForSelfExtract=true --output ${{ github.workspace }}/artifacts/${{ matrix.project }}/${{ matrix.os_flavor }}-${{ matrix.arch }} -maxcpucount:1
83 | fi
84 |
85 | - name: Check publish output
86 | run: ls -laR ${{ github.workspace }}/artifacts/${{ matrix.project }}/
87 |
88 | - name: Chmod (Unix)
89 | if: ${{ matrix.os_flavor == 'linux-x64' || matrix.os_flavor == 'osx-x64' || matrix.os_flavor == 'osx-arm64'}}
90 | run: chmod +x ${{ github.workspace }}/artifacts/${{ matrix.project }}/${{ matrix.os_flavor }}-${{ matrix.arch }}/${{ matrix.project_lowercase }}
91 |
92 | - name: Compress binaries
93 | run: 7z a ${{ github.workspace }}/${{ matrix.project }}-${{ matrix.os_flavor }}.${{ matrix.archive_ext }} ${{ github.workspace }}/artifacts/${{ matrix.project }}/${{ matrix.os_flavor }}-${{ matrix.arch }}/*
94 |
95 | - name: List directories and files
96 | run: find .
97 |
98 | - name: Upload Build Artifact
99 | uses: actions/upload-artifact@v4
100 | with:
101 | name: ${{ matrix.project }}-${{ matrix.os_flavor }}
102 | path: ${{ github.workspace }}/${{ matrix.project }}-${{ matrix.os_flavor }}.${{ matrix.archive_ext }}
103 | if-no-files-found: error
104 |
105 | release:
106 | needs: build
107 | runs-on: ubuntu-latest
108 | if: startsWith(github.ref, 'refs/heads/main') || startsWith(github.ref, 'refs/tags/')
109 | steps:
110 | - uses: actions/checkout@v4
111 | - uses: actions/download-artifact@v4
112 | id: artifacts
113 | with:
114 | path: pipeline/build/artifacts
115 | merge-multiple: true
116 | - name: List Files
117 | run: |
118 | ls -la ${{ steps.artifacts.outputs.download-path }}
119 | - name: Create draft Release
120 | uses: softprops/action-gh-release@v2
121 | if: startsWith(github.ref, 'refs/heads/main')
122 | with:
123 | tag_name: latest
124 | draft: true
125 | files: |
126 | ${{ github.workspace }}/Repackinator.nfo
127 | ${{ github.workspace }}/README.md
128 | ${{ steps.artifacts.outputs.download-path }}/*
129 | - name: Create Release
130 | uses: softprops/action-gh-release@v2
131 | if: startsWith(github.ref, 'refs/tags/')
132 | with:
133 | files: |
134 | ${{ github.workspace }}/Repackinator.nfo
135 | ${{ github.workspace }}/README.md
136 | ${{ steps.artifacts.outputs.download-path }}/*
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleExtract.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using XboxToolkit;
3 | using XboxToolkit.Interface;
4 | using Repackinator.Core.Models;
5 | using Repackinator.Core.Helpers;
6 |
7 | namespace Repackinator.Shell.Console
8 | {
9 | public static class ConsoleExtract
10 | {
11 | public const string Action = "Extract";
12 | public static string Input { get; set; } = string.Empty;
13 | public static string Output { get; set; } = string.Empty;
14 | public static bool ShowHelp { get; set; } = false;
15 | public static bool Wait { get; set; } = false;
16 | public static bool Quiet { get; set; } = false;
17 |
18 | public static OptionSet GetOptions()
19 | {
20 | return new OptionSet {
21 | { "i|input=", "Input file", i => Input = i },
22 | { "o|output=", "Output directory (optional, defaults to folder with input name)", o => Output = o },
23 | { "h|help", "show help", h => ShowHelp = h != null },
24 | { "w|wait", "Wait on exit", w => Wait = w != null },
25 | { "q|quiet", "Suppress status output", q => Quiet = q != null }
26 | };
27 | }
28 |
29 | public static void ShowOptionDescription()
30 | {
31 | System.Console.WriteLine();
32 | System.Console.WriteLine("Extract Action...");
33 | System.Console.WriteLine();
34 | System.Console.WriteLine("This action is used to extract files from xbox disk image.");
35 | System.Console.WriteLine();
36 | GetOptions().WriteOptionDescriptions(System.Console.Out);
37 | }
38 |
39 | public static void Process(string version, string[] args)
40 | {
41 | var config = Config.LoadConfig();
42 |
43 | try
44 | {
45 | var options = GetOptions();
46 | options.Parse(args);
47 | if (ShowHelp)
48 | {
49 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
50 | ConsoleUtil.ProcessWait(Wait);
51 | return;
52 | }
53 |
54 | if (string.IsNullOrEmpty(Input))
55 | {
56 | throw new OptionException("Input file not specified.", "input");
57 | }
58 |
59 | if (!File.Exists(Input))
60 | {
61 | throw new OptionException("Input file does not exist.", "input");
62 | }
63 |
64 | string outputPath;
65 | if (!string.IsNullOrEmpty(Output))
66 | {
67 | outputPath = Path.GetFullPath(Output);
68 | Directory.CreateDirectory(outputPath);
69 | }
70 | else
71 | {
72 | // Default behavior: use folder with input name
73 | var inputDir = Path.GetDirectoryName(Input);
74 | if (inputDir == null)
75 | {
76 | throw new IOException("Unable to get directory name from input.");
77 | }
78 | var baseName = Path.GetFileNameWithoutExtension(Input);
79 | var subExtension = Path.GetExtension(baseName);
80 | if (subExtension.Equals(".1") || subExtension.Equals(".2"))
81 | {
82 | baseName = Path.GetFileNameWithoutExtension(baseName);
83 | }
84 | outputPath = Path.Combine(inputDir, baseName);
85 | Directory.CreateDirectory(outputPath);
86 | }
87 |
88 | if (!Quiet)
89 | {
90 | System.Console.WriteLine("Extracting From:");
91 | var slices = ContainerUtility.GetSlicesFromFile(Input);
92 | foreach (var slice in slices)
93 | {
94 | System.Console.WriteLine(Path.GetFileName(slice));
95 | }
96 | System.Console.WriteLine($"To: {outputPath}");
97 | System.Console.WriteLine("Extracting...");
98 | }
99 |
100 | if (!ContainerUtility.TryAutoDetectContainerType(Input, out var containerReader) || containerReader == null)
101 | {
102 | throw new Exception("Unable to detect container type.");
103 | }
104 | using (containerReader)
105 | {
106 | if (!containerReader.TryMount())
107 | {
108 | throw new Exception("Unable to mount container.");
109 | }
110 | try
111 | {
112 | Action? extractProgress = null;
113 | if (!Quiet)
114 | {
115 | extractProgress = new Action((filename) =>
116 | {
117 | System.Console.WriteLine($" {filename}");
118 | });
119 | }
120 |
121 | if (!ContainerUtility.ExtractFilesFromContainer(containerReader, outputPath, extractProgress))
122 | {
123 | throw new Exception("Unable to extract files.");
124 | }
125 | }
126 | finally
127 | {
128 | containerReader.Dismount();
129 | }
130 | }
131 |
132 | if (!Quiet)
133 | {
134 | System.Console.WriteLine();
135 | System.Console.WriteLine("Extract completed.");
136 | }
137 | }
138 | catch (OptionException e)
139 | {
140 | ConsoleUtil.ShowOptionException(e, Action, version);
141 | }
142 |
143 | ConsoleUtil.ProcessWait(Wait);
144 | }
145 | }
146 | }
147 |
148 |
--------------------------------------------------------------------------------
/Repackinator/ViewModels/AttachUpdateViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Threading;
3 | using ReactiveUI;
4 | using Repackinator.Core.Actions;
5 | using Repackinator.Core.Logging;
6 | using Repackinator.Core.Models;
7 | using System;
8 | using System.Collections.ObjectModel;
9 | using System.Diagnostics;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using System.Windows.Input;
14 |
15 | namespace Repackinator.UI.ViewModels
16 | {
17 | public class AttachUpdateViewModel : ViewModelBase
18 | {
19 | private DispatcherTimer mTimer { get; set; }
20 |
21 | private Stopwatch _stopwatch = new();
22 | private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
23 |
24 | public Window Owner { get; set; }
25 |
26 | public GameData[] GameDataList { get; set; }
27 |
28 | public Config Config { get; set; }
29 |
30 | public ObservableCollection Log { get; set; }
31 |
32 | private string mProgress1Text = string.Empty;
33 | public string Progress1Text
34 | {
35 | get => mProgress1Text;
36 | set => this.RaiseAndSetIfChanged(ref mProgress1Text, value);
37 | }
38 |
39 | private float mProgress1 = 0;
40 | public float Progress1
41 | {
42 | get => mProgress1;
43 | set => this.RaiseAndSetIfChanged(ref mProgress1, value);
44 | }
45 |
46 | private string mProgress2Text = string.Empty;
47 | public string Progress2Text
48 | {
49 | get => mProgress2Text;
50 | set => this.RaiseAndSetIfChanged(ref mProgress2Text, value);
51 | }
52 |
53 | private float mProgress2 = 0;
54 | public float Progress2
55 | {
56 | get => mProgress2;
57 | set => this.RaiseAndSetIfChanged(ref mProgress2, value);
58 | }
59 |
60 | private string mSummary = "Totals: Warnings = 0, Errors = 0, Skipped = 0, Missing = 0, Completed = 0";
61 | public string Summary
62 | {
63 | get => mSummary;
64 | set => this.RaiseAndSetIfChanged(ref mSummary, value);
65 | }
66 |
67 | private string mTotalTime = "Total Time: 00:00:00";
68 | public string TotalTime
69 | {
70 | get => mTotalTime;
71 | set => this.RaiseAndSetIfChanged(ref mTotalTime, value);
72 | }
73 |
74 | public ICommand CloseCommand { get; set; }
75 |
76 | public ICommand CopyLogCommand { get; set; }
77 |
78 | private void RefreshDetails(object? sender, EventArgs e)
79 | {
80 | var totalWarnings = 0;
81 | var totalErrors = 0;
82 | var totalSkipped = 0;
83 | var totalNotFound = 0;
84 | var totalCompleted = 0;
85 |
86 | for (var i = 0; i < Log.Count; i++)
87 | {
88 | var logMessageLevel = Log[i].Level;
89 | if (logMessageLevel == LogMessageLevel.Warning)
90 | {
91 | totalWarnings++;
92 | }
93 | else if (logMessageLevel == LogMessageLevel.Error)
94 | {
95 | totalErrors++;
96 | }
97 | else if (logMessageLevel == LogMessageLevel.Skipped)
98 | {
99 | totalSkipped++;
100 | }
101 | else if (logMessageLevel == LogMessageLevel.NotFound)
102 | {
103 | totalNotFound++;
104 | }
105 | else if (logMessageLevel == LogMessageLevel.Completed)
106 | {
107 | totalCompleted++;
108 | }
109 | }
110 |
111 | Summary = $"Totals: Warnings = {totalWarnings}, Errors = {totalErrors}, Skipped = {totalSkipped}, Missing = {totalNotFound}, Completed = {totalCompleted}";
112 | TotalTime = $"Total Time: {_stopwatch.Elapsed.Hours:00}:{_stopwatch.Elapsed.Minutes:00}:{_stopwatch.Elapsed.Seconds:00}";
113 | }
114 |
115 | public AttachUpdateViewModel(Window owner, GameData[] gameDataList, Config config)
116 | {
117 | Log = [];
118 | Owner = owner;
119 | GameDataList = gameDataList;
120 | Config = config;
121 |
122 | CloseCommand = ReactiveCommand.Create(() =>
123 | {
124 | _cancellationTokenSource.Cancel();
125 | owner.Close();
126 | });
127 |
128 | CopyLogCommand = ReactiveCommand.Create(() =>
129 | {
130 | var logText = new StringBuilder();
131 | for (var i = 0; i < Log.Count; i++)
132 | {
133 | logText.Append(Log[i].ToLogFormat());
134 | }
135 | owner.Clipboard?.SetTextAsync(logText.ToString());
136 | });
137 |
138 | mTimer = new DispatcherTimer
139 | {
140 | Interval = TimeSpan.FromSeconds(1)
141 | };
142 | mTimer.Tick += RefreshDetails;
143 | mTimer.Start();
144 | }
145 |
146 | public async Task StartAsync()
147 | {
148 | await Task.Run(() => {
149 |
150 | var logger = new Action((logMessage) =>
151 | {
152 | Dispatcher.UIThread.Invoke(() =>
153 | {
154 | Log.Add(logMessage);
155 | });
156 | });
157 |
158 | var progress = new Action((progress) =>
159 | {
160 | Dispatcher.UIThread.Invoke(() =>
161 | {
162 | Progress1Text = progress.Progress1Text;
163 | Progress1 = progress.Progress1;
164 | Progress2Text = progress.Progress2Text;
165 | Progress2 = progress.Progress2;
166 | });
167 | });
168 |
169 | var attachUpdater = new AttachUpdater();
170 | attachUpdater.StartAttachUpdating(GameDataList, Config, progress, logger, _stopwatch, _cancellationTokenSource.Token);
171 | });
172 | }
173 |
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/Repackinator/ViewModels/ProcessViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Threading;
3 | using ReactiveUI;
4 | using Repackinator.Core.Actions;
5 | using Repackinator.Core.Logging;
6 | using Repackinator.Core.Models;
7 | using System;
8 | using System.Collections.ObjectModel;
9 | using System.Diagnostics;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using System.Windows.Input;
14 |
15 | namespace Repackinator.UI.ViewModels
16 | {
17 | public class ProcessViewModel : ViewModelBase
18 | {
19 | private DispatcherTimer mTimer { get; set; }
20 |
21 | private Stopwatch _stopwatch = new();
22 | private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
23 |
24 | public Window Owner { get; set; }
25 |
26 | public GameData[] GameDataList { get; set; }
27 |
28 | public Config Config { get; set; }
29 |
30 | public ObservableCollection Log { get; set; }
31 |
32 | private string mProgress1Text = string.Empty;
33 | public string Progress1Text
34 | {
35 | get => mProgress1Text;
36 | set => this.RaiseAndSetIfChanged(ref mProgress1Text, value);
37 | }
38 |
39 | private float mProgress1 = 0;
40 | public float Progress1
41 | {
42 | get => mProgress1;
43 | set => this.RaiseAndSetIfChanged(ref mProgress1, value);
44 | }
45 |
46 | private string mProgress2Text = string.Empty;
47 | public string Progress2Text
48 | {
49 | get => mProgress2Text;
50 | set => this.RaiseAndSetIfChanged(ref mProgress2Text, value);
51 | }
52 |
53 | private float mProgress2 = 0;
54 | public float Progress2
55 | {
56 | get => mProgress2;
57 | set => this.RaiseAndSetIfChanged(ref mProgress2, value);
58 | }
59 |
60 | private string mSummary = "Totals: Warnings = 0, Errors = 0, Skipped = 0, Missing = 0, Completed = 0";
61 | public string Summary
62 | {
63 | get => mSummary;
64 | set => this.RaiseAndSetIfChanged(ref mSummary, value);
65 | }
66 |
67 | private string mTotalTime = "Total Time: 00:00:00";
68 | public string TotalTime
69 | {
70 | get => mTotalTime;
71 | set => this.RaiseAndSetIfChanged(ref mTotalTime, value);
72 | }
73 |
74 | public ICommand CloseCommand { get; set; }
75 |
76 | public ICommand CopyLogCommand { get; set; }
77 |
78 | private void RefreshDetails(object? sender, EventArgs e)
79 | {
80 | var totalWarnings = 0;
81 | var totalErrors = 0;
82 | var totalSkipped = 0;
83 | var totalNotFound = 0;
84 | var totalCompleted = 0;
85 |
86 | for (var i = 0; i < Log.Count; i++)
87 | {
88 | var logMessageLevel = Log[i].Level;
89 | if (logMessageLevel == LogMessageLevel.Warning)
90 | {
91 | totalWarnings++;
92 | }
93 | else if (logMessageLevel == LogMessageLevel.Error)
94 | {
95 | totalErrors++;
96 | }
97 | else if (logMessageLevel == LogMessageLevel.Skipped)
98 | {
99 | totalSkipped++;
100 | }
101 | else if (logMessageLevel == LogMessageLevel.NotFound)
102 | {
103 | totalNotFound++;
104 | }
105 | else if (logMessageLevel == LogMessageLevel.Completed)
106 | {
107 | totalCompleted++;
108 | }
109 | }
110 |
111 | Summary = $"Totals: Warnings = {totalWarnings}, Errors = {totalErrors}, Skipped = {totalSkipped}, Missing = {totalNotFound}, Completed = {totalCompleted}";
112 | TotalTime = $"Total Time: {_stopwatch.Elapsed.Hours:00}:{_stopwatch.Elapsed.Minutes:00}:{_stopwatch.Elapsed.Seconds:00}";
113 | }
114 |
115 | public ProcessViewModel(Window owner, GameData[] gameDataList, Config config)
116 | {
117 | Log = [];
118 | Owner = owner;
119 | GameDataList = gameDataList;
120 | Config = config;
121 |
122 | CloseCommand = ReactiveCommand.Create(() =>
123 | {
124 | _cancellationTokenSource.Cancel();
125 | owner.Close();
126 | });
127 |
128 | CopyLogCommand = ReactiveCommand.Create(() =>
129 | {
130 | var logText = new StringBuilder();
131 | for (var i = 0; i < Log.Count; i++)
132 | {
133 | logText.Append(Log[i].ToLogFormat());
134 | }
135 | owner.Clipboard?.SetTextAsync(logText.ToString());
136 | });
137 |
138 | mTimer = new DispatcherTimer
139 | {
140 | Interval = TimeSpan.FromSeconds(1)
141 | };
142 | mTimer.Tick += RefreshDetails;
143 | mTimer.Start();
144 | }
145 |
146 | public async Task StartAsync()
147 | {
148 | return await Task.Run(() =>
149 | {
150 |
151 | var logger = new Action((logMessage) =>
152 | {
153 | Dispatcher.UIThread.Invoke(() =>
154 | {
155 | Log.Add(logMessage);
156 | });
157 | });
158 |
159 | var progress = new Action((progress) =>
160 | {
161 | Dispatcher.UIThread.Invoke(() =>
162 | {
163 | Progress1Text = progress.Progress1Text;
164 | Progress1 = progress.Progress1;
165 | Progress2Text = progress.Progress2Text;
166 | Progress2 = progress.Progress2;
167 | });
168 | });
169 |
170 | var repacker = new Repacker();
171 | repacker.StartRepacking(GameDataList, Config, progress, logger, _stopwatch, _cancellationTokenSource.Token);
172 | return GameDataList;
173 | });
174 | }
175 |
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/Repackinator/ViewModels/ScanOutputViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Threading;
3 | using ReactiveUI;
4 | using Repackinator.Core.Actions;
5 | using Repackinator.Core.Logging;
6 | using Repackinator.Core.Models;
7 | using System;
8 | using System.Collections.ObjectModel;
9 | using System.Diagnostics;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using System.Windows.Input;
14 |
15 | namespace Repackinator.UI.ViewModels
16 | {
17 | public class ScanOutputViewModel : ViewModelBase
18 | {
19 | private DispatcherTimer mTimer { get; set; }
20 |
21 | private Stopwatch _stopwatch = new();
22 | private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
23 |
24 | public Window Owner { get; set; }
25 |
26 | public GameData[] GameDataList { get; set; }
27 |
28 | public Config Config { get; set; }
29 |
30 | public ObservableCollection Log { get; set; }
31 |
32 | private string mProgress1Text = string.Empty;
33 | public string Progress1Text
34 | {
35 | get => mProgress1Text;
36 | set => this.RaiseAndSetIfChanged(ref mProgress1Text, value);
37 | }
38 |
39 | private float mProgress1 = 0;
40 | public float Progress1
41 | {
42 | get => mProgress1;
43 | set => this.RaiseAndSetIfChanged(ref mProgress1, value);
44 | }
45 |
46 | private string mProgress2Text = string.Empty;
47 | public string Progress2Text
48 | {
49 | get => mProgress2Text;
50 | set => this.RaiseAndSetIfChanged(ref mProgress2Text, value);
51 | }
52 |
53 | private float mProgress2 = 0;
54 | public float Progress2
55 | {
56 | get => mProgress2;
57 | set => this.RaiseAndSetIfChanged(ref mProgress2, value);
58 | }
59 |
60 | private string mSummary = "Totals: Warnings = 0, Errors = 0, Skipped = 0, Missing = 0, Completed = 0";
61 | public string Summary
62 | {
63 | get => mSummary;
64 | set => this.RaiseAndSetIfChanged(ref mSummary, value);
65 | }
66 |
67 | private string mTotalTime = "Total Time: 00:00:00";
68 | public string TotalTime
69 | {
70 | get => mTotalTime;
71 | set => this.RaiseAndSetIfChanged(ref mTotalTime, value);
72 | }
73 |
74 | public ICommand CloseCommand { get; set; }
75 |
76 | public ICommand CopyLogCommand { get; set; }
77 |
78 | private void RefreshDetails(object? sender, EventArgs e)
79 | {
80 | var totalWarnings = 0;
81 | var totalErrors = 0;
82 | var totalSkipped = 0;
83 | var totalNotFound = 0;
84 | var totalCompleted = 0;
85 |
86 | for (var i = 0; i < Log.Count; i++)
87 | {
88 | var logMessageLevel = Log[i].Level;
89 | if (logMessageLevel == LogMessageLevel.Warning)
90 | {
91 | totalWarnings++;
92 | }
93 | else if (logMessageLevel == LogMessageLevel.Error)
94 | {
95 | totalErrors++;
96 | }
97 | else if (logMessageLevel == LogMessageLevel.Skipped)
98 | {
99 | totalSkipped++;
100 | }
101 | else if (logMessageLevel == LogMessageLevel.NotFound)
102 | {
103 | totalNotFound++;
104 | }
105 | else if (logMessageLevel == LogMessageLevel.Completed)
106 | {
107 | totalCompleted++;
108 | }
109 | }
110 |
111 | Summary = $"Totals: Warnings = {totalWarnings}, Errors = {totalErrors}, Skipped = {totalSkipped}, Missing = {totalNotFound}, Completed = {totalCompleted}";
112 | TotalTime = $"Total Time: {_stopwatch.Elapsed.Hours:00}:{_stopwatch.Elapsed.Minutes:00}:{_stopwatch.Elapsed.Seconds:00}";
113 | }
114 |
115 | public ScanOutputViewModel(Window owner, GameData[] gameDataList, Config config)
116 | {
117 | Log = [];
118 | Owner = owner;
119 | GameDataList = gameDataList;
120 | Config = config;
121 |
122 | CloseCommand = ReactiveCommand.Create(() =>
123 | {
124 | _cancellationTokenSource.Cancel();
125 | owner.Close();
126 | });
127 |
128 | CopyLogCommand = ReactiveCommand.Create(() =>
129 | {
130 | var logText = new StringBuilder();
131 | for (var i = 0; i < Log.Count; i++)
132 | {
133 | logText.Append(Log[i].ToLogFormat());
134 | }
135 | owner.Clipboard?.SetTextAsync(logText.ToString());
136 | });
137 |
138 | mTimer = new DispatcherTimer
139 | {
140 | Interval = TimeSpan.FromSeconds(1)
141 | };
142 | mTimer.Tick += RefreshDetails;
143 | mTimer.Start();
144 | }
145 |
146 | public async Task StartAsync()
147 | {
148 | return await Task.Run(() =>
149 | {
150 | var logger = new Action((logMessage) =>
151 | {
152 | Dispatcher.UIThread.Invoke(() =>
153 | {
154 | Log.Add(logMessage);
155 | });
156 | });
157 |
158 | var progress = new Action((progress) =>
159 | {
160 | Dispatcher.UIThread.Invoke(() =>
161 | {
162 | Progress1Text = progress.Progress1Text;
163 | Progress1 = progress.Progress1;
164 | Progress2Text = progress.Progress2Text;
165 | Progress2 = progress.Progress2;
166 | });
167 | });
168 |
169 | var scanner = new Scanner();
170 | var success = scanner.StartScanning(GameDataList, Config, progress, logger, _stopwatch, _cancellationTokenSource.Token);
171 | if (success == true)
172 | {
173 | return scanner.GameDataList;
174 | }
175 | return null;
176 | });
177 | }
178 |
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleUtil.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 |
3 | namespace Repackinator.Shell.Console
4 | {
5 | public static class ConsoleUtil
6 | {
7 | private const string contributors = "HoRnEyDvL, Hazeno, Rocky5, navi, Fredr1kh, Natetronn, Incursion64, Zatchbot, Team Cerbios";
8 | public static void ShowHelpHeader(string version, OptionSet options, Dictionary actionDescriptions)
9 | {
10 | System.Console.WriteLine($"Repackinator {version}");
11 | System.Console.WriteLine("Repackinator by EqUiNoX, original xbox utility.");
12 | System.Console.WriteLine($"Credits go to {contributors}.");
13 | System.Console.WriteLine();
14 | System.Console.WriteLine("Usage: Repackinator -a= [options]+");
15 | System.Console.WriteLine();
16 | System.Console.WriteLine("Available Actions:");
17 | System.Console.WriteLine();
18 | foreach (var action in actionDescriptions)
19 | {
20 | System.Console.WriteLine($" {action.Key,-15} {action.Value}");
21 | }
22 | System.Console.WriteLine();
23 | System.Console.WriteLine("Global Options:");
24 | System.Console.WriteLine();
25 | options.WriteOptionDescriptions(System.Console.Out);
26 | System.Console.WriteLine();
27 | System.Console.WriteLine("For detailed help on a specific action, use:");
28 | System.Console.WriteLine(" Repackinator -a= -h");
29 | System.Console.WriteLine();
30 | System.Console.WriteLine("Examples:");
31 | System.Console.WriteLine(" Repackinator -a=pack -h Show help for Pack action");
32 | System.Console.WriteLine(" Repackinator -a=convert -h Show help for Convert action");
33 | System.Console.WriteLine(" Repackinator -a=repack -h Show help for Repack action");
34 | }
35 |
36 | public static void ShowHelpHeaderForAction(string version, string action, OptionSet options)
37 | {
38 | System.Console.WriteLine($"Repackinator {version}");
39 | System.Console.WriteLine("Repackinator by EqUiNoX, original xbox utility.");
40 | System.Console.WriteLine($"Credits go to {contributors}.");
41 | System.Console.WriteLine();
42 | System.Console.WriteLine($"Usage: Repackinator --action={action} [options]+");
43 | System.Console.WriteLine();
44 | options.WriteOptionDescriptions(System.Console.Out);
45 | }
46 |
47 | public static void ShowOptionException(OptionException optionException, string? action = null, string? version = null)
48 | {
49 | System.Console.Write("Repackinator by EqUiNoX: ");
50 | System.Console.WriteLine(optionException.Message);
51 |
52 | if (!string.IsNullOrEmpty(action) && !string.IsNullOrEmpty(version))
53 | {
54 | System.Console.WriteLine();
55 | System.Console.WriteLine($"Showing help for '{action}' action:");
56 | System.Console.WriteLine();
57 | ShowActionHelp(action, version);
58 | }
59 | else
60 | {
61 | System.Console.WriteLine("Try `Repackinator --help' for more information.");
62 | }
63 | }
64 |
65 | private static void ShowActionHelp(string action, string version)
66 | {
67 | var actionLower = action.ToLowerInvariant();
68 | OptionSet? options = null;
69 | string actionName = action;
70 |
71 | switch (actionLower)
72 | {
73 | case "pack":
74 | options = ConsolePack.GetOptions();
75 | actionName = ConsolePack.Action;
76 | break;
77 | case "repack":
78 | options = ConsoleRepack.GetOptions();
79 | actionName = ConsoleRepack.Action;
80 | break;
81 | case "convert":
82 | options = ConsoleConvert.GetOptions();
83 | actionName = ConsoleConvert.Action;
84 | break;
85 | case "extract":
86 | options = ConsoleExtract.GetOptions();
87 | actionName = ConsoleExtract.Action;
88 | break;
89 | case "compare":
90 | options = ConsoleCompare.GetOptions();
91 | actionName = ConsoleCompare.Action;
92 | break;
93 | case "info":
94 | options = ConsoleInfo.GetOptions();
95 | actionName = ConsoleInfo.Action;
96 | break;
97 | case "checksum":
98 | options = ConsoleChecksum.GetOptions();
99 | actionName = ConsoleChecksum.Action;
100 | break;
101 | case "xbeinfo":
102 | options = ConsoleXbeInfo.GetOptions();
103 | actionName = ConsoleXbeInfo.Action;
104 | break;
105 | case "register":
106 | if (OperatingSystem.IsWindows())
107 | {
108 | options = ConsoleRegister.GetOptions();
109 | actionName = ConsoleRegister.Action;
110 | }
111 | break;
112 | case "unregister":
113 | if (OperatingSystem.IsWindows())
114 | {
115 | options = ConsoleUnregister.GetOptions();
116 | actionName = ConsoleUnregister.Action;
117 | }
118 | break;
119 | }
120 |
121 | if (options != null)
122 | {
123 | ShowHelpHeaderForAction(version, actionName, options);
124 | }
125 | else
126 | {
127 | System.Console.WriteLine($"Unknown action: {action}");
128 | System.Console.WriteLine("Try `Repackinator --help' for more information.");
129 | }
130 | }
131 |
132 | public static void ProcessWait(bool wait)
133 | {
134 | if (!wait)
135 | {
136 | return;
137 | }
138 | System.Console.WriteLine();
139 | System.Console.Write("Press any key to continue.");
140 | System.Console.ReadKey();
141 | }
142 | }
143 | }
144 |
145 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleChecksum.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using Repackinator.Core.Helpers;
3 | using XboxToolkit;
4 | using XboxToolkit.Interface;
5 | using Repackinator.Core.Models;
6 |
7 | namespace Repackinator.Shell.Console
8 | {
9 | public static class ConsoleChecksum
10 | {
11 | public const string Action = "Checksum";
12 | public static string Input { get; set; } = string.Empty;
13 | public static string Output { get; set; } = string.Empty;
14 | public static bool ShowHelp { get; set; } = false;
15 | public static bool Wait { get; set; } = false;
16 | public static bool Quiet { get; set; } = false;
17 |
18 | public static OptionSet GetOptions()
19 | {
20 | return new OptionSet {
21 | { "i|input=", "Input file", i => Input = i },
22 | { "o|output=", "Output file (optional, defaults to console)", o => Output = o },
23 | { "h|help", "show help", h => ShowHelp = h != null },
24 | { "w|wait", "Wait on exit", w => Wait = w != null },
25 | { "q|quiet", "Suppress status output", q => Quiet = q != null }
26 | };
27 | }
28 |
29 | public static void ShowOptionDescription()
30 | {
31 | System.Console.WriteLine();
32 | System.Console.WriteLine("Checksum Action...");
33 | System.Console.WriteLine();
34 | System.Console.WriteLine("This action is used to checksum xbox disk image sectors after any decompression if applicable.");
35 | System.Console.WriteLine();
36 | GetOptions().WriteOptionDescriptions(System.Console.Out);
37 | }
38 |
39 | public static void Process(string version, string[] args)
40 | {
41 | var config = Config.LoadConfig();
42 |
43 | try
44 | {
45 | var options = GetOptions();
46 | options.Parse(args);
47 | if (ShowHelp)
48 | {
49 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
50 | ConsoleUtil.ProcessWait(Wait);
51 | return;
52 | }
53 |
54 | if (string.IsNullOrEmpty(Input))
55 | {
56 | throw new OptionException("Input file not specified.", "input");
57 | }
58 |
59 | if (!File.Exists(Input))
60 | {
61 | throw new OptionException("Input file does not exist.", "input");
62 | }
63 |
64 | StreamWriter? outputWriter = null;
65 | if (!string.IsNullOrEmpty(Output))
66 | {
67 | var outputPath = Path.GetFullPath(Output);
68 | var outputDir = Path.GetDirectoryName(outputPath);
69 | if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
70 | {
71 | Directory.CreateDirectory(outputDir);
72 | }
73 | outputWriter = new StreamWriter(outputPath, false, System.Text.Encoding.UTF8);
74 | }
75 |
76 | try
77 | {
78 | if (!Quiet)
79 | {
80 | System.Console.WriteLine("Calculating Checksum From:");
81 | var slices = ContainerUtility.GetSlicesFromFile(Input);
82 | foreach (var slice in slices)
83 | {
84 | System.Console.WriteLine(Path.GetFileName(slice));
85 | }
86 | System.Console.WriteLine("Calculating Checksums...");
87 | }
88 |
89 | if (!ContainerUtility.TryAutoDetectContainerType(Input, out var containerReader) || containerReader == null)
90 | {
91 | throw new Exception("Unable to detect container type.");
92 | }
93 | using (containerReader)
94 | {
95 | if (!containerReader.TryMount())
96 | {
97 | throw new Exception("Unable to mount container.");
98 | }
99 | try
100 | {
101 | var previousProgress = -1.0f;
102 | var result = ContainerUtility.GetChecksumFromContainer(containerReader, p =>
103 | {
104 | var amount = (float)Math.Round(p * 100);
105 | if (!Quiet && amount != previousProgress)
106 | {
107 | System.Console.Write($"Progress {amount}%");
108 | System.Console.CursorLeft = 0;
109 | previousProgress = amount;
110 | }
111 | }, default);
112 |
113 | var checksumLine = $"SHA256 = {result}";
114 | if (outputWriter != null)
115 | {
116 | outputWriter.WriteLine(result);
117 | }
118 | else
119 | {
120 | System.Console.WriteLine(checksumLine);
121 | }
122 | }
123 | finally
124 | {
125 | containerReader.Dismount();
126 | }
127 | }
128 |
129 | if (!Quiet)
130 | {
131 | System.Console.WriteLine();
132 | if (outputWriter != null)
133 | {
134 | System.Console.WriteLine($"Checksum saved to: {Output}");
135 | }
136 | System.Console.WriteLine("Checksum completed.");
137 | }
138 | }
139 | finally
140 | {
141 | outputWriter?.Dispose();
142 | }
143 | }
144 | catch (OptionException e)
145 | {
146 | ConsoleUtil.ShowOptionException(e, Action, version);
147 | }
148 |
149 | ConsoleUtil.ProcessWait(Wait);
150 | }
151 | }
152 | }
153 |
154 |
--------------------------------------------------------------------------------
/Linux/share/icons/pixmaps/cerbios.xpm:
--------------------------------------------------------------------------------
1 | /* XPM */
2 | static char *cerbios2[] = {
3 | /* columns rows colors chars-per-pixel */
4 | "32 32 211 2 ",
5 | " c None",
6 | ". c #680047",
7 | "X c #79055F",
8 | "o c #440374",
9 | "O c #AF0716",
10 | "+ c #9C0D2F",
11 | "@ c #C10020",
12 | "# c #CF193B",
13 | "$ c #840E5D",
14 | "% c #852F71",
15 | "& c #C21C45",
16 | "* c #00028B",
17 | "= c #00018C",
18 | "- c #00028C",
19 | "; c #00038C",
20 | ": c #00028D",
21 | "> c #00038D",
22 | ", c #01038F",
23 | "< c #010290",
24 | "1 c #020292",
25 | "2 c #040295",
26 | "3 c #040395",
27 | "4 c #040396",
28 | "5 c #050C94",
29 | "6 c #050E94",
30 | "7 c #050298",
31 | "8 c #060299",
32 | "9 c #08029D",
33 | "0 c #08039D",
34 | "q c #08029F",
35 | "w c #09029F",
36 | "e c #09139A",
37 | "r c #0A139B",
38 | "t c #0B149E",
39 | "y c #0C159E",
40 | "u c #0C179E",
41 | "i c #0D1A9F",
42 | "p c #390183",
43 | "a c #2F079B",
44 | "s c #2D019E",
45 | "d c #2C049C",
46 | "f c #0901A1",
47 | "g c #0902A1",
48 | "h c #0A04A1",
49 | "j c #0B01A4",
50 | "k c #0B00A6",
51 | "l c #0D04A7",
52 | "z c #0D07A7",
53 | "x c #0D09A5",
54 | "c c #0D01AA",
55 | "v c #0E02AB",
56 | "b c #0E04A9",
57 | "n c #0E06A9",
58 | "m c #0F01AC",
59 | "M c #0F00AD",
60 | "N c #0F0FA8",
61 | "B c #1107AF",
62 | "V c #0D10A3",
63 | "C c #0D13A2",
64 | "Z c #0E11A4",
65 | "A c #0F13A6",
66 | "S c #0F1DA2",
67 | "D c #101DA3",
68 | "F c #101AA6",
69 | "G c #1113AA",
70 | "H c #1118A8",
71 | "J c #141BAE",
72 | "K c #1000B2",
73 | "L c #1202B3",
74 | "P c #1601B0",
75 | "I c #1206B2",
76 | "U c #1201B5",
77 | "Y c #1301B6",
78 | "T c #1306B5",
79 | "R c #1A01B4",
80 | "E c #1408B5",
81 | "W c #150AB7",
82 | "Q c #1301BB",
83 | "! c #1401B8",
84 | "~ c #1500B9",
85 | "^ c #1503B8",
86 | "/ c #1501BA",
87 | "( c #1501BB",
88 | ") c #1605BB",
89 | "_ c #1707BB",
90 | "` c #1500BC",
91 | "' c #1500BD",
92 | "] c #1600BC",
93 | "[ c #1702BD",
94 | "{ c #1600BE",
95 | "} c #1700BE",
96 | "| c #1701BE",
97 | " . c #1505BD",
98 | ".. c #1706BD",
99 | "X. c #1803BF",
100 | "o. c #170CB8",
101 | "O. c #190FBD",
102 | "+. c #151AB0",
103 | "@. c #181BB6",
104 | "#. c #1712B8",
105 | "$. c #1912BB",
106 | "%. c #1913BB",
107 | "&. c #1917B9",
108 | "*. c #1B12BF",
109 | "=. c #1A1BBA",
110 | "-. c #1A1EBA",
111 | ";. c #2000A7",
112 | ":. c #3707A6",
113 | ">. c #2B04B6",
114 | ",. c #2501BE",
115 | "<. c #360CBB",
116 | "1. c #3C13B0",
117 | "2. c #1931AE",
118 | "3. c #1726B0",
119 | "4. c #1920B5",
120 | "5. c #1D23BC",
121 | "6. c #5B1588",
122 | "7. c #510E9D",
123 | "8. c #56249F",
124 | "9. c #7142A9",
125 | "0. c #1700C0",
126 | "q. c #1700C2",
127 | "w. c #1800C1",
128 | "e. c #1900C2",
129 | "r. c #1900C3",
130 | "t. c #1903C2",
131 | "y. c #1906C0",
132 | "u. c #1B06C3",
133 | "i. c #1A00C4",
134 | "p. c #1A01C4",
135 | "a. c #1A00C5",
136 | "s. c #1902C5",
137 | "d. c #1B03C5",
138 | "f. c #1B00C6",
139 | "g. c #1B00C7",
140 | "h. c #1C00C7",
141 | "j. c #1C05C7",
142 | "k. c #1B0CC3",
143 | "l. c #190AC6",
144 | "z. c #1C08C6",
145 | "x. c #1D0BC6",
146 | "c. c #1B00C8",
147 | "v. c #1B00C9",
148 | "b. c #1B00CA",
149 | "n. c #1A00CB",
150 | "m. c #1C00C8",
151 | "M. c #1C01C8",
152 | "N. c #1C00C9",
153 | "B. c #1D00C9",
154 | "V. c #1C01C9",
155 | "C. c #1C00CA",
156 | "Z. c #1C01CA",
157 | "A. c #1D03CA",
158 | "S. c #1E03CB",
159 | "D. c #1C04C8",
160 | "F. c #1D07C9",
161 | "G. c #1D04CA",
162 | "H. c #1E04CA",
163 | "J. c #1E05CB",
164 | "K. c #1D06CA",
165 | "L. c #1E06CB",
166 | "P. c #1900CC",
167 | "I. c #1A01CC",
168 | "U. c #1F07CC",
169 | "Y. c #1D0AC8",
170 | "T. c #1E0BC9",
171 | "R. c #1E09CA",
172 | "E. c #1E0CC8",
173 | "W. c #1F08CC",
174 | "Q. c #1E13C5",
175 | "!. c #1F10C9",
176 | "~. c #210ECD",
177 | "^. c #200ECE",
178 | "/. c #210FCE",
179 | "(. c #2F11C3",
180 | "). c #211FC7",
181 | "_. c #2011CA",
182 | "`. c #2210CE",
183 | "'. c #2212CF",
184 | "]. c #2313CF",
185 | "[. c #2315CF",
186 | "{. c #2812C9",
187 | "}. c #2118CA",
188 | "|. c #231BCD",
189 | " X c #2318CF",
190 | ".X c #2419CF",
191 | "XX c #2315D0",
192 | "oX c #2116D2",
193 | "OX c #2416D0",
194 | "+X c #2518D2",
195 | "@X c #241AD2",
196 | "#X c #251BD2",
197 | "$X c #261BD2",
198 | "%X c #232DC4",
199 | "&X c #2422CB",
200 | "*X c #2521CF",
201 | "=X c #2527CC",
202 | "-X c #2620D4",
203 | ";X c #2721D4",
204 | ":X c #2823D3",
205 | ">X c #2D31D8",
206 | ",X c #2E33DA",
207 | ".d Q *.%X6 6 J k.C.C.e.G ",
238 | " k g.C.d.l * : S h e.( C.C.e.} U r i : 3 _ C.C.( < ",
239 | " : c g.g.g m C.C.J.R.J.C.} U g.( 8 ",
240 | " w w L C.} o.g.g.w : 8 g 3 ",
241 | " : : : : N !._ 3 n R.%. : : : ",
242 | " : : : : F V H t : : : : ",
243 | " @.=.+. ",
244 | " x.J.J.R. ",
245 | " ",
246 | " ",
247 | " ",
248 | " "
249 | };
250 |
--------------------------------------------------------------------------------
/Linux/share/icons/pixmaps/repackinator.xpm:
--------------------------------------------------------------------------------
1 | /* XPM */
2 | static char *repackinator[] = {
3 | /* columns rows colors chars-per-pixel */
4 | "32 32 215 2 ",
5 | " c #0F0C0E",
6 | ". c #100D0D",
7 | "X c #0E0D12",
8 | "o c #110E11",
9 | "O c #0F1118",
10 | "+ c #151214",
11 | "@ c #181315",
12 | "# c #16151A",
13 | "$ c #18171C",
14 | "% c #1B191E",
15 | "& c #2B110F",
16 | "* c #270E12",
17 | "= c #221B1E",
18 | "- c #281717",
19 | "; c #371B1B",
20 | ": c #390E0F",
21 | "> c #1C1C23",
22 | ", c #1D1F28",
23 | "< c #221E23",
24 | "1 c #2B1E21",
25 | "2 c #1E212C",
26 | "3 c #1B2232",
27 | "4 c #242227",
28 | "5 c #23222E",
29 | "6 c #2D262B",
30 | "7 c #26282E",
31 | "8 c #29292F",
32 | "9 c #2B2224",
33 | "0 c #322123",
34 | "q c #332A2E",
35 | "w c #3D2929",
36 | "e c #3A2424",
37 | "r c #252431",
38 | "t c #282733",
39 | "y c #2A2A35",
40 | "u c #2D2D39",
41 | "i c #252A34",
42 | "p c #352E32",
43 | "a c #312F3B",
44 | "s c #382E33",
45 | "d c #2D323C",
46 | "f c #323137",
47 | "g c #3B3134",
48 | "h c #32323D",
49 | "j c #3A353B",
50 | "k c #3C383E",
51 | "l c #481C1B",
52 | "z c #571D1C",
53 | "x c #57201D",
54 | "c c #61231E",
55 | "v c #442B2C",
56 | "b c #492B2B",
57 | "n c #4E2421",
58 | "m c #592823",
59 | "M c #402F35",
60 | "N c #4D3331",
61 | "B c #483538",
62 | "V c #543332",
63 | "C c #573B3A",
64 | "Z c #5C312C",
65 | "A c #6E342F",
66 | "S c #663C38",
67 | "D c #723A37",
68 | "F c #5E413E",
69 | "G c #75433B",
70 | "H c #2A3443",
71 | "J c #353542",
72 | "K c #383742",
73 | "L c #333942",
74 | "P c #3C3B45",
75 | "I c #353D4B",
76 | "U c #3E3E49",
77 | "Y c #303749",
78 | "T c #413D44",
79 | "R c #403F4A",
80 | "E c #5C3F40",
81 | "W c #3E434D",
82 | "Q c #37424E",
83 | "! c #3F4C5C",
84 | "~ c #3C4656",
85 | "^ c #43434C",
86 | "/ c #4C454D",
87 | "( c #45454A",
88 | ") c #584648",
89 | "_ c #464651",
90 | "` c #484751",
91 | "' c #444B54",
92 | "] c #4B4B54",
93 | "[ c #4D4E5A",
94 | "{ c #414E5E",
95 | "} c #544E54",
96 | "| c #594C52",
97 | " . c #46515C",
98 | ".. c #4D525B",
99 | "X. c #4A5154",
100 | "o. c #54535C",
101 | "O. c #5A545A",
102 | "+. c #694A48",
103 | "@. c #784C49",
104 | "#. c #624E51",
105 | "$. c #6C5552",
106 | "%. c #655357",
107 | "&. c #795551",
108 | "*. c #785755",
109 | "=. c #76514E",
110 | "-. c #444F60",
111 | ";. c #4C5965",
112 | ":. c #495765",
113 | ">. c #545661",
114 | ",. c #5B5B64",
115 | "<. c #555B68",
116 | "1. c #4D5D71",
117 | "2. c #685E61",
118 | "3. c #775F61",
119 | "4. c #56616C",
120 | "5. c #5E616C",
121 | "6. c #566167",
122 | "7. c #4E687B",
123 | "8. c #516474",
124 | "9. c #5B6975",
125 | "0. c #516578",
126 | "q. c #5A6A7A",
127 | "w. c #586575",
128 | "e. c #63636B",
129 | "r. c #69686F",
130 | "t. c #6A6369",
131 | "y. c #796062",
132 | "u. c #656B74",
133 | "i. c #666A75",
134 | "p. c #67737E",
135 | "a. c #6C737F",
136 | "s. c #75747B",
137 | "d. c #7D7379",
138 | "f. c #8E4F48",
139 | "g. c #8A504D",
140 | "h. c #8A5956",
141 | "j. c #965853",
142 | "k. c #8A615C",
143 | "l. c #AC605E",
144 | "z. c #905D62",
145 | "x. c #836563",
146 | "c. c #8C6863",
147 | "v. c #976866",
148 | "b. c #94726C",
149 | "n. c #98736E",
150 | "m. c #977675",
151 | "M. c #877675",
152 | "N. c #AF6E6A",
153 | "B. c #A26A6D",
154 | "V. c #AA7B75",
155 | "C. c #BA7976",
156 | "Z. c #BD6D66",
157 | "A. c #C67871",
158 | "S. c #A2817D",
159 | "D. c #556C82",
160 | "F. c #5E7684",
161 | "G. c #647583",
162 | "H. c #677B8A",
163 | "J. c #7C7B83",
164 | "K. c #757A84",
165 | "L. c #637E98",
166 | "P. c #8A7F80",
167 | "I. c #6A818E",
168 | "U. c #79828E",
169 | "Y. c #6D8190",
170 | "T. c #798B96",
171 | "R. c #7892A2",
172 | "E. c #84868B",
173 | "W. c #8E878B",
174 | "Q. c #898B91",
175 | "!. c #928D92",
176 | "~. c #83919B",
177 | "^. c #8C939B",
178 | "/. c #989395",
179 | "(. c #A48D88",
180 | "). c #B0908B",
181 | "_. c #BC8F94",
182 | "`. c #A69A9D",
183 | "'. c #B89C97",
184 | "]. c #B38C8B",
185 | "[. c #8B9DA9",
186 | "{. c #8997A2",
187 | "}. c #9D9BA3",
188 | "|. c #A49EA7",
189 | " X c #94A2AB",
190 | ".X c #95A5B1",
191 | "XX c #9DADBB",
192 | "oX c #99A7B3",
193 | "OX c #A7ACB4",
194 | "+X c #A4B2BD",
195 | "@X c #ACB4BB",
196 | "#X c #A3A2A7",
197 | "$X c #C99C9A",
198 | "%X c #D7968F",
199 | "&X c #E2958A",
200 | "*X c #CCA49D",
201 | "=X c #D1BAB5",
202 | "-X c #E0AFAE",
203 | ";X c #E8B9B8",
204 | ":X c #D4C1B6",
205 | ">X c #95B6C7",
206 | ",X c #A9BBC5",
207 | ".P a t 5 5 5 5 5 5 5 5 5 5 ",
224 | "r r 5 5 5 5 5 r t J e.M.w v N V N | d.[ K u t 5 5 5 5 5 5 5 5 r ",
225 | "r r r 5 5 5 r t u [ t.- h.;X8X8X$X; 2.r.U h t r 5 5 5 5 5 5 r r ",
226 | "r r r r r r r y h e.} & A.-X9X8X&Xm ( s._ P u t r 5 5 5 5 r r r ",
227 | "r r r r r r t u P e.e.; Z.-X9X;X%XA / K.[ R h u t r 5 5 5 r r r ",
228 | "t r r r r t t J ^ e.s.b E g $./ 4 w 2.J.,.` U h y t r r r r r r ",
229 | "t t r t t t h U [ u.!.@.9 % d.y.1 V m.E.r.>.` P h u t t r r r r ",
230 | "t t t t y u K ^ ,.s.}.m.g.C._.].l.j.`.Q.J.r.o._ U J h u t t r t ",
231 | "t t y y a h U [ r.E.}.OXj.C.v.z.N.@.^.#XE.s.e.>.[ ^ P h u y t t ",
232 | "y u u h J U ` ,.s.Q.@Xu.Z j.N.B.f.n d +XOX}.J.e.o.[ ^ P h u u t ",
233 | "u u h J U ` o.e.Q.@XXXi - G N.C.D * X p.+X.XXXoXK.,...^ P h u u ",
234 | "u h J U ` o.r..X0X4X[.L & n S S l * % q.,X.; m x z * ;.{.T.T.4.:.t u 5.,.[ ^ P J ",
237 | "J P ^ ,.>.H { I I L p. X^.3 $ # O 3 T.G.' ] ' W % 7 ..e.o.` U K ",
238 | "J U ` ,.>.> d G.4.7 >.K.~.^.p.{ a.~. X~.W 5 d 5 > 4 h >.,.[ ^ P ",
239 | "J U ` ,...% > u ( h o.K. X1X2XU.^. X6X,X:.u.2Xu.X < 5 _ ,.o.` U ",
240 | "K U ` ,.^ % $ 7 X.f d .1.~.3Xu.4.q.R.G.d t 4.W o 5 d h O.o.` ^ ",
241 | "K U _ o.^ > 4 # o 8 t Q 9.T. # j O.| o.o.[ ^ ",
242 | "J P ^ [ P % $ # + 5 y L 8.I.R.;. .0.H.1.5 # + % 9 (.=X*.} o.[ ^ ",
243 | "J P U ` k q g 9 % 6 h W I.Y.;.d L ! :.Q y 4 + % 0 b.'.S.%.o.] ^ ",
244 | "h J U _ / b.:X).- q ..:. ...6.U ' ;.~ h W u X # = @.n.).m.} ` ^ ",
245 | "h J U ^ $.V.(.$.= 6 5 t .G.~.7X[.F.:.L i , + < N c.*.&.%.^ ^ ",
246 | "u J K / c.'.x.e 6 q $ 4 .H..X7XXXH.:.d 5 > + f 0 +.V b +./ U ",
247 | "u h J #.&.x.b.N y g $ 5 4.[..X7X1XR.0.d 2 > + P s C =.b =.| J ",
248 | "y u h y.C 0 C v j g # 7 ~ H.+X,XXXq.H i > > + T k +.*.v *.y.u ",
249 | "y u a m.) 0 b w ( s + 8 Y D.2X{.XX7.2 5 > > # T U ) &.v k.x.t ",
250 | "t y a n.b.v e q ] q o u ! L.>XY.R.D.H d r > + j T T +.F V.*.t ",
251 | "t y a b.$X$.- j ( s @ < d ~ 1.0.8.! d f 4 + o + p k k C h.V.C t ",
252 | "t y a c.*XS.w j T j 1 % = < % @ + + + + + + + = f j f B v.B.M u "
253 | };
254 |
--------------------------------------------------------------------------------
/Linux/share/icons/hicolor/scalable/apps/cerbios.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
--------------------------------------------------------------------------------
/.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 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # except Linux/share/mime/packages/, which is used for .cci mime-type support on Linux
187 | !Linux/share/mime/[Pp]ackages/*
188 | # Uncomment if necessary however generally it will be regenerated when needed
189 | #!**/[Pp]ackages/repositories.config
190 | # NuGet v3's project.json files produces more ignorable files
191 | *.nuget.props
192 | *.nuget.targets
193 |
194 | # Microsoft Azure Build Output
195 | csx/
196 | *.build.csdef
197 |
198 | # Microsoft Azure Emulator
199 | ecf/
200 | rcf/
201 |
202 | # Windows Store app package directories and files
203 | AppPackages/
204 | BundleArtifacts/
205 | Package.StoreAssociation.xml
206 | _pkginfo.txt
207 | *.appx
208 |
209 | # Visual Studio cache files
210 | # files ending in .cache can be ignored
211 | *.[Cc]ache
212 | # but keep track of directories ending in .cache
213 | !*.[Cc]ache/
214 |
215 | # Others
216 | ClientBin/
217 | ~$*
218 | *~
219 | *.dbmdl
220 | *.dbproj.schemaview
221 | *.jfm
222 | *.pfx
223 | *.publishsettings
224 | orleans.codegen.cs
225 |
226 | # Including strong name files can present a security risk
227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
228 | #*.snk
229 |
230 | # Since there are multiple workflows, uncomment next line to ignore bower_components
231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
232 | #bower_components/
233 |
234 | # RIA/Silverlight projects
235 | Generated_Code/
236 |
237 | # Backup & report files from converting an old project file
238 | # to a newer Visual Studio version. Backup files are not needed,
239 | # because we have git ;-)
240 | _UpgradeReport_Files/
241 | Backup*/
242 | UpgradeLog*.XML
243 | UpgradeLog*.htm
244 | ServiceFabricBackup/
245 | *.rptproj.bak
246 |
247 | # SQL Server files
248 | *.mdf
249 | *.ldf
250 | *.ndf
251 |
252 | # Business Intelligence projects
253 | *.rdl.data
254 | *.bim.layout
255 | *.bim_*.settings
256 | *.rptproj.rsuser
257 |
258 | # Microsoft Fakes
259 | FakesAssemblies/
260 |
261 | # GhostDoc plugin setting file
262 | *.GhostDoc.xml
263 |
264 | # Node.js Tools for Visual Studio
265 | .ntvs_analysis.dat
266 | node_modules/
267 |
268 | # Visual Studio 6 build log
269 | *.plg
270 |
271 | # Visual Studio 6 workspace options file
272 | *.opt
273 |
274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
275 | *.vbw
276 |
277 | # Visual Studio LightSwitch build output
278 | **/*.HTMLClient/GeneratedArtifacts
279 | **/*.DesktopClient/GeneratedArtifacts
280 | **/*.DesktopClient/ModelManifest.xml
281 | **/*.Server/GeneratedArtifacts
282 | **/*.Server/ModelManifest.xml
283 | _Pvt_Extensions
284 |
285 | # Paket dependency manager
286 | .paket/paket.exe
287 | paket-files/
288 |
289 | # FAKE - F# Make
290 | .fake/
291 |
292 | # JetBrains Rider
293 | .idea/
294 | *.sln.iml
295 |
296 | # CodeRush
297 | .cr/
298 |
299 | # Python Tools for Visual Studio (PTVS)
300 | __pycache__/
301 | *.pyc
302 |
303 | # Cake - Uncomment if you are using it
304 | # tools/**
305 | # !tools/packages.config
306 |
307 | # Tabs Studio
308 | *.tss
309 |
310 | # Telerik's JustMock configuration file
311 | *.jmconfig
312 |
313 | # BizTalk build output
314 | *.btp.cs
315 | *.btm.cs
316 | *.odx.cs
317 | *.xsd.cs
318 |
319 | # OpenCover UI analysis results
320 | OpenCover/
321 |
322 | # Azure Stream Analytics local run output
323 | ASALocalRun/
324 |
325 | # MSBuild Binary and Structured Log
326 | *.binlog
327 |
328 | # NVidia Nsight GPU debugger configuration file
329 | *.nvuser
330 |
331 | # MFractors (Xamarin productivity tool) working folder
332 | .mfractor/
333 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleInfo.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using Repackinator.Core.Helpers;
3 | using XboxToolkit;
4 | using XboxToolkit.Interface;
5 | using Repackinator.Core.Models;
6 |
7 | namespace Repackinator.Shell.Console
8 | {
9 | public static class ConsoleInfo
10 | {
11 | public const string Action = "Info";
12 | public static string Input { get; set; } = string.Empty;
13 | public static string Output { get; set; } = string.Empty;
14 | public static bool ShowHelp { get; set; } = false;
15 | public static bool Wait { get; set; } = false;
16 | public static bool Quiet { get; set; } = false;
17 |
18 | public static OptionSet GetOptions()
19 | {
20 | return new OptionSet {
21 | { "i|input=", "Input file", i => Input = i },
22 | { "o|output=", "Output file (optional, defaults to console)", o => Output = o },
23 | { "h|help", "show help", h => ShowHelp = h != null },
24 | { "w|wait", "Wait on exit", w => Wait = w != null },
25 | { "q|quiet", "Suppress status output", q => Quiet = q != null }
26 | };
27 | }
28 |
29 | public static void ShowOptionDescription()
30 | {
31 | System.Console.WriteLine();
32 | System.Console.WriteLine("Info Action...");
33 | System.Console.WriteLine();
34 | System.Console.WriteLine("This action is used to show xbox disk data sector information.");
35 | System.Console.WriteLine();
36 | GetOptions().WriteOptionDescriptions(System.Console.Out);
37 | }
38 |
39 | public static void Process(string version, string[] args)
40 | {
41 | var config = Config.LoadConfig();
42 |
43 | try
44 | {
45 | var options = GetOptions();
46 | options.Parse(args);
47 | if (ShowHelp)
48 | {
49 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
50 | ConsoleUtil.ProcessWait(Wait);
51 | return;
52 | }
53 |
54 | if (string.IsNullOrEmpty(Input))
55 | {
56 | throw new OptionException("Input file not specified.", "input");
57 | }
58 |
59 | if (!File.Exists(Input))
60 | {
61 | throw new OptionException("Input file does not exist.", "input");
62 | }
63 |
64 | StreamWriter? outputWriter = null;
65 | if (!string.IsNullOrEmpty(Output))
66 | {
67 | var outputPath = Path.GetFullPath(Output);
68 | var outputDir = Path.GetDirectoryName(outputPath);
69 | if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
70 | {
71 | Directory.CreateDirectory(outputDir);
72 | }
73 | outputWriter = new StreamWriter(outputPath, false, System.Text.Encoding.UTF8);
74 | }
75 |
76 | try
77 | {
78 | if (!Quiet)
79 | {
80 | System.Console.WriteLine("Getting Info From:");
81 | var slices = ContainerUtility.GetSlicesFromFile(Input);
82 | foreach (var slice in slices)
83 | {
84 | System.Console.WriteLine(Path.GetFileName(slice));
85 | }
86 | System.Console.WriteLine();
87 | }
88 |
89 | var headerLine = $"Type,Filename,Size,StartSector,EndSector,InSlices";
90 | if (outputWriter != null)
91 | {
92 | outputWriter.WriteLine(headerLine);
93 | }
94 | else
95 | {
96 | System.Console.WriteLine(headerLine);
97 | }
98 |
99 | if (!ContainerUtility.TryAutoDetectContainerType(Input, out var containerReader) || containerReader == null)
100 | {
101 | throw new Exception("Unable to detect container type.");
102 | }
103 | using (containerReader)
104 | {
105 | if (!containerReader.TryMount())
106 | {
107 | throw new Exception("Unable to mount container.");
108 | }
109 | try
110 | {
111 | var previousProgress = -1.0f;
112 | var progress = new Action((p) =>
113 | {
114 | var amount = (float)Math.Round(p * 100);
115 | if (!Quiet && amount != previousProgress)
116 | {
117 | if (amount < 10)
118 | {
119 | System.Console.Write($"Progress {amount}%");
120 | }
121 | else if (amount < 100)
122 | {
123 | System.Console.Write($"Progress {amount}%");
124 | }
125 | else
126 | {
127 | System.Console.Write($"Progress {amount}%");
128 | }
129 | System.Console.CursorLeft = 0;
130 | previousProgress = amount;
131 | }
132 | });
133 |
134 | ContainerUtility.GetFileInfoFromContainer(containerReader, f =>
135 | {
136 | var type = f.IsFile ? "F" : "D";
137 | var startSector = f.StartSector > 0 ? f.StartSector.ToString() : "N/A";
138 | var endSector = f.EndSector > 0 ? f.EndSector.ToString() : "N/A";
139 | var line = $"{type},{f.Filename},{f.Size},{startSector},{endSector},{f.InSlices}";
140 | if (outputWriter != null)
141 | {
142 | outputWriter.WriteLine(line);
143 | }
144 | else
145 | {
146 | System.Console.WriteLine(line);
147 | }
148 | }, progress, default);
149 | }
150 | finally
151 | {
152 | containerReader.Dismount();
153 | }
154 | }
155 |
156 | if (!Quiet)
157 | {
158 | System.Console.WriteLine();
159 | if (outputWriter != null)
160 | {
161 | System.Console.WriteLine($"Info saved to: {Output}");
162 | }
163 | System.Console.WriteLine("Info completed.");
164 | }
165 | }
166 | finally
167 | {
168 | outputWriter?.Dispose();
169 | }
170 | }
171 | catch (OptionException e)
172 | {
173 | ConsoleUtil.ShowOptionException(e, Action, version);
174 | }
175 |
176 | ConsoleUtil.ProcessWait(Wait);
177 | }
178 | }
179 | }
180 |
181 |
--------------------------------------------------------------------------------
/Repackinator.Core/Actions/AttachUpdater.cs:
--------------------------------------------------------------------------------
1 | using Repackinator.Core.Helpers;
2 | using Repackinator.Core.Logging;
3 | using Repackinator.Core.Models;
4 | using XboxToolkit;
5 | using XboxToolkit.Models.Xbe;
6 | using System.Diagnostics;
7 |
8 | namespace Repackinator.Core.Actions
9 | {
10 | public class AttachUpdater
11 | {
12 | private Action? Logger { get; set; }
13 |
14 | private Action? Progress { get; set; }
15 |
16 | private ProgressInfo CurrentProgress = new ProgressInfo();
17 |
18 | private void SendProgress()
19 | {
20 | if (Progress == null)
21 | {
22 | return;
23 | }
24 | Progress(CurrentProgress);
25 | }
26 |
27 | private void Log(LogMessageLevel level, string message)
28 | {
29 | if (Logger == null)
30 | {
31 | return;
32 | }
33 | var logMessage = new LogMessage(level, message);
34 | Logger(logMessage);
35 | File.AppendAllText("AttachUpdateLog.txt", logMessage.ToLogFormat());
36 | }
37 |
38 | private void ProcessFolder(string folder, Stopwatch procesTime, bool upperCase, GameData[]? gameDatas, CancellationToken cancellationToken)
39 | {
40 | var filesToProcess = Directory.GetFiles(folder, "default.xbe").OrderBy(o => o).ToArray();
41 | if (filesToProcess.Length != 1)
42 | {
43 | return;
44 | }
45 |
46 | try
47 | {
48 | CurrentProgress.Progress2 = 0;
49 | CurrentProgress.Progress2Text = folder;
50 | SendProgress();
51 |
52 | var xbeData = File.ReadAllBytes(filesToProcess[0]);
53 |
54 | if (!XbeUtility.TryGetXbeCert(xbeData, out var cert) || cert == null)
55 | {
56 | Log(LogMessageLevel.Error, $"Unable to get data from default.xbe.");
57 | return;
58 | }
59 |
60 | var titleId = cert.Title_Id.ToString("X8");
61 | var region = XbeCertificate.GameRegionToString(cert.Game_Region);
62 | var version = cert.Version.ToString("X8");
63 | var titleName = UnicodeHelper.GetUtf8String(cert.Title_Name);
64 |
65 | //GameData? gameData = null;
66 | if (gameDatas != null)
67 | {
68 | foreach (var currentGameData in gameDatas)
69 | {
70 | if (currentGameData.TitleID != titleId || currentGameData.Region != region || currentGameData.Version != version)
71 | {
72 | continue;
73 | }
74 | //gameData = currentGameData;
75 | titleName = currentGameData.XBETitle;
76 | }
77 | }
78 |
79 | if (upperCase)
80 | {
81 | titleName = titleName.ToUpper();
82 | }
83 |
84 | var attach = ResourceLoader.GetEmbeddedResourceBytes("attach.xbe");
85 | if (XbeUtility.TryGetXbeImage(xbeData, XbeUtility.ImageType.TitleImage, out var xprImage))
86 | {
87 | if (XprUtility.ConvertXprToJpeg(xprImage, out var jpgImage))
88 | {
89 | if (!XbeUtility.TryReplaceXbeTitleImage(attach, jpgImage))
90 | {
91 | Log(LogMessageLevel.Error, "Failed to replace image.");
92 | return;
93 | }
94 | }
95 | else
96 | {
97 | Log(LogMessageLevel.Error, "Failed to create jpg.");
98 | return;
99 | }
100 | }
101 | else
102 | {
103 | Log(LogMessageLevel.Warning, "Failed to extract xpr as probably missing, will use default image.");
104 | }
105 |
106 | if (XbeUtility.ReplaceCertInfo(attach, xbeData, titleName, out var patchedAttach) && patchedAttach != null)
107 | {
108 | File.WriteAllBytes(filesToProcess[0], patchedAttach);
109 | }
110 | else
111 | {
112 | Log(LogMessageLevel.Error, "failed creating attach xbe.");
113 | return;
114 | }
115 |
116 | Log(LogMessageLevel.Completed, $"Updated '{filesToProcess[0]}'.");
117 |
118 | CurrentProgress.Progress2 = 1.0f;
119 | SendProgress();
120 | }
121 | catch (Exception ex)
122 | {
123 | Log(LogMessageLevel.Error, $"Attach Updating '{filesToProcess[0]}' caused error '{ex}'.");
124 | }
125 | }
126 |
127 | public bool StartAttachUpdating(GameData[]? gameData, Config config, Action? progress, Action logger, Stopwatch stopwatch, CancellationToken cancellationToken)
128 | {
129 | try
130 | {
131 | Logger = logger;
132 | Progress = progress;
133 |
134 | if (gameData == null)
135 | {
136 | Log(LogMessageLevel.Error, "RepackList.json not found.");
137 | return false;
138 | }
139 |
140 | if (Directory.Exists(config.OutputPath) == false)
141 | {
142 | Log(LogMessageLevel.Error, "Output path is invalid.");
143 | return false;
144 | }
145 |
146 | stopwatch.Restart();
147 |
148 | if (File.Exists("AttachUpdateLog.txt"))
149 | {
150 | File.Delete("AttachUpdateLog.txt");
151 | }
152 |
153 | CurrentProgress.Progress1 = 0;
154 | CurrentProgress.Progress1Text = "Searching Directories";
155 | CurrentProgress.Progress2 = 0;
156 | CurrentProgress.Progress2Text = string.Empty;
157 | SendProgress();
158 |
159 | var pathsScanned = 0;
160 | var totalPaths = 1;
161 | var pathsToScan = new List();
162 | pathsToScan.Add(config.OutputPath);
163 | while (pathsToScan.Count > 0)
164 | {
165 | var pathToProcess = pathsToScan[0];
166 | pathsToScan.RemoveAt(0);
167 |
168 | CurrentProgress.Progress1 = pathsScanned / (float)totalPaths;
169 | SendProgress();
170 |
171 | ProcessFolder(pathToProcess, stopwatch, config.Uppercase, gameData, cancellationToken);
172 |
173 | try
174 | {
175 | var pathsToAdd = Directory.GetDirectories(pathToProcess);
176 | pathsToScan.AddRange(pathsToAdd);
177 | totalPaths += pathsToAdd.Length;
178 | }
179 | catch (Exception ex)
180 | {
181 | Log(LogMessageLevel.Error, $"Unable to get folders in '{pathToProcess}' as caused error '{ex}'.");
182 | }
183 |
184 | pathsScanned++;
185 |
186 | if (cancellationToken.IsCancellationRequested)
187 | {
188 | break;
189 | }
190 | }
191 |
192 | CurrentProgress.Progress1 = 1.0f;
193 | SendProgress();
194 |
195 | stopwatch.Stop();
196 |
197 | Log(LogMessageLevel.Done, $"Completed Attach Updating (Time Taken {stopwatch.Elapsed.TotalHours:00}:{stopwatch.Elapsed.Minutes:00}:{stopwatch.Elapsed.Seconds:00}).");
198 | return true;
199 | }
200 | catch (Exception ex)
201 | {
202 | Log(LogMessageLevel.Error, $"Exception occured '{ex}'.");
203 | }
204 | return false;
205 | }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleXbeInfo.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using XboxToolkit;
3 | using XboxToolkit.Interface;
4 | using XboxToolkit.Models.Xbe;
5 | using XboxToolkit.Internal;
6 |
7 | namespace Repackinator.Shell.Console
8 | {
9 | public static class ConsoleXbeInfo
10 | {
11 | public const string Action = "XbeInfo";
12 | public static string Input { get; set; } = string.Empty;
13 | public static string Output { get; set; } = string.Empty;
14 | public static bool ShowHelp { get; set; } = false;
15 | public static bool Wait { get; set; } = false;
16 | public static bool Quiet { get; set; } = false;
17 |
18 | public static OptionSet GetOptions()
19 | {
20 | return new OptionSet {
21 | { "i|input=", "Input file (ISO or CCI)", i => Input = i },
22 | { "o|output=", "Output file (optional, defaults to console)", o => Output = o },
23 | { "h|help", "show help", h => ShowHelp = h != null },
24 | { "w|wait", "Wait on exit", w => Wait = w != null },
25 | { "q|quiet", "Suppress status output", q => Quiet = q != null }
26 | };
27 | }
28 |
29 | public static void ShowOptionDescription()
30 | {
31 | System.Console.WriteLine();
32 | System.Console.WriteLine("XbeInfo Action...");
33 | System.Console.WriteLine();
34 | System.Console.WriteLine("This action extracts XBE certificate information from Xbox disk images.");
35 | System.Console.WriteLine();
36 | GetOptions().WriteOptionDescriptions(System.Console.Out);
37 | }
38 |
39 | public static void Process(string version, string[] args)
40 | {
41 | try
42 | {
43 | var options = GetOptions();
44 | options.Parse(args);
45 | if (ShowHelp)
46 | {
47 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
48 | ConsoleUtil.ProcessWait(Wait);
49 | return;
50 | }
51 |
52 | if (string.IsNullOrEmpty(Input))
53 | {
54 | throw new OptionException("Input file not specified.", "input");
55 | }
56 |
57 | if (!File.Exists(Input))
58 | {
59 | throw new OptionException("Input file does not exist.", "input");
60 | }
61 |
62 | StreamWriter? outputWriter = null;
63 | if (!string.IsNullOrEmpty(Output))
64 | {
65 | var outputPath = Path.GetFullPath(Output);
66 | var outputDir = Path.GetDirectoryName(outputPath);
67 | if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
68 | {
69 | Directory.CreateDirectory(outputDir);
70 | }
71 | outputWriter = new StreamWriter(outputPath, false, System.Text.Encoding.UTF8);
72 | }
73 |
74 | try
75 | {
76 | if (!Quiet)
77 | {
78 | System.Console.WriteLine("Extracting XBE Info From:");
79 | var slices = ContainerUtility.GetSlicesFromFile(Input);
80 | foreach (var slice in slices)
81 | {
82 | System.Console.WriteLine(Path.GetFileName(slice));
83 | }
84 | System.Console.WriteLine();
85 | }
86 |
87 | if (!ContainerUtility.TryAutoDetectContainerType(Input, out var containerReader) || containerReader == null)
88 | {
89 | throw new Exception("Unable to detect container type.");
90 | }
91 | using (containerReader)
92 | {
93 | if (!containerReader.TryMount())
94 | {
95 | throw new Exception("Unable to mount container.");
96 | }
97 | try
98 | {
99 | if (!Quiet)
100 | {
101 | System.Console.Write("Processing...");
102 | }
103 |
104 | if (!containerReader.TryGetDefault(out var xbeData, out var containerType))
105 | {
106 | throw new Exception("Unable to extract XBE from container.");
107 | }
108 |
109 | if (!Quiet)
110 | {
111 | System.Console.CursorLeft = 0;
112 | System.Console.WriteLine("Processing... Complete");
113 | }
114 |
115 | if (containerType != ContainerType.XboxOriginal)
116 | {
117 | throw new Exception("Input is not an Xbox Original disk image.");
118 | }
119 |
120 | if (!XbeUtility.TryGetXbeCert(xbeData, out var cert) || cert == null)
121 | {
122 | throw new Exception("Unable to extract XBE certificate.");
123 | }
124 |
125 | Action writeLine = (line) =>
126 | {
127 | if (outputWriter != null)
128 | {
129 | outputWriter.WriteLine(line);
130 | }
131 | else
132 | {
133 | System.Console.WriteLine(line);
134 | }
135 | };
136 |
137 | writeLine("XBE Certificate Information:");
138 | writeLine("============================");
139 | var certInstance = new XbeCertificate();
140 | writeLine($"Title ID: {cert.Title_Id:X08}");
141 | writeLine($"Title Name: {UnicodeHelper.GetUnicodeString(cert.Title_Name)}");
142 | writeLine($"Game Region: {XbeCertificate.GameRegionToString(cert.Game_Region)}");
143 | writeLine($"Version: {cert.Version:X08}");
144 | writeLine($"Allowed Media: {certInstance.AllowedMediaToString(cert.Allowed_Media)}");
145 | writeLine($"Game Ratings: {cert.Game_Ratings:X08}");
146 | writeLine($"Disk Number: {cert.Disk_Number}");
147 | writeLine($"Time Date: {cert.Time_Date:X08}");
148 |
149 | if (!Quiet && outputWriter == null)
150 | {
151 | System.Console.WriteLine();
152 | System.Console.WriteLine("XbeInfo completed.");
153 | }
154 | else if (outputWriter != null && !Quiet)
155 | {
156 | System.Console.WriteLine($"XBE info saved to: {Output}");
157 | }
158 | }
159 | finally
160 | {
161 | containerReader.Dismount();
162 | }
163 | }
164 | }
165 | finally
166 | {
167 | outputWriter?.Dispose();
168 | }
169 | }
170 | catch (OptionException e)
171 | {
172 | ConsoleUtil.ShowOptionException(e, Action, version);
173 | Environment.ExitCode = 1;
174 | }
175 | catch (Exception e)
176 | {
177 | if (!Quiet)
178 | {
179 | System.Console.WriteLine($"Error: {e.Message}");
180 | }
181 | Environment.ExitCode = 1;
182 | }
183 |
184 | ConsoleUtil.ProcessWait(Wait);
185 | }
186 | }
187 | }
188 |
189 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsoleCompare.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using Repackinator.Core.Helpers;
3 | using XboxToolkit;
4 | using XboxToolkit.Interface;
5 | using Repackinator.Core.Models;
6 |
7 | namespace Repackinator.Shell.Console
8 | {
9 | public static class ConsoleCompare
10 | {
11 | public const string Action = "Compare";
12 | public static string First { get; set; } = string.Empty;
13 | public static string Second { get; set; } = string.Empty;
14 | public static bool Compare { get; set; } = false;
15 | public static bool ShowHelp { get; set; } = false;
16 | public static bool Wait { get; set; } = false;
17 |
18 | public static OptionSet GetOptions()
19 | {
20 | return new OptionSet {
21 | { "f|first=", "First file to compare", f => First = f },
22 | { "s|second=", "Second file to compare", s => Second = s },
23 | { "c|compare", "Perform comparison (requires both -f and -s)", c => Compare = c != null },
24 | { "h|help", "show help", h => ShowHelp = h != null },
25 | { "w|wait", "Wait on exit", w => Wait = w != null }
26 | };
27 | }
28 |
29 | public static void ShowOptionDescription()
30 | {
31 | System.Console.WriteLine();
32 | System.Console.WriteLine("Compare Action...");
33 | System.Console.WriteLine();
34 | System.Console.WriteLine("This action is used to compare two Xbox disk images sector by sector.");
35 | System.Console.WriteLine("You can specify both files with -f and -s, or use -c to compare files previously set.");
36 | System.Console.WriteLine();
37 | GetOptions().WriteOptionDescriptions(System.Console.Out);
38 | }
39 |
40 | public static void Process(string version, string[] args)
41 | {
42 | try
43 | {
44 | var options = GetOptions();
45 | options.Parse(args);
46 | if (ShowHelp)
47 | {
48 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
49 | ConsoleUtil.ProcessWait(Wait);
50 | return;
51 | }
52 |
53 | // If no parameters provided, show help
54 | if (string.IsNullOrEmpty(First) && string.IsNullOrEmpty(Second) && !Compare)
55 | {
56 | throw new OptionException("No parameters specified. Use -f to set first file, -s to set second file, or -c to compare.", "compare");
57 | }
58 |
59 | string? firstFile = null;
60 | string? secondFile = null;
61 |
62 | // Load config to get/set comparison files
63 | var config = Config.LoadConfig();
64 |
65 | // Process first file: save to config if provided, or load from config if not provided
66 | if (!string.IsNullOrEmpty(First))
67 | {
68 | if (!File.Exists(First))
69 | {
70 | throw new OptionException("First is not a valid file.", "first");
71 | }
72 | firstFile = Path.GetFullPath(First);
73 | // Save to config for future use
74 | if (config != null)
75 | {
76 | config.CompareFirst = firstFile;
77 | Config.SaveConfig(config);
78 | }
79 | }
80 | else if (config != null && !string.IsNullOrEmpty(config.CompareFirst))
81 | {
82 | // Load from config if not provided via command line
83 | firstFile = config.CompareFirst;
84 | }
85 |
86 | // Process second file: save to config if provided, or load from config if not provided
87 | if (!string.IsNullOrEmpty(Second))
88 | {
89 | if (!File.Exists(Second))
90 | {
91 | throw new OptionException("Second is not a valid file.", "second");
92 | }
93 | secondFile = Path.GetFullPath(Second);
94 | // Save to config for future use
95 | if (config != null)
96 | {
97 | config.CompareSecond = secondFile;
98 | Config.SaveConfig(config);
99 | }
100 | }
101 | else if (config != null && !string.IsNullOrEmpty(config.CompareSecond))
102 | {
103 | // Load from config if not provided via command line
104 | secondFile = config.CompareSecond;
105 | }
106 |
107 | // If compare flag is set, ensure we have both files (from command line or config)
108 | if (Compare)
109 | {
110 | if (string.IsNullOrEmpty(firstFile) || string.IsNullOrEmpty(secondFile))
111 | {
112 | throw new OptionException("Both first and second files must be specified (use -f to set first, or it will be loaded from config).", "compare");
113 | }
114 | }
115 |
116 | if (!string.IsNullOrEmpty(firstFile) && !string.IsNullOrEmpty(secondFile))
117 | {
118 | if (!File.Exists(firstFile))
119 | {
120 | throw new OptionException("First file does not exist.", "first");
121 | }
122 |
123 | if (!File.Exists(secondFile))
124 | {
125 | throw new OptionException("Second file does not exist.", "second");
126 | }
127 |
128 | System.Console.WriteLine("Comparing:");
129 | var firstSlices = ContainerUtility.GetSlicesFromFile(firstFile);
130 | foreach (var slice in firstSlices)
131 | {
132 | System.Console.WriteLine(Path.GetFileName(slice));
133 | }
134 |
135 | System.Console.WriteLine("Against:");
136 | var secondSlices = ContainerUtility.GetSlicesFromFile(secondFile);
137 | foreach (var slice in secondSlices)
138 | {
139 | System.Console.WriteLine(Path.GetFileName(slice));
140 | }
141 |
142 | System.Console.WriteLine();
143 |
144 | if (!ContainerUtility.TryAutoDetectContainerType(firstFile, out var containerReader1) || containerReader1 == null)
145 | {
146 | throw new Exception("Unable to detect container type for first file.");
147 | }
148 | if (!ContainerUtility.TryAutoDetectContainerType(secondFile, out var containerReader2) || containerReader2 == null)
149 | {
150 | containerReader1.Dispose();
151 | throw new Exception("Unable to detect container type for second file.");
152 | }
153 |
154 | using (containerReader1)
155 | using (containerReader2)
156 | {
157 | if (!containerReader1.TryMount())
158 | {
159 | throw new Exception("Unable to mount first container.");
160 | }
161 | if (!containerReader2.TryMount())
162 | {
163 | containerReader1.Dismount();
164 | throw new Exception("Unable to mount second container.");
165 | }
166 | try
167 | {
168 | var previousProgress = -1.0f;
169 | ContainerUtility.CompareContainers(containerReader1, containerReader2, s =>
170 | {
171 | System.Console.WriteLine(s);
172 | }, p =>
173 | {
174 | var amount = (float)Math.Round(p * 100);
175 | if (amount != previousProgress)
176 | {
177 | System.Console.Write($"Progress {amount}%");
178 | System.Console.CursorLeft = 0;
179 | previousProgress = amount;
180 | }
181 | });
182 | }
183 | finally
184 | {
185 | containerReader1.Dismount();
186 | containerReader2.Dismount();
187 | }
188 | }
189 |
190 | System.Console.WriteLine();
191 | System.Console.WriteLine("Compare completed.");
192 | }
193 | }
194 | catch (OptionException e)
195 | {
196 | ConsoleUtil.ShowOptionException(e, Action, version);
197 | }
198 |
199 | ConsoleUtil.ProcessWait(Wait);
200 | }
201 | }
202 | }
203 |
204 |
--------------------------------------------------------------------------------
/Repackinator.Shell/Console/ConsolePack.cs:
--------------------------------------------------------------------------------
1 | using Mono.Options;
2 | using XboxToolkit;
3 | using System.Text;
4 |
5 | namespace Repackinator.Shell.Console
6 | {
7 | public static class ConsolePack
8 | {
9 | public const string Action = "Pack";
10 | public static string Input { get; set; } = string.Empty;
11 | public static string Output { get; set; } = string.Empty;
12 | public static bool Compress { get; set; } = false;
13 | public static bool NoSplit { get; set; } = false;
14 | public static string Log { get; set; } = string.Empty;
15 | public static bool ShowHelp { get; set; } = false;
16 | public static bool Wait { get; set; } = false;
17 | public static bool Quiet { get; set; } = false;
18 |
19 | public static OptionSet GetOptions()
20 | {
21 | return new OptionSet {
22 | { "i|input=", "Input folder", i => Input = i },
23 | { "o|output=", "Output file", o => Output = o },
24 | { "c|compress", "Compress (CCI)", c => Compress = c != null },
25 | { "n|nosplit", "No Split of output file", n => NoSplit = n != null },
26 | { "l|log=", "log file", l => Log = l },
27 | { "h|help", "show help", h => ShowHelp = h != null },
28 | { "w|wait", "Wait on exit", w => Wait = w != null },
29 | { "q|quiet", "Suppress status output", q => Quiet = q != null }
30 | };
31 | }
32 |
33 | public static void ShowOptionDescription()
34 | {
35 | System.Console.WriteLine();
36 | System.Console.WriteLine("Pack Action...");
37 | System.Console.WriteLine();
38 | System.Console.WriteLine("This action is used to pack a folder into an Xbox disk image (ISO or CCI).");
39 | System.Console.WriteLine();
40 | GetOptions().WriteOptionDescriptions(System.Console.Out);
41 | }
42 |
43 | public static void Process(string version, string[] args)
44 | {
45 | try
46 | {
47 | var options = GetOptions();
48 | options.Parse(args);
49 | if (ShowHelp)
50 | {
51 | ConsoleUtil.ShowHelpHeaderForAction(version, Action, options);
52 | ConsoleUtil.ProcessWait(Wait);
53 | return;
54 | }
55 |
56 | if (string.IsNullOrEmpty(Input))
57 | {
58 | throw new OptionException("input not specified.", "input");
59 | }
60 |
61 | string input;
62 | try
63 | {
64 | input = Path.GetFullPath(Input);
65 | }
66 | catch (ArgumentException)
67 | {
68 | throw new OptionException("input is not a valid directory.", "input");
69 | }
70 |
71 | if (!Directory.Exists(input))
72 | {
73 | throw new OptionException("input directory does not exist.", "input");
74 | }
75 |
76 | if (string.IsNullOrEmpty(Output))
77 | {
78 | throw new OptionException("output not specified.", "output");
79 | }
80 |
81 | string output;
82 | try
83 | {
84 | output = Path.GetFullPath(Output);
85 | }
86 | catch (ArgumentException)
87 | {
88 | throw new OptionException("output is not a valid filepath.", "output");
89 | }
90 |
91 | // Ensure output directory exists
92 | var outputDir = Path.GetDirectoryName(output);
93 | if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
94 | {
95 | Directory.CreateDirectory(outputDir);
96 | }
97 |
98 | // Repackinator is Xbox Original only
99 | var formatValue = ISOFormat.XboxOriginal;
100 |
101 | // Determine output extension based on compress option
102 | if (Compress)
103 | {
104 | if (!output.EndsWith(".cci", StringComparison.OrdinalIgnoreCase))
105 | {
106 | output = Path.ChangeExtension(output, ".cci");
107 | }
108 | }
109 | else
110 | {
111 | if (!output.EndsWith(".iso", StringComparison.OrdinalIgnoreCase))
112 | {
113 | output = Path.ChangeExtension(output, ".iso");
114 | }
115 | }
116 |
117 | // Split point is always 4GB unless NoSplit is specified
118 | var splitPoint = NoSplit ? 0L : 4L * 1024 * 1024 * 1024; // 4GB
119 |
120 | string log;
121 | FileStream? logStream = null;
122 | if (!string.IsNullOrEmpty(Log))
123 | {
124 | try
125 | {
126 | log = Path.GetFullPath(Log);
127 | string? dir = Path.GetDirectoryName(log);
128 | if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
129 | {
130 | Directory.CreateDirectory(dir);
131 | }
132 | logStream = File.OpenWrite(log);
133 | }
134 | catch (ArgumentException)
135 | {
136 | throw new OptionException("log is not a valid filepath.", "log");
137 | }
138 | }
139 |
140 | var logger = new Action((message) =>
141 | {
142 | if (!Quiet)
143 | {
144 | System.Console.WriteLine(message);
145 | }
146 | if (logStream != null)
147 | {
148 | var logMessage = $"{DateTime.Now:HH:mm:ss} - {message}\r\n";
149 | logStream.Write(Encoding.UTF8.GetBytes(logMessage));
150 | }
151 | });
152 |
153 | if (!Quiet)
154 | {
155 | System.Console.WriteLine("Packing folder:");
156 | System.Console.WriteLine(input);
157 | System.Console.WriteLine("To:");
158 | System.Console.WriteLine(output);
159 | System.Console.WriteLine($"Type: {(Compress ? "CCI" : "ISO")}");
160 | System.Console.WriteLine();
161 | }
162 |
163 | var previousProgress = -1.0f;
164 | var progress = new Action((p) =>
165 | {
166 | var amount = (float)Math.Round(p * 100);
167 | if (!Quiet && amount != previousProgress)
168 | {
169 | if (amount < 10)
170 | {
171 | System.Console.Write($"Progress {amount}%");
172 | }
173 | else if (amount < 100)
174 | {
175 | System.Console.Write($"Progress {amount}%");
176 | }
177 | else
178 | {
179 | System.Console.Write($"Progress {amount}%");
180 | }
181 | System.Console.CursorLeft = 0;
182 | previousProgress = amount;
183 | }
184 | });
185 |
186 | bool success;
187 | if (Compress)
188 | {
189 | success = ContainerUtility.ConvertFolderToCCI(input, formatValue, output, splitPoint, progress);
190 | }
191 | else
192 | {
193 | success = ContainerUtility.ConvertFolderToISO(input, formatValue, output, splitPoint, progress);
194 | }
195 |
196 | logStream?.Dispose();
197 |
198 | if (!Quiet)
199 | {
200 | System.Console.WriteLine();
201 | if (success)
202 | {
203 | System.Console.WriteLine("Pack completed successfully.");
204 | }
205 | else
206 | {
207 | System.Console.WriteLine("Pack failed.");
208 | }
209 | }
210 |
211 | if (!success)
212 | {
213 | Environment.ExitCode = 1;
214 | }
215 | }
216 | catch (OptionException e)
217 | {
218 | ConsoleUtil.ShowOptionException(e, Action, version);
219 | Environment.ExitCode = 1;
220 | }
221 | catch (Exception e)
222 | {
223 | if (!Quiet)
224 | {
225 | System.Console.WriteLine($"Error: {e.Message}");
226 | }
227 | Environment.ExitCode = 1;
228 | }
229 |
230 | ConsoleUtil.ProcessWait(Wait);
231 | }
232 | }
233 | }
234 |
235 |
--------------------------------------------------------------------------------
/Repackinator.Core/Actions/Scanner.cs:
--------------------------------------------------------------------------------
1 | using XboxToolkit;
2 | using XboxToolkit.Interface;
3 | using XboxToolkit.Models.Xbe;
4 | using System.Diagnostics;
5 | using Repackinator.Core.Models;
6 | using Repackinator.Core.Logging;
7 |
8 | namespace Repackinator.Core.Actions
9 | {
10 | public class Scanner
11 | {
12 | private Action? Logger { get; set; }
13 |
14 | private Action? Progress { get; set; }
15 |
16 | private ProgressInfo CurrentProgress = new ProgressInfo();
17 |
18 | public GameData[]? GameDataList { get; set; }
19 |
20 | private void SendProgress()
21 | {
22 | if (Progress == null)
23 | {
24 | return;
25 | }
26 | Progress(CurrentProgress);
27 | }
28 |
29 | private void Log(LogMessageLevel level, string message)
30 | {
31 | if (Logger == null)
32 | {
33 | return;
34 | }
35 | var logMessage = new LogMessage(level, message);
36 | Logger(logMessage);
37 | File.AppendAllText("ScanLog.txt", logMessage.ToLogFormat());
38 | }
39 |
40 | private void ProcessFolder(string folder, Config config, Stopwatch procesTime, CancellationToken cancellationToken)
41 | {
42 | if (GameDataList == null)
43 | {
44 | Log(LogMessageLevel.Error, "GameData should not be null.");
45 | return;
46 | }
47 |
48 | try
49 | {
50 | CurrentProgress.Progress2 = 0;
51 | CurrentProgress.Progress2Text = folder;
52 | SendProgress();
53 |
54 | var isoToProcess = Directory.GetFiles(folder, "*.iso").OrderBy(o => o).ToArray();
55 | var cciToProcess = Directory.GetFiles(folder, "*.cci").OrderBy(o => o).ToArray();
56 | var csoToProcess = Directory.GetFiles(folder, "*.cso").OrderBy(o => o).ToArray();
57 |
58 | var mixedCount = 0;
59 | if (isoToProcess.Length > 0)
60 | {
61 | mixedCount++;
62 | }
63 | if (cciToProcess.Length > 0)
64 | {
65 | mixedCount++;
66 | }
67 | if (csoToProcess.Length > 0)
68 | {
69 | mixedCount++;
70 | }
71 |
72 | if (mixedCount == 0)
73 | {
74 | return;
75 | }
76 | else if (mixedCount > 1)
77 | {
78 | Log(LogMessageLevel.Error, $"Folder '{folder}' contains mixed ISO and CCI.");
79 | return;
80 | }
81 |
82 | var xbeData = Array.Empty();
83 | ContainerReader? containerReader = null;
84 | if (isoToProcess.Length > 0)
85 | {
86 | containerReader = new ISOContainerReader(isoToProcess[0]);
87 | }
88 | else if (cciToProcess.Length > 0)
89 | {
90 | containerReader = new CCIContainerReader(cciToProcess[0]);
91 | }
92 | else if (csoToProcess.Length > 0)
93 | {
94 | // CSO files are not directly supported by XboxToolkit, skip for now
95 | Log(LogMessageLevel.Error, $"CSO files are not supported.");
96 | return;
97 | }
98 |
99 | if (containerReader != null)
100 | {
101 | try
102 | {
103 | if (!containerReader.TryMount())
104 | {
105 | Log(LogMessageLevel.Error, $"Unable to mount container.");
106 | return;
107 | }
108 |
109 | if (!containerReader.TryGetDefault(out xbeData, out _))
110 | {
111 | Log(LogMessageLevel.Error, $"Unable to extract default.xbe.");
112 | containerReader.Dismount();
113 | return;
114 | }
115 | }
116 | finally
117 | {
118 | containerReader.Dismount();
119 | containerReader.Dispose();
120 | }
121 | }
122 |
123 | if (!XboxToolkit.XbeUtility.TryGetXbeCert(xbeData, out var cert) || cert == null)
124 | {
125 | Log(LogMessageLevel.Error, $"Unable to get data from default.xbe.");
126 | return;
127 | }
128 |
129 | var titleId = cert.Title_Id.ToString("X8");
130 | var gameRegion = XbeCertificate.GameRegionToString(cert.Game_Region);
131 | var version = cert.Version.ToString("X8");
132 | var xbeTitle = string.Empty;
133 |
134 | bool found = false;
135 | for (int i = 0; i < GameDataList.Length; i++)
136 | {
137 | if (!config.Section.Equals("[AllSections]") && GameDataList[i].Section.Equals(config.Section, StringComparison.CurrentCultureIgnoreCase) == false)
138 | {
139 | continue;
140 | }
141 | if (GameDataList[i].TitleID == titleId && GameDataList[i].Region == gameRegion && GameDataList[i].Version == version)
142 | {
143 | found = true;
144 | GameDataList[i].Process = "N";
145 | xbeTitle = GameDataList[i].XBETitle;
146 | break;
147 | }
148 | }
149 |
150 | if (found)
151 | {
152 | Log(LogMessageLevel.Completed, $"Game found '{xbeTitle}'.");
153 | }
154 | else
155 | {
156 | Log(LogMessageLevel.Warning, $"Game not found with TitleID = {titleId}, Region = '{gameRegion}', Version = {version}.");
157 | }
158 |
159 | CurrentProgress.Progress2 = 1.0f;
160 | SendProgress();
161 | }
162 | catch (Exception ex)
163 | {
164 | Log(LogMessageLevel.Error, $"Scanning '{folder}' caused error '{ex}'.");
165 | }
166 | }
167 |
168 | public bool StartScanning(GameData[]? gameData, Config config, Action? progress, Action logger, Stopwatch stopwatch, CancellationToken cancellationToken)
169 | {
170 | try
171 | {
172 | Logger = logger;
173 | Progress = progress;
174 |
175 | GameDataList = gameData;
176 | if (GameDataList == null)
177 | {
178 | Log(LogMessageLevel.Error, "RepackList.json not found.");
179 | return false;
180 | }
181 |
182 | if (Directory.Exists(config.OutputPath) == false)
183 | {
184 | Log(LogMessageLevel.Error, "Output path is invalid.");
185 | return false;
186 | }
187 |
188 | stopwatch.Restart();
189 |
190 | if (File.Exists("ScanLog.txt"))
191 | {
192 | File.Delete("ScanLog.txt");
193 | }
194 |
195 | CurrentProgress.Progress1 = 0;
196 | CurrentProgress.Progress1Text = "Scanning Directories";
197 | CurrentProgress.Progress2 = 0;
198 | CurrentProgress.Progress2Text = string.Empty;
199 | SendProgress();
200 |
201 | var pathsScanned = 0;
202 | var totalPaths = 1;
203 | var pathsToScan = new List();
204 | pathsToScan.Add(config.OutputPath);
205 | while (pathsToScan.Count > 0)
206 | {
207 | var pathToProcess = pathsToScan[0];
208 | pathsToScan.RemoveAt(0);
209 |
210 | CurrentProgress.Progress1 = pathsScanned / (float)totalPaths;
211 | SendProgress();
212 |
213 | ProcessFolder(pathToProcess, config, stopwatch, cancellationToken);
214 |
215 | try
216 | {
217 | var pathsToAdd = Directory.GetDirectories(pathToProcess);
218 | pathsToScan.AddRange(pathsToAdd);
219 | totalPaths += pathsToAdd.Length;
220 | }
221 | catch (Exception ex)
222 | {
223 | Log(LogMessageLevel.Error, $"Unable to get folders in '{pathToProcess}' as caused error '{ex}'.");
224 | }
225 |
226 | pathsScanned++;
227 |
228 | if (cancellationToken.IsCancellationRequested)
229 | {
230 | break;
231 | }
232 | }
233 |
234 | CurrentProgress.Progress1 = 1.0f;
235 | SendProgress();
236 |
237 | stopwatch.Stop();
238 |
239 | Log(LogMessageLevel.Done, $"Completed Scanning List (Time Taken {stopwatch.Elapsed.TotalHours:00}:{stopwatch.Elapsed.Minutes:00}:{stopwatch.Elapsed.Seconds:00}).");
240 | return true;
241 | }
242 | catch (Exception ex)
243 | {
244 | Log(LogMessageLevel.Error, $"Exception occurred '{ex}'.");
245 | }
246 | return false;
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------