├── .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 |  | 
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
--------------------------------------------------------------------------------