├── .gitattributes ├── .gitignore ├── Properties ├── AssemblyInfo.cs └── PublishProfiles │ ├── Dependent.pubxml │ └── Portable.pubxml ├── README.md ├── XCI Explorer.csproj ├── XCI Explorer.sln ├── XCI_Explorer.Helpers └── BetterTreeNode.cs ├── XCI_Explorer ├── CNMT.cs ├── CenterWinDialog.cs ├── CertForm.Designer.cs ├── CertForm.cs ├── CertForm.resx ├── HFS0.cs ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── NACP.cs ├── NCA.cs ├── PFS0.cs ├── Program.cs ├── TreeViewFileSystem.cs ├── Util.cs └── XCI.cs ├── XTSSharp ├── RandomAccessSectorStream.cs ├── SectorStream.cs ├── Xts.cs ├── XtsAes128.cs ├── XtsAes256.cs ├── XtsCryptoTransform.cs ├── XtsSectorStream.cs └── XtsStream.cs └── tools ├── SOURCE.txt └── hactool.exe /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.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/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | #*.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | [assembly: CompilationRelaxations(8)] 7 | [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] 8 | [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] 9 | [assembly: AssemblyTitle("XCI Explorer")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("XCI Explorer")] 14 | [assembly: AssemblyCopyright("Open Source")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: ComVisible(false)] 17 | [assembly: Guid("206c6c47-87b1-477f-b6e6-f7e7c1a92f8f")] 18 | [assembly: AssemblyFileVersion("2.0.0")] 19 | [assembly: AssemblyVersion("2.0.0")] 20 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/Dependent.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\Dependent 10 | FileSystem 11 | <_TargetId>Folder 12 | net6.0-windows 13 | win-x64 14 | false 15 | true 16 | false 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/Portable.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\Portable 10 | FileSystem 11 | <_TargetId>Folder 12 | net6.0-windows 13 | win-x64 14 | true 15 | true 16 | false 17 | true 18 | true 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XCI Explorer 2 | 3 | Originally Released by Anonymous on MaxConsole 4 | 5 | View the contents of Switch files and more! 6 | 7 | ## Features 8 | * View metadata for XCI and NSP files 9 | * Explore partitions 10 | * Check NCA hashes 11 | * Extract NCA 12 | * Modify cert 13 | 14 | Main | Partitions 15 | :-------------------------:|:-------------------------: 16 | ![main](https://cdn.discordapp.com/attachments/373320120707055617/686252629852291095/1.JPG) | ![partitions](https://cdn.discordapp.com/attachments/373320120707055617/686252642204385288/2.JPG) 17 | 18 | ## Build Requirements 19 | * [Visual Studio Community 2022 with .NET Desktop Workflow](https://visualstudio.microsoft.com/vs/preview/) 20 | * [hactool](https://github.com/SciresM/hactool/releases) 21 | * [Lockpick](https://github.com/shchmue/Lockpick_RCM/releases) 22 | 23 | ## Build Instructions 24 | * Open **XCI Explorer.sln** 25 | * Change *Debug* to *Release* in the dropdown menu 26 | * Go to *Build*, then *Build Solution* 27 | * Extract **hactool.zip** to the `XCI-Explorer/bin/Release/tools/` folder 28 | * Run **XCI-Explorer.exe** 29 | 30 | ## Special Thanks 31 | * klks - CARD2, hash validation and bug fixes 32 | * garoxas - Game revision, QoL changes and bug fixes 33 | * CodingKoopa, zzpong, and everyone else that's helped this project! 34 | 35 | ## Disclaimers 36 | * This is not my original work 37 | * Some code may be unfinished and unoptimized 38 | * Please use at your own risk! 39 | -------------------------------------------------------------------------------- /XCI Explorer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-windows 4 | WinExe 5 | XCI-Explorer 6 | True 7 | false 8 | true 9 | true 10 | 11 | 12 | false 13 | None 14 | 15 | 16 | 17 | 18 | 19 | 20 | Always 21 | true 22 | 23 | 24 | -------------------------------------------------------------------------------- /XCI Explorer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31825.309 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XCI Explorer", "XCI Explorer.csproj", "{AF04D4B2-34AC-4514-A1A1-19810A25D308}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {AF04D4B2-34AC-4514-A1A1-19810A25D308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AF04D4B2-34AC-4514-A1A1-19810A25D308}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AF04D4B2-34AC-4514-A1A1-19810A25D308}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AF04D4B2-34AC-4514-A1A1-19810A25D308}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {B3F75FD3-9C94-4E83-83BD-783E98162C92} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /XCI_Explorer.Helpers/BetterTreeNode.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace XCI_Explorer.Helpers; 4 | 5 | public class BetterTreeNode : TreeNode 6 | { 7 | public long Offset; 8 | public long Size; 9 | public string ExpectedHash; 10 | public string ActualHash; 11 | public long HashedRegionSize; 12 | 13 | public BetterTreeNode(string t) 14 | { 15 | Text = t; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /XCI_Explorer/CNMT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace XCI_Explorer; 5 | 6 | internal static class CNMT 7 | { 8 | public class CNMT_Header 9 | { 10 | public byte[] Data; 11 | public long TitleID; 12 | public int TitleVersion; 13 | public byte Type; 14 | public byte Reserved1; 15 | public short Offset; 16 | public short ContentCount; 17 | public short MetaCount; 18 | public byte[] Reserved2; 19 | 20 | public enum TitleType 21 | { 22 | SYSTEM_PROGRAMS = 0x01, 23 | SYSTEM_DATA_ARCHIVES, 24 | SYSTEM_UPDATE, 25 | FIRMWARE_PACKAGE_A, 26 | FIRMWARE_PACKAGE_B, 27 | REGULAR_APPLICATION = 0x80, 28 | UPDATE_TITLE, 29 | ADD_ON_CONTENT, 30 | DELTA_TITLE 31 | } 32 | 33 | public CNMT_Header(byte[] data) 34 | { 35 | Data = data; 36 | TitleID = BitConverter.ToInt64(data, 0); 37 | TitleVersion = BitConverter.ToInt32(data, 8); 38 | Type = Data[12]; 39 | Reserved1 = Data[13]; 40 | Offset = BitConverter.ToInt16(data, 14); 41 | ContentCount = BitConverter.ToInt16(data, 16); 42 | MetaCount = BitConverter.ToInt16(data, 16); 43 | Reserved2 = Data.Skip(20).Take(12).ToArray(); 44 | } 45 | } 46 | 47 | public class CNMT_Entry 48 | { 49 | public byte[] Data; 50 | public byte[] Hash; 51 | public byte[] NcaId; 52 | public long Size; 53 | public byte Type; 54 | public byte Reserved; 55 | 56 | public enum ContentType 57 | { 58 | META, 59 | PROGRAM, 60 | DATA, 61 | CONTROL, 62 | OFFLINE_MANUAL, 63 | LEGAL, 64 | GAME_UPDATE 65 | } 66 | 67 | public CNMT_Entry(byte[] data) 68 | { 69 | Data = data; 70 | Hash = Data.Skip(0).Take(32).ToArray(); 71 | NcaId = Data.Skip(32).Take(16).ToArray(); 72 | Size = BitConverter.ToInt32(data, 48) + BitConverter.ToInt16(data, 52) * 65536; 73 | Type = Data[54]; 74 | Reserved = Data[55]; 75 | } 76 | } 77 | 78 | public static CNMT_Header[] CNMT_Headers = new CNMT_Header[1]; 79 | } 80 | -------------------------------------------------------------------------------- /XCI_Explorer/CenterWinDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | namespace XCI_Explorer; 8 | 9 | public class CenterWinDialog : IDisposable 10 | { 11 | private int mTries = 0; 12 | private Form mOwner; 13 | 14 | public CenterWinDialog(Form owner) 15 | { 16 | mOwner = owner; 17 | if (owner.WindowState != FormWindowState.Minimized) 18 | { 19 | owner.BeginInvoke(new MethodInvoker(findDialog)); 20 | } 21 | } 22 | 23 | private void findDialog() 24 | { 25 | // Enumerate windows to find the message box 26 | if (mTries < 0) 27 | { 28 | return; 29 | } 30 | EnumThreadWndProc callback = new(checkWindow); 31 | if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero)) 32 | { 33 | if (++mTries < 10) 34 | { 35 | mOwner.BeginInvoke(new MethodInvoker(findDialog)); 36 | } 37 | } 38 | } 39 | private bool checkWindow(IntPtr hWnd, IntPtr lp) 40 | { 41 | // Checks if is a dialog 42 | StringBuilder sb = new(260); 43 | GetClassName(hWnd, sb, sb.Capacity); 44 | if (sb.ToString() != "#32770") 45 | { 46 | return true; 47 | } 48 | // Got it 49 | Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size); 50 | RECT dlgRect; 51 | GetWindowRect(hWnd, out dlgRect); 52 | MoveWindow(hWnd, 53 | frmRect.Left + (frmRect.Width - dlgRect.Right + dlgRect.Left) / 2, 54 | frmRect.Top + (frmRect.Height - dlgRect.Bottom + dlgRect.Top) / 2, 55 | dlgRect.Right - dlgRect.Left, 56 | dlgRect.Bottom - dlgRect.Top, true); 57 | return false; 58 | } 59 | public void Dispose() 60 | { 61 | mTries = -1; 62 | } 63 | 64 | // P/Invoke declarations 65 | private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp); 66 | [DllImport("user32.dll")] 67 | private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp); 68 | [DllImport("kernel32.dll")] 69 | private static extern int GetCurrentThreadId(); 70 | [DllImport("user32.dll")] 71 | private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen); 72 | [DllImport("user32.dll")] 73 | private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc); 74 | [DllImport("user32.dll")] 75 | private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool repaint); 76 | private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } 77 | } -------------------------------------------------------------------------------- /XCI_Explorer/CertForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using Be.Windows.Forms; 2 | using System.ComponentModel; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace XCI_Explorer 7 | { 8 | partial class CertForm 9 | { 10 | /// 11 | /// Required designer variable. 12 | /// 13 | private IContainer components = null; 14 | private HexBox hbxHexView; 15 | 16 | /// 17 | /// Clean up any resources being used. 18 | /// 19 | /// true if managed resources should be disposed; otherwise, false. 20 | protected override void Dispose(bool disposing) 21 | { 22 | if (disposing && (components != null)) 23 | { 24 | components.Dispose(); 25 | } 26 | base.Dispose(disposing); 27 | } 28 | 29 | #region Windows Form Designer generated code 30 | 31 | /// 32 | /// Required method for Designer support - do not modify 33 | /// the contents of this method with the code editor. 34 | /// 35 | private void InitializeComponent() 36 | { 37 | hbxHexView = new HexBox(); 38 | SuspendLayout(); 39 | hbxHexView.Dock = DockStyle.Fill; 40 | hbxHexView.Font = new Font("Consolas", 9.75f, FontStyle.Regular, GraphicsUnit.Point, 0); 41 | hbxHexView.Location = new Point(0, 0); 42 | hbxHexView.Margin = new Padding(4); 43 | hbxHexView.Name = "hbxHexView"; 44 | hbxHexView.ReadOnly = true; 45 | hbxHexView.ShadowSelectionColor = Color.FromArgb(100, 60, 188, 255); 46 | hbxHexView.Size = new Size(573, 256); 47 | hbxHexView.StringViewVisible = true; 48 | hbxHexView.TabIndex = 7; 49 | hbxHexView.UseFixedBytesPerLine = true; 50 | hbxHexView.VScrollBarVisible = true; 51 | base.AutoScaleDimensions = new SizeF(6f, 13f); 52 | base.AutoScaleMode = AutoScaleMode.Font; 53 | base.ClientSize = new Size(573, 256); 54 | base.Controls.Add(hbxHexView); 55 | base.Name = "CertForm"; 56 | base.ShowIcon = false; 57 | base.StartPosition = FormStartPosition.CenterScreen; 58 | Text = "Cert Data"; 59 | ResumeLayout(false); 60 | } 61 | 62 | #endregion 63 | } 64 | } -------------------------------------------------------------------------------- /XCI_Explorer/CertForm.cs: -------------------------------------------------------------------------------- 1 | using Be.Windows.Forms; 2 | using System.IO; 3 | using System.Windows.Forms; 4 | 5 | namespace XCI_Explorer; 6 | 7 | public partial class CertForm : Form 8 | { 9 | public CertForm(MainForm mainForm) 10 | { 11 | InitializeComponent(); 12 | FileStream fileStream = new(mainForm.TB_File.Text, FileMode.Open, FileAccess.Read); 13 | byte[] array = new byte[512]; 14 | fileStream.Position = 28672L; 15 | fileStream.Read(array, 0, 512); 16 | hbxHexView.ByteProvider = new DynamicByteProvider(array); 17 | fileStream.Close(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /XCI_Explorer/CertForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /XCI_Explorer/HFS0.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace XCI_Explorer; 6 | 7 | internal static class HFS0 8 | { 9 | public class HFS0_Header 10 | { 11 | public byte[] Data; 12 | public string Magic; 13 | public int FileCount; 14 | public int StringTableSize; 15 | public int Reserved; 16 | 17 | public HFS0_Header(byte[] data) 18 | { 19 | Data = data; 20 | Magic = Encoding.UTF8.GetString(Data.Take(4).ToArray()); 21 | FileCount = BitConverter.ToInt32(data, 4); 22 | StringTableSize = BitConverter.ToInt32(data, 8); 23 | Reserved = BitConverter.ToInt32(data, 12); 24 | } 25 | } 26 | 27 | public class HSF0_Entry 28 | { 29 | public byte[] Data; 30 | public long Offset; 31 | public long Size; 32 | public int Name_ptr; 33 | public int HashedRegionSize; 34 | public long Padding; 35 | public byte[] Hash; 36 | public string Name; 37 | 38 | public HSF0_Entry(byte[] data) 39 | { 40 | Data = data; 41 | Offset = BitConverter.ToInt64(data, 0); 42 | Size = BitConverter.ToInt64(data, 8); 43 | Name_ptr = BitConverter.ToInt32(data, 16); 44 | HashedRegionSize = BitConverter.ToInt32(data, 20); 45 | Padding = BitConverter.ToInt64(data, 24); 46 | Hash = Data.Skip(32).Take(32).ToArray(); 47 | } 48 | } 49 | 50 | public static HFS0_Header[] HFS0_Headers = new HFS0_Header[1]; 51 | } 52 | -------------------------------------------------------------------------------- /XCI_Explorer/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Forms; 3 | using XCI_Explorer.Helpers; 4 | 5 | namespace XCI_Explorer 6 | { 7 | partial class MainForm 8 | { 9 | /// 10 | /// Required designer variable. 11 | /// 12 | private IContainer components = null; 13 | 14 | /// 15 | /// Clean up any resources being used. 16 | /// 17 | /// true if managed resources should be disposed; otherwise, false. 18 | protected override void Dispose(bool disposing) 19 | { 20 | if (disposing && (components != null)) 21 | { 22 | components.Dispose(); 23 | } 24 | base.Dispose(disposing); 25 | } 26 | 27 | #region Windows Form Designer generated code 28 | 29 | /// 30 | /// Required method for Designer support - do not modify 31 | /// the contents of this method with the code editor. 32 | /// 33 | private void InitializeComponent() 34 | { 35 | this.B_LoadROM = new System.Windows.Forms.Button(); 36 | this.TB_File = new System.Windows.Forms.TextBox(); 37 | this.TABC_Main = new System.Windows.Forms.TabControl(); 38 | this.TABP_XCI = new System.Windows.Forms.TabPage(); 39 | this.TB_GameRev = new System.Windows.Forms.RichTextBox(); 40 | this.label12 = new System.Windows.Forms.Label(); 41 | this.B_TrimXCI = new System.Windows.Forms.Button(); 42 | this.TB_ProdCode = new System.Windows.Forms.TextBox(); 43 | this.label8 = new System.Windows.Forms.Label(); 44 | this.groupBox2 = new System.Windows.Forms.GroupBox(); 45 | this.label11 = new System.Windows.Forms.Label(); 46 | this.TB_Dev = new System.Windows.Forms.TextBox(); 47 | this.label10 = new System.Windows.Forms.Label(); 48 | this.TB_Name = new System.Windows.Forms.TextBox(); 49 | this.label9 = new System.Windows.Forms.Label(); 50 | this.PB_GameIcon = new System.Windows.Forms.PictureBox(); 51 | this.CB_RegionName = new System.Windows.Forms.ComboBox(); 52 | this.label7 = new System.Windows.Forms.Label(); 53 | this.groupBox1 = new System.Windows.Forms.GroupBox(); 54 | this.B_ViewCert = new System.Windows.Forms.Button(); 55 | this.B_ClearCert = new System.Windows.Forms.Button(); 56 | this.B_ImportCert = new System.Windows.Forms.Button(); 57 | this.B_ExportCert = new System.Windows.Forms.Button(); 58 | this.TB_ExactUsedSpace = new System.Windows.Forms.TextBox(); 59 | this.TB_ROMExactSize = new System.Windows.Forms.TextBox(); 60 | this.TB_UsedSpace = new System.Windows.Forms.TextBox(); 61 | this.TB_ROMSize = new System.Windows.Forms.TextBox(); 62 | this.label6 = new System.Windows.Forms.Label(); 63 | this.label5 = new System.Windows.Forms.Label(); 64 | this.TB_MKeyRev = new System.Windows.Forms.TextBox(); 65 | this.label4 = new System.Windows.Forms.Label(); 66 | this.TB_SDKVer = new System.Windows.Forms.TextBox(); 67 | this.label3 = new System.Windows.Forms.Label(); 68 | this.TB_Capacity = new System.Windows.Forms.TextBox(); 69 | this.label2 = new System.Windows.Forms.Label(); 70 | this.label1 = new System.Windows.Forms.Label(); 71 | this.TB_TID = new System.Windows.Forms.TextBox(); 72 | this.tabPage2 = new System.Windows.Forms.TabPage(); 73 | this.LB_HashedRegionSize = new System.Windows.Forms.Label(); 74 | this.LB_ActualHash = new System.Windows.Forms.Label(); 75 | this.LB_ExpectedHash = new System.Windows.Forms.Label(); 76 | this.B_Extract = new System.Windows.Forms.Button(); 77 | this.LB_DataSize = new System.Windows.Forms.Label(); 78 | this.LB_DataOffset = new System.Windows.Forms.Label(); 79 | this.LB_SelectedData = new System.Windows.Forms.Label(); 80 | this.TV_Partitions = new System.Windows.Forms.TreeView(); 81 | this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); 82 | this.TABC_Main.SuspendLayout(); 83 | this.TABP_XCI.SuspendLayout(); 84 | this.groupBox2.SuspendLayout(); 85 | ((System.ComponentModel.ISupportInitialize)(this.PB_GameIcon)).BeginInit(); 86 | this.groupBox1.SuspendLayout(); 87 | this.tabPage2.SuspendLayout(); 88 | this.SuspendLayout(); 89 | // 90 | // B_LoadROM 91 | // 92 | this.B_LoadROM.Location = new System.Drawing.Point(4, 11); 93 | this.B_LoadROM.Name = "B_LoadROM"; 94 | this.B_LoadROM.Size = new System.Drawing.Size(75, 21); 95 | this.B_LoadROM.TabIndex = 0; 96 | this.B_LoadROM.Text = "Load Game"; 97 | this.B_LoadROM.UseVisualStyleBackColor = true; 98 | this.B_LoadROM.Click += new System.EventHandler(this.B_LoadROM_Click); 99 | // 100 | // TB_File 101 | // 102 | this.TB_File.AllowDrop = true; 103 | this.TB_File.Location = new System.Drawing.Point(85, 12); 104 | this.TB_File.Name = "TB_File"; 105 | this.TB_File.ReadOnly = true; 106 | this.TB_File.Size = new System.Drawing.Size(268, 21); 107 | this.TB_File.TabIndex = 1; 108 | // 109 | // TABC_Main 110 | // 111 | this.TABC_Main.Controls.Add(this.TABP_XCI); 112 | this.TABC_Main.Controls.Add(this.tabPage2); 113 | this.TABC_Main.Location = new System.Drawing.Point(4, 38); 114 | this.TABC_Main.Name = "TABC_Main"; 115 | this.TABC_Main.SelectedIndex = 0; 116 | this.TABC_Main.Size = new System.Drawing.Size(364, 511); 117 | this.TABC_Main.TabIndex = 2; 118 | // 119 | // TABP_XCI 120 | // 121 | this.TABP_XCI.AllowDrop = true; 122 | this.TABP_XCI.Controls.Add(this.TB_GameRev); 123 | this.TABP_XCI.Controls.Add(this.label12); 124 | this.TABP_XCI.Controls.Add(this.B_TrimXCI); 125 | this.TABP_XCI.Controls.Add(this.TB_ProdCode); 126 | this.TABP_XCI.Controls.Add(this.label8); 127 | this.TABP_XCI.Controls.Add(this.groupBox2); 128 | this.TABP_XCI.Controls.Add(this.label7); 129 | this.TABP_XCI.Controls.Add(this.groupBox1); 130 | this.TABP_XCI.Controls.Add(this.TB_ExactUsedSpace); 131 | this.TABP_XCI.Controls.Add(this.TB_ROMExactSize); 132 | this.TABP_XCI.Controls.Add(this.TB_UsedSpace); 133 | this.TABP_XCI.Controls.Add(this.TB_ROMSize); 134 | this.TABP_XCI.Controls.Add(this.label6); 135 | this.TABP_XCI.Controls.Add(this.label5); 136 | this.TABP_XCI.Controls.Add(this.TB_MKeyRev); 137 | this.TABP_XCI.Controls.Add(this.label4); 138 | this.TABP_XCI.Controls.Add(this.TB_SDKVer); 139 | this.TABP_XCI.Controls.Add(this.label3); 140 | this.TABP_XCI.Controls.Add(this.TB_Capacity); 141 | this.TABP_XCI.Controls.Add(this.label2); 142 | this.TABP_XCI.Controls.Add(this.label1); 143 | this.TABP_XCI.Controls.Add(this.TB_TID); 144 | this.TABP_XCI.Location = new System.Drawing.Point(4, 22); 145 | this.TABP_XCI.Name = "TABP_XCI"; 146 | this.TABP_XCI.Padding = new System.Windows.Forms.Padding(3); 147 | this.TABP_XCI.Size = new System.Drawing.Size(356, 485); 148 | this.TABP_XCI.TabIndex = 0; 149 | this.TABP_XCI.Text = "Main"; 150 | this.TABP_XCI.UseVisualStyleBackColor = true; 151 | this.TABP_XCI.Click += new System.EventHandler(this.TABP_XCI_Click); 152 | this.TABP_XCI.DragDrop += new System.Windows.Forms.DragEventHandler(this.TB_File_DragDrop); 153 | this.TABP_XCI.DragEnter += new System.Windows.Forms.DragEventHandler(this.TB_File_DragEnter); 154 | // 155 | // TB_GameRev 156 | // 157 | this.TB_GameRev.Location = new System.Drawing.Point(15, 63); 158 | this.TB_GameRev.Name = "TB_GameRev"; 159 | this.TB_GameRev.ReadOnly = true; 160 | this.TB_GameRev.Size = new System.Drawing.Size(237, 99); 161 | this.TB_GameRev.TabIndex = 23; 162 | this.TB_GameRev.Text = ""; 163 | // 164 | // label12 165 | // 166 | this.label12.AutoSize = true; 167 | this.label12.Location = new System.Drawing.Point(75, 48); 168 | this.label12.Name = "label12"; 169 | this.label12.Size = new System.Drawing.Size(125, 12); 170 | this.label12.TabIndex = 22; 171 | this.label12.Text = "0 BASE, 0 UPD, 0 DLC"; 172 | // 173 | // B_TrimXCI 174 | // 175 | this.B_TrimXCI.Location = new System.Drawing.Point(307, 178); 176 | this.B_TrimXCI.Name = "B_TrimXCI"; 177 | this.B_TrimXCI.Size = new System.Drawing.Size(38, 46); 178 | this.B_TrimXCI.TabIndex = 21; 179 | this.B_TrimXCI.Text = "Trim XCI"; 180 | this.B_TrimXCI.UseVisualStyleBackColor = true; 181 | this.B_TrimXCI.Click += new System.EventHandler(this.B_TrimXCI_Click); 182 | // 183 | // TB_ProdCode 184 | // 185 | this.TB_ProdCode.Location = new System.Drawing.Point(258, 141); 186 | this.TB_ProdCode.Name = "TB_ProdCode"; 187 | this.TB_ProdCode.ReadOnly = true; 188 | this.TB_ProdCode.Size = new System.Drawing.Size(87, 21); 189 | this.TB_ProdCode.TabIndex = 20; 190 | // 191 | // label8 192 | // 193 | this.label8.AutoSize = true; 194 | this.label8.Location = new System.Drawing.Point(258, 126); 195 | this.label8.Name = "label8"; 196 | this.label8.Size = new System.Drawing.Size(83, 12); 197 | this.label8.TabIndex = 19; 198 | this.label8.Text = "Product Code:"; 199 | // 200 | // groupBox2 201 | // 202 | this.groupBox2.Controls.Add(this.label11); 203 | this.groupBox2.Controls.Add(this.TB_Dev); 204 | this.groupBox2.Controls.Add(this.label10); 205 | this.groupBox2.Controls.Add(this.TB_Name); 206 | this.groupBox2.Controls.Add(this.label9); 207 | this.groupBox2.Controls.Add(this.PB_GameIcon); 208 | this.groupBox2.Controls.Add(this.CB_RegionName); 209 | this.groupBox2.Location = new System.Drawing.Point(15, 285); 210 | this.groupBox2.Name = "groupBox2"; 211 | this.groupBox2.Size = new System.Drawing.Size(330, 178); 212 | this.groupBox2.TabIndex = 18; 213 | this.groupBox2.TabStop = false; 214 | this.groupBox2.Text = "Game Infos"; 215 | // 216 | // label11 217 | // 218 | this.label11.AutoSize = true; 219 | this.label11.Location = new System.Drawing.Point(6, 69); 220 | this.label11.Name = "label11"; 221 | this.label11.Size = new System.Drawing.Size(59, 12); 222 | this.label11.TabIndex = 25; 223 | this.label11.Text = "Language:"; 224 | this.label11.Click += new System.EventHandler(this.label11_Click); 225 | // 226 | // TB_Dev 227 | // 228 | this.TB_Dev.Location = new System.Drawing.Point(6, 138); 229 | this.TB_Dev.Name = "TB_Dev"; 230 | this.TB_Dev.ReadOnly = true; 231 | this.TB_Dev.Size = new System.Drawing.Size(154, 21); 232 | this.TB_Dev.TabIndex = 24; 233 | // 234 | // label10 235 | // 236 | this.label10.AutoSize = true; 237 | this.label10.Location = new System.Drawing.Point(6, 123); 238 | this.label10.Name = "label10"; 239 | this.label10.Size = new System.Drawing.Size(65, 12); 240 | this.label10.TabIndex = 23; 241 | this.label10.Text = "Developer:"; 242 | // 243 | // TB_Name 244 | // 245 | this.TB_Name.Location = new System.Drawing.Point(6, 35); 246 | this.TB_Name.Name = "TB_Name"; 247 | this.TB_Name.ReadOnly = true; 248 | this.TB_Name.Size = new System.Drawing.Size(154, 21); 249 | this.TB_Name.TabIndex = 22; 250 | // 251 | // label9 252 | // 253 | this.label9.AutoSize = true; 254 | this.label9.Location = new System.Drawing.Point(6, 20); 255 | this.label9.Name = "label9"; 256 | this.label9.Size = new System.Drawing.Size(35, 12); 257 | this.label9.TabIndex = 21; 258 | this.label9.Text = "Name:"; 259 | // 260 | // PB_GameIcon 261 | // 262 | this.PB_GameIcon.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; 263 | this.PB_GameIcon.Location = new System.Drawing.Point(172, 20); 264 | this.PB_GameIcon.Name = "PB_GameIcon"; 265 | this.PB_GameIcon.Size = new System.Drawing.Size(152, 147); 266 | this.PB_GameIcon.TabIndex = 18; 267 | this.PB_GameIcon.TabStop = false; 268 | // 269 | // CB_RegionName 270 | // 271 | this.CB_RegionName.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 272 | this.CB_RegionName.FormattingEnabled = true; 273 | this.CB_RegionName.Location = new System.Drawing.Point(6, 84); 274 | this.CB_RegionName.Name = "CB_RegionName"; 275 | this.CB_RegionName.Size = new System.Drawing.Size(154, 20); 276 | this.CB_RegionName.TabIndex = 17; 277 | this.CB_RegionName.SelectedIndexChanged += new System.EventHandler(this.CB_RegionName_SelectedIndexChanged); 278 | // 279 | // label7 280 | // 281 | this.label7.AutoSize = true; 282 | this.label7.Location = new System.Drawing.Point(11, 48); 283 | this.label7.Name = "label7"; 284 | this.label7.Size = new System.Drawing.Size(59, 12); 285 | this.label7.TabIndex = 15; 286 | this.label7.Text = "Contents:"; 287 | // 288 | // groupBox1 289 | // 290 | this.groupBox1.Controls.Add(this.B_ViewCert); 291 | this.groupBox1.Controls.Add(this.B_ClearCert); 292 | this.groupBox1.Controls.Add(this.B_ImportCert); 293 | this.groupBox1.Controls.Add(this.B_ExportCert); 294 | this.groupBox1.Location = new System.Drawing.Point(15, 230); 295 | this.groupBox1.Name = "groupBox1"; 296 | this.groupBox1.Size = new System.Drawing.Size(330, 49); 297 | this.groupBox1.TabIndex = 14; 298 | this.groupBox1.TabStop = false; 299 | this.groupBox1.Text = "Cert"; 300 | // 301 | // B_ViewCert 302 | // 303 | this.B_ViewCert.Location = new System.Drawing.Point(168, 18); 304 | this.B_ViewCert.Name = "B_ViewCert"; 305 | this.B_ViewCert.Size = new System.Drawing.Size(74, 21); 306 | this.B_ViewCert.TabIndex = 3; 307 | this.B_ViewCert.Text = "View Cert"; 308 | this.B_ViewCert.UseVisualStyleBackColor = true; 309 | this.B_ViewCert.Click += new System.EventHandler(this.B_ViewCert_Click); 310 | // 311 | // B_ClearCert 312 | // 313 | this.B_ClearCert.Location = new System.Drawing.Point(250, 18); 314 | this.B_ClearCert.Name = "B_ClearCert"; 315 | this.B_ClearCert.Size = new System.Drawing.Size(74, 21); 316 | this.B_ClearCert.TabIndex = 2; 317 | this.B_ClearCert.Text = "Clear Cert"; 318 | this.B_ClearCert.UseVisualStyleBackColor = true; 319 | this.B_ClearCert.Click += new System.EventHandler(this.B_ClearCert_Click); 320 | // 321 | // B_ImportCert 322 | // 323 | this.B_ImportCert.Location = new System.Drawing.Point(86, 18); 324 | this.B_ImportCert.Name = "B_ImportCert"; 325 | this.B_ImportCert.Size = new System.Drawing.Size(74, 21); 326 | this.B_ImportCert.TabIndex = 1; 327 | this.B_ImportCert.Text = "Import Cert"; 328 | this.B_ImportCert.UseVisualStyleBackColor = true; 329 | this.B_ImportCert.Click += new System.EventHandler(this.B_ImportCert_Click); 330 | // 331 | // B_ExportCert 332 | // 333 | this.B_ExportCert.Location = new System.Drawing.Point(6, 18); 334 | this.B_ExportCert.Name = "B_ExportCert"; 335 | this.B_ExportCert.Size = new System.Drawing.Size(74, 21); 336 | this.B_ExportCert.TabIndex = 0; 337 | this.B_ExportCert.Text = "Export Cert"; 338 | this.B_ExportCert.UseVisualStyleBackColor = true; 339 | this.B_ExportCert.Click += new System.EventHandler(this.B_ExportCert_Click); 340 | // 341 | // TB_ExactUsedSpace 342 | // 343 | this.TB_ExactUsedSpace.Location = new System.Drawing.Point(152, 203); 344 | this.TB_ExactUsedSpace.Name = "TB_ExactUsedSpace"; 345 | this.TB_ExactUsedSpace.ReadOnly = true; 346 | this.TB_ExactUsedSpace.Size = new System.Drawing.Size(152, 21); 347 | this.TB_ExactUsedSpace.TabIndex = 13; 348 | // 349 | // TB_ROMExactSize 350 | // 351 | this.TB_ROMExactSize.Location = new System.Drawing.Point(152, 178); 352 | this.TB_ROMExactSize.Name = "TB_ROMExactSize"; 353 | this.TB_ROMExactSize.ReadOnly = true; 354 | this.TB_ROMExactSize.Size = new System.Drawing.Size(152, 21); 355 | this.TB_ROMExactSize.TabIndex = 12; 356 | // 357 | // TB_UsedSpace 358 | // 359 | this.TB_UsedSpace.Location = new System.Drawing.Point(77, 203); 360 | this.TB_UsedSpace.Name = "TB_UsedSpace"; 361 | this.TB_UsedSpace.ReadOnly = true; 362 | this.TB_UsedSpace.Size = new System.Drawing.Size(69, 21); 363 | this.TB_UsedSpace.TabIndex = 11; 364 | // 365 | // TB_ROMSize 366 | // 367 | this.TB_ROMSize.Location = new System.Drawing.Point(77, 178); 368 | this.TB_ROMSize.Name = "TB_ROMSize"; 369 | this.TB_ROMSize.ReadOnly = true; 370 | this.TB_ROMSize.Size = new System.Drawing.Size(69, 21); 371 | this.TB_ROMSize.TabIndex = 10; 372 | // 373 | // label6 374 | // 375 | this.label6.AutoSize = true; 376 | this.label6.Location = new System.Drawing.Point(4, 203); 377 | this.label6.Name = "label6"; 378 | this.label6.Size = new System.Drawing.Size(71, 12); 379 | this.label6.TabIndex = 9; 380 | this.label6.Text = "Used space:"; 381 | // 382 | // label5 383 | // 384 | this.label5.AutoSize = true; 385 | this.label5.Location = new System.Drawing.Point(13, 181); 386 | this.label5.Name = "label5"; 387 | this.label5.Size = new System.Drawing.Size(59, 12); 388 | this.label5.TabIndex = 8; 389 | this.label5.Text = "ROM Size:"; 390 | // 391 | // TB_MKeyRev 392 | // 393 | this.TB_MKeyRev.Location = new System.Drawing.Point(191, 21); 394 | this.TB_MKeyRev.Name = "TB_MKeyRev"; 395 | this.TB_MKeyRev.ReadOnly = true; 396 | this.TB_MKeyRev.Size = new System.Drawing.Size(154, 21); 397 | this.TB_MKeyRev.TabIndex = 7; 398 | // 399 | // label4 400 | // 401 | this.label4.AutoSize = true; 402 | this.label4.Location = new System.Drawing.Point(189, 6); 403 | this.label4.Name = "label4"; 404 | this.label4.Size = new System.Drawing.Size(119, 12); 405 | this.label4.TabIndex = 6; 406 | this.label4.Text = "MasterKey Revision:"; 407 | // 408 | // TB_SDKVer 409 | // 410 | this.TB_SDKVer.Location = new System.Drawing.Point(258, 102); 411 | this.TB_SDKVer.Name = "TB_SDKVer"; 412 | this.TB_SDKVer.ReadOnly = true; 413 | this.TB_SDKVer.Size = new System.Drawing.Size(87, 21); 414 | this.TB_SDKVer.TabIndex = 5; 415 | // 416 | // label3 417 | // 418 | this.label3.AutoSize = true; 419 | this.label3.Location = new System.Drawing.Point(258, 87); 420 | this.label3.Name = "label3"; 421 | this.label3.Size = new System.Drawing.Size(77, 12); 422 | this.label3.TabIndex = 4; 423 | this.label3.Text = "SDK Version:"; 424 | // 425 | // TB_Capacity 426 | // 427 | this.TB_Capacity.Location = new System.Drawing.Point(258, 63); 428 | this.TB_Capacity.Name = "TB_Capacity"; 429 | this.TB_Capacity.ReadOnly = true; 430 | this.TB_Capacity.Size = new System.Drawing.Size(87, 21); 431 | this.TB_Capacity.TabIndex = 3; 432 | // 433 | // label2 434 | // 435 | this.label2.AutoSize = true; 436 | this.label2.Location = new System.Drawing.Point(258, 48); 437 | this.label2.Name = "label2"; 438 | this.label2.Size = new System.Drawing.Size(59, 12); 439 | this.label2.TabIndex = 2; 440 | this.label2.Text = "Capacity:"; 441 | // 442 | // label1 443 | // 444 | this.label1.AutoSize = true; 445 | this.label1.Location = new System.Drawing.Point(12, 6); 446 | this.label1.Name = "label1"; 447 | this.label1.Size = new System.Drawing.Size(59, 12); 448 | this.label1.TabIndex = 1; 449 | this.label1.Text = "Title ID:"; 450 | // 451 | // TB_TID 452 | // 453 | this.TB_TID.Location = new System.Drawing.Point(15, 21); 454 | this.TB_TID.Name = "TB_TID"; 455 | this.TB_TID.ReadOnly = true; 456 | this.TB_TID.Size = new System.Drawing.Size(170, 21); 457 | this.TB_TID.TabIndex = 0; 458 | // 459 | // tabPage2 460 | // 461 | this.tabPage2.Controls.Add(this.LB_HashedRegionSize); 462 | this.tabPage2.Controls.Add(this.LB_ActualHash); 463 | this.tabPage2.Controls.Add(this.LB_ExpectedHash); 464 | this.tabPage2.Controls.Add(this.B_Extract); 465 | this.tabPage2.Controls.Add(this.LB_DataSize); 466 | this.tabPage2.Controls.Add(this.LB_DataOffset); 467 | this.tabPage2.Controls.Add(this.LB_SelectedData); 468 | this.tabPage2.Controls.Add(this.TV_Partitions); 469 | this.tabPage2.Location = new System.Drawing.Point(4, 22); 470 | this.tabPage2.Name = "tabPage2"; 471 | this.tabPage2.Padding = new System.Windows.Forms.Padding(3); 472 | this.tabPage2.Size = new System.Drawing.Size(356, 485); 473 | this.tabPage2.TabIndex = 1; 474 | this.tabPage2.Text = "Partitions"; 475 | this.tabPage2.UseVisualStyleBackColor = true; 476 | // 477 | // LB_HashedRegionSize 478 | // 479 | this.LB_HashedRegionSize.AutoSize = true; 480 | this.LB_HashedRegionSize.Location = new System.Drawing.Point(6, 439); 481 | this.LB_HashedRegionSize.Name = "LB_HashedRegionSize"; 482 | this.LB_HashedRegionSize.Size = new System.Drawing.Size(107, 12); 483 | this.LB_HashedRegionSize.TabIndex = 7; 484 | this.LB_HashedRegionSize.Text = "HashedRegionSize:"; 485 | // 486 | // LB_ActualHash 487 | // 488 | this.LB_ActualHash.AutoSize = true; 489 | this.LB_ActualHash.Location = new System.Drawing.Point(6, 464); 490 | this.LB_ActualHash.Name = "LB_ActualHash"; 491 | this.LB_ActualHash.Size = new System.Drawing.Size(77, 12); 492 | this.LB_ActualHash.TabIndex = 6; 493 | this.LB_ActualHash.Text = "Actual Hash:"; 494 | this.LB_ActualHash.DoubleClick += new System.EventHandler(this.LB_ActualHash_DoubleClick); 495 | // 496 | // LB_ExpectedHash 497 | // 498 | this.LB_ExpectedHash.AutoSize = true; 499 | this.LB_ExpectedHash.Location = new System.Drawing.Point(6, 452); 500 | this.LB_ExpectedHash.Name = "LB_ExpectedHash"; 501 | this.LB_ExpectedHash.Size = new System.Drawing.Size(77, 12); 502 | this.LB_ExpectedHash.TabIndex = 5; 503 | this.LB_ExpectedHash.Text = "Header Hash:"; 504 | this.LB_ExpectedHash.DoubleClick += new System.EventHandler(this.LB_ExpectedHash_DoubleClick); 505 | // 506 | // B_Extract 507 | // 508 | this.B_Extract.Enabled = false; 509 | this.B_Extract.Location = new System.Drawing.Point(296, 394); 510 | this.B_Extract.Name = "B_Extract"; 511 | this.B_Extract.Size = new System.Drawing.Size(48, 21); 512 | this.B_Extract.TabIndex = 4; 513 | this.B_Extract.Text = "Extract"; 514 | this.B_Extract.UseVisualStyleBackColor = true; 515 | this.B_Extract.Click += new System.EventHandler(this.B_Extract_Click); 516 | // 517 | // LB_DataSize 518 | // 519 | this.LB_DataSize.AutoSize = true; 520 | this.LB_DataSize.Location = new System.Drawing.Point(6, 427); 521 | this.LB_DataSize.Name = "LB_DataSize"; 522 | this.LB_DataSize.Size = new System.Drawing.Size(35, 12); 523 | this.LB_DataSize.TabIndex = 3; 524 | this.LB_DataSize.Text = "Size:"; 525 | // 526 | // LB_DataOffset 527 | // 528 | this.LB_DataOffset.AutoSize = true; 529 | this.LB_DataOffset.Location = new System.Drawing.Point(6, 415); 530 | this.LB_DataOffset.Name = "LB_DataOffset"; 531 | this.LB_DataOffset.Size = new System.Drawing.Size(47, 12); 532 | this.LB_DataOffset.TabIndex = 2; 533 | this.LB_DataOffset.Text = "Offset:"; 534 | // 535 | // LB_SelectedData 536 | // 537 | this.LB_SelectedData.AutoSize = true; 538 | this.LB_SelectedData.Location = new System.Drawing.Point(6, 394); 539 | this.LB_SelectedData.Name = "LB_SelectedData"; 540 | this.LB_SelectedData.Size = new System.Drawing.Size(53, 12); 541 | this.LB_SelectedData.TabIndex = 1; 542 | this.LB_SelectedData.Text = "FileName"; 543 | // 544 | // TV_Partitions 545 | // 546 | this.TV_Partitions.Dock = System.Windows.Forms.DockStyle.Top; 547 | this.TV_Partitions.HideSelection = false; 548 | this.TV_Partitions.Location = new System.Drawing.Point(3, 3); 549 | this.TV_Partitions.Name = "TV_Partitions"; 550 | this.TV_Partitions.Size = new System.Drawing.Size(350, 385); 551 | this.TV_Partitions.TabIndex = 0; 552 | this.TV_Partitions.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.TV_Partitions_AfterSelect); 553 | // 554 | // backgroundWorker1 555 | // 556 | this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork); 557 | this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); 558 | // 559 | // MainForm 560 | // 561 | this.AllowDrop = true; 562 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 563 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 564 | this.ClientSize = new System.Drawing.Size(370, 553); 565 | this.Controls.Add(this.TABC_Main); 566 | this.Controls.Add(this.TB_File); 567 | this.Controls.Add(this.B_LoadROM); 568 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 569 | this.MaximizeBox = false; 570 | this.Name = "MainForm"; 571 | this.ShowIcon = false; 572 | this.Text = "XCI Explorer"; 573 | this.TABC_Main.ResumeLayout(false); 574 | this.TABP_XCI.ResumeLayout(false); 575 | this.TABP_XCI.PerformLayout(); 576 | this.groupBox2.ResumeLayout(false); 577 | this.groupBox2.PerformLayout(); 578 | ((System.ComponentModel.ISupportInitialize)(this.PB_GameIcon)).EndInit(); 579 | this.groupBox1.ResumeLayout(false); 580 | this.tabPage2.ResumeLayout(false); 581 | this.tabPage2.PerformLayout(); 582 | this.ResumeLayout(false); 583 | this.PerformLayout(); 584 | 585 | } 586 | 587 | #endregion 588 | 589 | private long[] SecureSize; 590 | private long[] NormalSize; 591 | private long[] SecureOffset; 592 | private long[] NormalOffset; 593 | private string[] SecureName = { }; 594 | private long gameNcaOffset; 595 | private long gameNcaSize; 596 | private long PFS0Offset; 597 | private long PFS0Size; 598 | private long selectedOffset; 599 | private long selectedSize; 600 | private TreeViewFileSystem TV_Parti; 601 | private BetterTreeNode rootNode; 602 | private Button B_LoadROM; 603 | private TabControl TABC_Main; 604 | private TabPage TABP_XCI; 605 | private TabPage tabPage2; 606 | private TreeView TV_Partitions; 607 | private TextBox TB_SDKVer; 608 | private Label label3; 609 | private TextBox TB_Capacity; 610 | private Label label2; 611 | private Label label1; 612 | private TextBox TB_TID; 613 | private TextBox TB_MKeyRev; 614 | private Label label4; 615 | private TextBox TB_ExactUsedSpace; 616 | private TextBox TB_ROMExactSize; 617 | private TextBox TB_UsedSpace; 618 | private TextBox TB_ROMSize; 619 | private Label label6; 620 | private Label label5; 621 | private Label label7; 622 | private GroupBox groupBox1; 623 | private Button B_ClearCert; 624 | private Button B_ImportCert; 625 | private Button B_ExportCert; 626 | private ComboBox CB_RegionName; 627 | private TextBox TB_ProdCode; 628 | private Label label8; 629 | private GroupBox groupBox2; 630 | private TextBox TB_Dev; 631 | private Label label10; 632 | private TextBox TB_Name; 633 | private Label label9; 634 | private PictureBox PB_GameIcon; 635 | private Button B_ViewCert; 636 | public TextBox TB_File; 637 | private Label LB_SelectedData; 638 | private Label LB_DataOffset; 639 | private Label LB_DataSize; 640 | private Button B_Extract; 641 | private Label LB_ExpectedHash; 642 | private Label LB_ActualHash; 643 | private Label LB_HashedRegionSize; 644 | private BackgroundWorker backgroundWorker1; 645 | private Button B_TrimXCI; 646 | private Label label11; 647 | private Label label12; 648 | private RichTextBox TB_GameRev; 649 | } 650 | } -------------------------------------------------------------------------------- /XCI_Explorer/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Reflection; 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | using System.Text.RegularExpressions; 13 | using System.Windows.Forms; 14 | using System.Xml.Linq; 15 | using XCI_Explorer.Helpers; 16 | using XTSSharp; 17 | 18 | namespace XCI_Explorer; 19 | 20 | public partial class MainForm : Form 21 | { 22 | public List chars = new(); 23 | public byte[] NcaHeaderEncryptionKey1_Prod; 24 | public byte[] NcaHeaderEncryptionKey2_Prod; 25 | public string Mkey; 26 | public double UsedSize; 27 | private Image[] Icons = new Image[16]; 28 | private readonly string[] Language = new string[16] { 29 | "American English", 30 | "British English", 31 | "Japanese", 32 | "French", 33 | "German", 34 | "Latin American Spanish", 35 | "Spanish", 36 | "Italian", 37 | "Dutch", 38 | "Canadian French", 39 | "Portuguese", 40 | "Russian", 41 | "Korean", 42 | "Traditional Chinese", 43 | "Simplified Chinese", 44 | "???" 45 | }; 46 | 47 | public MainForm() 48 | { 49 | InitializeComponent(); 50 | 51 | Text = $"XCI Explorer v{getAssemblyVersion()}"; 52 | 53 | LB_SelectedData.Text = ""; 54 | LB_DataOffset.Text = ""; 55 | LB_DataSize.Text = ""; 56 | LB_HashedRegionSize.Text = ""; 57 | LB_ActualHash.Text = ""; 58 | LB_ExpectedHash.Text = ""; 59 | 60 | Show(); 61 | 62 | //MAC - Set Current Directory to application directory so it can find the keys 63 | String startupPath = Application.StartupPath; 64 | Directory.SetCurrentDirectory(startupPath); 65 | 66 | if (!File.Exists("keys.txt")) 67 | { 68 | new CenterWinDialog(this); 69 | if (MessageBox.Show("keys.txt is missing.\nDo you want to automatically download it now?\n\nBy pressing 'Yes' you agree that you own these keys.\n", "XCI Explorer", MessageBoxButtons.YesNo) == DialogResult.Yes) 70 | { 71 | using HttpClient client = new(); 72 | using HttpResponseMessage response = client.Send(new HttpRequestMessage(HttpMethod.Get, Util.Base64Decode("aHR0cHM6Ly9wYXN0ZWJpbi5jb20vcmF3L0Z2M25GRzJR"))); 73 | using Stream stream = response.Content.ReadAsStream(); 74 | using FileStream fs = new("keys.txt", FileMode.CreateNew); 75 | stream.CopyTo(fs); 76 | } 77 | 78 | if (!File.Exists("keys.txt")) 79 | { 80 | new CenterWinDialog(this); 81 | MessageBox.Show("keys.txt failed to load.\nPlease include keys.txt in the root folder."); 82 | Environment.Exit(0); 83 | } 84 | } 85 | 86 | if (!File.Exists(Path.Join("tools", "hactool.exe"))) 87 | { 88 | Directory.CreateDirectory("tools"); 89 | new CenterWinDialog(this); 90 | MessageBox.Show("hactool.exe is missing.\nPlease include hactool.exe in the 'tools' folder."); 91 | Environment.Exit(0); 92 | } 93 | 94 | getKey(); 95 | 96 | //MAC - Set the double clicked file name into the UI and process file 97 | String[] args = Environment.GetCommandLineArgs(); 98 | if (args.Length > 1) 99 | { 100 | TB_File.Text = args[1]; 101 | Application.DoEvents(); 102 | ProcessFile(); 103 | } 104 | 105 | } 106 | 107 | private string getAssemblyVersion() 108 | { 109 | string assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); 110 | string[] versionArray = assemblyVersion.Split('.'); 111 | 112 | assemblyVersion = string.Join(".", versionArray.Take(3)); 113 | 114 | return assemblyVersion; 115 | } 116 | 117 | private void getKey() 118 | { 119 | string text = (from x in File.ReadAllLines("keys.txt") 120 | select x.Split('=') into x 121 | where x.Length > 1 122 | select x).ToDictionary((string[] x) => x[0].Trim(), (string[] x) => x[1])["header_key"].Replace(" ", ""); 123 | NcaHeaderEncryptionKey1_Prod = Util.StringToByteArray(text.Remove(32, 32)); 124 | NcaHeaderEncryptionKey2_Prod = Util.StringToByteArray(text.Remove(0, 32)); 125 | } 126 | 127 | public bool getMKey() 128 | { 129 | Dictionary dictionary = (from x in File.ReadAllLines("keys.txt") 130 | select x.Split('=') into x 131 | where x.Length > 1 132 | select x).ToDictionary((string[] x) => x[0].Trim(), (string[] x) => x[1]); 133 | Mkey = "master_key_"; 134 | string MkeyL = "master_key_"; 135 | if (NCA.NCA_Headers[0].MasterKeyRev == 0 || NCA.NCA_Headers[0].MasterKeyRev == 1) 136 | { 137 | Mkey += "00"; 138 | } 139 | else if (NCA.NCA_Headers[0].MasterKeyRev < 17) 140 | { 141 | int num = NCA.NCA_Headers[0].MasterKeyRev - 1; 142 | string capchar = num.ToString("X"); 143 | string lowchar = capchar.ToLower(); 144 | Mkey += $"0{capchar}"; 145 | MkeyL += $"0{lowchar}"; 146 | } 147 | else if (NCA.NCA_Headers[0].MasterKeyRev >= 17) 148 | { 149 | int num2 = NCA.NCA_Headers[0].MasterKeyRev - 1; 150 | string capchar = num2.ToString("X"); 151 | string lowchar = capchar.ToLower(); 152 | Mkey += num2.ToString(); 153 | MkeyL += num2.ToString(); 154 | } 155 | try 156 | { 157 | Mkey = dictionary[Mkey].Replace(" ", ""); 158 | return true; 159 | } 160 | catch 161 | { 162 | try 163 | { 164 | MkeyL = dictionary[MkeyL].Replace(" ", ""); 165 | return true; 166 | } 167 | catch 168 | { 169 | return false; 170 | } 171 | } 172 | } 173 | 174 | private void ProcessFile() 175 | { 176 | // Code needs refactoring 177 | LB_SelectedData.Text = ""; 178 | LB_DataOffset.Text = ""; 179 | LB_DataSize.Text = ""; 180 | LB_HashedRegionSize.Text = ""; 181 | LB_ExpectedHash.Text = ""; 182 | LB_ActualHash.Text = ""; 183 | B_Extract.Enabled = false; 184 | 185 | try 186 | { 187 | if (CheckNSP()) 188 | { 189 | B_TrimXCI.Enabled = false; 190 | B_ExportCert.Enabled = false; 191 | B_ImportCert.Enabled = false; 192 | B_ViewCert.Enabled = false; 193 | B_ClearCert.Enabled = false; 194 | 195 | LoadNSP(); 196 | } 197 | else if (CheckXCI()) 198 | { 199 | B_TrimXCI.Enabled = true; 200 | B_ExportCert.Enabled = true; 201 | B_ImportCert.Enabled = true; 202 | B_ViewCert.Enabled = true; 203 | B_ClearCert.Enabled = true; 204 | 205 | LoadXCI(); 206 | } 207 | else 208 | { 209 | TB_File.Text = null; 210 | new CenterWinDialog(this); 211 | MessageBox.Show("File is corrupt or unsupported."); 212 | } 213 | } 214 | catch (Exception e) 215 | { 216 | new CenterWinDialog(this); 217 | MessageBox.Show($"File is corrupt or unsupported.\nException: {e.Message}"); 218 | } 219 | 220 | } 221 | 222 | private void B_LoadROM_Click(object sender, EventArgs e) 223 | { 224 | OpenFileDialog openFileDialog = new() 225 | { 226 | Filter = "Switch Game File (*.xci, *.nsp, *.nsz)|*.xci;*.nsp;*.nsz|All Files (*.*)|*.*" 227 | }; 228 | new CenterWinDialog(this); 229 | if (openFileDialog.ShowDialog() == DialogResult.OK) 230 | { 231 | TB_File.Text = openFileDialog.FileName; 232 | ProcessFile(); 233 | } 234 | } 235 | 236 | private void LoadXCI() 237 | { 238 | string[] array = new string[5] 239 | { 240 | "B", 241 | "KB", 242 | "MB", 243 | "GB", 244 | "TB" 245 | }; 246 | double num = new FileInfo(TB_File.Text).Length; 247 | TB_ROMExactSize.Text = $"({num} bytes)"; 248 | int num2 = 0; 249 | while (num >= 1024.0 && num2 < array.Length - 1) 250 | { 251 | num2++; 252 | num /= 1024.0; 253 | } 254 | TB_ROMSize.Text = $"{num:0.##} {array[num2]}"; 255 | double num3 = UsedSize = XCI.XCI_Headers[0].CardSize2 * 512 + 512; 256 | TB_ExactUsedSpace.Text = $"({num3} bytes)"; 257 | if (isTrimmed()) 258 | { 259 | B_TrimXCI.Enabled = false; 260 | } 261 | 262 | num2 = 0; 263 | while (num3 >= 1024.0 && num2 < array.Length - 1) 264 | { 265 | num2++; 266 | num3 /= 1024.0; 267 | } 268 | TB_UsedSpace.Text = $"{num3:0.##} {array[num2]}"; 269 | TB_Capacity.Text = Util.GetCapacity(XCI.XCI_Headers[0].CardSize1); 270 | LoadPartitions(); 271 | LoadNCAData(); 272 | LoadGameInfos(); 273 | } 274 | 275 | // Giba's better implementation (more native) 276 | public void LoadNSP() 277 | { 278 | CB_RegionName.Items.Clear(); 279 | CB_RegionName.Enabled = true; 280 | TB_TID.Text = ""; 281 | TB_Capacity.Text = ""; 282 | TB_MKeyRev.Text = ""; 283 | TB_SDKVer.Text = ""; 284 | TB_GameRev.Text = ""; 285 | TB_ProdCode.Text = ""; 286 | TB_Name.Text = ""; 287 | TB_Dev.Text = ""; 288 | 289 | int basenum = 0; 290 | int updnum = 0; 291 | int dlcnum = 0; 292 | string pversion = ""; 293 | int patchflag = 0; 294 | int patchnum = 0; 295 | string patchver = ""; 296 | int baseflag = 0; 297 | string[] basetitle = new string[5]; 298 | string[] updtitle = new string[10]; 299 | string[] dlctitle = new string[300]; 300 | 301 | PB_GameIcon.BackgroundImage = null; 302 | Array.Clear(Icons, 0, Icons.Length); 303 | TV_Partitions.Nodes.Clear(); 304 | FileInfo fi = new(TB_File.Text); 305 | string contentType = ""; 306 | 307 | // Maximum number of files in NSP to read in 308 | const int MAXFILES = 250; 309 | 310 | //Get File Size 311 | string[] array_fs = new string[5] { "B", "KB", "MB", "GB", "TB" }; 312 | double num_fs = fi.Length; 313 | int num2_fs = 0; 314 | TB_ROMExactSize.Text = $"({num_fs} bytes)"; 315 | TB_ExactUsedSpace.Text = TB_ROMExactSize.Text; 316 | 317 | while (num_fs >= 1024.0 && num2_fs < array_fs.Length - 1) 318 | { 319 | num2_fs++; 320 | num_fs /= 1024.0; 321 | } 322 | TB_ROMSize.Text = $"{num_fs:0.##} {array_fs[num2_fs]}"; 323 | TB_UsedSpace.Text = TB_ROMSize.Text; 324 | 325 | LoadNSPPartitions(); 326 | 327 | Process process = new(); 328 | try 329 | { 330 | FileStream fileStream = File.OpenRead(TB_File.Text); 331 | string ncaTarget = ""; 332 | string xmlVersion = ""; 333 | 334 | List chars = new(); 335 | byte[] array = new byte[16]; 336 | byte[] array2 = new byte[24]; 337 | fileStream.Read(array, 0, 16); 338 | PFS0.PFS0_Headers[0] = new(array); 339 | if (!PFS0.PFS0_Headers[0].Magic.Contains("PFS0")) 340 | { 341 | return; 342 | } 343 | PFS0.PFS0_Entry[] array3; 344 | array3 = new PFS0.PFS0_Entry[Math.Max(PFS0.PFS0_Headers[0].FileCount, MAXFILES)]; //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files 345 | 346 | for (int m = 0; m < PFS0.PFS0_Headers[0].FileCount; m++) 347 | { 348 | fileStream.Position = 16 + 24 * m; 349 | fileStream.Read(array2, 0, 24); 350 | array3[m] = new(array2); 351 | 352 | if (m == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files 353 | { 354 | break; 355 | } 356 | } 357 | for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++) 358 | { 359 | fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + array3[n].Name_ptr; 360 | int num4; 361 | while ((num4 = fileStream.ReadByte()) != 0 && num4 != 0) 362 | { 363 | chars.Add((char)num4); 364 | } 365 | array3[n].Name = new(chars.ToArray()); 366 | chars.Clear(); 367 | 368 | if (array3[n].Name.EndsWith(".cnmt.xml")) 369 | { 370 | byte[] array4 = new byte[array3[n].Size]; 371 | fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset; 372 | fileStream.Read(array4, 0, (int)array3[n].Size); 373 | 374 | XDocument xml = XDocument.Parse(Encoding.UTF8.GetString(array4)); 375 | TB_TID.Text = xml.Element("ContentMeta").Element("Id").Value.Remove(1, 2).ToUpper(); //id 376 | pversion = xml.Element("ContentMeta").Element("Version").Value; //version 377 | contentType = xml.Element("ContentMeta").Element("Type").Value; //content 378 | var test = NACP.NACP_Datas[0].GameVer.Replace("\0", ""); 379 | 380 | if (contentType == "Patch") 381 | { 382 | patchflag = 1; 383 | if (Convert.ToInt32(pversion) > patchnum) 384 | { 385 | patchnum = Convert.ToInt32(pversion); 386 | xmlVersion = $"v{xml.Element("ContentMeta").Element("Version").Value}"; 387 | int number = Convert.ToInt32(pversion); 388 | patchver = $"v{Convert.ToString((double)number / 65536)}"; 389 | } 390 | 391 | updtitle[updnum] = $"[{TB_TID.Text}][v{pversion}]"; 392 | updnum++; 393 | } 394 | else if (contentType == "Application") 395 | { 396 | baseflag = 1; 397 | if (patchflag != 1) 398 | { 399 | xmlVersion = $"v{xml.Element("ContentMeta").Element("Version").Value}"; 400 | } 401 | 402 | basetitle[basenum] = $"[{TB_TID.Text}][v{pversion}]"; 403 | basenum++; 404 | } 405 | else 406 | { 407 | if (baseflag == 0 && patchflag == 0) 408 | { 409 | xmlVersion = $"v{xml.Element("ContentMeta").Element("Version").Value}"; 410 | } 411 | 412 | dlctitle[dlcnum] = $"[{TB_TID.Text}][v{pversion}]"; 413 | dlcnum++; 414 | } 415 | 416 | if (contentType != "AddOnContent") 417 | { 418 | foreach (XElement xe in xml.Descendants("Content")) 419 | { 420 | if (xe.Element("Type").Value != "Control") 421 | { 422 | continue; 423 | } 424 | 425 | ncaTarget = $"{xe.Element("Id").Value}.nca"; 426 | break; 427 | } 428 | } 429 | else //This is a DLC 430 | { 431 | foreach (XElement xe in xml.Descendants("Content")) 432 | { 433 | if (xe.Element("Type").Value != "Meta") 434 | { 435 | continue; 436 | } 437 | 438 | ncaTarget = $"{xe.Element("Id").Value}.cnmt.nca"; 439 | break; 440 | } 441 | } 442 | } 443 | 444 | if (n == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files 445 | { 446 | break; 447 | } 448 | } 449 | 450 | if (string.IsNullOrEmpty(ncaTarget)) 451 | { 452 | //Missing content metadata xml. Read from content metadata nca instead 453 | for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++) 454 | { 455 | if (array3[n].Name.EndsWith(".cnmt.nca")) 456 | { 457 | try 458 | { 459 | File.Delete("meta"); 460 | Directory.Delete("data", true); 461 | } 462 | catch { } 463 | 464 | using (FileStream fileStream2 = File.OpenWrite("meta")) 465 | { 466 | fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset; 467 | byte[] buffer = new byte[8192]; 468 | long num = array3[n].Size; 469 | int num4; 470 | while ((num4 = fileStream.Read(buffer, 0, 8192)) > 0 && num > 0) 471 | { 472 | fileStream2.Write(buffer, 0, num4); 473 | num -= num4; 474 | } 475 | fileStream2.Close(); 476 | } 477 | 478 | process = new() 479 | { 480 | StartInfo = new ProcessStartInfo 481 | { 482 | WindowStyle = ProcessWindowStyle.Hidden, 483 | FileName = Path.Join("tools", "hactool.exe"), 484 | Arguments = "-k keys.txt --section0dir=data meta", 485 | UseShellExecute = false, 486 | RedirectStandardOutput = true, 487 | CreateNoWindow = true 488 | } 489 | }; 490 | process.Start(); 491 | 492 | string masterkey = ""; 493 | while (!process.StandardOutput.EndOfStream) 494 | { 495 | string output = process.StandardOutput.ReadLine(); 496 | if (output.StartsWith("Master Key Revision")) 497 | { 498 | masterkey = Regex.Replace(output, @"\s+", " "); 499 | } 500 | } 501 | process.WaitForExit(); 502 | 503 | if (!Directory.Exists("data")) 504 | { 505 | new CenterWinDialog(this); 506 | MessageBox.Show($"{masterkey} is missing!"); 507 | } 508 | else 509 | { 510 | try 511 | { 512 | string[] cnmt = Directory.GetFiles("data", "*.cnmt"); 513 | if (cnmt.Length == 0) 514 | { 515 | return; 516 | } 517 | using FileStream fileStream3 = File.OpenRead(cnmt[0]); 518 | byte[] buffer = new byte[32]; 519 | byte[] buffer2 = new byte[56]; 520 | CNMT.CNMT_Header[] array7 = new CNMT.CNMT_Header[1]; 521 | 522 | fileStream3.Read(buffer, 0, 32); 523 | array7[0] = new CNMT.CNMT_Header(buffer); 524 | 525 | byte[] TitleID = BitConverter.GetBytes(array7[0].TitleID); 526 | Array.Reverse(TitleID); 527 | TB_TID.Text = BitConverter.ToString(TitleID).Replace("-", ""); 528 | 529 | if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.REGULAR_APPLICATION) 530 | { 531 | contentType = "Application"; 532 | baseflag = 1; 533 | if (patchflag != 1) 534 | { 535 | xmlVersion = $"v{array7[0].TitleVersion}"; 536 | } 537 | 538 | basetitle[basenum] = $"[{TB_TID.Text}][v{array7[0].TitleVersion}]"; 539 | basenum++; 540 | } 541 | else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.UPDATE_TITLE) 542 | { 543 | contentType = "Patch"; 544 | 545 | patchflag = 1; 546 | if (array7[0].TitleVersion > patchnum) 547 | { 548 | patchnum = array7[0].TitleVersion; 549 | xmlVersion = $"v{array7[0].TitleVersion}"; 550 | int number = Convert.ToInt32(array7[0].TitleVersion); 551 | patchver = $"v{Convert.ToString((double)number / 65536)}"; 552 | } 553 | 554 | updtitle[updnum] = $"[{TB_TID.Text}][v{array7[0].TitleVersion}]"; 555 | updnum++; 556 | } 557 | else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.ADD_ON_CONTENT) 558 | { 559 | if (baseflag == 0 && patchflag == 0) 560 | { 561 | xmlVersion = $"v{array7[0].TitleVersion}"; 562 | } 563 | 564 | contentType = "AddOnContent"; 565 | dlctitle[dlcnum] = $"[{TB_TID.Text}][v{array7[0].TitleVersion}]"; 566 | dlcnum++; 567 | } 568 | 569 | fileStream3.Position = array7[0].Offset + 32; 570 | CNMT.CNMT_Entry[] array9 = new CNMT.CNMT_Entry[array7[0].ContentCount]; 571 | for (int k = 0; k < array7[0].ContentCount; k++) 572 | { 573 | fileStream3.Read(buffer2, 0, 56); 574 | array9[k] = new CNMT.CNMT_Entry(buffer2); 575 | if (array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.CONTROL || array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.DATA) 576 | { 577 | ncaTarget = $"{BitConverter.ToString(array9[k].NcaId).ToLower().Replace("-", "")}.nca"; 578 | break; 579 | } 580 | } 581 | 582 | fileStream3.Close(); 583 | } 584 | catch 585 | { 586 | } 587 | } 588 | } 589 | } 590 | } 591 | 592 | for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++) 593 | { 594 | if (array3[n].Name.Equals(ncaTarget)) 595 | { 596 | Directory.CreateDirectory("tmp"); 597 | 598 | byte[] array5 = new byte[64 * 1024]; 599 | fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset; 600 | 601 | using (Stream output = File.Create(Path.Join("tmp", ncaTarget))) 602 | { 603 | long Size = array3[n].Size; 604 | int result = 0; 605 | while ((result = fileStream.Read(array5, 0, (int)Math.Min(array5.Length, Size))) > 0) 606 | { 607 | output.Write(array5, 0, result); 608 | Size -= result; 609 | } 610 | } 611 | 612 | break; 613 | } 614 | 615 | if (n == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files 616 | { 617 | break; 618 | } 619 | } 620 | 621 | fileStream.Close(); 622 | 623 | if (contentType != "AddOnContent") 624 | { 625 | process = new Process 626 | { 627 | StartInfo = new ProcessStartInfo 628 | { 629 | WindowStyle = ProcessWindowStyle.Hidden, 630 | FileName = Path.Join("tools", "hactool.exe"), 631 | Arguments = $"-k keys.txt --romfsdir=tmp tmp/{ncaTarget}", 632 | UseShellExecute = false, 633 | CreateNoWindow = true 634 | } 635 | }; 636 | 637 | process.Start(); 638 | process.WaitForExit(); 639 | process.Close(); 640 | byte[] flux = new byte[200]; 641 | 642 | try 643 | { 644 | byte[] source = File.ReadAllBytes(Path.Join("tmp", "control.nacp")); 645 | NACP.NACP_Datas[0] = new NACP.NACP_Data(source.Skip(0x3000).Take(0x1000).ToArray()); 646 | 647 | for (int i = 0; i < NACP.NACP_Strings.Length; i++) 648 | { 649 | NACP.NACP_Strings[i] = new NACP.NACP_String(source.Skip(i * 0x300).Take(0x300).ToArray()); 650 | 651 | if (NACP.NACP_Strings[i].Check != 0) 652 | { 653 | CB_RegionName.Items.Add(Language[i]); 654 | string icon_filename = Path.Join("tmp", $"icon_{Language[i].Replace(" ", "")}.dat"); 655 | if (File.Exists(icon_filename)) 656 | { 657 | using Bitmap original = new(icon_filename); 658 | Icons[i] = new Bitmap(original); 659 | PB_GameIcon.BackgroundImage = Icons[i]; 660 | } 661 | } 662 | } 663 | 664 | string gameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", ""); 665 | if (xmlVersion.Trim() == "") 666 | { 667 | TB_GameRev.Text = $"GENERAL:{Environment.NewLine}({gameVer}){Environment.NewLine}"; 668 | } 669 | else 670 | { 671 | string cache = $"GENERAL:{Environment.NewLine}{gameVer}{((patchflag == 1) ? $" ({patchver})" : "")}{Environment.NewLine}"; 672 | 673 | if (basenum != 0) 674 | { 675 | cache += $"BASE:{Environment.NewLine}"; 676 | for (int i = 0; i < basenum; i++) 677 | { 678 | cache += basetitle[i] + Environment.NewLine; 679 | } 680 | } 681 | else 682 | { 683 | cache += $"BASE:{Environment.NewLine}EMPTY{Environment.NewLine}"; 684 | } 685 | if (updnum != 0) 686 | { 687 | cache += $"UPD:{Environment.NewLine}"; 688 | for (int i = 0; i < updnum; i++) 689 | { 690 | cache += updtitle[i] + Environment.NewLine; 691 | } 692 | } 693 | else 694 | { 695 | cache += $"UPD:{Environment.NewLine}EMPTY{Environment.NewLine}"; 696 | } 697 | if (dlcnum != 0) 698 | { 699 | cache += $"DLC:{Environment.NewLine}"; 700 | for (int i = 0; i < dlcnum; i++) 701 | { 702 | if (i < dlcnum - 1) 703 | { 704 | cache += dlctitle[i] + Environment.NewLine; 705 | } 706 | else 707 | { 708 | cache += dlctitle[i]; 709 | } 710 | } 711 | } 712 | else 713 | { 714 | cache += $"DLC:{Environment.NewLine}EMPTY"; 715 | } 716 | TB_GameRev.Text = cache; 717 | label12.Text = $"{basenum} BASE, {updnum} UPD, {dlcnum} DLC"; 718 | } 719 | 720 | TB_ProdCode.Text = NACP.NACP_Datas[0].GameProd.Replace("\0", ""); 721 | if (TB_ProdCode.Text == "") 722 | { 723 | TB_ProdCode.Text = "No Prod. ID"; 724 | } 725 | 726 | for (int z = 0; z < NACP.NACP_Strings.Length; z++) 727 | { 728 | if (NACP.NACP_Strings[z].GameName.Replace("\0", "") != "") 729 | { 730 | TB_Name.Text = NACP.NACP_Strings[z].GameName.Replace("\0", ""); 731 | break; 732 | } 733 | } 734 | for (int z = 0; z < NACP.NACP_Strings.Length; z++) 735 | { 736 | if (NACP.NACP_Strings[z].GameAuthor.Replace("\0", "") != "") 737 | { 738 | TB_Dev.Text = NACP.NACP_Strings[z].GameAuthor.Replace("\0", ""); 739 | break; 740 | } 741 | } 742 | } 743 | catch { } 744 | 745 | } 746 | else 747 | { 748 | if (xmlVersion.Trim() == "") 749 | { 750 | TB_GameRev.Text = $"GENERAL:{Environment.NewLine}{Environment.NewLine}"; 751 | } 752 | else 753 | { 754 | string gameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", ""); 755 | string cache = $"GENERAL:{Environment.NewLine}{gameVer}{((patchflag == 1) ? $" ({patchver})" : "")}{Environment.NewLine}"; 756 | 757 | if (basenum != 0) 758 | { 759 | cache += $"BASE:{Environment.NewLine}"; 760 | for (int i = 0; i < basenum; i++) 761 | { 762 | cache += basetitle[i] + Environment.NewLine; 763 | } 764 | } 765 | else 766 | { 767 | cache += $"BASE:{Environment.NewLine} EMPTY {Environment.NewLine}"; 768 | } 769 | if (updnum != 0) 770 | { 771 | cache += $"UPD:{Environment.NewLine}"; 772 | for (int i = 0; i < updnum; i++) 773 | { 774 | cache += updtitle[i] + Environment.NewLine; 775 | } 776 | } 777 | else 778 | { 779 | cache += $"UPD:{Environment.NewLine} EMPTY {Environment.NewLine}"; 780 | } 781 | if (dlcnum != 0) 782 | { 783 | cache += $"DLC:{Environment.NewLine}"; 784 | for (int i = 0; i < dlcnum; i++) 785 | { 786 | if (i < dlcnum - 1) 787 | { 788 | cache += dlctitle[i] + Environment.NewLine; 789 | } 790 | else 791 | { 792 | cache += dlctitle[i]; 793 | } 794 | } 795 | } 796 | else 797 | { 798 | cache += $"DLC:{Environment.NewLine}EMPTY"; 799 | } 800 | TB_GameRev.Text = cache; 801 | label12.Text = $"{basenum} BASE, {updnum} UPD, {dlcnum} DLC"; 802 | } 803 | TB_ProdCode.Text = "No Prod. ID"; 804 | } 805 | 806 | // Lets get SDK Version, Distribution Type and Masterkey revision 807 | // This is far from the best aproach, but it's what we have for now 808 | process = new Process 809 | { 810 | StartInfo = new ProcessStartInfo 811 | { 812 | WindowStyle = ProcessWindowStyle.Hidden, 813 | FileName = Path.Join("tools", "hactool.exe"), 814 | Arguments = $"-k keys.txt tmp/{ncaTarget}", 815 | RedirectStandardOutput = true, 816 | UseShellExecute = false, 817 | CreateNoWindow = true 818 | } 819 | }; 820 | process.Start(); 821 | StreamReader sr = process.StandardOutput; 822 | 823 | while (sr.Peek() >= 0) 824 | { 825 | string str; 826 | string[] strArray; 827 | str = sr.ReadLine(); 828 | strArray = str.Split(':'); 829 | if (strArray[0] == "SDK Version") 830 | { 831 | TB_SDKVer.Text = strArray[1].Trim(); 832 | } 833 | else if (strArray[0] == "Master Key Revision") 834 | { 835 | string MasterKey = strArray[1].Trim(); 836 | int keyblob; 837 | 838 | MasterKey = MasterKey.Split(new char[2] { 'x', ' ' })[1]; 839 | keyblob = Convert.ToInt32(MasterKey, 16); 840 | MasterKey = Util.GetMkey((byte)(keyblob + 1)); 841 | TB_MKeyRev.Text = MasterKey; 842 | break; 843 | } 844 | } 845 | process.WaitForExit(); 846 | process.Close(); 847 | } 848 | catch { } 849 | 850 | try 851 | { 852 | File.Delete("meta"); 853 | Directory.Delete("data", true); 854 | } 855 | catch 856 | { 857 | } 858 | 859 | try 860 | { 861 | Directory.Delete("tmp", true); 862 | } 863 | catch 864 | { 865 | } 866 | 867 | TB_Capacity.Text = "eShop"; 868 | 869 | if (TB_Name.Text.Trim() != "") 870 | { 871 | CB_RegionName.SelectedIndex = 0; 872 | } 873 | } 874 | 875 | private void LoadGameInfos() 876 | { 877 | CB_RegionName.Items.Clear(); 878 | CB_RegionName.Enabled = true; 879 | TB_Name.Text = ""; 880 | TB_Dev.Text = ""; 881 | PB_GameIcon.BackgroundImage = null; 882 | 883 | int basenum = 0; 884 | int updnum = 0; 885 | int dlcnum = 0; 886 | int patchflag = 0; 887 | int patchnum = 0; 888 | string patchver = ""; 889 | int baseflag = 0; 890 | string[] basetitle = new string[5]; 891 | string[] updtitle = new string[10]; 892 | string[] dlctitle = new string[300]; 893 | string xmlVersion = ""; 894 | string saveTID = ""; 895 | 896 | Array.Clear(Icons, 0, Icons.Length); 897 | if (getMKey()) 898 | { 899 | using FileStream fileStream = File.OpenRead(TB_File.Text); 900 | List ncaTarget = new(); 901 | string GameRevision = ""; 902 | 903 | for (int si = 0; si < SecureSize.Length; si++) 904 | { 905 | if (SecureSize[si] > 0x4E20000) 906 | { 907 | continue; 908 | } 909 | 910 | if (!SecureName[si].EndsWith(".cnmt.nca")) 911 | { 912 | continue; 913 | } 914 | 915 | try 916 | { 917 | File.Delete("meta"); 918 | Directory.Delete("data", true); 919 | } 920 | catch 921 | { 922 | } 923 | 924 | using (FileStream fileStream2 = File.OpenWrite("meta")) 925 | { 926 | fileStream.Position = SecureOffset[si]; 927 | byte[] fsBuffer = new byte[8192]; 928 | long num = SecureSize[si]; 929 | int num4; 930 | while ((num4 = fileStream.Read(fsBuffer, 0, 8192)) > 0 && num > 0) 931 | { 932 | fileStream2.Write(fsBuffer, 0, num4); 933 | num -= num4; 934 | } 935 | fileStream2.Close(); 936 | } 937 | 938 | Process process1 = new Process 939 | { 940 | StartInfo = new ProcessStartInfo 941 | { 942 | WindowStyle = ProcessWindowStyle.Hidden, 943 | FileName = Path.Join("tools", "hactool.exe"), 944 | Arguments = "-k keys.txt --section0dir=data meta", 945 | UseShellExecute = false, 946 | CreateNoWindow = true 947 | } 948 | }; 949 | process1.Start(); 950 | process1.WaitForExit(); 951 | 952 | string[] cnmt = Directory.GetFiles("data", "*.cnmt"); 953 | if (cnmt.Length == 0) 954 | { 955 | continue; 956 | } 957 | 958 | using FileStream fileStream3 = File.OpenRead(cnmt[0]); 959 | byte[] buffer = new byte[32]; 960 | byte[] buffer2 = new byte[56]; 961 | CNMT.CNMT_Header[] array7 = new CNMT.CNMT_Header[1]; 962 | 963 | fileStream3.Read(buffer, 0, 32); 964 | array7[0] = new CNMT.CNMT_Header(buffer); 965 | 966 | byte[] TitleID = BitConverter.GetBytes(array7[0].TitleID); 967 | Array.Reverse(TitleID); 968 | saveTID = BitConverter.ToString(TitleID).Replace("-", ""); 969 | 970 | if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.REGULAR_APPLICATION) 971 | { 972 | baseflag = 1; 973 | if (patchflag != 1) 974 | { 975 | xmlVersion = $"v{array7[0].TitleVersion}"; 976 | } 977 | 978 | basetitle[basenum] = $"[{saveTID}][v{array7[0].TitleVersion}]"; 979 | basenum++; 980 | } 981 | else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.UPDATE_TITLE) 982 | { 983 | patchflag = 1; 984 | if (array7[0].TitleVersion > patchnum) 985 | { 986 | patchnum = array7[0].TitleVersion; 987 | xmlVersion = $"v{array7[0].TitleVersion}"; 988 | int number = Convert.ToInt32(array7[0].TitleVersion); 989 | patchver = $"v{Convert.ToString((double)number / 65536)}"; 990 | } 991 | 992 | updtitle[updnum] = $"[{saveTID}][v{array7[0].TitleVersion}]"; 993 | updnum++; 994 | } 995 | else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.ADD_ON_CONTENT) 996 | { 997 | if (patchflag == 0 && baseflag == 0) 998 | { 999 | xmlVersion = $"v{array7[0].TitleVersion}"; 1000 | } 1001 | 1002 | dlctitle[dlcnum] = $"[{saveTID}][v{array7[0].TitleVersion}]"; 1003 | dlcnum++; 1004 | } 1005 | 1006 | fileStream3.Position = array7[0].Offset + 32; 1007 | CNMT.CNMT_Entry[] array9 = new CNMT.CNMT_Entry[array7[0].ContentCount]; 1008 | for (int k = 0; k < array7[0].ContentCount; k++) 1009 | { 1010 | fileStream3.Read(buffer2, 0, 56); 1011 | array9[k] = new CNMT.CNMT_Entry(buffer2); 1012 | if (array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.CONTROL || array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.DATA) 1013 | { 1014 | ncaTarget.Add($"{BitConverter.ToString(array9[k].NcaId).ToLower().Replace("-", "")}.nca"); 1015 | break; 1016 | } 1017 | } 1018 | fileStream3.Close(); 1019 | 1020 | } 1021 | 1022 | for (int si = 0; si < SecureSize.Length; si++) 1023 | { 1024 | if (SecureSize[si] > 0x4E20000) 1025 | { 1026 | continue; 1027 | } 1028 | 1029 | if (!ncaTarget.Contains(SecureName[si])) 1030 | { 1031 | continue; 1032 | } 1033 | 1034 | try 1035 | { 1036 | File.Delete("meta"); 1037 | Directory.Delete("data", true); 1038 | } 1039 | catch 1040 | { 1041 | } 1042 | 1043 | using (FileStream fileStream2 = File.OpenWrite("meta")) 1044 | { 1045 | fileStream.Position = SecureOffset[si]; 1046 | byte[] buffer = new byte[8192]; 1047 | long num = SecureSize[si]; 1048 | int num2; 1049 | while ((num2 = fileStream.Read(buffer, 0, 8192)) > 0 && num > 0) 1050 | { 1051 | fileStream2.Write(buffer, 0, num2); 1052 | num -= num2; 1053 | } 1054 | fileStream2.Close(); 1055 | } 1056 | 1057 | 1058 | Process process = new(); 1059 | process.StartInfo = new ProcessStartInfo 1060 | { 1061 | WindowStyle = ProcessWindowStyle.Hidden, 1062 | FileName = Path.Join("tools", "hactool.exe"), 1063 | Arguments = "-k keys.txt --romfsdir=data meta", 1064 | UseShellExecute = false, 1065 | CreateNoWindow = true 1066 | }; 1067 | process.Start(); 1068 | process.WaitForExit(); 1069 | 1070 | if (!File.Exists(Path.Join("data", "control.nacp"))) 1071 | { 1072 | continue; 1073 | } 1074 | 1075 | byte[] source = File.ReadAllBytes(Path.Join("data", "control.nacp")); 1076 | NACP.NACP_Datas[0] = new NACP.NACP_Data(source.Skip(0x3000).Take(0x1000).ToArray()); 1077 | 1078 | string GameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", ""); 1079 | Version version1, version2; 1080 | if (!Version.TryParse(Regex.Replace(GameRevision, @"[^\d.].*$", ""), out version1)) 1081 | { 1082 | version1 = new Version(); 1083 | } 1084 | if (!Version.TryParse(Regex.Replace(GameVer, @"[^\d.].*$", ""), out version2)) 1085 | { 1086 | version2 = new Version(); 1087 | } 1088 | if (version2.CompareTo(version1) > 0) 1089 | { 1090 | GameRevision = GameVer; 1091 | 1092 | for (int i = 0; i < NACP.NACP_Strings.Length; i++) 1093 | { 1094 | NACP.NACP_Strings[i] = new NACP.NACP_String(source.Skip(i * 0x300).Take(0x300).ToArray()); 1095 | if (NACP.NACP_Strings[i].Check == 0 || CB_RegionName.Items.Contains(Language[i])) 1096 | { 1097 | continue; 1098 | } 1099 | 1100 | CB_RegionName.Items.Add(Language[i]); 1101 | string icon_filename = Path.Join("data", $"icon_{Language[i].Replace(" ", "")}.dat"); 1102 | if (File.Exists(icon_filename)) 1103 | { 1104 | using Bitmap original = new(icon_filename); 1105 | Icons[i] = new Bitmap(original); 1106 | PB_GameIcon.BackgroundImage = Icons[i]; 1107 | } 1108 | } 1109 | TB_ProdCode.Text = NACP.NACP_Datas[0].GameProd; 1110 | if (TB_ProdCode.Text == "") 1111 | { 1112 | TB_ProdCode.Text = "No Prod. ID"; 1113 | } 1114 | try 1115 | { 1116 | File.Delete("meta"); 1117 | Directory.Delete("data", true); 1118 | } 1119 | catch 1120 | { 1121 | } 1122 | } 1123 | } 1124 | 1125 | string gameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", ""); 1126 | string cache = $"GENERAL:{Environment.NewLine}{gameVer}{((patchflag == 1) ? $" ({patchver})" : "")}{Environment.NewLine}"; 1127 | 1128 | if (basenum != 0) 1129 | { 1130 | cache += $"BASE:{Environment.NewLine}"; 1131 | for (int i = 0; i < basenum; i++) 1132 | { 1133 | cache += basetitle[i] + System.Environment.NewLine; 1134 | } 1135 | } 1136 | else 1137 | { 1138 | cache += $"BASE:{Environment.NewLine}EMPTY{Environment.NewLine}"; 1139 | } 1140 | if (updnum != 0) 1141 | { 1142 | cache += $"UPD:{Environment.NewLine}"; 1143 | for (int i = 0; i < updnum; i++) 1144 | { 1145 | cache += updtitle[i] + Environment.NewLine; 1146 | } 1147 | } 1148 | else 1149 | { 1150 | cache += $"UPD:{Environment.NewLine}EMPTY{Environment.NewLine}"; 1151 | } 1152 | if (dlcnum != 0) 1153 | { 1154 | cache += $"DLC:{Environment.NewLine}"; 1155 | for (int i = 0; i < dlcnum; i++) 1156 | { 1157 | if (i < dlcnum - 1) 1158 | { 1159 | cache += dlctitle[i] + Environment.NewLine; 1160 | } 1161 | else 1162 | { 1163 | cache += dlctitle[i]; 1164 | } 1165 | } 1166 | } 1167 | else 1168 | { 1169 | cache += $"DLC:{Environment.NewLine}EMPTY"; 1170 | } 1171 | TB_GameRev.Text = cache; 1172 | label12.Text = $"{basenum} BASE, {updnum} UPD, {dlcnum} DLC"; 1173 | 1174 | CB_RegionName.SelectedIndex = 0; 1175 | 1176 | fileStream.Close(); 1177 | } 1178 | else 1179 | { 1180 | TB_Dev.Text = $"{Mkey} not found"; 1181 | TB_Name.Text = $"{Mkey} not found"; 1182 | } 1183 | } 1184 | 1185 | private void LoadNCAData() 1186 | { 1187 | NCA.NCA_Headers[0] = new NCA.NCA_Header(DecryptNCAHeader(gameNcaOffset)); 1188 | TB_TID.Text = $"0{NCA.NCA_Headers[0].TitleID.ToString("X")}"; 1189 | TB_SDKVer.Text = $"{NCA.NCA_Headers[0].SDKVersion4}.{NCA.NCA_Headers[0].SDKVersion3}.{NCA.NCA_Headers[0].SDKVersion2}.{NCA.NCA_Headers[0].SDKVersion1}"; 1190 | TB_MKeyRev.Text = Util.GetMkey(NCA.NCA_Headers[0].MasterKeyRev); 1191 | } 1192 | 1193 | //https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa 1194 | public static string ByteArrayToString(byte[] ba) 1195 | { 1196 | StringBuilder hex = new(ba.Length * 2 + 2); 1197 | hex.Append("0x"); 1198 | foreach (byte b in ba) 1199 | { 1200 | hex.AppendFormat("{0:x2}", b); 1201 | } 1202 | 1203 | return hex.ToString(); 1204 | } 1205 | 1206 | public static string SHA256Bytes(byte[] ba) 1207 | { 1208 | SHA256 mySHA256 = SHA256.Create(); 1209 | byte[] hashValue; 1210 | hashValue = mySHA256.ComputeHash(ba); 1211 | return ByteArrayToString(hashValue); 1212 | } 1213 | 1214 | public bool isTrimmed() => TB_ROMExactSize.Text == TB_ExactUsedSpace.Text; 1215 | 1216 | private void LoadPartitions() 1217 | { 1218 | string actualHash; 1219 | byte[] hashBuffer; 1220 | long offset; 1221 | 1222 | TV_Partitions.Nodes.Clear(); 1223 | TV_Parti = new TreeViewFileSystem(TV_Partitions); 1224 | rootNode = new BetterTreeNode("root") 1225 | { 1226 | Offset = -1L, 1227 | Size = -1L 1228 | }; 1229 | TV_Partitions.Nodes.Add(rootNode); 1230 | bool LogoPartition = false; 1231 | FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read); 1232 | HFS0.HSF0_Entry[] array = new HFS0.HSF0_Entry[HFS0.HFS0_Headers[0].FileCount]; 1233 | fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition + 16 + 64 * HFS0.HFS0_Headers[0].FileCount; 1234 | long num = XCI.XCI_Headers[0].HFS0OffsetPartition + XCI.XCI_Headers[0].HFS0SizeParition; 1235 | byte[] array2 = new byte[64]; 1236 | byte[] array3 = new byte[16]; 1237 | byte[] array4 = new byte[24]; 1238 | for (int i = 0; i < HFS0.HFS0_Headers[0].FileCount; i++) 1239 | { 1240 | fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition + 16 + 64 * i; 1241 | fileStream.Read(array2, 0, 64); 1242 | array[i] = new HFS0.HSF0_Entry(array2); 1243 | fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition + 16 + 64 * HFS0.HFS0_Headers[0].FileCount + array[i].Name_ptr; 1244 | int num2; 1245 | while ((num2 = fileStream.ReadByte()) != 0 && num2 != 0) 1246 | { 1247 | chars.Add((char)num2); 1248 | } 1249 | array[i].Name = new string(chars.ToArray()); 1250 | chars.Clear(); 1251 | offset = num + array[i].Offset; 1252 | hashBuffer = new byte[array[i].HashedRegionSize]; 1253 | fileStream.Position = offset; 1254 | fileStream.Read(hashBuffer, 0, array[i].HashedRegionSize); 1255 | actualHash = SHA256Bytes(hashBuffer); 1256 | 1257 | TV_Parti.AddFile($"{array[i].Name}.hfs0", rootNode, offset, array[i].Size, array[i].HashedRegionSize, ByteArrayToString(array[i].Hash), actualHash); 1258 | BetterTreeNode betterTreeNode = TV_Parti.AddDir(array[i].Name, rootNode); 1259 | HFS0.HFS0_Header[] array5 = new HFS0.HFS0_Header[1]; 1260 | fileStream.Position = array[i].Offset + num; 1261 | fileStream.Read(array3, 0, 16); 1262 | array5[0] = new HFS0.HFS0_Header(array3); 1263 | if (array[i].Name == "secure") 1264 | { 1265 | SecureSize = new long[array5[0].FileCount]; 1266 | SecureOffset = new long[array5[0].FileCount]; 1267 | SecureName = new string[array5[0].FileCount]; 1268 | } 1269 | if (array[i].Name == "normal") 1270 | { 1271 | NormalSize = new long[array5[0].FileCount]; 1272 | NormalOffset = new long[array5[0].FileCount]; 1273 | } 1274 | if (array[i].Name == "logo") 1275 | { 1276 | if (array5[0].FileCount > 0) 1277 | { 1278 | LogoPartition = true; 1279 | } 1280 | } 1281 | HFS0.HSF0_Entry[] array6 = new HFS0.HSF0_Entry[array5[0].FileCount]; 1282 | for (int j = 0; j < array5[0].FileCount; j++) 1283 | { 1284 | fileStream.Position = array[i].Offset + num + 16 + 64 * j; 1285 | fileStream.Read(array2, 0, 64); 1286 | array6[j] = new HFS0.HSF0_Entry(array2); 1287 | fileStream.Position = array[i].Offset + num + 16 + 64 * array5[0].FileCount + array6[j].Name_ptr; 1288 | while ((num2 = fileStream.ReadByte()) != 0 && num2 != 0) 1289 | { 1290 | chars.Add((char)num2); 1291 | } 1292 | array6[j].Name = new string(chars.ToArray()); 1293 | chars.Clear(); 1294 | if (array[i].Name == "secure") 1295 | { 1296 | SecureSize[j] = array6[j].Size; 1297 | SecureOffset[j] = array[i].Offset + array6[j].Offset + num + 16 + array5[0].StringTableSize + array5[0].FileCount * 64; 1298 | SecureName[j] = array6[j].Name; 1299 | } 1300 | if (array[i].Name == "normal") 1301 | { 1302 | NormalSize[j] = array6[j].Size; 1303 | NormalOffset[j] = array[i].Offset + array6[j].Offset + num + 16 + array5[0].StringTableSize + array5[0].FileCount * 64; 1304 | } 1305 | offset = array[i].Offset + array6[j].Offset + num + 16 + array5[0].StringTableSize + array5[0].FileCount * 64; 1306 | hashBuffer = new byte[array6[j].HashedRegionSize]; 1307 | fileStream.Position = offset; 1308 | fileStream.Read(hashBuffer, 0, array6[j].HashedRegionSize); 1309 | actualHash = SHA256Bytes(hashBuffer); 1310 | 1311 | TV_Parti.AddFile(array6[j].Name, betterTreeNode, offset, array6[j].Size, array6[j].HashedRegionSize, ByteArrayToString(array6[j].Hash), actualHash); 1312 | TreeNode[] array7 = TV_Partitions.Nodes.Find(betterTreeNode.Text, true); 1313 | if (array7.Length != 0) 1314 | { 1315 | TV_Parti.AddFile(array6[j].Name, (BetterTreeNode)array7[0], 0L, 0L); 1316 | } 1317 | } 1318 | } 1319 | long num3 = -9223372036854775808L; 1320 | for (int k = 0; k < SecureSize.Length; k++) 1321 | { 1322 | if (SecureSize[k] > num3) 1323 | { 1324 | gameNcaSize = SecureSize[k]; 1325 | gameNcaOffset = SecureOffset[k]; 1326 | num3 = SecureSize[k]; 1327 | } 1328 | } 1329 | PFS0Offset = gameNcaOffset + 32768; 1330 | fileStream.Position = PFS0Offset; 1331 | fileStream.Read(array3, 0, 16); 1332 | PFS0.PFS0_Headers[0] = new(array3); 1333 | if (PFS0.PFS0_Headers[0].FileCount == 2 || !LogoPartition) 1334 | { 1335 | PFS0.PFS0_Entry[] array8; 1336 | try 1337 | { 1338 | array8 = new PFS0.PFS0_Entry[PFS0.PFS0_Headers[0].FileCount]; 1339 | } 1340 | catch (Exception ex) 1341 | { 1342 | array8 = new PFS0.PFS0_Entry[0]; 1343 | Debug.WriteLine($"Partitions Error: {ex.Message}"); 1344 | } 1345 | for (int m = 0; m < PFS0.PFS0_Headers[0].FileCount; m++) 1346 | { 1347 | fileStream.Position = PFS0Offset + 16 + 24 * m; 1348 | fileStream.Read(array4, 0, 24); 1349 | array8[m] = new(array4); 1350 | PFS0Size += array8[m].Size; 1351 | } 1352 | TV_Parti.AddFile("boot.psf0", rootNode, PFS0Offset, 16 + 24 * PFS0.PFS0_Headers[0].FileCount + 64 + PFS0Size); 1353 | BetterTreeNode betterTreeNode2 = TV_Parti.AddDir("boot", rootNode); 1354 | for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++) 1355 | { 1356 | fileStream.Position = PFS0Offset + 16 + 24 * PFS0.PFS0_Headers[0].FileCount + array8[n].Name_ptr; 1357 | int num4; 1358 | while ((num4 = fileStream.ReadByte()) != 0 && num4 != 0) 1359 | { 1360 | chars.Add((char)num4); 1361 | } 1362 | array8[n].Name = new string(chars.ToArray()); 1363 | chars.Clear(); 1364 | TV_Parti.AddFile(array8[n].Name, betterTreeNode2, PFS0Offset + array8[n].Offset + 16 + PFS0.PFS0_Headers[0].StringTableSize + PFS0.PFS0_Headers[0].FileCount * 24, array8[n].Size); 1365 | TreeNode[] array9 = TV_Partitions.Nodes.Find(betterTreeNode2.Text, true); 1366 | if (array9.Length != 0) 1367 | { 1368 | TV_Parti.AddFile(array8[n].Name, (BetterTreeNode)array9[0], 0L, 0L); 1369 | } 1370 | } 1371 | } 1372 | fileStream.Close(); 1373 | } 1374 | 1375 | 1376 | private void LoadNSPPartitions() 1377 | { 1378 | long offset; 1379 | 1380 | TV_Partitions.Nodes.Clear(); 1381 | TV_Parti = new TreeViewFileSystem(TV_Partitions); 1382 | rootNode = new BetterTreeNode("root") 1383 | { 1384 | Offset = -1L, 1385 | Size = -1L 1386 | }; 1387 | TV_Partitions.Nodes.Add(rootNode); 1388 | 1389 | 1390 | // Maximum number of files in NSP to read in 1391 | const int MAXFILES = 250; 1392 | 1393 | FileStream fileStream = File.OpenRead(TB_File.Text); 1394 | List chars = new(); 1395 | byte[] array = new byte[16]; 1396 | byte[] array2 = new byte[24]; 1397 | fileStream.Read(array, 0, 16); 1398 | PFS0.PFS0_Headers[0] = new(array); 1399 | if (!PFS0.PFS0_Headers[0].Magic.Contains("PFS0")) 1400 | { 1401 | return; 1402 | } 1403 | PFS0.PFS0_Entry[] array3; 1404 | array3 = new PFS0.PFS0_Entry[Math.Max(PFS0.PFS0_Headers[0].FileCount, MAXFILES)]; //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files 1405 | for (int m = 0; m < PFS0.PFS0_Headers[0].FileCount; m++) 1406 | { 1407 | fileStream.Position = 16 + 24 * m; 1408 | fileStream.Read(array2, 0, 24); 1409 | array3[m] = new(array2); 1410 | 1411 | if (m == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files 1412 | { 1413 | break; 1414 | } 1415 | } 1416 | for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++) 1417 | { 1418 | fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + array3[n].Name_ptr; 1419 | int num4; 1420 | while ((num4 = fileStream.ReadByte()) != 0 && num4 != 0) 1421 | { 1422 | chars.Add((char)num4); 1423 | } 1424 | array3[n].Name = new(chars.ToArray()); 1425 | chars.Clear(); 1426 | offset = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset; 1427 | fileStream.Position = offset; 1428 | 1429 | TV_Parti.AddFile(array3[n].Name, rootNode, offset, array3[n].Size); 1430 | } 1431 | 1432 | fileStream.Close(); 1433 | } 1434 | 1435 | 1436 | private void TV_Partitions_AfterSelect(object sender, TreeViewEventArgs e) 1437 | { 1438 | BetterTreeNode betterTreeNode = (BetterTreeNode)TV_Partitions.SelectedNode; 1439 | if (betterTreeNode.Offset == -1) 1440 | { 1441 | LB_SelectedData.Text = ""; 1442 | LB_DataOffset.Text = ""; 1443 | LB_DataSize.Text = ""; 1444 | LB_HashedRegionSize.Text = ""; 1445 | LB_ExpectedHash.Text = ""; 1446 | LB_ActualHash.Text = ""; 1447 | B_Extract.Enabled = false; 1448 | return; 1449 | } 1450 | 1451 | selectedOffset = betterTreeNode.Offset; 1452 | selectedSize = betterTreeNode.Size; 1453 | string expectedHash = betterTreeNode.ExpectedHash; 1454 | string actualHash = betterTreeNode.ActualHash; 1455 | long HashedRegionSize = betterTreeNode.HashedRegionSize; 1456 | 1457 | LB_DataOffset.Text = $"Offset: 0x{selectedOffset:X}"; 1458 | LB_SelectedData.Text = e.Node.Text; 1459 | if (backgroundWorker1.IsBusy != true) 1460 | { 1461 | B_Extract.Enabled = true; 1462 | } 1463 | string[] array = new string[5] 1464 | { 1465 | "B", 1466 | "KB", 1467 | "MB", 1468 | "GB", 1469 | "TB" 1470 | }; 1471 | double num = selectedSize; 1472 | int num2 = 0; 1473 | while (num >= 1024.0 && num2 < array.Length - 1) 1474 | { 1475 | num2++; 1476 | num /= 1024.0; 1477 | } 1478 | LB_DataSize.Text = $"Size: 0x{selectedSize.ToString("X")} ({num}{array[num2]})"; 1479 | 1480 | if (HashedRegionSize != 0) 1481 | { 1482 | LB_HashedRegionSize.Text = $"HashedRegionSize: 0x{HashedRegionSize:X}"; 1483 | } 1484 | else 1485 | { 1486 | LB_HashedRegionSize.Text = ""; 1487 | } 1488 | 1489 | if (!string.IsNullOrEmpty(expectedHash)) 1490 | { 1491 | LB_ExpectedHash.Text = $"Header Hash: {expectedHash[..32]}"; 1492 | } 1493 | else 1494 | { 1495 | LB_ExpectedHash.Text = ""; 1496 | } 1497 | 1498 | if (!string.IsNullOrEmpty(actualHash)) 1499 | { 1500 | LB_ActualHash.Text = $"Actual Hash: {actualHash[..32]}"; 1501 | if (actualHash == expectedHash) 1502 | { 1503 | LB_ActualHash.ForeColor = Color.Green; 1504 | } 1505 | else 1506 | { 1507 | LB_ActualHash.ForeColor = Color.Red; 1508 | } 1509 | } 1510 | else 1511 | { 1512 | LB_ActualHash.Text = ""; 1513 | } 1514 | } 1515 | 1516 | public bool CheckXCI() 1517 | { 1518 | FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read); 1519 | byte[] array = new byte[61440]; 1520 | byte[] array2 = new byte[16]; 1521 | fileStream.Read(array, 0, 61440); 1522 | XCI.XCI_Headers[0] = new XCI.XCI_Header(array); 1523 | if (!XCI.XCI_Headers[0].Magic.Contains("HEAD")) 1524 | { 1525 | return false; 1526 | } 1527 | fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition; 1528 | fileStream.Read(array2, 0, 16); 1529 | HFS0.HFS0_Headers[0] = new HFS0.HFS0_Header(array2); 1530 | fileStream.Close(); 1531 | return true; 1532 | } 1533 | 1534 | public bool CheckNSP() 1535 | { 1536 | FileStream fileStream = File.OpenRead(TB_File.Text); 1537 | byte[] array = new byte[16]; 1538 | fileStream.Read(array, 0, 16); 1539 | PFS0.PFS0_Headers[0] = new(array); 1540 | fileStream.Close(); 1541 | if (!PFS0.PFS0_Headers[0].Magic.Contains("PFS0")) 1542 | { 1543 | return false; 1544 | } 1545 | return true; 1546 | } 1547 | 1548 | private void B_ExportCert_Click(object sender, EventArgs e) 1549 | { 1550 | new CenterWinDialog(this); 1551 | if (!File.Exists(TB_File.Text)) 1552 | { 1553 | MessageBox.Show("File not found"); 1554 | return; 1555 | } 1556 | 1557 | SaveFileDialog saveFileDialog = new SaveFileDialog(); 1558 | saveFileDialog.Filter = "gamecard_cert.dat (*.dat)|*.dat"; 1559 | saveFileDialog.FileName = Path.GetFileName("gamecard_cert.dat"); 1560 | if (saveFileDialog.ShowDialog() != DialogResult.OK) 1561 | { 1562 | return; 1563 | } 1564 | 1565 | FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read); 1566 | byte[] array = new byte[512]; 1567 | fileStream.Position = 28672L; 1568 | fileStream.Read(array, 0, 512); 1569 | File.WriteAllBytes(saveFileDialog.FileName, array); 1570 | fileStream.Close(); 1571 | MessageBox.Show($"Cert successfully exported to:\n\n{saveFileDialog.FileName}"); 1572 | } 1573 | 1574 | private void B_ImportCert_Click(object sender, EventArgs e) 1575 | { 1576 | new CenterWinDialog(this); 1577 | if (!File.Exists(TB_File.Text)) 1578 | { 1579 | MessageBox.Show("File not found"); 1580 | return; 1581 | } 1582 | OpenFileDialog openFileDialog = new OpenFileDialog(); 1583 | openFileDialog.Filter = "gamecard_cert (*.dat)|*.dat|All files (*.*)|*.*"; 1584 | if (openFileDialog.ShowDialog() == DialogResult.OK && new FileInfo(openFileDialog.FileName).Length == 512) 1585 | { 1586 | using (Stream stream = File.Open(TB_File.Text, FileMode.Open)) 1587 | { 1588 | stream.Position = 28672L; 1589 | stream.Write(File.ReadAllBytes(openFileDialog.FileName), 0, 512); 1590 | } 1591 | MessageBox.Show($"Cert successfully imported from:\n\n{openFileDialog.FileName}"); 1592 | } 1593 | } 1594 | 1595 | private void B_ViewCert_Click(object sender, EventArgs e) 1596 | { 1597 | new CenterWinDialog(this); 1598 | if (!File.Exists(TB_File.Text)) 1599 | { 1600 | MessageBox.Show("File not found"); 1601 | return; 1602 | } 1603 | CertForm cert = new(this) 1604 | { 1605 | Text = $"Cert Data - {TB_File.Text}" 1606 | }; 1607 | cert.Show(); 1608 | } 1609 | 1610 | private void B_ClearCert_Click(object sender, EventArgs e) 1611 | { 1612 | new CenterWinDialog(this); 1613 | 1614 | if (!File.Exists(TB_File.Text)) 1615 | { 1616 | MessageBox.Show("File not found"); 1617 | return; 1618 | } 1619 | 1620 | if (MessageBox.Show("The cert will be deleted permanently.\nContinue?", "XCI Explorer", MessageBoxButtons.YesNo) != DialogResult.Yes) 1621 | { 1622 | return; 1623 | } 1624 | 1625 | using Stream stream = File.Open(TB_File.Text, FileMode.Open); 1626 | byte[] array = new byte[512]; 1627 | for (int i = 0; i < array.Length; i++) 1628 | { 1629 | array[i] = byte.MaxValue; 1630 | } 1631 | stream.Position = 28672L; 1632 | stream.Write(array, 0, array.Length); 1633 | 1634 | new CenterWinDialog(this); 1635 | MessageBox.Show("Cert deleted."); 1636 | 1637 | } 1638 | 1639 | private void B_Extract_Click(object sender, EventArgs e) 1640 | { 1641 | SaveFileDialog saveFileDialog = new SaveFileDialog 1642 | { 1643 | FileName = LB_SelectedData.Text 1644 | }; 1645 | 1646 | if (saveFileDialog.ShowDialog() != DialogResult.OK) 1647 | { 1648 | return; 1649 | } 1650 | 1651 | if (backgroundWorker1.IsBusy) 1652 | { 1653 | return; 1654 | } 1655 | 1656 | B_Extract.Enabled = false; 1657 | B_LoadROM.Enabled = false; 1658 | B_TrimXCI.Enabled = false; 1659 | B_ImportCert.Enabled = false; 1660 | B_ClearCert.Enabled = false; 1661 | 1662 | // Start the asynchronous operation. 1663 | backgroundWorker1.RunWorkerAsync(saveFileDialog.FileName); 1664 | } 1665 | 1666 | public byte[] DecryptNCAHeader(long offset) 1667 | { 1668 | byte[] array = new byte[3072]; 1669 | 1670 | if (!File.Exists(TB_File.Text)) 1671 | { 1672 | return array; 1673 | } 1674 | 1675 | FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read) 1676 | { 1677 | Position = offset 1678 | }; 1679 | fileStream.Read(array, 0, 3072); 1680 | File.WriteAllBytes($"{TB_File.Text}.tmp", array); 1681 | Xts xts = XtsAes128.Create(NcaHeaderEncryptionKey1_Prod, NcaHeaderEncryptionKey2_Prod); 1682 | using (BinaryReader binaryReader = new(File.OpenRead($"{TB_File.Text}.tmp"))) 1683 | { 1684 | using XtsStream xtsStream = new(binaryReader.BaseStream, xts, 512); 1685 | xtsStream.Read(array, 0, 3072); 1686 | } 1687 | File.Delete($"{TB_File.Text}.tmp"); 1688 | fileStream.Close(); 1689 | return array; 1690 | } 1691 | 1692 | private void CB_RegionName_SelectedIndexChanged(object sender, EventArgs e) 1693 | { 1694 | int num = Array.FindIndex(Language, (string element) => element.StartsWith(CB_RegionName.Text, StringComparison.Ordinal)); 1695 | // Icons for 1-2 Switch in some languages are "missing" 1696 | // This just shows the first real icon instead of a blank 1697 | if (Icons[num] != null) 1698 | { 1699 | PB_GameIcon.BackgroundImage = Icons[num]; 1700 | } 1701 | else 1702 | { 1703 | for (int i = 0; i < CB_RegionName.Items.Count; i++) 1704 | { 1705 | if (Icons[i] == null) 1706 | { 1707 | continue; 1708 | } 1709 | 1710 | PB_GameIcon.BackgroundImage = Icons[i]; 1711 | break; 1712 | } 1713 | } 1714 | TB_Name.Text = NACP.NACP_Strings[num].GameName; 1715 | TB_Dev.Text = NACP.NACP_Strings[num].GameAuthor; 1716 | } 1717 | 1718 | private void B_TrimXCI_Click(object sender, EventArgs e) 1719 | { 1720 | new CenterWinDialog(this); 1721 | 1722 | if (!File.Exists(TB_File.Text)) 1723 | { 1724 | MessageBox.Show("File not found"); 1725 | return; 1726 | } 1727 | 1728 | if (MessageBox.Show("Trim XCI?", "XCI Explorer", MessageBoxButtons.YesNo) != DialogResult.Yes) 1729 | { 1730 | return; 1731 | } 1732 | 1733 | new CenterWinDialog(this); 1734 | 1735 | if (isTrimmed()) 1736 | { 1737 | return; 1738 | } 1739 | 1740 | FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Write); 1741 | fileStream.SetLength((long)UsedSize); 1742 | fileStream.Close(); 1743 | B_TrimXCI.Enabled = false; 1744 | MessageBox.Show("Done."); 1745 | string[] array = new string[5] 1746 | { 1747 | "B", 1748 | "KB", 1749 | "MB", 1750 | "GB", 1751 | "TB" 1752 | }; 1753 | double num = new FileInfo(TB_File.Text).Length; 1754 | TB_ROMExactSize.Text = $"({num} bytes)"; 1755 | int num2 = 0; 1756 | while (num >= 1024.0 && num2 < array.Length - 1) 1757 | { 1758 | num2++; 1759 | num /= 1024.0; 1760 | } 1761 | TB_ROMSize.Text = $"{num:0.##} {array[num2]}"; 1762 | double num3 = UsedSize = XCI.XCI_Headers[0].CardSize2 * 512 + 512; 1763 | TB_ExactUsedSpace.Text = $"({num3} bytes)"; 1764 | num2 = 0; 1765 | while (num3 >= 1024.0 && num2 < array.Length - 1) 1766 | { 1767 | num2++; 1768 | num3 /= 1024.0; 1769 | } 1770 | TB_UsedSpace.Text = $"{num3:0.##} {array[num2]}"; 1771 | } 1772 | 1773 | private void LB_ExpectedHash_DoubleClick(object sender, EventArgs e) 1774 | { 1775 | BetterTreeNode betterTreeNode = (BetterTreeNode)TV_Partitions.SelectedNode; 1776 | if (betterTreeNode.Offset != -1) 1777 | { 1778 | Clipboard.SetText(betterTreeNode.ExpectedHash); 1779 | } 1780 | } 1781 | 1782 | private void LB_ActualHash_DoubleClick(object sender, EventArgs e) 1783 | { 1784 | BetterTreeNode betterTreeNode = (BetterTreeNode)TV_Partitions.SelectedNode; 1785 | if (betterTreeNode.Offset != -1) 1786 | { 1787 | Clipboard.SetText(betterTreeNode.ActualHash); 1788 | } 1789 | } 1790 | 1791 | private void TB_File_DragDrop(object sender, DragEventArgs e) 1792 | { 1793 | if (backgroundWorker1.IsBusy != true) 1794 | { 1795 | string[] files = e.Data.GetData(DataFormats.FileDrop) as string[]; 1796 | TB_File.Text = files[0]; 1797 | ProcessFile(); 1798 | } 1799 | } 1800 | 1801 | private void TB_File_DragEnter(object sender, DragEventArgs e) 1802 | { 1803 | if (backgroundWorker1.IsBusy) 1804 | { 1805 | return; 1806 | } 1807 | 1808 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) 1809 | { 1810 | e.Effect = DragDropEffects.Copy; 1811 | } 1812 | else 1813 | { 1814 | e.Effect = DragDropEffects.None; 1815 | } 1816 | } 1817 | 1818 | private void TABP_XCI_DragDrop(object sender, DragEventArgs e) 1819 | { 1820 | if (backgroundWorker1.IsBusy) 1821 | { 1822 | return; 1823 | } 1824 | 1825 | string[] files = e.Data.GetData(DataFormats.FileDrop) as string[]; 1826 | TB_File.Text = files[0]; 1827 | ProcessFile(); 1828 | } 1829 | 1830 | private void TABP_XCI_DragEnter(object sender, DragEventArgs e) 1831 | { 1832 | if (backgroundWorker1.IsBusy) 1833 | { 1834 | return; 1835 | } 1836 | 1837 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) 1838 | { 1839 | e.Effect = DragDropEffects.Copy; 1840 | } 1841 | else 1842 | { 1843 | e.Effect = DragDropEffects.None; 1844 | } 1845 | } 1846 | 1847 | private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 1848 | { 1849 | BackgroundWorker worker = sender as BackgroundWorker; 1850 | string fileName = (string)e.Argument; 1851 | 1852 | using FileStream fileStream = File.OpenRead(TB_File.Text); 1853 | using FileStream fileStream2 = File.OpenWrite(fileName); 1854 | new BinaryReader(fileStream); 1855 | new BinaryWriter(fileStream2); 1856 | fileStream.Position = selectedOffset; 1857 | long num = selectedSize; 1858 | 1859 | if (selectedSize < 10000) 1860 | { 1861 | byte[] buffer = new byte[1]; 1862 | int num2; 1863 | while ((num2 = fileStream.Read(buffer, 0, 1)) > 0 && num > 0) 1864 | { 1865 | fileStream2.Write(buffer, 0, num2); 1866 | num -= num2; 1867 | } 1868 | } 1869 | else 1870 | { 1871 | byte[] buffer = new byte[8192]; 1872 | int num2; 1873 | while ((num2 = fileStream.Read(buffer, 0, 8192)) > 0 && num > 0) 1874 | { 1875 | fileStream2.Write(buffer, 0, num2); 1876 | num -= num2; 1877 | } 1878 | } 1879 | 1880 | fileStream.Close(); 1881 | } 1882 | 1883 | private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 1884 | { 1885 | new CenterWinDialog(this); 1886 | B_Extract.Enabled = true; 1887 | B_LoadROM.Enabled = true; 1888 | B_TrimXCI.Enabled = true; 1889 | B_ImportCert.Enabled = true; 1890 | B_ClearCert.Enabled = true; 1891 | 1892 | if (e.Error != null) 1893 | { 1894 | MessageBox.Show($"Error: {e.Error.Message}"); 1895 | } 1896 | else 1897 | { 1898 | MessageBox.Show("Done extracting NCA!"); 1899 | } 1900 | } 1901 | 1902 | private void TABP_XCI_Click(object sender, EventArgs e) 1903 | { 1904 | 1905 | } 1906 | 1907 | private void label11_Click(object sender, EventArgs e) 1908 | { 1909 | 1910 | } 1911 | 1912 | } -------------------------------------------------------------------------------- /XCI_Explorer/MainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /XCI_Explorer/NACP.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text; 3 | 4 | namespace XCI_Explorer; 5 | 6 | public static class NACP 7 | { 8 | public class NACP_String 9 | { 10 | public byte[] Data; 11 | public byte Check; 12 | public string GameName; 13 | public string GameAuthor; 14 | 15 | public NACP_String(byte[] data) 16 | { 17 | Data = data; 18 | Check = Data[0]; 19 | GameName = Encoding.UTF8.GetString(Data.Take(512).ToArray()); 20 | GameAuthor = Encoding.UTF8.GetString(Data.Skip(512).Take(256).ToArray()); 21 | } 22 | } 23 | 24 | public class NACP_Data 25 | { 26 | public byte[] Data; 27 | public string GameVer; 28 | public string GameProd; 29 | 30 | public NACP_Data(byte[] data) 31 | { 32 | Data = data; 33 | GameVer = Encoding.UTF8.GetString(Data.Skip(0x60).Take(16).ToArray()); 34 | GameProd = Encoding.UTF8.GetString(Data.Skip(0xA8).Take(8).ToArray()); 35 | } 36 | } 37 | 38 | public static NACP_String[] NACP_Strings = new NACP_String[16]; 39 | 40 | public static NACP_Data[] NACP_Datas = new NACP_Data[1]; 41 | } 42 | -------------------------------------------------------------------------------- /XCI_Explorer/NCA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace XCI_Explorer; 6 | 7 | internal static class NCA 8 | { 9 | public class NCA_Header 10 | { 11 | public byte[] Data; 12 | public string Magic; 13 | public long TitleID; 14 | public byte SDKVersion1; 15 | public byte SDKVersion2; 16 | public byte SDKVersion3; 17 | public byte SDKVersion4; 18 | public byte MasterKeyRev; 19 | 20 | public NCA_Header(byte[] data) 21 | { 22 | Data = data; 23 | Magic = Encoding.UTF8.GetString(Data.Skip(512).Take(4).ToArray()); 24 | TitleID = BitConverter.ToInt64(data, 528); 25 | SDKVersion1 = Data[540]; 26 | SDKVersion2 = Data[541]; 27 | SDKVersion3 = Data[542]; 28 | SDKVersion4 = Data[543]; 29 | MasterKeyRev = Data[544]; 30 | } 31 | } 32 | 33 | public static NCA_Header[] NCA_Headers = new NCA_Header[1]; 34 | } 35 | -------------------------------------------------------------------------------- /XCI_Explorer/PFS0.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace XCI_Explorer; 6 | 7 | internal static class PFS0 8 | { 9 | public class PFS0_Header 10 | { 11 | public byte[] Data; 12 | public string Magic; 13 | public int FileCount; 14 | public int StringTableSize; 15 | public int Reserved; 16 | 17 | public PFS0_Header(byte[] data) 18 | { 19 | Data = data; 20 | Magic = Encoding.UTF8.GetString(Data.Take(4).ToArray()); 21 | FileCount = BitConverter.ToInt32(data, 4); 22 | StringTableSize = BitConverter.ToInt32(data, 8); 23 | Reserved = BitConverter.ToInt32(data, 12); 24 | } 25 | } 26 | 27 | public class PFS0_Entry 28 | { 29 | public byte[] Data; 30 | public long Offset; 31 | public long Size; 32 | public int Name_ptr; 33 | public int Reserved; 34 | public string Name; 35 | 36 | public PFS0_Entry(byte[] data) 37 | { 38 | Data = data; 39 | Offset = BitConverter.ToInt64(data, 0); 40 | Size = BitConverter.ToInt64(data, 8); 41 | Name_ptr = BitConverter.ToInt32(data, 16); 42 | Reserved = BitConverter.ToInt32(data, 20); 43 | } 44 | } 45 | 46 | public static PFS0_Header[] PFS0_Headers = new PFS0_Header[1]; 47 | } 48 | -------------------------------------------------------------------------------- /XCI_Explorer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows.Forms; 4 | 5 | namespace XCI_Explorer; 6 | 7 | internal static class Program 8 | { 9 | [STAThread] 10 | private static void Main() 11 | { 12 | AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs args) => 13 | { 14 | System.Reflection.AssemblyName embeddedAssembly = new System.Reflection.AssemblyName(args.Name); 15 | string resourceName = "XCI_Explorer" + "." + embeddedAssembly.Name + ".dll"; 16 | 17 | using Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); 18 | byte[] assemblyData = new byte[stream.Length]; 19 | stream.Read(assemblyData, 0, assemblyData.Length); 20 | return System.Reflection.Assembly.Load(assemblyData); 21 | }; 22 | Application.EnableVisualStyles(); 23 | Application.SetCompatibleTextRenderingDefault(false); 24 | Application.Run(new MainForm()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /XCI_Explorer/TreeViewFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using XCI_Explorer.Helpers; 3 | 4 | namespace XCI_Explorer; 5 | 6 | public class TreeViewFileSystem 7 | { 8 | public TreeView treeView; 9 | 10 | public TreeViewFileSystem(TreeView tv) 11 | { 12 | } 13 | 14 | public BetterTreeNode AddDir(string name, BetterTreeNode parent = null) 15 | { 16 | BetterTreeNode betterTreeNode = new(name) 17 | { 18 | Offset = -1L, 19 | Size = -1L 20 | }; 21 | parent.Nodes.Add(betterTreeNode); 22 | return betterTreeNode; 23 | } 24 | 25 | public BetterTreeNode AddFile(string name, BetterTreeNode parent, long offset, long size) => AddFile(name, parent, offset, size, 0, "", ""); 26 | 27 | public BetterTreeNode AddFile(string name, BetterTreeNode parent, long offset, long size, long HashedRegionSize, string ExpectedHash, string ActualHash) 28 | { 29 | BetterTreeNode betterTreeNode = new(name) 30 | { 31 | Offset = offset, 32 | Size = size, 33 | ExpectedHash = ExpectedHash, 34 | ActualHash = ActualHash, 35 | HashedRegionSize = HashedRegionSize 36 | }; 37 | parent.Nodes.Add(betterTreeNode); 38 | return betterTreeNode; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /XCI_Explorer/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace XCI_Explorer; 6 | 7 | internal static class Util 8 | { 9 | public static string GetCapacity(int id) 10 | { 11 | return id switch 12 | { 13 | 250 => "1GB", 14 | 248 => "2GB", 15 | 240 => "4GB", 16 | 224 => "8GB", 17 | 225 => "16GB", 18 | 226 => "32GB", 19 | _ => "?", 20 | }; 21 | } 22 | 23 | public static string GetMkey(byte id) 24 | { 25 | return id switch 26 | { 27 | 0 or 1 => "MasterKey0 (1.0.0-2.3.0)", 28 | 2 => "MasterKey1 (3.0.0)", 29 | 3 => "MasterKey2 (3.0.1-3.0.2)", 30 | 4 => "MasterKey3 (4.0.0-4.1.0)", 31 | 5 => "MasterKey4 (5.0.0-5.1.0)", 32 | 6 => "MasterKey5 (6.0.0-6.1.0)", 33 | 7 => "MasterKey6 (6.2.0)", 34 | 8 => "MasterKey7 (7.0.0-8.0.1)", 35 | 9 => "MasterKey8 (8.1.0-8.1.1)", 36 | 10 => "MasterKey9 (9.0.0-9.0.1)", 37 | 11 => "MasterKey10 (9.1.0-12.0.3)", 38 | 12 => "MasterKey11 (12.1.0)", 39 | 13 => "MasterKey12 (13.0.0-13.2.1)", 40 | 14 => "MasterKey13 (14.0.0-14.1.2)", 41 | 15 => "MasterKey14 (15.0.0-?)", 42 | 16 => "MasterKey15", 43 | 17 => "MasterKey16", 44 | 18 => "MasterKey17", 45 | 19 => "MasterKey18", 46 | 20 => "MasterKey19", 47 | 21 => "MasterKey20", 48 | 22 => "MasterKey21", 49 | 23 => "MasterKey22", 50 | 24 => "MasterKey23", 51 | 25 => "MasterKey24", 52 | 26 => "MasterKey25", 53 | 27 => "MasterKey26", 54 | 28 => "MasterKey27", 55 | 29 => "MasterKey28", 56 | 30 => "MasterKey29", 57 | 31 => "MasterKey30", 58 | 32 => "MasterKey31", 59 | 33 => "MasterKey32", 60 | _ => "?", 61 | }; 62 | } 63 | 64 | public static byte[] StringToByteArray(string hex) => (from x in Enumerable.Range(0, hex.Length) 65 | where x % 2 == 0 66 | select Convert.ToByte(hex.Substring(x, 2), 16)).ToArray(); 67 | 68 | public static string Base64Encode(string plainText) 69 | => Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText)); 70 | 71 | public static string Base64Decode(string base64EncodedData) 72 | => Encoding.UTF8.GetString(Convert.FromBase64String(base64EncodedData)); 73 | } 74 | -------------------------------------------------------------------------------- /XCI_Explorer/XCI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace XCI_Explorer; 6 | 7 | public static class XCI 8 | { 9 | public class XCI_Header 10 | { 11 | public byte[] Data; 12 | public string Magic; 13 | public byte CardSize1; 14 | public long CardSize2; 15 | public long HFS0OffsetPartition; 16 | public long HFS0SizeParition; 17 | 18 | public XCI_Header(byte[] data) 19 | { 20 | Data = data; 21 | Magic = Encoding.UTF8.GetString(Data.Skip(256).Take(4).ToArray()); 22 | CardSize1 = Data[269]; 23 | CardSize2 = BitConverter.ToInt64(data, 280); 24 | HFS0OffsetPartition = BitConverter.ToInt64(data, 304); 25 | HFS0SizeParition = BitConverter.ToInt64(data, 312); 26 | } 27 | } 28 | 29 | public static XCI_Header[] XCI_Headers = new XCI_Header[1]; 30 | } 31 | -------------------------------------------------------------------------------- /XTSSharp/RandomAccessSectorStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace XTSSharp; 5 | 6 | public class RandomAccessSectorStream : Stream 7 | { 8 | private readonly byte[] _buffer; 9 | private readonly int _bufferSize; 10 | private readonly SectorStream _s; 11 | private readonly bool _isStreamOwned; 12 | private bool _bufferDirty; 13 | private bool _bufferLoaded; 14 | private int _bufferPos; 15 | public override bool CanRead => _s.CanRead; 16 | public override bool CanSeek => _s.CanSeek; 17 | public override bool CanWrite => _s.CanWrite; 18 | public override long Length => _s.Length + _bufferPos; 19 | 20 | public override long Position 21 | { 22 | get 23 | { 24 | if (!_bufferLoaded) 25 | { 26 | return _s.Position + _bufferPos; 27 | } 28 | return _s.Position - _bufferSize + _bufferPos; 29 | } 30 | set 31 | { 32 | if (value < 0) 33 | { 34 | throw new ArgumentOutOfRangeException(nameof(value)); 35 | } 36 | long num = value % _bufferSize; 37 | long position = value - num; 38 | if (_bufferLoaded) 39 | { 40 | long num2 = _s.Position - _bufferSize; 41 | if (value > num2 && value < num2 + _bufferSize) 42 | { 43 | _bufferPos = (int)num; 44 | return; 45 | } 46 | } 47 | if (_bufferDirty) 48 | { 49 | WriteSector(); 50 | } 51 | _s.Position = position; 52 | ReadSector(); 53 | _bufferPos = (int)num; 54 | } 55 | } 56 | 57 | public RandomAccessSectorStream(SectorStream s) 58 | : this(s, false) 59 | { 60 | } 61 | 62 | public RandomAccessSectorStream(SectorStream s, bool isStreamOwned) 63 | { 64 | _s = s; 65 | _isStreamOwned = isStreamOwned; 66 | _buffer = new byte[s.SectorSize]; 67 | _bufferSize = s.SectorSize; 68 | } 69 | 70 | protected override void Dispose(bool disposing) 71 | { 72 | Flush(); 73 | base.Dispose(disposing); 74 | if (_isStreamOwned) 75 | { 76 | _s.Dispose(); 77 | } 78 | } 79 | 80 | public override void Flush() 81 | { 82 | if (_bufferDirty) 83 | { 84 | WriteSector(); 85 | } 86 | } 87 | 88 | public override long Seek(long offset, SeekOrigin origin) 89 | { 90 | long num = origin switch 91 | { 92 | SeekOrigin.Begin => offset, 93 | SeekOrigin.End => Length - offset, 94 | _ => Position + offset, 95 | }; 96 | Position = num; 97 | return num; 98 | } 99 | 100 | public override void SetLength(long value) 101 | { 102 | long num = value % _s.SectorSize; 103 | if (num > 0) 104 | { 105 | value = value - num + _bufferSize; 106 | } 107 | _s.SetLength(value); 108 | } 109 | 110 | public override int Read(byte[] buffer, int offset, int count) 111 | { 112 | long position = Position; 113 | if (position + count > _s.Length) 114 | { 115 | count = (int)(_s.Length - position); 116 | } 117 | if (!_bufferLoaded) 118 | { 119 | ReadSector(); 120 | } 121 | int num = 0; 122 | while (count > 0) 123 | { 124 | int num2 = Math.Min(count, _bufferSize - _bufferPos); 125 | Buffer.BlockCopy(_buffer, _bufferPos, buffer, offset, num2); 126 | offset += num2; 127 | _bufferPos += num2; 128 | count -= num2; 129 | num += num2; 130 | if (_bufferPos == _bufferSize) 131 | { 132 | ReadSector(); 133 | } 134 | } 135 | return num; 136 | } 137 | 138 | public override void Write(byte[] buffer, int offset, int count) 139 | { 140 | while (count > 0) 141 | { 142 | if (!_bufferLoaded) 143 | { 144 | ReadSector(); 145 | } 146 | int num = Math.Min(count, _bufferSize - _bufferPos); 147 | Buffer.BlockCopy(buffer, offset, _buffer, _bufferPos, num); 148 | offset += num; 149 | _bufferPos += num; 150 | count -= num; 151 | _bufferDirty = true; 152 | if (_bufferPos == _bufferSize) 153 | { 154 | WriteSector(); 155 | } 156 | } 157 | } 158 | 159 | private void ReadSector() 160 | { 161 | if (_bufferLoaded && _bufferDirty) 162 | { 163 | WriteSector(); 164 | } 165 | if (_s.Position != _s.Length) 166 | { 167 | int num = _s.Read(_buffer, 0, _buffer.Length); 168 | if (num != _bufferSize) 169 | { 170 | Array.Clear(_buffer, num, _buffer.Length - num); 171 | } 172 | _bufferLoaded = true; 173 | _bufferPos = 0; 174 | _bufferDirty = false; 175 | } 176 | } 177 | 178 | private void WriteSector() 179 | { 180 | if (_bufferLoaded) 181 | { 182 | _s.Seek(-_bufferSize, SeekOrigin.Current); 183 | } 184 | _s.Write(_buffer, 0, _bufferSize); 185 | _bufferDirty = false; 186 | _bufferLoaded = false; 187 | _bufferPos = 0; 188 | Array.Clear(_buffer, 0, _bufferSize); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /XTSSharp/SectorStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace XTSSharp; 5 | 6 | public class SectorStream : Stream 7 | { 8 | private readonly Stream _baseStream; 9 | private readonly long _offset; 10 | private ulong _currentSector; 11 | 12 | public int SectorSize 13 | { 14 | get; 15 | private set; 16 | } 17 | 18 | public override bool CanRead => _baseStream.CanRead; 19 | public override bool CanSeek => _baseStream.CanSeek; 20 | public override bool CanWrite => _baseStream.CanWrite; 21 | public override long Length => _baseStream.Length - _offset; 22 | 23 | public override long Position 24 | { 25 | get 26 | { 27 | return _baseStream.Position - _offset; 28 | } 29 | set 30 | { 31 | ValidateSizeMultiple(value); 32 | _baseStream.Position = value + _offset; 33 | _currentSector = (ulong)(value / SectorSize); 34 | } 35 | } 36 | 37 | protected ulong CurrentSector => _currentSector; 38 | 39 | public SectorStream(Stream baseStream, int sectorSize) 40 | : this(baseStream, sectorSize, 0L) 41 | { 42 | } 43 | 44 | public SectorStream(Stream baseStream, int sectorSize, long offset) 45 | { 46 | SectorSize = sectorSize; 47 | _baseStream = baseStream; 48 | _offset = offset; 49 | } 50 | 51 | private void ValidateSizeMultiple(long value) 52 | { 53 | if (value % SectorSize == 0L) 54 | { 55 | return; 56 | } 57 | throw new ArgumentException($"Value needs to be a multiple of {SectorSize}"); 58 | } 59 | 60 | protected void ValidateSize(long value) 61 | { 62 | if (value == SectorSize) 63 | { 64 | return; 65 | } 66 | throw new ArgumentException($"Value needs to be {SectorSize}"); 67 | } 68 | 69 | protected void ValidateSize(int value) 70 | { 71 | if (value == SectorSize) 72 | { 73 | return; 74 | } 75 | throw new ArgumentException($"Value needs to be {SectorSize}"); 76 | } 77 | 78 | public override void Flush() 79 | { 80 | _baseStream.Flush(); 81 | } 82 | 83 | public override long Seek(long offset, SeekOrigin origin) 84 | { 85 | long num = origin switch 86 | { 87 | SeekOrigin.Begin => offset, 88 | SeekOrigin.End => Length - offset, 89 | _ => Position + offset, 90 | }; 91 | Position = num; 92 | return num; 93 | } 94 | 95 | public override void SetLength(long value) 96 | { 97 | ValidateSizeMultiple(value); 98 | _baseStream.SetLength(value); 99 | } 100 | 101 | public override int Read(byte[] buffer, int offset, int count) 102 | { 103 | ValidateSize(count); 104 | int result = _baseStream.Read(buffer, offset, count); 105 | _currentSector++; 106 | return result; 107 | } 108 | 109 | public override void Write(byte[] buffer, int offset, int count) 110 | { 111 | ValidateSize(count); 112 | _baseStream.Write(buffer, offset, count); 113 | _currentSector++; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /XTSSharp/Xts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace XTSSharp; 5 | 6 | public class Xts 7 | { 8 | private readonly SymmetricAlgorithm _key1; 9 | private readonly SymmetricAlgorithm _key2; 10 | 11 | protected Xts(Func create, byte[] key1, byte[] key2) 12 | { 13 | if (create == null) 14 | { 15 | throw new ArgumentNullException(nameof(create)); 16 | } 17 | if (key1 == null) 18 | { 19 | throw new ArgumentNullException(nameof(key1)); 20 | } 21 | if (key2 == null) 22 | { 23 | throw new ArgumentNullException(nameof(key2)); 24 | } 25 | _key1 = create(); 26 | _key2 = create(); 27 | if (key1.Length != key2.Length) 28 | { 29 | throw new ArgumentException("Key lengths don't match"); 30 | } 31 | _key1.KeySize = key1.Length * 8; 32 | _key2.KeySize = key2.Length * 8; 33 | _key1.Key = key1; 34 | _key2.Key = key2; 35 | _key1.Mode = CipherMode.ECB; 36 | _key2.Mode = CipherMode.ECB; 37 | _key1.Padding = PaddingMode.None; 38 | _key2.Padding = PaddingMode.None; 39 | _key1.BlockSize = 128; 40 | _key2.BlockSize = 128; 41 | } 42 | 43 | public XtsCryptoTransform CreateEncryptor() 44 | { 45 | return new XtsCryptoTransform(_key1.CreateEncryptor(), _key2.CreateEncryptor(), false); 46 | } 47 | 48 | public XtsCryptoTransform CreateDecryptor() 49 | { 50 | return new XtsCryptoTransform(_key1.CreateDecryptor(), _key2.CreateEncryptor(), true); 51 | } 52 | 53 | protected static byte[] VerifyKey(int expectedSize, byte[] key) 54 | { 55 | if (key == null) 56 | { 57 | throw new ArgumentNullException(nameof(key)); 58 | } 59 | if (key.Length * 8 != expectedSize) 60 | { 61 | throw new ArgumentException($"Expected key length of {expectedSize} bits, got {key.Length * 8}"); 62 | } 63 | return key; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /XTSSharp/XtsAes128.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace XTSSharp; 5 | 6 | public class XtsAes128 : Xts 7 | { 8 | private const int KEY_LENGTH = 128; 9 | private const int KEY_BYTE_LENGTH = 16; 10 | 11 | protected XtsAes128(Func create, byte[] key1, byte[] key2) 12 | : base(create, VerifyKey(128, key1), VerifyKey(128, key2)) 13 | { 14 | } 15 | 16 | public static Xts Create(byte[] key1, byte[] key2) 17 | { 18 | VerifyKey(128, key1); 19 | VerifyKey(128, key2); 20 | return new XtsAes128(Aes.Create, key1, key2); 21 | } 22 | 23 | public static Xts Create(byte[] key) 24 | { 25 | VerifyKey(256, key); 26 | byte[] array = new byte[16]; 27 | byte[] array2 = new byte[16]; 28 | Buffer.BlockCopy(key, 0, array, 0, 16); 29 | Buffer.BlockCopy(key, 16, array2, 0, 16); 30 | return new XtsAes128(Aes.Create, array, array2); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /XTSSharp/XtsAes256.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace XTSSharp; 5 | 6 | public class XtsAes256 : Xts 7 | { 8 | private const int KEY_LENGTH = 256; 9 | private const int KEY_BYTE_LENGTH = 32; 10 | 11 | protected XtsAes256(Func create, byte[] key1, byte[] key2) 12 | : base(create, VerifyKey(256, key1), VerifyKey(256, key2)) 13 | { 14 | } 15 | 16 | public static Xts Create(byte[] key1, byte[] key2) 17 | { 18 | VerifyKey(256, key1); 19 | VerifyKey(256, key2); 20 | return new XtsAes256(Aes.Create, key1, key2); 21 | } 22 | 23 | public static Xts Create(byte[] key) 24 | { 25 | VerifyKey(512, key); 26 | byte[] array = new byte[32]; 27 | byte[] array2 = new byte[32]; 28 | Buffer.BlockCopy(key, 0, array, 0, 32); 29 | Buffer.BlockCopy(key, 32, array2, 0, 32); 30 | return new XtsAes256(Aes.Create, array, array2); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /XTSSharp/XtsCryptoTransform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace XTSSharp; 5 | 6 | public class XtsCryptoTransform : IDisposable 7 | { 8 | private readonly byte[] _cc = new byte[16]; 9 | private readonly bool _decrypting; 10 | private readonly ICryptoTransform _key1; 11 | private readonly ICryptoTransform _key2; 12 | private readonly byte[] _pp = new byte[16]; 13 | private readonly byte[] _t = new byte[16]; 14 | private readonly byte[] _tweak = new byte[16]; 15 | 16 | public XtsCryptoTransform(ICryptoTransform key1, ICryptoTransform key2, bool decrypting) 17 | { 18 | if (key1 == null) 19 | { 20 | throw new ArgumentNullException(nameof(key1)); 21 | } 22 | if (key2 == null) 23 | { 24 | throw new ArgumentNullException(nameof(key2)); 25 | } 26 | _key1 = key1; 27 | _key2 = key2; 28 | _decrypting = decrypting; 29 | } 30 | 31 | public void Dispose() 32 | { 33 | _key1.Dispose(); 34 | _key2.Dispose(); 35 | } 36 | 37 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset, ulong sector) 38 | { 39 | FillArrayFromSectorLittleEndian(_tweak, sector); 40 | int num = inputCount >> 4; 41 | int num2 = inputCount & 0xF; 42 | _key2.TransformBlock(_tweak, 0, _tweak.Length, _t, 0); 43 | int num3 = (num2 != 0) ? (num - 1) : num; 44 | for (int i = 0; i < num3; i++) 45 | { 46 | TweakCrypt(inputBuffer, inputOffset, outputBuffer, outputOffset, _t); 47 | inputOffset += 16; 48 | outputOffset += 16; 49 | } 50 | if (num2 > 0) 51 | { 52 | if (_decrypting) 53 | { 54 | Buffer.BlockCopy(_t, 0, _cc, 0, 16); 55 | MultiplyByX(_cc); 56 | TweakCrypt(inputBuffer, inputOffset, _pp, 0, _cc); 57 | int j; 58 | for (j = 0; j < num2; j++) 59 | { 60 | _cc[j] = inputBuffer[16 + j + inputOffset]; 61 | outputBuffer[16 + j + outputOffset] = _pp[j]; 62 | } 63 | for (; j < 16; j++) 64 | { 65 | _cc[j] = _pp[j]; 66 | } 67 | TweakCrypt(_cc, 0, outputBuffer, outputOffset, _t); 68 | } 69 | else 70 | { 71 | TweakCrypt(inputBuffer, inputOffset, _cc, 0, _t); 72 | int k; 73 | for (k = 0; k < num2; k++) 74 | { 75 | _pp[k] = inputBuffer[16 + k + inputOffset]; 76 | outputBuffer[16 + k + outputOffset] = _cc[k]; 77 | } 78 | for (; k < 16; k++) 79 | { 80 | _pp[k] = _cc[k]; 81 | } 82 | TweakCrypt(_pp, 0, outputBuffer, outputOffset, _t); 83 | } 84 | } 85 | return inputCount; 86 | } 87 | 88 | private static void FillArrayFromSectorBigEndian(byte[] value, ulong sector) 89 | { 90 | value[7] = (byte)((sector >> 56) & 0xFF); 91 | value[6] = (byte)((sector >> 48) & 0xFF); 92 | value[5] = (byte)((sector >> 40) & 0xFF); 93 | value[4] = (byte)((sector >> 32) & 0xFF); 94 | value[3] = (byte)((sector >> 24) & 0xFF); 95 | value[2] = (byte)((sector >> 16) & 0xFF); 96 | value[1] = (byte)((sector >> 8) & 0xFF); 97 | value[0] = (byte)(sector & 0xFF); 98 | } 99 | 100 | private static void FillArrayFromSectorLittleEndian(byte[] value, ulong sector) 101 | { 102 | value[8] = (byte)((sector >> 56) & 0xFF); 103 | value[9] = (byte)((sector >> 48) & 0xFF); 104 | value[10] = (byte)((sector >> 40) & 0xFF); 105 | value[11] = (byte)((sector >> 32) & 0xFF); 106 | value[12] = (byte)((sector >> 24) & 0xFF); 107 | value[13] = (byte)((sector >> 16) & 0xFF); 108 | value[14] = (byte)((sector >> 8) & 0xFF); 109 | value[15] = (byte)(sector & 0xFF); 110 | } 111 | 112 | private void TweakCrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] t) 113 | { 114 | for (int i = 0; i < 16; i++) 115 | { 116 | outputBuffer[i + outputOffset] = (byte)(inputBuffer[i + inputOffset] ^ t[i]); 117 | } 118 | _key1.TransformBlock(outputBuffer, outputOffset, 16, outputBuffer, outputOffset); 119 | for (int j = 0; j < 16; j++) 120 | { 121 | outputBuffer[j + outputOffset] = (byte)(outputBuffer[j + outputOffset] ^ t[j]); 122 | } 123 | MultiplyByX(t); 124 | } 125 | 126 | private static void MultiplyByX(byte[] i) 127 | { 128 | byte b = 0; 129 | byte b2 = 0; 130 | for (int j = 0; j < 16; j++) 131 | { 132 | b2 = (byte)(i[j] >> 7); 133 | i[j] = (byte)(((i[j] << 1) | b) & 0xFF); 134 | b = b2; 135 | } 136 | if (b2 > 0) 137 | { 138 | i[0] ^= 135; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /XTSSharp/XtsSectorStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace XTSSharp; 4 | 5 | public class XtsSectorStream : SectorStream 6 | { 7 | public const int DEFAULT_SECTOR_SIZE = 512; 8 | private readonly byte[] _tempBuffer; 9 | private readonly Xts _xts; 10 | private XtsCryptoTransform _decryptor; 11 | private XtsCryptoTransform _encryptor; 12 | 13 | public XtsSectorStream(Stream baseStream, Xts xts) 14 | : this(baseStream, xts, 512) 15 | { 16 | } 17 | 18 | public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize) 19 | : this(baseStream, xts, sectorSize, 0L) 20 | { 21 | } 22 | 23 | public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize, long offset) 24 | : base(baseStream, sectorSize, offset) 25 | { 26 | _xts = xts; 27 | _tempBuffer = new byte[sectorSize]; 28 | } 29 | 30 | protected override void Dispose(bool disposing) 31 | { 32 | base.Dispose(disposing); 33 | if (_encryptor != null) 34 | { 35 | _encryptor.Dispose(); 36 | } 37 | if (_decryptor != null) 38 | { 39 | _decryptor.Dispose(); 40 | } 41 | } 42 | 43 | public override void Write(byte[] buffer, int offset, int count) 44 | { 45 | ValidateSize(count); 46 | if (count != 0) 47 | { 48 | ulong currentSector = base.CurrentSector; 49 | if (_encryptor == null) 50 | { 51 | _encryptor = _xts.CreateEncryptor(); 52 | } 53 | int count2 = _encryptor.TransformBlock(buffer, offset, count, _tempBuffer, 0, currentSector); 54 | base.Write(_tempBuffer, 0, count2); 55 | } 56 | } 57 | 58 | public override int Read(byte[] buffer, int offset, int count) 59 | { 60 | ValidateSize(count); 61 | ulong currentSector = base.CurrentSector; 62 | int num = base.Read(_tempBuffer, 0, count); 63 | if (num == 0) 64 | { 65 | return 0; 66 | } 67 | if (_decryptor == null) 68 | { 69 | _decryptor = _xts.CreateDecryptor(); 70 | } 71 | return _decryptor.TransformBlock(_tempBuffer, 0, num, buffer, offset, currentSector); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /XTSSharp/XtsStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace XTSSharp; 4 | 5 | public class XtsStream : RandomAccessSectorStream 6 | { 7 | public XtsStream(Stream baseStream, Xts xts) 8 | : this(baseStream, xts, 512) 9 | { 10 | } 11 | 12 | public XtsStream(Stream baseStream, Xts xts, int sectorSize) 13 | : base(new XtsSectorStream(baseStream, xts, sectorSize), true) 14 | { 15 | } 16 | 17 | public XtsStream(Stream baseStream, Xts xts, int sectorSize, long offset) 18 | : base(new XtsSectorStream(baseStream, xts, sectorSize, offset), true) 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/SOURCE.txt: -------------------------------------------------------------------------------- 1 | Source: https://github.com/SciresM/hactool/releases/tag/1.4.0 2 | 3 | Algorithm Hash Path 4 | --------- ---- ---- 5 | SHA256 36E9A221C8A7949C86ADA9388EB703C90663AEDFE9F65B6032429614C5E1ABE8 hactool-1.4.0-win.zip 6 | SHA256 01E6CCB916C74071645DF84F13E05D0185C9B2F5D748E469C84A157A53395CF9 hactool.exe 7 | -------------------------------------------------------------------------------- /tools/hactool.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudentBlake/XCI-Explorer/5a511366a126a6e75d02cf4ab8d4df43c1458975/tools/hactool.exe --------------------------------------------------------------------------------