First commit.

This commit is contained in:
Pablo Curiel 2020-04-11 01:28:26 -04:00
commit e5a4532a63
57 changed files with 55025 additions and 0 deletions

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
Add screenshots to help explain your problem.
**Please complete the following information:**
- Horizon OS (Switch FW) version: [e.g. 9.0.1]
- CFW: [e.g. Atmosphère, SX OS, etc.]
- CFW version: [e.g. 0.9.4, 2.9.2, etc.]
- Atmosphère launch method (if applicable): [e.g. Hekate, fusee-primary]
- NXDumpTool version: [e.g. 1.1.7]
- Homebrew launch method: [e.g. title override, Album applet]
- Source storage used with the application (if applicable): [e.g. gamecard, SD/eMMC]
- SD card specs: [e.g. Samsung EVO 256 GB]
**Additional context**
Add any other context about the problem here.

13
.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
build
/*.elf
/*.nacp
/*.nro
/*.nso
/*.map
/*.pfs0
/*.lst
# Clion files
.idea
cmake-build-debug
CMakeLists.txt

124
LICENSE.md Normal file
View file

@ -0,0 +1,124 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
One line to give the program's name and a brief idea of what it does.
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.
signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.

205
Makefile Normal file
View file

@ -0,0 +1,205 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
#
# NO_ICON: if set to anything, do not use icon.
# NO_NACP: if set to anything, no .nacp file is generated.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/default_icon.jpg
#---------------------------------------------------------------------------------
VERSION_MAJOR := 1
VERSION_MINOR := 1
VERSION_MICRO := 9
APP_TITLE := nxdumptool
APP_AUTHOR := DarkMatterCore
APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}
TARGET := ${APP_TITLE}
BUILD := build
SOURCES := source source/fatfs
DATA := data
INCLUDES := include
EXEFS_SRC := exefs_src
ROMFS := romfs
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -Wextra -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__
CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_VERSION=\"${APP_VERSION}\"
CFLAGS += `freetype-config --cflags`
CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags`
CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libturbojpeg --cflags`
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lnx -ljson-c -lm `freetype-config --libs` -lturbojpeg
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.jpg)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).pfs0 $(OUTPUT).nro
$(OUTPUT).pfs0 : $(OUTPUT).nso
$(OUTPUT).nso : $(OUTPUT).elf
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf
endif
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

484
README.md Normal file
View file

@ -0,0 +1,484 @@
# nxdumptool
<img width="200" src="icon.jpg">
Nintendo Switch Dump Tool
## The application is being rewritten from scratch using a temporary private repository to improve things such as code quality, code maintenance and code readability. Improvements such as a proper GUI with touchscreen support and threaded events will be added.
## All the changes will be pushed to this repository once the new codebase reaches an usable + stable state. Sorry for any possible inconveniences.
Main features
--------------
* Generates NX Card Image (XCI) dumps from the inserted gamecard, with optional certificate removal and/or trimming.
* Generates installable Nintendo Submission Packages (NSP) from base applications, updates and DLCs stored in the inserted gamecard, SD card and eMMC storage devices.
* The generated dumps follow the `AuditingTool` format from Scene releases.
* Capable of generating dumps without console specific information (common ticket).
* Capable of generating ticket-less (standard crypto) dumps.
* Capable of generating dumps from installed updates/DLCs with missing base applications (orphan titles).
* Compatible with game pre-installs.
* Batch mode available, with customizable dump settings.
* Manual gamecard certificate dump.
* Manual ticket dump from installed SD/eMMC titles + optional removal of console specific data.
* Compatible with multigame carts.
* CRC32 checksum calculation for XCI/NSP dumps.
* XCI/NSP dump verification through CRC32 checksum lookup.
* Using offline XML database from NSWDB.COM (NSWreleases.xml) (XCI only).
* Performing an online lookup against the No-Intro database.
* Bundled-in update capabilities via libcurl.
* Update to the latest version by downloading it right from GitHub.
* Update the NSWDB.COM XML database.
* Precise HFS0 raw partition dumping, using the root HFS0 header from the gamecard.
* HFS0 partition file data dumping + browser with manual file dump support.
* Program NCA ExeFS/RomFS section & Data NCA RomFS section file data dumping + browser with manual file dump support.
* Compatible with base applications, updates and DLCs (if available).
* Supports manual RomFS directory dumping.
* Free SD card space checks in place.
* File splitting support for all operations.
* Capable of storing split XCI/NSP dumps in directories with the archive bit set.
* Sequential (multi-session) dump support, in case there's not enough storage space available for a XCI/NSP full dump.
* Metadata retrieval using NCM and NS services.
* Dump speed calculation, ETA calculation and progress bar.
Operations related to installed SD/eMMC titles require a keys file located at "sdmc:/switch/prod.keys". Use [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM) to generate it.
Please launch the application through title override (hold R while launching a game) whenever possible to avoid memory allocation problems.
Thanks to
--------------
* [MCMrARM](https://github.com/MCMrARM), for creating the original application.
* [RSDuck](https://github.com/RSDuck), for vba-next-switch port. Its UI menu code was taken as a basis for this application.
* [foen](https://github.com/foen), for giving me some pretty good hints about how to use the NCM service.
* [yellows8](https://github.com/yellows8), for helping me fix a silly bug in my implementation of some NCM service IPC calls.
* [SciresM](https://github.com/SciresM), for [hactool](https://github.com/SciresM/hactool) (licensed under [ISC](https://github.com/SciresM/hactool/blob/master/LICENSE)). Its NCA content handling procedures are reproduced in many parts of the application.
* [The-4n](https://github.com/The-4n), for [4NXCI](https://github.com/The-4n/4NXCI) (licensed under [ISC](https://github.com/The-4n/4NXCI/blob/master/LICENSE)) and [hacPack](https://github.com/The-4n/hacPack) (licensed under [GPLv2](https://github.com/The-4n/hacPack/blob/master/LICENSE)). The NCA content patching procedure used in 4NXCI is replicated in the application, as well as the NACP XML generation from hacPack.
* [shchmue](https://github.com/shchmue), for [Lockpick](https://github.com/shchmue/Lockpick) (licensed under [GPLv2](https://github.com/shchmue/Lockpick/blob/master/LICENSE)). It is used as a reference for the runtime key-collection algorithm needed for the NSP dump, ExeFS dump/browse and RomFS dump/browse procedures.
* Björn Samuelsson, for his [public domain CRC32 checksum calculation C-code](http://home.thep.lu.se/~bjorn/crc).
* [Adubbz](https://github.com/Adubbz), for [Tinfoil](https://github.com/Adubbz/Tinfoil) (licensed under [MIT](https://github.com/Adubbz/Tinfoil/blob/master/LICENSE)). Its wrappers for ES service IPC calls are used in the application.
* [WerWolv](https://github.com/WerWolv), for the SX OS detection procedure used in [EdiZon](https://github.com/WerWolv/EdiZon) (licensed under [GPLv2](https://github.com/WerWolv/EdiZon/blob/master/LICENSE)).
* ChaN, for the [FatFs module](http://elm-chan.org/fsw/ff/00index_e.html) (licensed under [FatFs license](http://elm-chan.org/fsw/ff/doc/appnote.html#license)). It is used to read ES savedata files from the BIS System partition.
* [hthh](https://github.com/hthh), for his [switch-reversing](https://github.com/hthh/switch-reversing) repository, which was very helpful to understand how to parse and read information from NSO binaries.
* The [LZ4 project](http://www.lz4.org), for the LZ4 C-code implementation (licensed under [BSD 2-Clause](https://github.com/lz4/lz4/blob/master/lib/LICENSE)).
* [AnalogMan](https://github.com/AnalogMan151) and [0Liam](https://github.com/0Liam), for their constant support and ideas.
* [RattletraPM](https://github.com/RattletraPM), for the awesome icon used in the application.
* [FennecTECH](https://github.com/fennectech), for testing / breaking stuff inside the application on a regular basis.
* The folks from NSWDB.COM and No-Intro.org, for being kind enough to put up a HTTP(S) endpoint(s) to perform CRC32 checksum lookups.
* The GNOME project, from which the [high contrast icons](https://commons.wikimedia.org/wiki/GNOME_High_contrast_icons) were retrieved.
* The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem.
* The Comfy Boyes, for being both awesome and supportive. You know who you are.
* My girlfriend, for putting up with me even though I dedicate most of my free time to this little project, and for doing her best to cheer me up and keep me going. I love you.
Donate
--------------
If you like my work and you'd like to support me in any way, it's not necessary, but a donation would be greatly appreciated!
[![Donate](https://img.shields.io/static/v1.svg?label=PayPal&message=Donate&color=blue&style=flat&logo=paypal)](https://paypal.me/DarkMatterCore)
Changelog
--------------
**v1.1.9:**
* Built using libnx commit d7e6207.
* Removed unnecessary code in NSP dumping steps.
* Improved GitHub JSON parsing code.
* Added NSP/ExeFS/RomFS support for titles with multiple Program NCAs (populated ID offset fields). Big thanks to [Cirosan](https://github.com/Cirosan) and [ITotalJustice](https://github.com/ITotalJustice) for testing!
* Fixed compatibility with consoles that use the new PRODINFO key generation scheme. Big thanks to dimitriblaiddyd78 from GBAtemp for reporting the issue and providing with testing!
* Fixed ExeFS/RomFS browsing/dumping support for bundled-in game updates in gamecards.
* Recursive directory removal after a failed HFS0/ExeFS/RomFS data dump is now optional.
* Fixed RomFS section dump support for titles that hold enough files in a single directory to exceed the max file count per directory limit in FAT32 (e.g. Animal Crossing: New Horizons).
* In order to overcome this problem, a secondary directory is created using the current RomFS directory name + a counter value (e.g. `/Model` -> `/Model_0`).
* This directory is used to save the rest of the data from the current RomFS directory until:
* All files from the current RomFS directory have been dumped, or...
* The directory reaches the max file count as well and another directory must be created to continue the process (e.g. `/Model_0` -> `/Model_1`).
* Big thanks to [Michael18751](https://github.com/Michael18751), [TechGeekGamer](https://github.com/TechGeekGamer) and [SusejLav](https://github.com/SusejLav) for testing!
* Button presses are now retrieved from all connected controllers.
* HOME button presses are now only blocked during dump operations. Fixes problems with homebrew forwarders and qlaunch replacements.
* Additionally, long HOME button presses are now blocked as well.
* Removed max entry count limit for HFS0/ExeFS/RomFS browsers. All filenames are now dynamically allocated, as it should have been from the very start.
* Updated NACP struct to reflect latest discoveries made by [0Liam](https://github.com/0Liam).
* The application now displays a FW update warning when the contents from an inserted gamecard can't be parsed because they use an unsupported NCA keygen. Thanks to [ITotalJustice](https://github.com/ITotalJustice) for spotting it!
This is only a bugfix release. I don't expect to release any new versions until the rewrite is finished - the only exception being fixing some kind of feature-breaking bug. Please understand.
**v1.1.8:**
* Added compatibility with latest devkitA64 and libnx releases. Thanks to [HookedBehemoth](https://github.com/HookedBehemoth) for porting the extra IPC calls used by the application to the new IPC system!
* Now using global title contexts instead of global variables for each different title property (ID, version, source storage, etc.). Simplifies metadata retrieval functions.
* Refactored the HFS0/IStorage parsing code, optimizing all gamecard reads performed by the application.
* Increased dump buffer sizes to 4 MiB.
* NCA content size is now calculated and displayed for all titles.
* Content size for updates and DLCs is displayed in the title selector from the NSP menus.
* Additionally, the application now displays the size for each title in the batch dump summary screen. Plus, an *approximate* total dump size is calculated according to the selected titles.
* Please bear in mind that the displayed information does not reflect output NSP dump sizes.
* Changes to the HFS0, ExeFS and RomFS browsers:
* File sizes are now displayed for all file entries.
* Dumping a file/directory won't reset the cursor position anymore.
* Displayed lists are now lexicographically sorted.
* It is now possible to perform CRC32 checksum lookups using the No-Intro database. Big thanks to the folks from No-Intro.org!
* This new method requires a working Internet connection at runtime.
* For XCI dumps, this is merely offered as an alternative to the NSWDB.COM XML database method, without replacing it. A new option has been added to the XCI dump menu, which lets the user select the verification method they wish to use.
* For NSP dumps, on the other hand, this offers a way to actually validate dumps:
* The "CRC32 checksum calculation" feature, which was a bit pointless, has been entirely removed. The new "Verify dump using No-Intro database" option has taken its place.
* NSP dump verification is achieved by just calculating the CRC32 checksum from the output CNMT NCA and performing a lookup using the No-Intro database. This works because of the way CNMT data is handled by the application:
* The SHA-256 checksum for each NCA is always recalculated during the dump process, and the CNMT NCA is always patched afterwards. However, if no NCA modifications are performed, the CNMT NCA ends up being identical to its original counterpart, because the content records won't have changed at all.
* This lets the application verify the NSP dump by performing a CRC32 checksum lookup using the CNMT NCA data, as long as no NCA modifications take place.
* As such, this method only works with SD card / eMMC titles, as long as the "Generate ticket-less dump" option is disabled.
* This option doesn't appear in gamecard-related menus, and it's not compatible with batch dumps.
* By popular demand, an option has been added in XCI, NSP and batch dump menus to change the naming scheme used with output files to the following:
* XCI dumps:
* Single game: `TitleName [TitleID][TitleVersion]`.
* Multigame: `TitleName1 [TitleID1][TitleVersion1] + TitleName2 [TitleID2][TitleVersion2] + ... + TitleNameN [TitleIDN][TitleVersionN]`.
* NSP/Batch dumps: `TitleName [TitleID][TitleVersion][TitleType]`.
* The "Remember dumped titles" feature available in batch mode isn't affected by this new setting - batch overrides will keep using the regular naming scheme.
* Added an option to include delta fragment NCAs in output NSP dumps from installed SD/eMMC updates. It is disabled by default.
* Added a small settings menu to the ExeFS/RomFS sections with the following options:
* `Split files bigger than 4 GiB (FAT32 support)`: unlike previous versions, it is now possible to control if file splitting will take place for ExeFS/RomFS file dumps, instead of always splitting them. If this option is enabled, files bigger than 4 GiB will now be split and stored in a subdirectory with the archive bit set (like NSPs).
* `Save data to CFW directory (LayeredFS)`: enabling this option will save output data to the directory from the CFW you're running, using the LayeredFS layout.
* Added a new option to the batch mode menu to control if the batch dump process should halt on any errors. If disabled, it'll make the batch dump process wait for 5 seconds on any errors, then it will keep going.
* Free SD card space is now always displayed on every UI state. It is also displayed and updated during batch mode operations.
* ExeFS submenu is now available for updates in the orphan content list (Y button menu).
* It is now possible to exit the application from the batch dump summary screen.
* A warning is now displayed in the main menu if the application is launched using applet mode. NSP dumps from base applications and updates can fail if there's not enough heap available to hold the uncompressed `main` NSO while generating the `programinfo.xml`.
* Improved XPath query used when looking for checksum matches in the NSWDB.COM XML database. Fixes CRC32 checksum lookup for multigame cartridges.
* Empty RomFS directories are now properly handled by the RomFS browser.
* Removed a BKTR RomFS section offset check that was causing trouble while trying to perform RomFS-related operations with some updates (e.g. Luigi's Mansion 3).
* Physical IStorage reads are now performed to retrieve NCAs from gamecards, instead of using `ncmContentStorageReadContentIdFile()`. Fixes gamecard NSP/ExeFS/RomFS operations under FW versions < 4.0.0.
* Fixed unaligned IStorage reads in manual file dumps files from HFS0 partitions in gamecards. Unaligned files dumped this way should no longer contain garbage data.
* Fixed a memory leak in the XML database verification code.
* Fixed an indexing bug in the RomFS browser that could potentially cause problems when performing any action from the root directory.
* Fixed gamecard hotswapping in gamecard-related submenus.
* Fixed a free SD card space check in sequential XCI/NSP dump procedures.
* Fixed a bug where the output dump name wouldn't be generated for orphan content when no base applications are installed, preventing the NSP dump procedure from starting. Thanks to [snes878](https://github.com/snes878) for reporting this!
* Fixed a bug that prevented to retrieve the ticket for a bundled-in gamecard update from the Secure HFS0 partition during a NSP dump procedure. Thanks to [snes878](https://github.com/snes878) for reporting this!
* Fixed a bug where a NSP dump process would stop if no personalized ticket certificate is found in the ES system savefile (e.g. when no titles with personalized titlekey crypto have been downloaded from the eShop). Thanks to [satel](https://gbatemp.net/members/satel.27798/) for reporting this!
* Fixed a bug where an empty orphan content list would have been generated if no base applications are installed. Thanks to `Newb_3DS#6287` for reporting this issue!
Thanks to [FennecTECH](https://github.com/fennectech) and MUXI from PSXTools forums for providing with testing!
**v1.1.7:**
* Tickets and RSA certificates are now properly parsed from their respective system savedata files, thanks to the efforts of [shchmue](https://github.com/shchmue)!
* Speeds up ticket / titlekey retrieval for NSP/ExeFS/RomFS operations.
* Removes the need to bundle RSA certificates inside the application. Yay!
* As a bonus, the new `XS00000024` personalized ticket certificate introduced in 9.0.0 is now supported. Thanks to [SimonTime](https://github.com/simontime) for providing insight on this matter!
* Added NSP dump support for pre-installed titles.
* If the selected title uses titlekey crypto and no ticket for it can be found, a prompt will be displayed, asking the user if they want to proceed anyway (even though content decryption won't be possible).
* This prompt will *not* appear in batch dump operations. The dump procedure will always go ahead.
* Sequential NSP dump operations will only display the prompt during their first run.
* Added a new Ticket submenu for SD/eMMC titles. It can be used to only dump the Ticket from a specific base application / update / DLC, without having to dump its entire NSP.
* Dumped tickets are stored in `sdmc:/switch/nxdumptool/Ticket`.
* A configurable option is also available to remove console specific data from dumped tickets.
* The encrypted + decrypted title key is displayed during the dumping process, along with the Rights ID for the title.
* Just so you know, if you want to dump tickets from base application updates bundled in gamecards, use the HFS0 browser.
* Added an option in NSP/batch dump menus to control the replacement of the NPDM RSA key/sig in Program NCAs from base applications and updates:
* Up until now, replacing both the public RSA key in the ACID section from the main.npdm file (ExeFS) and the NPDM header signature (NCA header) has been the default, non-configurable behaviour whenever Program NCA modifications were needed.
* This option is enabled by default - if Program NCA modifications are needed, disabling this option will make the output NSP require ACID patches to function properly under any CFW (but at the same time, it will make the Program NCA verifiable by PC tools).
* The rest of the possible Program NCA modifications (content distribution change and/or Rights ID removal + key area replacement) will be applied when needed, even if this option is disabled.
* Changes related to the orphan content menu (Y button):
* Parent base application name is now retrieved for orphan updates and DLCs whenever possible, and used in menus and output NSP dumps.
* Moved the orphan content hint from the orphan content menu to the SD/eMMC menu.
* Changed application behaviour regarding the Lockpick_RCM keys file existence:
* SD/eMMC menu and NSP/ExeFS/RomFS related operations are now disabled if the keys file at "sdmc:/switch/prod.keys" is not available.
* An error message telling the user to run Lockpick_RCM will be displayed in the main menu if the keys file is not available.
* Additionally, error messages related to data decryption will now also suggest the user to run Lockpick_RCM.
* Changes to the generated update NSPs (thanks to [The-4n](https://github.com/The-4n) and [suchmememanyskill](https://github.com/suchmememanyskill)):
* Delta Fragments are, again, always excluded from output NSP dumps, regardless of their source storage and the selected dump settings.
* Patch Extended Data is no longer wiped from the CNMT NCA in update NSPs - only the content records are replaced accordingly.
* Furthermore, content records from Delta Fragments are preserved as well.
* Fixed CNMT PFS0 block hash calculation when the total PFS0 size exceeds the hash block size from the PFS0 superblock in the NCA header. Removes the `0x236E02` / `2002-4535` error in Goldleaf about an invalid PFS0, triggered by update NSPs with long a CNMT PFS0 section.
* Changes to the generated NSP XMLs:
* `RequiredDownloadSystemVersion` and `IdOffset` elements from the CNMT XML are now properly retrieved from their true locations in the CNMT NCA.
* Added support for the `RuntimeParameterDelivery` NACP field (introduced in HOS 9.X).
* Added support for the `IARCGeneric` value in the `RatingAge` NACP field (introduced in HOS 9.X).
* Fixed handling of `PlayLogQueryableApplicationId` values.
* Big thanks to [0Liam](https://github.com/0Liam) for documenting these changes!
* Changes related to the application update feature:
* Added a forced update prompt if the application is already on the latest version.
* The application update option will now be disabled after a successful update.
* Removed the FS service reinitialize step after closing the application's RomFS at startup. This was done because `romfsExit()` didn't close all open file handles to the NRO when I tested it with libnx v2.2.0 some time ago, thus making the application update fail. Nonetheless, the problem has been fixed.
* Fixed UI flickering when HFS0 partition data can't be retrieved from the gamecard.
* Furthermore, a warning about `nogc` spoofing is now displayed under this particular case.
* Added an extra NSP offset validation step for sequential NSP dumps.
* Minor codestyle fixes.
Big thanks to [FennecTECH](https://github.com/fennectech) and `Hannah (Luna)#8459` for providing with **lots** of testing for this release!
PSA: if you downloaded any new games from the eShop after updating to 9.0.0+ and used a previous release of nxdumptool to dump NSPs **with console specific data**, please redump them - their RSA certificate chain isn't the proper one. Dumps without console specific data (or without a ticket) are not affected by this.
**v1.1.6:**
* Added sequential dump support: it is now possible to start a XCI/NSP dump procedure even if there's not enough space available in the SD card!
* No setting has to be modified in order to enable this feature - the application will automatically ask the user if they want to use this mode if there's not enough space for the full dump.
* At least 1 GiB (2^30 bytes) of free space must be available in order to trigger this feature.
* A file-based checkpoint system is used to keep track of the already dumped parts (à la Hekate).
* The part(s) generated in each run must be transferred to a PC before continuing the process - except for the `.xci.seq`/`.nsp.seq` files used to keep track of the current dump status.
* NSPs generated using this method will also include a `.nsp.hdr` file, which holds the PFS0 header data. The information from this header is filled after writing all the NCAs, thus it is saved as an additional file. This *must* be used as the first file (placed before `.nsp.00`) when merging all the parts into a full NSP.
* The following options are ignored when this feature is triggered:
* `Split output dump (FAT32 support)` (XCI/NSP). File splitting *will* take place, regardless of the filesystem used by the SD card. Additionally, the creation of a directory with the archive bit set isn't performed with NSP dumps.
* `Create directory with archive bit set` (XCI only).
* `CRC32 checksum calculation` (NSP only). CRC32 checksum calculation is still available for XCI dumps.
* This feature is *not* compatible with batch dump operations.
* General changes to batch dump operations:
* Entries from the summary list displayed in the batch dump menu can now be manually excluded from the dump operation before starting it.
* It is possible to disable all entries, enable all entries and/or handpick specific titles from the summary list, thus letting the user further customize the batch dump process.
* A new option has been added to keep track of previous successful dumps created using batch mode: "Remember dumped titles".
* If enabled, a 0-byte file will be created for each successful dump in a separate subdirectory.
* These files act as an override: they will make the application skip the titles they represent in later batch mode operations even if the "Skip already dumped titles" option is disabled.
* This is specially useful if someone wants to skip titles that have already been successfully dumped using batch mode - even more so if their NSPs have already been moved or deleted from the SD card.
* To restore the original behaviour, simply delete the contents from the "BatchOverrides" subdirectory inside "NSP".
* Free storage space is now properly recalculated after each successful dump during a batch mode operation.
* UI code cleanup:
* `uiDrawString()`, `uiGetStrWidth()` and `uiPrintOption()` are now compatible with variable argument lists, removing the need to format a string beforehand and pass its variable to any of those functions.
* Preprocessor definitions are now used to specify RGB colors and for calculating vertical line coordinates, greatly simplifying calls to UI functions.
* Menu code now properly waits for any user input before drawing changes to the screen.
* Other minor coordinate fixes.
* The application is now capable of automatically reading/saving dump settings from/to a configuration file.
* The "Split output dump" option is, once again, enabled by default. FAT32 is the recommended filesystem for Switch SD cards if someone wants to use homebrew applications, so it's only logical to do this.
* Filenames for NACP icons in NSPs now properly reflect the NCA ID from its respective content file if it was modified.
* Fixed a bug that prevented to dump a specific file in the RomFS section from any update.
* Fixed a bug in the RomFS block collision check code that prevented to generate NSP dumps from certain titles with a RomFS section in Control/Manual NCAs that falls under an edge case that wasn't being handled properly. Thanks to [Zet-sensei](https://github.com/Zet-sensei) for reporting this problem!
Thanks to [FennecTECH](https://github.com/fennectech) for providing with testing!
**v1.1.5:**
* Built with latest libnx release, in order to fix HID problems under HOS 9.0.0+.
* Added support for Korean and Chinese character sets.
* Added browsing/dumping support for RomFS sections in Data NCAs from DLCs.
* Compatible with orphan DLCs (Y button) as well.
* Output directories for ExeFS/RomFS operations are now properly tagged as "(BASE)", "(UPD)" or "(DLC)" (RomFS only), depending on the title type being processed.
* Some measures have been taken to help speed up dumping operations:
* CPU boost mode type 1 is now used with `appletSetCpuBoostMode` - only effective under HOS 7.0.0+!
* Removed the need for dynamic memory allocations in NCA AES-CTR block decryption/encryption steps.
* Although these changes get me some extra ~4 MiB/s in most operations, keep in mind this doesn't do much to help with RomFS dumps from titles with lots of (small) file entries. Even so, although the calculated ETA can sometimes be discouraging, the dump most likely *won't* take that much time - just let the process advance until it hits bigger files. Sequential write speeds for the inserted SD card still play a huge role in these cases.
* Moved base output directory from "sdmc:/nxdumptool/" to "sdmc:/switch/nxdumptool/". Both the NSWreleases.xml file and the NRO binary are also expected to be inside this directory.
**v1.1.4:**
* Fixed building with latest libnx release.
* Optimized RomFS recursive file dump function to not rely on code recursion as much as before, avoiding stack memory exhaustion problems. Fixes crashes while dumping RomFS data from games with lots of file entries.
* Fixed a bug that made file splitting not take place while manually dumping a file bigger than 4 GiB from the RomFS section of any title.
* Reduced max part size for split files to `0xFFFF0000` bytes in all operations (except for XCI dumps when the "Create directory with archive bit set" option is disabled). Fixes file access problems if the parts are used inside a directory with the archive bit set.
* Removed the `removeDirectory()` function. `fsdevDeleteDirectoryRecursively()` is now used instead.
* If a HFS0/ExeFS/RomFS data dump operation is cancelled or fails, a message telling the user to wait until the output directory is fully deleted will now be displayed.
* Improved the cancel button detection mechanism. Regardless of the ongoing operation, holding the button for 2 seconds will now consistently cancel it.
* Progress bar movement is now smoother.
**v1.1.3:**
* General changes to the NSP dumping procedure:
* Corrected and updated CNMT XML and NACP XML generation. Thanks to [0Liam](https://github.com/0Liam)!
* Added NACP icon retrieval for each available language.
* Added legalinfo.xml retrieval.
* Added programinfo.xml generation.
* Changed the PFS0 file order to the following:
1. NCA content files.
2. CNMT NCA.
3. CNMT XML.
4. programinfo.xml (if available).
5. NACP icons (if available).
6. NACP XML (if available).
7. legalinfo.xml (if available).
8. Ticket + Certificate chain (if available).
* These changes essentially make the NSP dumps generated by the application comparable to Scene releases that follow the `AuditingTool` format (like those from groups like BigBlueBox or JRP), as long as the "Remove console specific data" option is enabled and the "Generate ticket-less dump" option is disabled. Happy dumping!
* Because of this, dumping update NSPs from gamecards will require the keys file at "sdmc:/switch/prod.keys" from now on (but only if the bundled update uses titlekey crypto). Base applications and DLCs can still be dumped from gamecards without needing a keys file.
* Added ExeFS/RomFS browsing/dumping from game updates.
* Upon entering ExeFS/RomFS menus, it is now possible to select which update is going to be used for ExeFS/RomFS procedures.
* In order to dump ExeFS/RomFS content from a installed update for a gamecard title, its respective gamecard must be inserted in the console.
* Likewise, in order to dump ExeFS/RomFS content from a installed update for a SD/eMMC title, its respective base application must be already installed as well.
* Added NSP batch dump mode. Press X while on the SD/eMMC title list to configure the batch dump options and start the process. Supports skipping already dumped titles, dumping selected title types (base applications, updates, DLCs) and dumping titles from a specific source storage (SD, eMMC).
* Added manual directory dumping feature to the RomFS browser. Just enter the directory to be dumped and then press the Y button.
* Added a forced XCI dump option when either the gamecard base application count or their Title IDs can't be retrieved (useful for rare Kiosk gamecards). Press Y at the error message screen to dump the cartridge image to "gamecard.xci".
* Dumped content information is now displayed in the gamecard menu.
* Additionally, if the XCI has already been dumped, information about it will be displayed as well.
* The displayed information about dumped content is now updated after each new dump procedure in both gamecard and SD/eMMC menus.
* The NPDM ACID patching procedure is now performed with Program NCAs from bundled gamecard updates and SD/eMMC titles if the "Generate ticket-less dump" option is enabled.
* Fixed XCI dumping under SX OS.
* Fixed a bug in the DLC NSP dump submenu that made it impossible to change the DLC to be dumped from the selected base application if more than a single DLC is available for it. Thanks to [ckurtz22](https://github.com/ckurtz22)!
* Fixed a bug that made the application get stuck in an endless loop after selecting the SD/eMMC dump option from the main menu if no SD/eMMC titles are available. Thanks to [ckurtz22](https://github.com/ckurtz22)!
* Fixed a bug that made the application return an empty title list if no SD card is inserted or if it contains a "Nintendo" directory from another console (even if there are installed titles in the eMMC). Thanks to [ckurtz22](https://github.com/ckurtz22)!
**v1.1.2:**
* Delta fragment NCAs are now included in update NSPs dumped from SD/eMMC if the "Generate ticket-less dump" option is disabled.
* It is now possible to generate ticket-less NSP dumps from bundled updates in gamecards. Please bear in mind that this option requires the external "sdmc:/switch/prod.keys" file.
* UI tweaks:
* The application now keeps track of the selected title in SD/eMMC and "orphan" content modes when entering a menu and then going back to the list.
* After selecting a title in the SD/eMMC menu, information about content already dumped related to the selected title will now be displayed (BASE / UPD / DLC).
* Likewise, after selecting a title in the "orphan" title list (Y button), an additional line will now display if the selected title has been dumped or not.
* This also informs the user if the dumps contain console-specific data.
* Three additional entries will now be displayed in the "orphan" title list.
* Upwards and downwards arrows will now be displayed for lists that exceed the max element count.
* Because of this change, max element count for the SD/eMMC title list had to be reduced from 4 to 3.
* Leftwards and rightwards arrowheads are now displayed in menus with options.
* A "hint" message is now displayed in the "orphan" content mode to let the user know they'll be able to find gamecard updates in that section.
* If a file has been already dumped, the application will display a prompt asking the user if they want to proceed anyway or not. This doesn't apply to full HFS0/ExeFS/RomFS data dumps.
* It is now possible to jump from the first list element to the last one and viceversa using the D-Pad Up/Down and Left Stick Up/Down. The Right Stick is still used exclusively for fast scrolling and won't be affected by this change.
* Fixed a bug where NSP/ExeFS/RomFS dumping would fail if the written entry count returned by `ncmContentMetaDatabaseListApplication()` didn't match the total entry count for the selected NSP dump type.
* Fixed a bug where NSP/ExeFS/RomFS dumping would fail if an invalid title index was used with `ncmContentMetaDatabaseGet()`.
Thanks to [Maschell](https://github.com/Maschell), [DuIslingr](https://github.com/DuIslingr) and MUXI from PSXTools forums for reporting these bugs and providing with testing!
**v1.1.1:**
* Project name changed to `nxdumptool`. This is no longer a gamecard-only tool.
* Added ExeFS dumping/browsing support. This feature, along with the already available RomFS options, makes the application an excellent tool for modders!
* Added compatibility with FS process memory layout in the key retrieval procedure while using emuMMC. Thanks to [shchmue](https://github.com/shchmue)!
* Due to public demand, NSP dumping, ExeFS dumping/browsing and RomFS dumping/browsing support has been added for base applications, updates and DLCs available in both SD card and eMMC!
* Now it's possible to select the source storage device (gamecard, SD card / eMMC) for any operation right after launching the application.
* The gamecard submenu works exactly like the main menu has worked up to this point (except for the update options, which are now displayed in the new main menu).
* The SD card / eMMC submenu shows installed base applications along with their icons. Upon selecting a title, a submenu with NSP and RomFS options will show up. It's also possible to dump updates/DLCs for an installed base application this way.
* If there's installed content (updates/DLCs) with missing base application titles, pressing Y on the SD card / eMMC submenu will display this "orphan" content list and let you dump titles from it nonetheless.
* It is possible to generate console-specific NSP dumps, dumps with modified tickets to remove console-specific data, and ticket-less dumps with standard NCA key area crypto.
* Two new options are available in the NSP dump submenus for SD/eMMC titles: "Remove console specific data" and "Generate ticket-less dump". The latter won't appear if the former isn't enabled.
* "Remove console specific data" cleans console specific data fields from a "personalized" ticket and replaces its RSA titlekey block with a 16-byte encrypted titlekey, essentially converting it to a "common" ticket. This option has no effect if the title already uses a "common" ticket.
* "Generate ticket-less dump" goes another step ahead by cleaning up the Rights ID field in every NCA content file that includes it, stores the decrypted titlekey in the NCA key area and then encrypts this area using standard crypto, removing the need for a tik/cert combination.
* Console-specific NSP dumps and dumps with modified tickets include both `tik` and `cert` files.
* All NSP dumps generated from installed SD/eMMC titles include both `.cnmt.xml` and `.nacp.xml` files whenever possible.
* Sadly, due to limitations in the methods currently used to perform key retrieval/derivation at runtime, NSP dumping, ExeFS dumping/browsing and RomFS dumping/browsing for SD/eMMC titles require the "sdmc:/switch/prod.keys" file. Specifically, these are the needed keys:
* `eticket_rsa_kek`.
* `titlekek_##` (varies from `00` to `1F`).
* Additionally, ticket-less NSP dumps for SD/eMMC titles also require the following keys:
* `key_area_key_application_##` (varies from `00` to `1F`).
* `key_area_key_ocean_##` (varies from `00` to `1F`).
* `key_area_key_system_##` (varies from `00` to `1F`).
* All gamecard-related operations can still be performed without the need for a keys file!
* Output data generated by the application will now be saved to its corresponding subdirectory in "sdmc:/nxdumptool/":
* XCI dumps: "sdmc:/nxdumptool/XCI/".
* NSP dumps: "sdmc:/nxdumptool/NSP/".
* HFS0 data: "sdmc:/nxdumptool/HFS0/".
* ExeFS data: "sdmc:/nxdumptool/ExeFS/".
* RomFS data: "sdmc:/nxdumptool/RomFS/".
* Certificate dumps: "sdmc:/nxdumptool/Certificate/".
* The location for the NSWDB.COM XML database has been moved to "sdmc:/nxdumptool/NSWreleases.xml".
* Tickets from updates with titlekey crypto dumped from gamecards are now converted to regular "common" tickets before being written to the output NSP dump.
* The content distribution type for updates dumped from custom XCIs mounted through SX OS is now set to "download".
* Fixed a NCM service handle exhaustion bug if an error ocurred while reading the RomFS section entry from the Program NCA for any base application.
* Changed the application icon yet again. Big thanks to RattletraPM!
* Minor changes and other various general fixes.
Thanks to [simontime](https://github.com/simontime) for helping me out with the RSA certificate chain retrieval process! Also thanks to MUXI from PSXTools forums for providing with testing!
**v1.1.0:**
* Replaced the application icon with a new, stylish one made by RattletraPM. Thanks a lot!
* Gamecard base application icons are now retrieved and displayed in the menu.
* L/ZL/R/ZR buttons can now be used to change the displayed base application info if a multigame cart is inserted, instead of displaying everything right away.
* The Nintendo Extension shared font is now used to display bitmaps representing controller buttons and sticks instead of just using text to reference them.
* Replaced the mbedtls-based AES and SHA-256 implementations with functions from the hardware accelerated cryptography API from libnx.
* Added an option to generate split XCI dumps using a directory with the archive bit set, just like split NSP dumps. It will only appear if "Split output dump" is enabled.
* Fixed ETA calculation.
* Enabled ETA calculation in full HFS0 partition data dumps.
* Fixed CRC32 checksum calculation for gamecard certificate dumps.
* Added Program NCA RomFS section parser:
- Supports filesystem dumping, filesystem browsing, manual file dumping and file splitting. Enjoy datamining your gamecards!
- Compatible with multigame carts. You'll be able to choose which base application RomFS will be dumped/browsed from a submenu.
- Output files will be saved to: "sdmc:/[GameName] v[GameVersion] ([TitleID]) (RomFS)/".
* Added high contrast directory/file icons from GNOME project to file browsing modes (HFS0 / RomFS).
* Fixed the NSP generation code (based on 4NXCI / hacPack):
- Delta Fragment NCAs are now discarded.
- The SHA-256 checksum is recalculated for every NCA content after being modified, resulting in new NCA IDs.
- The ACID public key is replaced in the NPDM section from the Program NCA. All the related NCA/PFS0 Superblock SHA-256 hashes are recalculated.
- The NPDM signature in the Program NCA header is now replaced as well.
- The content records from the Application CNMT are updated with proper SHA-256 hashes and new NCA IDs. All the related NCA/PFS0 Superblock hashes are recalculated.
- NACP XMLs are now generated as well.
- Because of all these changes, the CRC32 checksum can't be calculated until the dump procedure is complete.
- If this option is enabled, the application will take extra time after the NSP dump has been completed to calculate the CRC32 checksum. Nonetheless, you'll be able to cancel this procedure.
- A warning message will appear in the NSP dump menu if CRC32 checksum calculation is enabled to inform the user about this extra step.
- Furthermore, the output CRC32 checksum will be different on each new dump. This is because the NPDM signature in the Program NCA header uses a random seed.
- This effectively makes the generated NSPs only need ES patches to work. ACID patches shouldn't be needed anymore.
* Added NSP dumping support for Patch and AddOnContent title types with gamecards that include bundled Updates/DLCs:
- The information displayed in the main menu now shows how many Updates/DLCs are bundled in the inserted gamecard (per application and in total).
- If a bundled gamecard update features a populated Rights ID bitfield, both its Ticket and Certificate will get added to the output NSP.
- Additionally, the NSP dump menu has been divided in three subcategories: base application, update and DLC.
- Each submenu will only appear if the inserted gamecard holds at least one title belonging to the category it represents.
- If only the base application is included, like most gamecards, choosing the NSP dump option in the main menu will take you right to the base application dump menu.
- Once you enter a submenu, you'll be able to choose exactly which title to dump belonging to that category.
- Output update NSPs will not be modified in any way. Thus, unlike NSPs from base applications and DLCs, their CRC32 checksums will always be the same.
* Fixed the minimum system version field size in the extended CNMT header struct. Thanks to [0Liam](https://github.com/0Liam)!
* Changed the naming convention for output NSP dumps:
- Base application: "sdmc:/[GameName] v[GameVersion] ([TitleID]) (BASE).nsp".
- Update: "sdmc:/[GameName] v[UpdateVersion] ([UpdateTitleID]) (UPD).nsp".
- If a matching base application isn't found: "sdmc:/[UpdateTitleID] v[UpdateVersion] (UPD).nsp".
- DLC: "sdmc:/[GameName] v[DLCVersion] ([DLCTitleID]) (DLC).nsp".
- If a matching base application isn't found: "sdmc:/[DLCTitleID] v[DLCVersion] (DLC).nsp".
* The application is now able to retrieve the NCA header key and perform NCA key area decryption at runtime, using the SPL services. Thus, is isn't needed to run Lockpick beforehand anymore to dump NSPs (nor to dump/browse RomFS data).
* If the inserted gamecard includes a bundled update, its version number will now be used in the output filename for XCI, HFS0 and gamecard certificate dumps.
* Minor improvements to the file splitting code.
- Additionally, the filename for the current part will now be displayed and updated for all operations if file splitting is enabled.
* The application update feature will now use the launch path from argv if it's available. Otherwise, it defaults to "sdmc:/switch/gcdumptool.nro".
* Cosmetic fixes to the UI layout.
* NCM service resources are now properly closed.
* Removed unnecessary service (de)initializations.
Big thanks to PatrickD85, unvaluablespace, wartutor and Slim45 for testing these changes!
**v1.0.8:**
* Added proper metadata reading from multigame carts.
* Added gamecard -> NSP dump option:
- Compatible with file splitting (for FAT32 support). The same layout from splitNSP.py is used: a directory with numbered part files (00, 01, etc.). The archive bit is enabled right away in this directory to allow HOS to treat it as if it were a whole file. This way, it can be used with any application with NSP-handling capabilities.
- Compatible with CRC32 checksum calculation. Disclaimer: NSP dumps can't be verified against the XML database.
- Output NSPs contain a metadata XML file based on the information from the CNMT NCA for the application, which is decrypted using code from hactool. The necessary keyset is loaded from "sdmc:/switch/prod.keys", which can be generated using Lockpick.
- If a multigame cart is used, you'll be able to choose which application to dump from the menu.
* Dump verification process tweaked for multigame carts: it'll now look for a possible checksum match using the Title IDs from all bundled applications.
* Improved error reporting in dumper.c when a write operation fails. Furthermore, if a write error is produced when trying to write data to an offset past the FAT32 file size limit (0xFFFFFFFF bytes), the application will suggest the user to enable the file splitting option.
* Tweaked part sizes for splitted dumps: XCI/raw partition/manual file dump part size now matches the one used by XCI-Cutter, while the NSP part size matches the one used by splitNSP.py.
* Minor fixes to the UI code.
**v1.0.7:**
* Fixed a segmentation fault when trying to free an invalid XML node data pointer when a Scene release from NSWReleases.xml with a matching Title ID misses data related to that node.
* Added a message suggesting the user to restart the application after a successful update.
**v1.0.6:**
* Updated application codebase in order to make it compatible with the latest devkitA64 and libnx releases.
* Removed some fs-srv service functions from fsext.c/h that have been included in libnx (and fixed the ones that haven't).
* Revamped the GFX code to replace the 8x8 ASCII font with the shared system font, using the pl service and FreeType.
* Enabled (and fixed) the in-app update option. HTTPS compatibility is achieved through the mbedtls portlib.
* Disabled screen dimming and auto sleep.
* Added file counter to partition browser.
* Changed the naming convention for split gamecard dumps to *.xc[part number], in order to make them compatible with SX OS and other tools right away.
* Increased the delay after inserting a new gamecard by 1 second.
* Added a gamecard detection thread to monitor gamecard state changes in a better way. This thread is hooked to a gamecard detection kernel handle retrieved through an IEventNotifier object.
* Replaced partition filesystem mounting through fs-srv service calls with manual HFS0 partition header parsing. This should fix issues when browsing the Logo partition from type 0x02 gamecards.
* Blocked HOME button presses when running as a regular/system application instead of an applet. A warning message will be displayed whenever any operation is started if the application is running as an applet.
* Added detection for bundled FW versions 6.0.0 - 8.0.0.
**v1.0.5:**
* Fixed gamecard version reading (now using the ncm service instead of retrieving it from the cached Control.nacp).
* Added ability to read and identify FW update versions bundled with gamecards.
* In case an error occurs while reading the gamecard Title ID, the application will also display the FW version update bundled with it along with an explanation.
* Removed output XCI dump renaming based on the XML database from nswdb.com.
* Output naming scheme changed. Characters out of the ASCII range are replaced with underscores:
- XCI dump: "sdmc:/[GameName] v[GameVersion] ([TitleID]).xci".
- Raw partition dump: "sdmc:/[GameName] v[GameVersion] ([TitleID]) - Partition [PartitionIndex] ([PartitionName]).hfs0".
- Partition data dump (directory): "sdmc:/[GameName] v[GameVersion] ([TitleID]) - Partition [PartitionIndex] ([PartitionName])/".
- Certificate dump: "sdmc:/[GameName] v[GameVersion] ([TitleID]) - Certificate ([CRC32]).bin".
* Manual file dumps will now be saved to their corresponding directory instead of the SD card root.
* Added a XML database update option in the main menu.
* Added an update application option in the main menu. It isn't working at this moment because libcurl has problems dealing with secure connections, and as such the option has been disabled (pressing A on it does nothing). Nonetheless, the code to parse JSON responses from the GitHub API is pretty much ready, so it's just a matter of time.
**v1.0.4:**
* exFAT mode turned on by default.
* Replaced padding option with a trim output dump option (same as XCI-Cutter).
* Added dump speed and ETA calculation.
* Added XCI dump verification using XML database from nswdb.com (NSWreleases.xml). The file must be saved to the SD card root directory. Also, keep in mind that dump verification is only performed if you choose to create a full dump (with or without cert), not a trimmed one.
* Made CRC32 checksum calculation + XCI dump verification a configurable option.
* Output XCI dumps will get renamed to their corresponding Scene release if a match is found using the XML database from nswdb.com (e.g. "sdmc:/0100000000010000_20180625-234930.xci" -> "sdmc:/Super.Mario.Odyssey.NSW-BigBlueBox.xci").
**v1.0.3:**
* Made the 0xFF padding feature a configurable option.
* Added CRC32 checksum calculation for XCI dumps.
**v1.0.2:**
* Fixed a silly bug in the file splitting code.
**v1.0.1:**
* Minor UI fixes and tweaks.
* Added some missing Title ID checks in uiLoop().
* All calls to uiStatusMsg() are now properly identified.
* Increased wait time to 2 seconds when a new gamecard is detected.
**v1.0.0:**
Initial release.

BIN
icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

5577
source/dumper.c Normal file

File diff suppressed because it is too large Load diff

71
source/dumper.h Normal file
View file

@ -0,0 +1,71 @@
#pragma once
#ifndef __DUMPER_H__
#define __DUMPER_H__
#include <switch.h>
#include "util.h"
#define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF // 4 GiB - 1 (4294967295 bytes)
#define SPLIT_FILE_XCI_PART_SIZE (u64)0xFFFF8000 // 4 GiB - 0x8000 (4294934528 bytes) (based on XCI-Cutter)
#define SPLIT_FILE_NSP_PART_SIZE (u64)0xFFFF0000 // 4 GiB - 0x10000 (4294901760 bytes) (based on splitNSP.py)
#define SPLIT_FILE_GENERIC_PART_SIZE SPLIT_FILE_NSP_PART_SIZE
#define SPLIT_FILE_SEQUENTIAL_SIZE (u64)0x40000000 // 1 GiB (used for sequential dumps when there's not enough storage space available)
#define CERT_OFFSET 0x7000
#define CERT_SIZE 0x200
typedef struct {
bool keepCert; // Original value for the "Keep certificate" option. Overrides the selected setting in the current session
bool trimDump; // Original value for the "Trim output dump" option. Overrides the selected setting in the current session
bool calcCrc; // "CRC32 checksum calculation + dump verification" option
u8 partNumber; // Next part number
u32 partitionIndex; // IStorage partition index
u64 partitionOffset; // IStorage partition offset
u32 certCrc; // CRC32 checksum accumulator (XCI with cert). Only used if calcCrc == true and keepCert == true
u32 certlessCrc; // CRC32 checksum accumulator (certless XCI). Only used if calcCrc == true
} PACKED sequentialXciCtx;
// This struct is followed by 'ncaCount' SHA-256 checksums in the output file and 'programNcaModCount' modified + reencrypted Program NCA headers
// The modified NCA headers are only needed if their NPDM signature is replaced (it uses cryptographically secure random numbers). The RSA public key used in the ACID section from the main.npdm file is constant, so we don't need to keep track of that
typedef struct {
NcmStorageId storageId; // Source storage from which the data is dumped
bool removeConsoleData; // Original value for the "Remove console specific data" option. Overrides the selected setting in the current session
bool tiklessDump; // Original value for the "Generate ticket-less dump" option. Overrides the selected setting in the current session. Ignored if removeConsoleData == false
bool npdmAcidRsaPatch; // Original value for the "Change NPDM RSA key/sig in Program NCA" option. Overrides the selected setting in the current session
bool preInstall; // Indicates if we're dealing with a preinstalled title - e.g. if the user already accepted the missing ticket prompt
u8 partNumber; // Next part number
u32 pfs0FileCount; // PFS0 file count
u32 ncaCount; // NCA count
u32 programNcaModCount; // Program NCA mod count
u32 fileIndex; // Current PFS0 file entry index
u64 fileOffset; // Current PFS0 file entry offset
Sha256Context hashCtx; // Current NCA SHA-256 checksum context. Only used when dealing with the same NCA between different parts
} PACKED sequentialNspCtx;
typedef struct {
bool enabled;
nspDumpType titleType;
u32 titleIndex;
u64 contentSize;
char *contentSizeStr;
char nspFilename[NAME_BUF_LEN];
char truncatedNspFilename[NAME_BUF_LEN];
} batchEntry;
bool dumpNXCardImage(xciOptions *xciDumpCfg);
int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch);
int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg);
bool dumpRawHfs0Partition(u32 partition, bool doSplitting);
bool dumpHfs0PartitionData(u32 partition, bool doSplitting);
bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, bool doSplitting);
bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg);
bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg);
bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg);
bool dumpGameCardCertificate();
bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOptions *tikDumpCfg);
#endif

1163
source/keys.c Normal file

File diff suppressed because it is too large Load diff

65
source/keys.h Normal file
View file

@ -0,0 +1,65 @@
#pragma once
#ifndef __KEYS_H__
#define __KEYS_H__
#include <switch.h>
#include "nca.h"
#define FS_TID (u64)0x0100000000000000
#define SEG_TEXT BIT(0)
#define SEG_RODATA BIT(1)
#define SEG_DATA BIT(2)
#define ETICKET_DEVKEY_RSA_CTR_SIZE 0x10
#define ETICKET_DEVKEY_RSA_OFFSET ETICKET_DEVKEY_RSA_CTR_SIZE
#define ETICKET_DEVKEY_RSA_SIZE 0x230
#define SIGTYPE_RSA2048_SHA1 (u32)0x10001
#define SIGTYPE_RSA2048_SHA256 (u32)0x10004
typedef struct {
u64 titleID;
u8 mask;
u8 *data;
u64 dataSize;
} keyLocation;
typedef struct {
char name[128];
u8 hash[SHA256_HASH_SIZE];
u64 size;
} keyInfo;
typedef struct {
u16 memory_key_cnt; /* Key counter for keys retrieved from memory. */
u16 ext_key_cnt; /* Key counter for keys retrieved from keysfile. */
u32 total_key_cnt; /* Total key counter. */
// Needed to decrypt the NCA header using AES-128-XTS
u8 header_kek_source[0x10]; /* Seed for header kek. Retrieved from the .rodata section in the FS sysmodule. */
u8 header_key_source[0x20]; /* Seed for NCA header key. Retrieved from the .data section in the FS sysmodule. */
u8 header_kek[0x10]; /* NCA header kek. Generated from header_kek_source. */
u8 header_key[0x20]; /* NCA header key. Generated from header_kek and header_key_source. */
// Needed to derive the KAEK used to decrypt the NCA key area
u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. Retrieved from the .rodata section in the FS sysmodule. */
u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. Retrieved from the .rodata section in the FS sysmodule. */
u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. Retrieved from the .rodata section in the FS sysmodule. */
// Needed to decrypt the title key block from an eTicket. Retrieved from the Lockpick_RCM keys file.
u8 eticket_rsa_kek[0x10]; /* eTicket RSA kek. */
u8 titlekeks[0x20][0x10]; /* Title key encryption keys. */
// Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file.
u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */
} nca_keyset_t;
bool loadMemoryKeys();
bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out);
bool loadExternalKeys();
int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key);
bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
#endif

177
source/main.c Normal file
View file

@ -0,0 +1,177 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include "ui.h"
#include "util.h"
int main(int argc, char *argv[])
{
int ret = 0;
bool exitMainLoop = false;
/* Initialize application resources */
if (!initApplicationResources(argc, argv))
{
ret = -1;
goto out;
}
/* Main application loop */
while(appletMainLoop())
{
UIResult result = uiProcess();
switch(result)
{
case resultShowMainMenu:
uiSetState(stateMainMenu);
break;
case resultShowGameCardMenu:
uiSetState(stateGameCardMenu);
break;
case resultShowXciDumpMenu:
uiSetState(stateXciDumpMenu);
break;
case resultDumpXci:
uiSetState(stateDumpXci);
break;
case resultShowNspDumpMenu:
uiSetState(stateNspDumpMenu);
break;
case resultShowNspAppDumpMenu:
uiSetState(stateNspAppDumpMenu);
break;
case resultShowNspPatchDumpMenu:
uiSetState(stateNspPatchDumpMenu);
break;
case resultShowNspAddOnDumpMenu:
uiSetState(stateNspAddOnDumpMenu);
break;
case resultDumpNsp:
uiSetState(stateDumpNsp);
break;
case resultShowHfs0Menu:
uiSetState(stateHfs0Menu);
break;
case resultShowRawHfs0PartitionDumpMenu:
uiSetState(stateRawHfs0PartitionDumpMenu);
break;
case resultDumpRawHfs0Partition:
uiSetState(stateDumpRawHfs0Partition);
break;
case resultShowHfs0PartitionDataDumpMenu:
uiSetState(stateHfs0PartitionDataDumpMenu);
break;
case resultDumpHfs0PartitionData:
uiSetState(stateDumpHfs0PartitionData);
break;
case resultShowHfs0BrowserMenu:
uiSetState(stateHfs0BrowserMenu);
break;
case resultHfs0BrowserGetList:
uiSetState(stateHfs0BrowserGetList);
break;
case resultShowHfs0Browser:
uiSetState(stateHfs0Browser);
break;
case resultHfs0BrowserCopyFile:
uiSetState(stateHfs0BrowserCopyFile);
break;
case resultShowExeFsMenu:
uiSetState(stateExeFsMenu);
break;
case resultShowExeFsSectionDataDumpMenu:
uiSetState(stateExeFsSectionDataDumpMenu);
break;
case resultDumpExeFsSectionData:
uiSetState(stateDumpExeFsSectionData);
break;
case resultShowExeFsSectionBrowserMenu:
uiSetState(stateExeFsSectionBrowserMenu);
break;
case resultExeFsSectionBrowserGetList:
uiSetState(stateExeFsSectionBrowserGetList);
break;
case resultShowExeFsSectionBrowser:
uiSetState(stateExeFsSectionBrowser);
break;
case resultExeFsSectionBrowserCopyFile:
uiSetState(stateExeFsSectionBrowserCopyFile);
break;
case resultShowRomFsMenu:
uiSetState(stateRomFsMenu);
break;
case resultShowRomFsSectionDataDumpMenu:
uiSetState(stateRomFsSectionDataDumpMenu);
break;
case resultDumpRomFsSectionData:
uiSetState(stateDumpRomFsSectionData);
break;
case resultShowRomFsSectionBrowserMenu:
uiSetState(stateRomFsSectionBrowserMenu);
break;
case resultRomFsSectionBrowserGetEntries:
uiSetState(stateRomFsSectionBrowserGetEntries);
break;
case resultShowRomFsSectionBrowser:
uiSetState(stateRomFsSectionBrowser);
break;
case resultRomFsSectionBrowserChangeDir:
uiSetState(stateRomFsSectionBrowserChangeDir);
break;
case resultRomFsSectionBrowserCopyFile:
uiSetState(stateRomFsSectionBrowserCopyFile);
break;
case resultRomFsSectionBrowserCopyDir:
uiSetState(stateRomFsSectionBrowserCopyDir);
break;
case resultDumpGameCardCertificate:
uiSetState(stateDumpGameCardCertificate);
break;
case resultShowSdCardEmmcMenu:
uiSetState(stateSdCardEmmcMenu);
break;
case resultShowSdCardEmmcTitleMenu:
uiSetState(stateSdCardEmmcTitleMenu);
break;
case resultShowSdCardEmmcOrphanPatchAddOnMenu:
uiSetState(stateSdCardEmmcOrphanPatchAddOnMenu);
break;
case resultShowSdCardEmmcBatchModeMenu:
uiSetState(stateSdCardEmmcBatchModeMenu);
break;
case resultSdCardEmmcBatchDump:
uiSetState(stateSdCardEmmcBatchDump);
break;
case resultShowTicketMenu:
uiSetState(stateTicketMenu);
break;
case resultDumpTicket:
uiSetState(stateDumpTicket);
break;
case resultShowUpdateMenu:
uiSetState(stateUpdateMenu);
break;
case resultUpdateNSWDBXml:
uiSetState(stateUpdateNSWDBXml);
break;
case resultUpdateApplication:
uiSetState(stateUpdateApplication);
break;
case resultExit:
exitMainLoop = true;
break;
default:
break;
}
if (exitMainLoop) break;
}
out:
/* Deinitialize application resources */
deinitApplicationResources();
return ret;
}

3465
source/nca.c Normal file

File diff suppressed because it is too large Load diff

761
source/nca.h Normal file
View file

@ -0,0 +1,761 @@
#pragma once
#ifndef __NCA_H__
#define __NCA_H__
#include <switch.h>
#define NCA3_MAGIC (u32)0x4E434133 // "NCA3"
#define NCA2_MAGIC (u32)0x4E434132 // "NCA2"
#define NCA_HEADER_LENGTH 0x400
#define NCA_SECTION_HEADER_LENGTH 0x200
#define NCA_SECTION_HEADER_CNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT))
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_KEY_AREA_KEY_CNT 4
#define NCA_KEY_AREA_KEY_SIZE 0x10
#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEY_AREA_KEY_SIZE)
#define NCA_FS_HEADER_PARTITION_PFS0 0x01
#define NCA_FS_HEADER_FSTYPE_PFS0 0x02
#define NCA_FS_HEADER_PARTITION_ROMFS 0x00
#define NCA_FS_HEADER_FSTYPE_ROMFS 0x03
#define NCA_FS_HEADER_CRYPT_NONE 0x01
#define NCA_FS_HEADER_CRYPT_XTS 0x02
#define NCA_FS_HEADER_CRYPT_CTR 0x03
#define NCA_FS_HEADER_CRYPT_BKTR 0x04
#define PFS0_MAGIC (u32)0x50465330 // "PFS0"
#define IVFC_MAGIC (u32)0x49564643 // "IVFC"
#define IVFC_MAX_LEVEL 6
#define BKTR_MAGIC (u32)0x424B5452 // "BKTR"
#define ROMFS_HEADER_SIZE 0x50
#define ROMFS_ENTRY_EMPTY (u32)0xFFFFFFFF
#define ROMFS_NONAME_DIRENTRY_SIZE 0x18
#define ROMFS_NONAME_FILEENTRY_SIZE 0x20
#define ROMFS_ENTRY_DIR 1
#define ROMFS_ENTRY_FILE 2
#define META_MAGIC (u32)0x4D455441 // "META"
#define NPDM_SIGNATURE_SIZE 0x100
#define NPDM_SIGNATURE_AREA_SIZE 0x200
#define NSP_NCA_FILENAME_LENGTH 0x25 // NCA ID + ".nca" + NULL terminator
#define NSP_CNMT_FILENAME_LENGTH 0x2A // NCA ID + ".cnmt.nca" / ".cnmt.xml" + NULL terminator
#define NSP_PROGRAM_XML_FILENAME_LENGTH 0x31 // NCA ID + ".programinfo.xml" + NULL terminator
#define NSP_NACP_XML_FILENAME_LENGTH 0x2A // NCA ID + ".nacp.xml" + NULL terminator
#define NSP_LEGAL_XML_FILENAME_LENGTH 0x2F // NCA ID + ".legalinfo.xml" + NULL terminator
#define NSP_TIK_FILENAME_LENGTH 0x25 // Rights ID + ".tik" + NULL terminator
#define NSP_CERT_FILENAME_LENGTH 0x26 // Rights ID + ".cert" + NULL terminator
#define ETICKET_ENTRY_SIZE 0x400
#define ETICKET_TITLEKEY_OFFSET 0x180
#define ETICKET_RIGHTSID_OFFSET 0x2A0
#define ETICKET_UNKNOWN_FIELD_SIZE 0x140
#define ETICKET_DATA_OFFSET 0x140
#define ETICKET_CA_CERT_SIZE 0x400
#define ETICKET_XS_CERT_SIZE 0x300
#define ETICKET_TIK_FILE_SIZE (ETICKET_ENTRY_SIZE - 0x140)
#define ETICKET_CERT_FILE_SIZE (ETICKET_CA_CERT_SIZE + ETICKET_XS_CERT_SIZE)
#define ETICKET_TITLEKEY_COMMON 0
#define ETICKET_TITLEKEY_PERSONALIZED 1
typedef enum {
DUMP_APP_NSP = 0,
DUMP_PATCH_NSP,
DUMP_ADDON_NSP
} nspDumpType;
typedef struct {
u32 magic;
u32 file_cnt;
u32 str_table_size;
u32 reserved;
} PACKED pfs0_header;
typedef struct {
u64 file_offset;
u64 file_size;
u32 filename_offset;
u32 reserved;
} PACKED pfs0_file_entry;
typedef struct {
u32 media_start_offset;
u32 media_end_offset;
u8 _0x8[0x8]; /* Padding. */
} PACKED nca_section_entry_t;
typedef struct {
u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */
u32 block_size; /* In bytes. */
u32 always_2;
u64 hash_table_offset; /* Normally zero. */
u64 hash_table_size;
u64 pfs0_offset;
u64 pfs0_size;
u8 _0x48[0xF0];
} PACKED pfs0_superblock_t;
typedef struct {
u64 logical_offset;
u64 hash_data_size;
u32 block_size;
u32 reserved;
} PACKED ivfc_level_hdr_t;
typedef struct {
u32 magic;
u32 id;
u32 master_hash_size;
u32 num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
u8 _0xA0[0x20];
u8 master_hash[0x20];
} PACKED ivfc_hdr_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x58];
} PACKED romfs_superblock_t;
typedef struct {
u64 offset;
u64 size;
u32 magic; /* "BKTR" */
u32 _0x14; /* Version? */
u32 num_entries;
u32 _0x1C; /* Reserved? */
} PACKED bktr_header_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x18];
bktr_header_t relocation_header;
bktr_header_t subsection_header;
} PACKED bktr_superblock_t;
/* NCA FS header. */
typedef struct {
u8 _0x0;
u8 _0x1;
u8 partition_type;
u8 fs_type;
u8 crypt_type;
u8 _0x5[0x3];
union { /* FS-specific superblock. Size = 0x138. */
pfs0_superblock_t pfs0_superblock;
romfs_superblock_t romfs_superblock;
bktr_superblock_t bktr_superblock;
};
union {
u8 section_ctr[0x8];
struct {
u32 section_ctr_low;
u32 section_ctr_high;
};
};
u8 _0x148[0xB8]; /* Padding. */
} PACKED nca_fs_header_t;
/* Nintendo content archive header. */
typedef struct {
u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */
u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */
u32 magic;
u8 distribution; /* System vs gamecard. */
u8 content_type;
u8 crypto_type; /* Which keyblob (field 1) */
u8 kaek_ind; /* Which kaek index? */
u64 nca_size; /* Entire archive size. */
u64 title_id;
u8 _0x218[0x4]; /* Padding. */
union {
u32 sdk_version; /* What SDK was this built with? */
struct {
u8 sdk_revision;
u8 sdk_micro;
u8 sdk_minor;
u8 sdk_major;
};
};
u8 crypto_type2; /* Which keyblob (field 2) */
u8 fixed_key_generation;
u8 _0x222[0xE]; /* Padding. */
u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */
nca_section_entry_t section_entries[4]; /* Section entry metadata. */
u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */
u8 nca_keys[4][0x10]; /* Key area (encrypted) */
u8 _0x340[0xC0]; /* Padding. */
nca_fs_header_t fs_headers[4]; /* FS section headers. */
} PACKED nca_header_t;
typedef struct {
u32 magic;
u32 _0x4;
u32 _0x8;
u8 mmu_flags;
u8 _0xD;
u8 main_thread_prio;
u8 default_cpuid;
u64 _0x10;
u32 process_category;
u32 main_stack_size;
char title_name[0x50];
u32 aci0_offset;
u32 aci0_size;
u32 acid_offset;
u32 acid_size;
} PACKED npdm_t;
typedef struct {
u64 title_id;
u32 version;
u8 type;
u8 unk1;
u16 extended_header_size;
u16 content_cnt;
u16 content_meta_cnt;
u8 attr;
u8 unk2[0x03];
u32 required_dl_sysver;
u8 unk3[0x04];
} PACKED cnmt_header;
typedef struct {
u64 patch_tid; /* Patch TID / Application TID */
u32 min_sysver; /* Minimum system/application version */
u32 min_appver; /* Minimum application version (only for base applications) */
} PACKED cnmt_extended_header;
typedef struct {
u8 hash[0x20];
u8 nca_id[0x10];
u8 size[6];
u8 type;
u8 id_offset;
} PACKED cnmt_content_record;
typedef struct {
u8 type;
u64 title_id;
u32 version;
u32 required_dl_sysver;
u32 nca_cnt;
u8 digest[SHA256_HASH_SIZE];
char digest_str[(SHA256_HASH_SIZE * 2) + 1];
u8 min_keyblob;
u32 min_sysver;
u64 patch_tid;
u32 min_appver;
} cnmt_xml_program_info;
typedef struct {
u8 type;
u8 nca_id[SHA256_HASH_SIZE / 2];
char nca_id_str[SHA256_HASH_SIZE + 1];
u64 size;
u8 hash[SHA256_HASH_SIZE];
char hash_str[(SHA256_HASH_SIZE * 2) + 1];
u8 keyblob;
u8 id_offset;
u64 cnt_record_offset; // Relative to the start of the content records section in the CNMT
u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE];
u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
} cnmt_xml_content_info;
typedef struct {
u32 nca_index;
u8 *hash_table;
u64 hash_table_offset; // Relative to NCA start
u64 hash_table_size;
u8 block_mod_cnt;
u8 *block_data[2];
u64 block_offset[2]; // Relative to NCA start
u64 block_size[2];
} nca_program_mod_data;
typedef struct {
char filename[100];
u64 icon_size;
u8 icon_data[0x20000];
} nacp_icons_ctx;
typedef struct {
u32 nca_index;
u64 xml_size;
char *xml_data;
u8 nacp_icon_cnt; // Only used with Control NCAs
nacp_icons_ctx *nacp_icons; // Only used with Control NCAs
} xml_record_info;
typedef struct {
u64 section_offset; // Relative to NCA start
u64 section_size;
u64 hash_table_offset; // Relative to NCA start
u64 hash_block_size;
u32 hash_block_cnt;
u64 pfs0_offset; // Relative to NCA start
u64 pfs0_size;
u64 title_cnmt_offset; // Relative to NCA start
u64 title_cnmt_size;
} nca_cnmt_mod_data;
typedef struct {
u32 sig_type;
u8 signature[0x100];
u8 padding[0x3C];
char sig_issuer[0x40];
u8 titlekey_block[0x100];
u8 unk1;
u8 titlekey_type;
u8 unk2[0x03];
u8 master_key_rev;
u8 unk3[0x0A];
u64 ticket_id;
u64 device_id;
u8 rights_id[0x10];
u32 account_id;
u8 unk4[0x0C];
} PACKED rsa2048_sha256_ticket;
typedef struct {
bool has_rights_id;
u8 rights_id[0x10];
char rights_id_str[33];
char tik_filename[37];
char cert_filename[38];
u8 enc_titlekey[0x10];
u8 dec_titlekey[0x10];
u8 cert_data[ETICKET_CERT_FILE_SIZE];
rsa2048_sha256_ticket tik_data;
bool retrieved_tik;
bool missing_tik;
} title_rights_ctx;
typedef struct {
NcmStorageId storageId;
NcmContentStorage ncmStorage;
NcmContentId ncaId;
u8 idOffset;
Aes128CtrContext aes_ctx;
u64 exefs_offset; // Relative to NCA start
u64 exefs_size;
pfs0_header exefs_header;
u64 exefs_entries_offset; // Relative to NCA start
pfs0_file_entry *exefs_entries;
u64 exefs_str_table_offset; // Relative to NCA start
char *exefs_str_table;
u64 exefs_data_offset; // Relative to NCA start
} exefs_ctx_t;
typedef struct {
NcmStorageId storageId;
NcmContentStorage ncmStorage;
NcmContentId ncaId;
u8 idOffset;
Aes128CtrContext aes_ctx;
u64 section_offset; // Relative to NCA start
u64 section_size;
u64 romfs_offset; // Relative to NCA start
u64 romfs_size;
u64 romfs_dirtable_offset; // Relative to NCA start
u64 romfs_dirtable_size;
romfs_dir *romfs_dir_entries;
u64 romfs_filetable_offset; // Relative to NCA start
u64 romfs_filetable_size;
romfs_file *romfs_file_entries;
u64 romfs_filedata_offset; // Relative to NCA start
} romfs_ctx_t;
typedef struct {
u64 virt_offset;
u64 phys_offset;
u32 is_patch;
} PACKED bktr_relocation_entry_t;
typedef struct {
u32 _0x0;
u32 num_entries;
u64 virtual_offset_end;
bktr_relocation_entry_t entries[0x3FF0 / sizeof(bktr_relocation_entry_t)];
u8 padding[0x3FF0 % sizeof(bktr_relocation_entry_t)];
} PACKED bktr_relocation_bucket_t;
typedef struct {
u32 _0x0;
u32 num_buckets;
u64 total_size;
u64 bucket_virtual_offsets[0x3FF0 / sizeof(u64)];
bktr_relocation_bucket_t buckets[];
} PACKED bktr_relocation_block_t;
typedef struct {
u64 offset;
u32 _0x8;
u32 ctr_val;
} PACKED bktr_subsection_entry_t;
typedef struct {
u32 _0x0;
u32 num_entries;
u64 physical_offset_end;
bktr_subsection_entry_t entries[0x3FF];
} PACKED bktr_subsection_bucket_t;
typedef struct {
u32 _0x0;
u32 num_buckets;
u64 total_size;
u64 bucket_physical_offsets[0x3FF0 / sizeof(u64)];
bktr_subsection_bucket_t buckets[];
} PACKED bktr_subsection_block_t;
typedef struct {
NcmStorageId storageId;
NcmContentStorage ncmStorage;
NcmContentId ncaId;
u8 idOffset;
Aes128CtrContext aes_ctx;
u64 section_offset; // Relative to NCA start
u64 section_size;
bktr_superblock_t superblock;
bktr_relocation_block_t *relocation_block;
bktr_subsection_block_t *subsection_block;
u64 virtual_seek; // Relative to section start
u64 bktr_seek; // Relative to section start (patch BKTR section)
u64 base_seek; // Relative to section start (base application RomFS section)
u64 romfs_offset; // Relative to section start
u64 romfs_size;
u64 romfs_dirtable_offset; // Relative to section start
u64 romfs_dirtable_size;
romfs_dir *romfs_dir_entries;
u64 romfs_filetable_offset; // Relative to section start
u64 romfs_filetable_size;
romfs_file *romfs_file_entries;
u64 romfs_filedata_offset; // Relative to section start
} bktr_ctx_t;
// Used in HFS0 / ExeFS / RomFS browsers
typedef struct {
u64 size;
char sizeStr[32];
} browser_entry_size_info;
typedef struct {
u8 type; // 1 = Dir, 2 = File
u64 offset; // Relative to directory/file table, depending on type
browser_entry_size_info sizeInfo; // Only used if type == 2
} romfs_browser_entry;
typedef struct {
char name[0x200];
char publisher[0x100];
} Title;
typedef enum {
Language_AmericanEnglish = 0,
Language_BritishEnglish = 1,
Language_Japanese = 2,
Language_French = 3,
Language_German = 4,
Language_LatinAmericanSpanish = 5,
Language_Spanish = 6,
Language_Italian = 7,
Language_Dutch = 8,
Language_CanadianFrench = 9,
Language_Portuguese = 10,
Language_Russian = 11,
Language_Korean = 12,
Language_TraditionalChinese = 13,
Language_SimplifiedChinese = 14
} Language;
typedef enum {
StartupUserAccount_None = 0,
StartupUserAccount_Required = 1,
StartupUserAccount_RequiredWithNetworkServiceAccountAvailable = 2
} StartupUserAccount;
/* Introduced as of SDK 6.4.0 */
typedef enum {
UserAccountSwitchLock_Disable = 0,
UserAccountSwitchLock_Enable = 1
} UserAccountSwitchLock;
/* Introduced as of SDK 3.4.0 */
typedef enum {
AddOnContentRegistrationType_AllOnLaunch = 0,
AddOnContentRegistrationType_OnDemand = 1
} AddOnContentRegistrationType;
typedef struct {
u32 AttributeFlag_Demo : 1;
u32 AttributeFlag_RetailInteractiveDisplay : 1; /* Introduced as of SDK 3.4.0 */
} AttributeFlag;
typedef struct {
u32 SupportedLanguageFlag_AmericanEnglish : 1;
u32 SupportedLanguageFlag_BritishEnglish : 1;
u32 SupportedLanguageFlag_Japanese : 1;
u32 SupportedLanguageFlag_French : 1;
u32 SupportedLanguageFlag_German : 1;
u32 SupportedLanguageFlag_LatinAmericanSpanish : 1;
u32 SupportedLanguageFlag_Spanish : 1;
u32 SupportedLanguageFlag_Italian : 1;
u32 SupportedLanguageFlag_Dutch : 1;
u32 SupportedLanguageFlag_CanadianFrench : 1;
u32 SupportedLanguageFlag_Portuguese : 1;
u32 SupportedLanguageFlag_Russian : 1;
u32 SupportedLanguageFlag_Korean : 1;
u32 SupportedLanguageFlag_TraditionalChinese : 1;
u32 SupportedLanguageFlag_SimplifiedChinese : 1;
} SupportedLanguageFlag;
typedef struct {
u32 ParentalControlFlag_FreeCommunication : 1;
} ParentalControlFlag;
typedef enum {
Screenshot_Allow = 0,
Screenshot_Deny = 1
} Screenshot;
typedef enum {
VideoCapture_Disable = 0,
VideoCapture_Manual = 1,
VideoCapture_Enable = 2
} VideoCapture;
typedef enum {
DataLossConfirmation_None = 0,
DataLossConfirmation_Required = 1
} DataLossConfirmation;
typedef enum {
PlayLogPolicy_All = 0,
PlayLogPolicy_LogOnly = 1,
PlayLogPolicy_None = 2
} PlayLogPolicy;
typedef enum {
RatingAgeOrganization_CERO = 0,
RatingAgeOrganization_GRACGCRB = 1,
RatingAgeOrganization_GSRMR = 2,
RatingAgeOrganization_ESRB = 3,
RatingAgeOrganization_ClassInd = 4,
RatingAgeOrganization_USK = 5,
RatingAgeOrganization_PEGI = 6,
RatingAgeOrganization_PEGIPortugal = 7,
RatingAgeOrganization_PEGIBBFC = 8,
RatingAgeOrganization_Russian = 9,
RatingAgeOrganization_ACB = 10,
RatingAgeOrganization_OFLC = 11,
RatingAgeOrganization_IARCGeneric = 12 /* Introduced as of SDK 9.3.1 */
} RatingAgeOrganization;
typedef struct {
u8 cero;
u8 gracgcrb;
u8 gsrmr;
u8 esrb;
u8 class_ind;
u8 usk;
u8 pegi;
u8 pegi_portugal;
u8 pegibbfc;
u8 russian;
u8 acb;
u8 oflc;
u8 iarc_generic;
u8 reserved_1[0x13];
} RatingAge;
typedef enum {
LogoType_LicensedByNintendo = 0,
LogoType_DistributedByNintendo = 1, /* Removed in SDK 3.5.2 */
LogoType_Nintendo = 2
} LogoType;
typedef enum {
LogoHandling_Auto = 0,
LogoHandling_Manual = 1
} LogoHandling;
/* Introduced as of SDK 4.5.0 */
typedef enum {
RuntimeAddOnContentInstall_Deny = 0,
RuntimeAddOnContentInstall_AllowAppend = 1
} RuntimeAddOnContentInstall;
/* Introduced as of SDK 9.3.1 */
typedef enum {
RuntimeParameterDelivery_Always = 0,
RuntimeParameterDelivery_AlwaysIfUserStateMatched = 1,
RuntimeParameterDelivery_OnRestart = 2
} RuntimeParameterDelivery;
/* Introduced as of SDK 3.5.2 */
typedef enum {
CrashReport_Deny = 0,
CrashReport_Allow = 1
} CrashReport;
typedef enum {
Hdcp_None = 0,
Hdcp_Required = 1
} Hdcp;
/* Introduced as of SDK 7.6.0 */
typedef struct {
u8 StartupUserAccountOptionFlag_IsOptional : 1;
} StartupUserAccountOptionFlag;
/* Introduced as of SDK 5.3.0 */
typedef enum {
PlayLogQueryCapability_None = 0,
PlayLogQueryCapability_WhiteList = 1,
PlayLogQueryCapability_All = 2
} PlayLogQueryCapability;
/* Introduced as of SDK 5.3.0 */
typedef struct {
u8 RepairFlag_SuppressGameCardAccess : 1;
} RepairFlag;
/* Introduced as of SDK 6.4.0 */
typedef struct {
u8 RequiredNetworkServiceLicenseOnLaunchFlag_Common : 1;
} RequiredNetworkServiceLicenseOnLaunchFlag;
typedef struct {
u64 group_id;
u8 key[0x10];
} NeighborDetectionGroupConfiguration;
typedef struct {
NeighborDetectionGroupConfiguration send_group_configuration;
NeighborDetectionGroupConfiguration receivable_group_configurations[0x10];
} NeighborDetectionClientConfiguration;
/* Introduced as of SDK 7.6.0 */
typedef enum {
JitConfigurationFlag_None = 0,
JitConfigurationFlag_Enabled = 1
} JitConfigurationFlag;
typedef struct {
u64 jit_configuration_flag;
u64 memory_size;
} JitConfiguration;
typedef struct {
Title titles[0x10];
char isbn[0x25];
u8 startup_user_account;
u8 user_account_switch_lock; /* Old: touch_screen_usage (None, Supported, Required) */
u8 add_on_content_registration_type;
AttributeFlag attribute_flag;
SupportedLanguageFlag supported_language_flag;
ParentalControlFlag parental_control_flag;
u8 screenshot;
u8 video_capture;
u8 data_loss_confirmation;
u8 play_log_policy;
u64 presence_group_id;
RatingAge rating_ages;
char display_version[0x10];
u64 add_on_content_base_id;
u64 save_data_owner_id;
u64 user_account_save_data_size;
u64 user_account_save_data_journal_size;
u64 device_save_data_size;
u64 device_save_data_journal_size;
u64 bcat_delivery_cache_storage_size;
char application_error_code_category[0x8];
u64 local_communication_ids[0x8];
u8 logo_type;
u8 logo_handling;
u8 runtime_add_on_content_install;
u8 runtime_parameter_delivery;
u8 reserved_1[0x2];
u8 crash_report;
u8 hdcp;
u64 seed_for_pseudo_device_id;
char bcat_passphrase[0x41];
StartupUserAccountOptionFlag startup_user_account_option;
u8 reserved_2[0x6];
u64 user_account_save_data_size_max;
u64 user_account_save_data_journal_size_max;
u64 device_save_data_size_max;
u64 device_save_data_journal_size_max;
u64 temporary_storage_size;
u64 cache_storage_size;
u64 cache_storage_journal_size;
u64 cache_storage_data_and_journal_size_max;
u16 cache_storage_index_max;
u8 reserved_3[0x6];
u64 play_log_queryable_application_ids[0x10];
u8 play_log_query_capability;
RepairFlag repair_flag;
u8 program_index;
RequiredNetworkServiceLicenseOnLaunchFlag required_network_service_license_on_launch_flag;
u8 reserved_4[0x4];
NeighborDetectionClientConfiguration neighbor_detection_client_configuration;
JitConfiguration jit_configuration;
u8 reserved_5[0xC40];
} nacp_t;
char *getContentType(u8 type);
void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out);
void convertNcaSizeToU64(const u8 size[0x6], u64 *out);
void convertU64ToNcaSize(const u64 size, u8 out[0x6]);
bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize);
bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt);
bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize);
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize);
bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData);
bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys);
bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx);
bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info);
bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod);
bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, bool useCustomAcidRsaPubKey, char **outBuf, u64 *outBufSize);
bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt);
bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize);
#endif

296
source/new/cert.c Normal file
View file

@ -0,0 +1,296 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "cert.h"
#include "save.h"
#include "utils.h"
#define CERT_SAVEFILE_PATH "sys:/save/80000000000000e0"
#define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/"
#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig_PubKeyRsa4096 : (pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig_PubKeyRsa2048 : CertType_Sig##sig_PubKeyEcsda240))
static u8 certGetCertificateType(const void *data, u64 data_size);
static u32 certGetCertificateCountInSignatureIssuer(const char *issuer);
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain);
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain);
bool certRetrieveCertificateByName(Certificate *dst, const char *name)
{
if (!dst || !name || !strlen(name))
{
LOGFILE("Invalid parameters!");
return false;
}
save_ctx_t *save_ctx = NULL;
allocation_table_storage_ctx_t fat_storage = {0};
u64 cert_size = 0;
char cert_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = {0};
bool success = false;
snprintf(cert_path, SAVE_FS_LIST_MAX_NAME_LENGTH, "%s%s", CERT_SAVEFILE_STORAGE_BASE_PATH, name);
save_ctx = save_open_savefile(CERT_SAVEFILE_PATH, 0);
if (!save_ctx)
{
LOGFILE("Failed to open ES certificate system savefile!");
return false;
}
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, cert_path, &fat_storage, &cert_size))
{
LOGFILE("Failed to locate certificate \"%s\" in ES certificate system save!", name);
goto out;
}
if (cert_size < CERT_MIN_SIZE || cert_size > CERT_MAX_SIZE)
{
LOGFILE("Invalid size for certificate \"%s\"! (0x%lX)", name, cert_size);
goto out;
}
dst->size = cert_size;
u64 br = save_allocation_table_storage_read(&fat_storage, dst->data, 0, dst->size);
if (br != dst->size)
{
LOGFILE("Failed to read 0x%lX bytes from certificate \"%s\"! Read 0x%lX bytes.", dst->size, name, br);
goto out;
}
dst->type = certGetCertificateType(dst->data, dst->size);
if (dst->type == CertType_Invalid)
{
LOGFILE("Invalid certificate type for \"%s\"!", name);
goto out;
}
success = true;
out:
if (save_ctx) save_close_savefile(save_ctx);
return success;
}
void certFreeCertificateChain(CertificateChain *chain)
{
if (!chain || !chain->certs) return;
chain->count = 0;
free(chain->certs);
chain->certs = NULL;
}
bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer)
{
if (!dst || !issuer || !strlen(issuer) || strncmp(issuer, "Root-", 5) != 0)
{
LOGFILE("Invalid parameters!");
return false;
}
u32 i = 0;
char issuer_copy[0x40] = {0};
bool success = true;
dst->count = certGetCertificateCountInSignatureIssuer(issuer);
if (!dst->count)
{
LOGFILE("Invalid signature issuer string!");
return false;
}
dst->certs = calloc(dst->count, sizeof(Certificate));
if (!dst->certs)
{
LOGFILE("Unable to allocate memory for the certificate chain! (0x%lX)", dst->count * sizeof(Certificate));
return false;
}
/* Copy string to avoid problems with strtok */
/* The "Root-" parent from the issuer string is skipped */
snprintf(issuer_copy, 0x40, issuer + 5);
char *pch = strtok(issuer_copy, "-");
while(pch != NULL)
{
if (!certRetrieveCertificateByName(&(dst->certs[i]), pch))
{
LOGFILE("Unable to retrieve certificate \"%s\"!", pch);
success = false;
break;
}
i++;
pch = strtok(NULL, "-");
}
if (!success) certFreeCertificateChain(dst);
return success;
}
u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size)
{
if (!issuer || !strlen(issuer) || !out_size)
{
LOGFILE("Invalid parameters!");
return NULL;
}
CertificateChain chain = {0};
u8 *raw_chain = NULL;
u64 raw_chain_size = 0;
if (!certRetrieveCertificateChainBySignatureIssuer(&chain, issuer))
{
LOGFILE("Error retrieving certificate chain for \"%s\"!", issuer);
return NULL;
}
raw_chain_size = certCalculateRawCertificateChainSize(&chain);
raw_chain = malloc(raw_chain_size);
if (!raw_chain)
{
LOGFILE("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX)", issuer, raw_chain_size);
goto out;
}
certCopyCertificateChainDataToMemoryBuffer(raw_chain, &chain);
*out_size = raw_chain_size;
out:
certFreeCertificateChain(&chain);
return raw_chain;
}
static u8 certGetCertificateType(const void *data, u64 data_size)
{
if (!data || data_size < CERT_MIN_SIZE || data_size > CERT_MAX_SIZE)
{
LOGFILE("Invalid parameters!");
return CertType_Invalid;
}
u8 type = CertType_Invalid;
const u8 *data_u8 = (const u8*)data;
u32 sig_type, pub_key_type;
u64 offset = 0;
memcpy(&sig_type, data_u8, sizeof(u32));
sig_type = __builtin_bswap32(sig_type);
switch(sig_type)
{
case SignatureType_Rsa4096Sha1:
case SignatureType_Rsa4096Sha256:
offset += sizeof(SignatureBlockRsa4096);
break;
case SignatureType_Rsa2048Sha1:
case SignatureType_Rsa2048Sha256:
offset += sizeof(SignatureBlockRsa2048);
break;
case SignatureType_Ecsda240Sha1:
case SignatureType_Ecsda240Sha256:
offset += sizeof(SignatureBlockEcsda240);
break;
default:
LOGFILE("Invalid signature type value! (0x%08X)", sig_type);
return type;
}
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, issuer);
memcpy(&pub_key_type, data_u8 + offset, sizeof(u32));
pub_key_type = __builtin_bswap32(pub_key_type);
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, pub_key_type);
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, name);
offset += MEMBER_SIZE(CertSigRsa4096PubKeyRsa4096, cert_id);
switch(pub_key_type)
{
case CertPubKeyType_Rsa4096:
offset += sizeof(CertPublicKeyBlockRsa4096);
break;
case CertPubKeyType_Rsa2048:
offset += sizeof(CertPublicKeyBlockRsa2048);
break;
case CertPubKeyType_Ecsda240:
offset += sizeof(CertPublicKeyBlockEcsda240);
break;
default:
LOGFILE("Invalid public key type value! (0x%08X)", pub_key_type);
return type;
}
if (offset != data_size)
{
LOGFILE("Calculated end offset doesn't match certificate size! 0x%lX != 0x%lX", offset, data_size);
return type;
}
if (sig_type == SignatureType_Rsa4096Sha1 || sig_type == SignatureType_Rsa4096Sha256)
{
type = CERT_TYPE(Rsa4096);
} else
if (sig_type == SignatureType_Rsa2048Sha1 || sig_type == SignatureType_Rsa2048Sha256)
{
type = CERT_TYPE(Rsa2048);
} else
if (sig_type == SignatureType_Ecsda240Sha1 || sig_type == SignatureType_Ecsda240Sha256)
{
type = CERT_TYPE(Ecsda240);
}
return type;
}
static u32 certGetCertificateCountInSignatureIssuer(const char *issuer)
{
if (!issuer || !strlen(issuer)) return 0;
u32 count = 0;
char issuer_copy[0x40] = {0};
/* Copy string to avoid problems with strtok */
/* The "Root-" parent from the issuer string is skipped */
snprintf(issuer_copy, 0x40, issuer + 5);
char *pch = strtok(issuer_copy, "-");
while(pch != NULL)
{
count++;
pch = strtok(NULL, "-");
}
return count;
}
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain)
{
if (!chain || !chain->count || !chain->certs) return 0;
u64 chain_size = 0;
for(u32 i = 0; i < chain->count; i++) chain_size += chain->certs[i].size;
return chain_size;
}
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain)
{
if (!chain || !chain->count || !chain->certs) return;
u8 *dst_u8 = (u8*)dst;
for(u32 i = 0; i < chain->count; i++)
{
memcpy(dst_u8, chain->certs[i].data, chain->certs[i].size);
dst_u8 += chain->certs[i].size;
}
}

150
source/new/cert.h Normal file
View file

@ -0,0 +1,150 @@
#pragma once
#ifndef __CERT_H__
#define __CERT_H__
#include "signature.h"
#define CERT_MAX_SIZE 0x500 /* Equivalent to sizeof(CertSigRsa4096PubKeyRsa4096) */
#define CERT_MIN_SIZE 0x180 /* Equivalent to sizeof(CertSigEcsda240PubKeyEcsda240) */
typedef enum {
CertType_SigRsa4096_PubKeyRsa4096 = 0,
CertType_SigRsa4096_PubKeyRsa2048 = 1,
CertType_SigRsa4096_PubKeyEcsda240 = 2,
CertType_SigRsa2048_PubKeyRsa4096 = 3,
CertType_SigRsa2048_PubKeyRsa2048 = 4,
CertType_SigRsa2048_PubKeyEcsda240 = 5,
CertType_SigEcsda240_PubKeyRsa4096 = 6,
CertType_SigEcsda240_PubKeyRsa2048 = 7,
CertType_SigEcsda240_PubKeyEcsda240 = 8,
CertType_Invalid = 255
} CertType;
/// Always stored using big endian byte order.
typedef enum {
CertPubKeyType_Rsa4096 = 0,
CertPubKeyType_Rsa2048 = 1,
CertPubKeyType_Ecsda240 = 2
} CertPubKeyType;
typedef struct {
u8 public_key[0x200];
u32 public_exponent;
u8 padding[0x34];
} CertPublicKeyBlockRsa4096;
typedef struct {
u8 public_key[0x100];
u32 public_exponent;
u8 padding[0x34];
} CertPublicKeyBlockRsa2048;
typedef struct {
u8 public_key[0x3C];
u8 padding[0x3C];
} CertPublicKeyBlockEcsda240;
typedef struct {
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Rsa4096.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockRsa4096 pub_key_block;
} CertSigRsa4096PubKeyRsa4096;
typedef struct {
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Rsa2048.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockRsa2048 pub_key_block;
} CertSigRsa4096PubKeyRsa2048;
typedef struct {
SignatureBlockRsa4096 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Ecsda240.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockEcsda240 pub_key_block;
} CertSigRsa4096PubKeyEcsda240;
typedef struct {
SignatureBlockRsa2048 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Rsa4096.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockRsa4096 pub_key_block;
} CertSigRsa2048PubKeyRsa4096;
typedef struct {
SignatureBlockRsa2048 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Rsa2048.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockRsa2048 pub_key_block;
} CertSigRsa2048PubKeyRsa2048;
typedef struct {
SignatureBlockRsa2048 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Ecsda240.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockEcsda240 pub_key_block;
} CertSigRsa2048PubKeyEcsda240;
typedef struct {
SignatureBlockEcsda240 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Rsa4096.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockRsa4096 pub_key_block;
} CertSigEcsda240PubKeyRsa4096;
typedef struct {
SignatureBlockEcsda240 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Rsa2048.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockRsa2048 pub_key_block;
} CertSigEcsda240PubKeyRsa2048;
typedef struct {
SignatureBlockEcsda240 sig_block; ///< sig_type field is stored using big endian byte order.
char issuer[0x40];
u32 pub_key_type; ///< CertPubKeyType_Ecsda240.
char name[0x40];
u32 cert_id;
CertPublicKeyBlockEcsda240 pub_key_block;
} CertSigEcsda240PubKeyEcsda240;
/// Used to store certificate type, size and raw data.
typedef struct {
u8 type; ///< CertType.
u64 size;
u8 data[CERT_MAX_SIZE];
} Certificate;
/// Used to store two or more certificates.
typedef struct {
u32 count;
Certificate *certs;
} CertificateChain;
bool certRetrieveCertificateByName(Certificate *dst, const char *name);
void certFreeCertificateChain(CertificateChain *chain);
bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer);
/// Returns a pointer to a heap allocated buffer that must be freed by the user.
u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size);
#endif /* __CERT_H__ */

52
source/new/crc32_fast.c Normal file
View file

@ -0,0 +1,52 @@
/* Standard CRC32 checksum: fast public domain implementation for
* little-endian architectures. Written for compilation with an
* optimizer set to perform loop unwinding. Outputs the checksum for
* each file given as a command line argument. Invalid file names and
* files that cause errors are silently skipped. The program reads
* from stdin if it is called with no arguments. */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "crc32_fast.h"
u32 crc32_for_byte(u32 r)
{
for(int j = 0; j < 8; ++j) r = (r & 1 ? 0 : (u32)0xEDB88320L) ^ r >> 1;
return r ^ (u32)0xFF000000L;
}
/* Any unsigned integer type with at least 32 bits may be used as
* accumulator type for fast crc32-calulation, but unsigned long is
* probably the optimal choice for most systems. */
typedef unsigned long accum_t;
void init_tables(u32 *table, u32 *wtable)
{
for(u64 i = 0; i < 0x100; ++i) table[i] = crc32_for_byte(i);
for(u64 k = 0; k < sizeof(accum_t); ++k)
{
for(u64 w, i = 0; i < 0x100; ++i)
{
for(u64 j = w = 0; j < sizeof(accum_t); ++j) w = table[(u8)(j == k ? w ^ i : w)] ^ w >> 8;
wtable[(k << 8) + i] = w ^ (k ? wtable[0] : 0);
}
}
}
void crc32(const void *data, u64 n_bytes, u32 *crc)
{
static u32 table[0x100], wtable[0x100 * sizeof(accum_t)];
u64 n_accum = n_bytes / sizeof(accum_t);
if (!*table) init_tables(table, wtable);
for(u64 i = 0; i < n_accum; ++i)
{
accum_t a = *crc ^ ((accum_t*)data)[i];
for(u64 j = *crc = 0; j < sizeof(accum_t); ++j) *crc ^= wtable[(j << 8) + (u8)(a >> 8 * j)];
}
for(u64 i = n_accum * sizeof(accum_t); i < n_bytes; ++i) *crc = table[(u8)*crc ^ ((u8*)data)[i]] ^ *crc >> 8;
}

10
source/new/crc32_fast.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#ifndef __CRC32_FAST_H__
#define __CRC32_FAST_H__
#include <switch/types.h>
void crc32(const void *data, u64 n_bytes, u32 *crc);
#endif /* __CRC32_FAST_H__ */

75
source/new/es.c Normal file
View file

@ -0,0 +1,75 @@
#include <switch/arm/atomics.h>
#include <switch/services/sm.h>
#include <stdlib.h>
#include <string.h>
#include "es.h"
#include "service_guard.h"
static Service g_esSrv;
NX_GENERATE_SERVICE_GUARD(es);
Result _esInitialize() {
return smGetService(&g_esSrv, "es");
}
void _esCleanup() {
serviceClose(&g_esSrv);
}
Result esCountCommonTicket(s32 *out_count)
{
struct {
s32 num_tickets;
} out;
Result rc = serviceDispatchOut(&g_esSrv, 9, out);
if (R_SUCCEEDED(rc) && out_count) *out_count = out.num_tickets;
return rc;
}
Result esCountPersonalizedTicket(s32 *out_count)
{
struct {
s32 num_tickets;
} out;
Result rc = serviceDispatchOut(&g_esSrv, 10, out);
if (R_SUCCEEDED(rc) && out_count) *out_count = out.num_tickets;
return rc;
}
Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 count);
{
struct {
s32 num_rights_ids_written;
} out;
Result rc = serviceDispatchInOut(&g_esSrv, 11, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_ids, count * sizeof(FsRightsId) } },
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
return rc;
}
Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 count)
{
struct {
s32 num_rights_ids_written;
} out;
Result rc = serviceDispatchInOut(&g_esSrv, 12, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { out_ids, count * sizeof(FsRightsId) } },
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
return rc;
}

16
source/new/es.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#ifndef __ES_H__
#define __ES_H__
#include <switch.h>
Result esInitialize();
void esExit();
Result esCountCommonTicket(s32 *out_count);
Result esCountPersonalizedTicket(s32 *out_count);
Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 count);
Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 count);
#endif /* __ES_H__ */

View file

@ -0,0 +1,24 @@
FatFs License
FatFs has being developped as a personal project of the author, ChaN. It is free from the code anyone else wrote at current release. Following code block shows a copy of the FatFs license document that heading the source files.
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem Module Rx.xx /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 20xx, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/----------------------------------------------------------------------------*/
Therefore FatFs license is one of the BSD-style licenses but there is a significant feature. FatFs is mainly intended for embedded systems. In order to extend the usability for commercial products, the redistributions of FatFs in binary form, such as embedded code, binary library and any forms without source code, does not need to include about FatFs in the documentations. This is equivalent to the 1-clause BSD license. Of course FatFs is compatible with the most of open source software licenses including GNU GPL. When you redistribute the FatFs source code with any changes or create a fork, the license can also be changed to GNU GPL, BSD-style license or any open source software license that not conflict with FatFs license.

113
source/new/fatfs/diskio.c Normal file
View file

@ -0,0 +1,113 @@
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include <switch.h>
#include "utils.h"
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
(void)pdrv;
return 0;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
(void)pdrv;
return 0;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive number to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
(void)pdrv;
Result rc = 0;
u64 start_offset = ((u64)FF_MAX_SS * (u64)sector);
u64 read_size = ((u64)FF_MAX_SS * (u64)count);
FsStorage *emmc_storage = utilsGetEmmcBisSystemStorage();
if (!emmc_storage) return RES_ERROR;
rc = fsStorageRead(emmc_storage, start_offset, buff, read_size);
return (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR);
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* Physical drive number to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
(void)pdrv;
(void)buff;
(void)sector;
(void)count;
return RES_OK;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
(void)pdrv;
(void)cmd;
(void)buff;
return RES_OK;
}

77
source/new/fatfs/diskio.h Normal file
View file

@ -0,0 +1,77 @@
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2019 /
/-----------------------------------------------------------------------*/
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
/* Status of Disk Functions */
typedef BYTE DSTATUS;
/* Results of Disk Functions */
typedef enum {
RES_OK = 0, /* 0: Successful */
RES_ERROR, /* 1: R/W Error */
RES_WRPRT, /* 2: Write Protected */
RES_NOTRDY, /* 3: Not Ready */
RES_PARERR /* 4: Invalid Parameter */
} DRESULT;
/*---------------------------------------*/
/* Prototypes for disk control functions */
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
/* Disk Status Bits (DSTATUS) */
#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected */
/* Command code for disk_ioctrl fucntion */
/* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */
/* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media */
/* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status */
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
/* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */
#ifdef __cplusplus
}
#endif
#endif

6862
source/new/fatfs/ff.c Normal file

File diff suppressed because it is too large Load diff

426
source/new/fatfs/ff.h Normal file
View file

@ -0,0 +1,426 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.14 /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2019, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/
/----------------------------------------------------------------------------*/
#ifndef FF_DEFINED
#define FF_DEFINED 86606 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "ffconf.h" /* FatFs configuration options */
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
/* Integer types used for FatFs API */
#if defined(_WIN32) /* Main development platform */
#define FF_INTDEF 2
#include <windows.h>
typedef unsigned __int64 QWORD;
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#endif
/* Definitions of volume management */
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif
#endif
/* Type of path name strings on FatFs API */
#ifndef _INC_TCHAR
#define _INC_TCHAR
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
typedef char TCHAR;
#define _T(x) u8 ## x
#define _TEXT(x) u8 ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
typedef DWORD TCHAR;
#define _T(x) U ## x
#define _TEXT(x) U ## x
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
#error Wrong FF_LFN_UNICODE setting
#else /* ANSI/OEM code in SBCS/DBCS */
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
#endif
/* Type of file size and LBA variables */
#if FF_FS_EXFAT
#if FF_INTDEF != 2
#error exFAT feature wants C99 or later
#endif
typedef QWORD FSIZE_t;
#if FF_LBA64
typedef QWORD LBA_t;
#else
typedef DWORD LBA_t;
#endif
#else
#if FF_LBA64
#error exFAT needs to be enabled when enable 64-bit LBA
#endif
typedef DWORD FSIZE_t;
typedef DWORD LBA_t;
#endif
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE pdrv; /* Associated physical drive */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if FF_FS_REENTRANT
FF_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
LBA_t volbase; /* Volume base sector */
LBA_t fatbase; /* FAT base sector */
LBA_t dirbase; /* Root directory base sector/cluster */
LBA_t database; /* Data base sector */
#if FF_FS_EXFAT
LBA_t bitbase; /* Allocation bitmap base sector */
#endif
LBA_t winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (FFOBJID) */
typedef struct {
FATFS* fs; /* Pointer to the hosting volume of this object */
WORD id; /* Hosting volume mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if FF_FS_EXFAT
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
#endif
#if FF_FS_LOCK
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;
/* File object structure (FIL) */
typedef struct {
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
#endif
#if FF_USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
FFOBJID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
LBA_t sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if FF_USE_LFN
TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
#else
TCHAR fname[12 + 1]; /* File name */
#endif
} FILINFO;
/* Format parameter structure (MKFS_PARM) */
typedef struct {
BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
BYTE n_fat; /* Number of FATs */
UINT align; /* Data area alignment (sector) */
UINT n_root; /* Number of root directory entries */
DWORD au_size; /* Cluster size (byte) */
} MKFS_PARM;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs module application interface */
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
#ifndef EOF
#define EOF (-1)
#endif
/*--------------------------------------------------------------*/
/* Additional user defined functions */
/* RTC function */
#if !FF_FS_READONLY && !FF_FS_NORTC
DWORD get_fattime (void);
#endif
/* LFN support functions */
#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
#endif
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
/* Sync functions */
#if FF_FS_REENTRANT
int ff_cre_syncobj (BYTE vol, FF_SYNC_t* sobj); /* Create a sync object */
int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */
void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */
int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and offset address */
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* FF_DEFINED */

298
source/new/fatfs/ffconf.h Normal file
View file

@ -0,0 +1,298 @@
/*---------------------------------------------------------------------------/
/ FatFs Functional Configurations
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 86606 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 1
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 1
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 0
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 850
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 3
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 0
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 0
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
#define FF_FS_RPATH 0
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 1
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 1
#define FF_VOLUME_STRS "sys"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table needs to be defined as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_LBA64 0
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
#define FF_MIN_GPT 0x100000000
/* Minimum number of sectors to switch GPT format to create partition in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2020
/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
/* #include <somertos.h> // O/S definitions */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/*--- End of configuration options ---*/

View file

@ -0,0 +1,34 @@
/*------------------------------------------------------------------------*/
/* Sample Code of OS Dependent Functions for FatFs */
/* (C)ChaN, 2018 */
/*------------------------------------------------------------------------*/
#include <stdlib.h>
#include "ff.h"
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
free(mblock); /* Free the memory block with POSIX API */
}
#endif

15593
source/new/fatfs/ffunicode.c Normal file

File diff suppressed because it is too large Load diff

47
source/new/fs_ext.c Normal file
View file

@ -0,0 +1,47 @@
#include <switch/services/fs.h>
#include <stdlib.h>
#include <string.h>
#include "fs_ext.h"
// IFileSystemProxy
Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition)
{
struct {
u32 handle;
u32 partition;
} in = { handle->value, partition };
return serviceDispatchIn(fsGetServiceSession(), 30, in,
.out_num_objects = 1,
.out_objects = &out->s
);
}
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out)
{
return serviceDispatch(fsGetServiceSession(), 501,
.out_num_objects = 1,
.out_objects = &out->s
);
}
// IDeviceOperator
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id)
{
struct {
u32 handle;
} in = { handle->value };
struct {
u32 title_ver;
u64 title_id;
} out;
Result rc = serviceDispatchInOut(&d->s, 203, in, out);
if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_ver;
if (R_SUCCEEDED(rc) && out_title_id) *out_title_id = out.title_id;
return rc;
}

16
source/new/fs_ext.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#ifndef __FS_EXT_H__
#define __FS_EXT_H__
#include <switch/types.h>
#include <switch/services/fs.h>
// IFileSystemProxy
Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition);
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out);
// IDeviceOperator
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id);
#endif /* __FS_EXT_H__ */

766
source/new/keys.c Normal file
View file

@ -0,0 +1,766 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "keys.h"
#include "nca.h"
#include "utils.h"
#define FS_SYSMODULE_TID (u64)0x0100000000000000
#define SEGMENT_TEXT BIT(0)
#define SEGMENT_RODATA BIT(1)
#define SEGMENT_DATA BIT(2)
typedef struct {
u64 program_id;
u8 mask;
u8 *data;
u64 data_size;
} keysMemoryLocation;
typedef struct {
char name[64];
u8 hash[SHA256_HASH_SIZE];
u64 size;
void *dst;
} keysMemoryKey;
typedef struct {
keysMemoryLocation location;
u32 key_count;
keysMemoryKey keys[];
} keysMemoryInfo;
typedef struct {
///< Needed to decrypt the NCA header using AES-128-XTS
u8 header_kek_source[0x10]; ///< Seed for header kek. Retrieved from the .rodata section in the FS sysmodule.
u8 header_key_source[0x20]; ///< Seed for NCA header key. Retrieved from the .data section in the FS sysmodule.
u8 header_kek[0x10]; ///< NCA header kek. Generated from header_kek_source.
u8 header_key[0x20]; ///< NCA header key. Generated from header_kek and header_key_source.
///< Needed to derive the KAEK used to decrypt the NCA key area
u8 key_area_key_application_source[0x10]; ///< Seed for kaek 0. Retrieved from the .rodata section in the FS sysmodule.
u8 key_area_key_ocean_source[0x10]; ///< Seed for kaek 1. Retrieved from the .rodata section in the FS sysmodule.
u8 key_area_key_system_source[0x10]; ///< Seed for kaek 2. Retrieved from the .rodata section in the FS sysmodule.
///< Needed to decrypt the titlekey block from a ticker. Retrieved from the Lockpick_RCM keys file.
u8 eticket_rsa_kek[0x10]; ///< eTicket RSA kek.
u8 titlekeks[0x20][0x10]; ///< Title key encryption keys.
///< Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file.
u8 key_area_keys[0x20][3][0x10]; ///< Key area encryption keys.
} keysNcaKeyset;
static keysNcaKeyset g_ncaKeyset = {0};
static keysMemoryInfo g_fsRodataMemoryInfo = {
.location = {
.program_id = FS_SYSMODULE_TID,
.mask = SEGMENT_RODATA,
.data = NULL,
.data_size = 0
},
.key_count = 4,
.keys = {
{
.name = "header_kek_source",
.hash = { 0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68,
0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2 },
.size = 0x10,
.dst = g_ncaKeyset.header_kek_source
},
{
.name = "key_area_key_application_source",
.hash = { 0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F,
0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E },
.size = 0x10,
.dst = g_ncaKeyset.key_area_key_application_source
},
{
.name = "key_area_key_ocean_source",
.hash = { 0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B,
0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17 },
.size = 0x10,
.dst = g_ncaKeyset.key_area_key_ocean_source
},
{
.name = "key_area_key_system_source",
.hash = { 0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E,
0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7 },
.size = 0x10,
.dst = g_ncaKeyset.key_area_key_system_source
}
}
};
static keysMemoryInfo g_fsDataMemoryInfo = {
.location = {
.program_id = FS_SYSMODULE_TID,
.mask = SEGMENT_DATA,
.data = NULL,
.data_size = 0
},
.key_count = 1,
.keys = {
{
.name = "header_key_source",
.hash = { 0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA,
0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6 },
.size = 0x20,
.dst = g_ncaKeyset.header_key_source
}
}
};
static bool keysRetrieveDebugHandleFromProcessByProgramId(Handle *out, u64 program_id);
static bool keysRetrieveProcessMemory(keysMemoryLocation *location);
static void keysFreeProcessMemory(keysMemoryLocation *location);
static bool keysRetrieveKeysFromProcessMemory(keysMemoryInfo *info);
static bool keysDeriveNcaHeaderKey(void);
static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value);
static char keysConvertHexCharToBinary(char c);
static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size);
static bool keysReadKeysFromFile(void);
bool keysLoadNcaKeyset(void)
{
if (!(envIsSyscallHinted(0x60) && /* svcDebugActiveProcess */
envIsSyscallHinted(0x63) && /* svcGetDebugEvent */
envIsSyscallHinted(0x65) && /* svcGetProcessList */
envIsSyscallHinted(0x69) && /* svcQueryDebugProcessMemory */
envIsSyscallHinted(0x6A))) /* svcReadDebugProcessMemory */
{
LOGFILE("Debug SVC permissions not available!");
return false;
}
if (!keysRetrieveKeysFromProcessMemory(&g_fsRodataMemoryInfo))
{
LOGFILE("Unable to retrieve keys from FS .rodata section!");
return false;
}
if (!keysRetrieveKeysFromProcessMemory(&g_fsDataMemoryInfo))
{
LOGFILE("Unable to retrieve keys from FS .data section!");
return false;
}
if (!keysDeriveNcaHeaderKey())
{
LOGFILE("Unable to derive NCA header key!");
return false;
}
if (!keysReadKeysFromFile()) return false;
return true;
}
const u8 *keysGetNcaHeaderKey(void)
{
return (const u8*)(g_ncaKeyset.header_key);
}
const u8 *keysGetKeyAreaEncryptionKeySource(u8 kaek_index)
{
if (kaek_index > NcaKeyAreaEncryptionKeyIndex_System)
{
LOGFILE("Invalid KAEK index! (0x%02X)", kaek_index);
return NULL;
}
const u8 *ptr = NULL;
switch(kaek_index)
{
case NcaKeyAreaEncryptionKeyIndex_Application:
ptr = (const u8*)(g_ncaKeyset.key_area_key_application_source);
break;
case NcaKeyAreaEncryptionKeyIndex_Ocean:
ptr = (const u8*)(g_ncaKeyset.key_area_key_ocean_source);
break;
case NcaKeyAreaEncryptionKeyIndex_System:
ptr = (const u8*)(g_ncaKeyset.key_area_key_system_source);
break;
default:
break;
}
return ptr;
}
const u8 *keysGetEticketRsaKek(void)
{
return (const u8*)(g_ncaKeyset.eticket_rsa_kek);
}
const u8 *keysGetTitlekek(u8 key_generation)
{
if (key_generation > 0x20)
{
LOGFILE("Invalid key generation value! (0x%02X)", key_generation);
return NULL;
}
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
return (const u8*)(g_ncaKeyset.titlekeks[key_gen_val]);
}
const u8 *keysGetKeyAreaEncryptionKey(u8 key_generation, u8 kaek_index)
{
if (key_generation > 0x20)
{
LOGFILE("Invalid key generation value! (0x%02X)", key_generation);
return NULL;
}
if (kaek_index > NcaKeyAreaEncryptionKeyIndex_System)
{
LOGFILE("Invalid KAEK index! (0x%02X)", kaek_index);
return NULL;
}
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
return (const u8*)(g_ncaKeyset.key_area_keys[key_gen_val][kaek_index]);
}
static bool keysRetrieveDebugHandleFromProcessByProgramId(Handle *out, u64 program_id)
{
if (!out || !program_id)
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
u64 d[8] = {0};
Handle debug_handle = INVALID_HANDLE;
if (program_id > 0x0100000000000005 && program_id != 0x0100000000000028)
{
/* If not a kernel process, get PID from pm:dmnt */
u64 pid;
rc = pmdmntGetProcessId(&pid, program_id);
if (R_FAILED(rc))
{
LOGFILE("pmdmntGetProcessId failed! (0x%08X)", rc);
return false;
}
rc = svcDebugActiveProcess(&debug_handle, pid);
if (R_FAILED(rc))
{
LOGFILE("svcDebugActiveProcess failed! (0x%08X)", rc);
return false;
}
rc = svcGetDebugEvent((u8*)&d, debug_handle);
if (R_FAILED(rc))
{
LOGFILE("svcGetDebugEvent failed! (0x%08X)", rc);
return false;
}
} else {
/* Otherwise, query svc for the process list */
u32 i, num_processes = 0;
u64 *pids = calloc(300, sizeof(u64));
if (!pids)
{
LOGFILE("Failed to allocate memory for PID list!");
return false;
}
rc = svcGetProcessList(&num_processes, pids, 300);
if (R_FAILED(rc))
{
LOGFILE("svcGetProcessList failed! (0x%08X)", rc);
return false;
}
for(i = 0; i < (num_processes - 1); i++)
{
rc = svcDebugActiveProcess(&debug_handle, pids[i]);
if (R_FAILED(rc)) continue;
rc = svcGetDebugEvent((u8*)&d, debug_handle);
if (R_SUCCEEDED(rc) && d[2] == program_id) break;
svcCloseHandle(debug_handle);
debug_handle = INVALID_HANDLE;
}
free(pids);
if (i == (num_processes - 1))
{
LOGFILE("Kernel process lookup failed! (0x%08X)", rc);
return false;
}
}
*out = debug_handle;
return true;
}
static bool keysRetrieveProcessMemory(keysMemoryLocation *location)
{
if (!location || !location->program_id || !location->mask)
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
Handle debug_handle = INVALID_HANDLE;
MemoryInfo mem_info = {0};
u32 page_info = 0;
u64 addr = 0, last_text_addr = 0;
u8 segment = 0;
u8 *tmp = NULL;
bool success = true;
if (!keysRetrieveDebugHandleFromProcessByProgramId(&debug_handle, location->program_id))
{
LOGFILE("Unable to retrieve debug handle for program %016lX!", location->program_id);
return false;
}
/* Locate "real" .text segment as Atmosphere emuMMC has two */
for(;;)
{
rc = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
if (R_FAILED(rc))
{
LOGFILE("svcQueryDebugProcessMemory failed! (0x%08X)", rc);
success = false;
goto out;
}
if ((mem_info.perm & Perm_X) && ((mem_info.type & 0xFF) >= MemType_CodeStatic) && ((mem_info.type & 0xFF) < MemType_Heap)) last_text_addr = mem_info.addr;
addr = (mem_info.addr + mem_info.size);
if (!addr) break;
}
addr = last_text_addr;
for(segment = 1; segment < BIT(3);)
{
rc = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
if (R_FAILED(rc))
{
LOGFILE("svcQueryDebugProcessMemory failed! (0x%08X)", rc);
success = false;
break;
}
/* Code to allow for bitmasking segments */
if ((mem_info.perm & Perm_R) && ((mem_info.type & 0xFF) >= MemType_CodeStatic) && ((mem_info.type & 0xFF) < MemType_Heap) && ((segment <<= 1) >> 1 & location->mask) > 0)
{
/* If location->data == NULL, realloc will essentially act as a malloc */
tmp = realloc(location->data, location->data_size + mem_info.size);
if (!tmp)
{
LOGFILE("Failed to resize key location data buffer to 0x%lX bytes.", location->data_size + mem_info.size);
success = false;
break;
}
location->data = tmp;
tmp = NULL;
rc = svcReadDebugProcessMemory(location->data + location->data_size, debug_handle, mem_info.addr, mem_info.size);
if (R_FAILED(rc))
{
LOGFILE("svcReadDebugProcessMemory failed! (0x%08X)", rc);
success = false;
break;
}
location->data_size += mem_info.size;
}
addr = (mem_info.addr + mem_info.size);
if (addr == 0) break;
}
out:
svcCloseHandle(debug_handle);
if (success && (!location->data || !location->data_size)) success = false;
return success;
}
static void keysFreeProcessMemory(keysMemoryLocation *location)
{
if (location && location->data)
{
free(location->data);
location->data = NULL;
}
}
static bool keysRetrieveKeysFromProcessMemory(keysMemoryInfo *info)
{
if (!info || !info->key_count)
{
LOGFILE("Invalid parameters!");
return false;
}
bool found;
u8 tmp_hash[SHA256_HASH_SIZE];
bool success = false;
if (!keysRetrieveProcessMemory(&(info->location)))
{
LOGFILE("Unable to retrieve process memory from program %016lX!", info->location.program_id);
return false;
}
for(u32 i = 0; i < info->key_count; i++)
{
found = false;
if (!info->keys[i].dst)
{
LOGFILE("Invalid destination pointer for key \"%s\" in program %016lX!", info->keys[i].name, info->location.program_id);
goto out;
}
/* Hash every key length-sized byte chunk in the process memory buffer until a match is found */
for(u64 j = 0; j < info->location.data_size; j++)
{
if ((info->location.data_size - j) < info->keys[i].size) break;
sha256CalculateHash(tmp_hash, info->location.data + j, info->keys[i].size);
if (!memcmp(tmp_hash, info->keys[i].hash, SHA256_HASH_SIZE))
{
/* Jackpot */
memcpy(info->keys[i].dst, info->location.data + j, info->keys[i].size);
found = true;
break;
}
}
if (!found)
{
LOGFILE("Unable to locate key \"%s\" in process memory from program %016lX!", info->keys[i].name, info->location.program_id);
goto out;
}
}
success = true;
out:
keysFreeProcessMemory(&(info->location));
return success;
}
static bool keysDeriveNcaHeaderKey(void)
{
Result rc = 0;
rc = splCryptoGenerateAesKek(g_ncaKeyset.header_kek_source, 0, 0, g_ncaKeyset.header_kek);
if (R_FAILED(rc))
{
LOGFILE("splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", rc);
return false;
}
rc = splCryptoGenerateAesKey(g_ncaKeyset.header_kek, g_ncaKeyset.header_key_source + 0x00, g_ncaKeyset.header_key + 0x00);
if (R_FAILED(rc))
{
LOGFILE("splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", rc);
return false;
}
rc = splCryptoGenerateAesKey(g_ncaKeyset.header_kek, g_ncaKeyset.header_key_source + 0x10, g_ncaKeyset.header_key + 0x10);
if (R_FAILED(rc))
{
LOGFILE("splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", rc);
return false;
}
return true;
}
/**
* Reads a line from file f and parses out the key and value from it.
* The format of a line must match /^ *[A-Za-z0-9_] *[,=] *.+$/.
* If a line ends in \r, the final \r is stripped.
* The input file is assumed to have been opened with the 'b' flag.
* The input file is assumed to contain only ASCII.
*
* A line cannot exceed 512 bytes in length.
* Lines that are excessively long will be silently truncated.
*
* On success, *key and *value will be set to point to the key and value in
* the input line, respectively.
* *key and *value may also be NULL in case of empty lines.
* On failure, *key and *value will be set to NULL.
* End of file is considered failure.
*
* Because *key and *value will point to a static buffer, their contents must be
* copied before calling this function again.
* For the same reason, this function is not thread-safe.
*
* The key will be converted to lowercase.
* An empty key is considered a parse error, but an empty value is returned as
* success.
*
* This function assumes that the file can be trusted not to contain any NUL in
* the contents.
*
* Whitespace (' ', ASCII 0x20, as well as '\t', ASCII 0x09) at the beginning of
* the line, at the end of the line as well as around = (or ,) will be ignored.
*
* @param f the file to read
* @param key pointer to change to point to the key
* @param value pointer to change to point to the value
* @return 0 on success,
* 1 on end of file,
* -1 on parse error (line too long, line malformed)
* -2 on I/O error
*/
static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value)
{
if (!f || !key || !value)
{
LOGFILE("Invalid parameters!");
return -2;
}
#define SKIP_SPACE(p) do {\
for (; (*p == ' ' || *p == '\t'); ++p);\
} while(0);
static char line[512] = {0};
char *k, *v, *p, *end;
*key = *value = NULL;
errno = 0;
if (fgets(line, (int)sizeof(line), f) == NULL)
{
if (feof(f))
{
return 1;
} else {
return -2;
}
}
if (errno != 0) return -2;
if (*line == '\n' || *line == '\r' || *line == '\0') return 0;
/* Not finding \r or \n is not a problem.
* The line might just be exactly 512 characters long, we have no way to
* tell.
* Additionally, it's possible that the last line of a file is not actually
* a line (i.e., does not end in '\n'); we do want to handle those.
*/
if ((p = strchr(line, '\r')) != NULL || (p = strchr(line, '\n')) != NULL)
{
end = p;
*p = '\0';
} else {
end = (line + strlen(line) + 1);
}
p = line;
SKIP_SPACE(p);
k = p;
/* Validate key and convert to lower case. */
for (; *p != ' ' && *p != ',' && *p != '\t' && *p != '='; ++p)
{
if (*p == '\0') return -1;
if (*p >= 'A' && *p <= 'Z')
{
*p = 'a' + (*p - 'A');
continue;
}
if (*p != '_' && (*p < '0' && *p > '9') && (*p < 'a' && *p > 'z')) return -1;
}
/* Bail if the final ++p put us at the end of string */
if (*p == '\0') return -1;
/* We should be at the end of key now and either whitespace or [,=]
* follows.
*/
if (*p == '=' || *p == ',')
{
*p++ = '\0';
} else {
*p++ = '\0';
SKIP_SPACE(p);
if (*p != '=' && *p != ',') return -1;
*p++ = '\0';
}
/* Empty key is an error. */
if (*k == '\0') return -1;
SKIP_SPACE(p);
v = p;
/* Skip trailing whitespace */
for (p = end - 1; *p == '\t' || *p == ' '; --p);
*(p + 1) = '\0';
*key = k;
*value = v;
return 0;
#undef SKIP_SPACE
}
static char keysConvertHexCharToBinary(char c)
{
if ('a' <= c && c <= 'f') return (c - 'a' + 0xA);
if ('A' <= c && c <= 'F') return (c - 'A' + 0xA);
if ('0' <= c && c <= '9') return (c - '0');
return 'z';
}
static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size)
{
if (!out || !key || !strlen(key) || !value || !strlen(value) || !size)
{
LOGFILE("Invalid parameters!");
return false;
}
u32 hex_str_len = (2 * size);
if (strlen(value) != hex_str_len)
{
LOGFILE("Key \"%s\" must be %u hex digits long!", key, hex_str_len);
return false;
}
memset(out, 0, size);
for(u32 i = 0; i < hex_str_len; i++)
{
char val = keysConvertHexCharToBinary(value[i]);
if (val == 'z')
{
LOGFILE("Invalid hex character in key \"%s\" at position %u!", key, i);
return false;
}
if ((i & 1) == 0) val <<= 4;
out[i >> 1] |= val;
}
return true;
}
static bool keysReadKeysFromFile(void)
{
int ret = 0;
u32 key_count = 0;
FILE *keys_file = NULL;
char *key = NULL, *value = NULL;
char test_name[0x40] = {0};
bool common_eticket_rsa_kek = false, personalized_eticket_rsa_kek = false;
keys_file = fopen(KEYS_FILE_PATH, "rb");
if (!keys_file)
{
LOGFILE("Unable to open \"%s\" to retrieve keys!", KEYS_FILE_PATH);
return false;
}
while(true)
{
ret = keysGetKeyAndValueFromFile(keys_file, &key, &value);
if (ret == 1 || ret == -2) break; /* Break from the while loop if EOF is reached or if an I/O error occurs */
/* Ignore malformed lines */
if (ret != 0 || !key || !value) continue;
if (!common_eticket_rsa_kek && !personalized_eticket_rsa_kek && !strcasecmp(key, "eticket_rsa_kek"))
{
if (!keysParseHexKey(g_ncaKeyset.eticket_rsa_kek, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek))) return false;
common_eticket_rsa_kek = true;
key_count++;
} else
if (!personalized_eticket_rsa_kek && !strcasecmp(key, "eticket_rsa_kek_personalized"))
{
/* Use the personalized eTicket RSA kek if available */
/* This only appears on consoles that use the new PRODINFO key generation scheme */
if (!keysParseHexKey(g_ncaKeyset.eticket_rsa_kek, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek))) return false;
personalized_eticket_rsa_kek = true;
key_count++;
} else {
for(u32 i = 0; i < 0x20; i++)
{
snprintf(test_name, sizeof(test_name), "titlekek_%02x", i);
if (!strcasecmp(key, test_name))
{
if (!keysParseHexKey(g_ncaKeyset.titlekeks[i], key, value, sizeof(g_ncaKeyset.titlekeks[i]))) return false;
key_count++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_application_%02x", i);
if (!strcasecmp(key, test_name))
{
if (!keysParseHexKey(g_ncaKeyset.key_area_keys[i][0], key, value, sizeof(g_ncaKeyset.key_area_keys[i][0]))) return false;
key_count++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_ocean_%02x", i);
if (!strcasecmp(key, test_name))
{
if (!keysParseHexKey(g_ncaKeyset.key_area_keys[i][1], key, value, sizeof(g_ncaKeyset.key_area_keys[i][1]))) return false;
key_count++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_system_%02x", i);
if (!strcasecmp(key, test_name))
{
if (!keysParseHexKey(g_ncaKeyset.key_area_keys[i][2], key, value, sizeof(g_ncaKeyset.key_area_keys[i][2]))) return false;
key_count++;
break;
}
}
}
}
fclose(keys_file);
if (!key_count)
{
LOGFILE("Unable to parse necessary keys from \"%s\"! (keys file empty?)", KEYS_FILE_PATH);
return false;
}
return true;
}

16
source/new/keys.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#ifndef __KEYS_H__
#define __KEYS_H__
#define KEYS_FILE_PATH "sdmc:/switch/prod.keys" /* Location used by Lockpick_RCM */
bool keysLoadNcaKeyset(void);
const u8 *keysGetNcaHeaderKey(void);
const u8 *keysGetKeyAreaEncryptionKeySource(u8 kaek_index);
const u8 *keysGetEticketRsaKek(void);
const u8 *keysGetTitlekek(u8 key_generation);
const u8 *keysGetKeyAreaEncryptionKey(u8 key_generation, u8 kaek_index);
#endif /* __KEYS_H__ */

2299
source/new/lz4.c Normal file

File diff suppressed because it is too large Load diff

682
source/new/lz4.h Normal file
View file

@ -0,0 +1,682 @@
/*
* LZ4 - Fast LZ compression algorithm
* Header File
* Copyright (C) 2011-present, Yann Collet.
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You can contact the author at :
- LZ4 homepage : http://www.lz4.org
- LZ4 source repository : https://github.com/lz4/lz4
*/
#if defined (__cplusplus)
extern "C" {
#endif
#ifndef LZ4_H_2983827168210
#define LZ4_H_2983827168210
/* --- Dependency --- */
#include <stddef.h> /* size_t */
/**
Introduction
LZ4 is lossless compression algorithm, providing compression speed at 500 MB/s per core,
scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
The LZ4 compression library provides in-memory compression and decompression functions.
It gives full buffer control to user.
Compression can be done in:
- a single step (described as Simple Functions)
- a single step, reusing a context (described in Advanced Functions)
- unbounded multiple steps (described as Streaming compression)
lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md).
Decompressing a block requires additional metadata, such as its compressed size.
Each application is free to encode and pass such metadata in whichever way it wants.
lz4.h only handle blocks, it can not generate Frames.
Blocks are different from Frames (doc/lz4_Frame_format.md).
Frames bundle both blocks and metadata in a specified manner.
This are required for compressed data to be self-contained and portable.
Frame format is delivered through a companion API, declared in lz4frame.h.
Note that the `lz4` CLI can only manage frames.
*/
/*^***************************************************************
* Export parameters
*****************************************************************/
/*
* LZ4_DLL_EXPORT :
* Enable exporting of functions when building a Windows DLL
* LZ4LIB_VISIBILITY :
* Control library symbols visibility.
*/
#ifndef LZ4LIB_VISIBILITY
# if defined(__GNUC__) && (__GNUC__ >= 4)
# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
# else
# define LZ4LIB_VISIBILITY
# endif
#endif
#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
#else
# define LZ4LIB_API LZ4LIB_VISIBILITY
#endif
/*------ Version ------*/
#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */
#define LZ4_VERSION_RELEASE 1 /* for tweaks, bug-fixes, or development */
#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
#define LZ4_QUOTE(str) #str
#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION)
LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */
LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */
/*-************************************
* Tuning parameter
**************************************/
/*!
* LZ4_MEMORY_USAGE :
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
* Increasing memory usage improves compression ratio.
* Reduced memory usage may improve speed, thanks to better cache locality.
* Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache
*/
#ifndef LZ4_MEMORY_USAGE
# define LZ4_MEMORY_USAGE 14
#endif
/*-************************************
* Simple Functions
**************************************/
/*! LZ4_compress_default() :
Compresses 'srcSize' bytes from buffer 'src'
into already allocated 'dst' buffer of size 'dstCapacity'.
Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
It also runs faster, so it's a recommended setting.
If the function cannot compress 'src' into a more limited 'dst' budget,
compression stops *immediately*, and the function result is zero.
In which case, 'dst' content is undefined (invalid).
srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
dstCapacity : size of buffer 'dst' (which must be already allocated)
@return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
or 0 if compression fails
Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
*/
LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
/*! LZ4_decompress_safe() :
compressedSize : is the exact complete size of the compressed block.
dstCapacity : is the size of destination buffer, which must be already allocated.
@return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
If destination buffer is not large enough, decoding will stop and output an error code (negative value).
If the source stream is detected malformed, the function will stop decoding and return a negative result.
Note : This function is protected against malicious data packets (never writes outside 'dst' buffer, nor read outside 'source' buffer).
*/
LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
/*-************************************
* Advanced Functions
**************************************/
#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */
#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
/*! LZ4_compressBound() :
Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
This function is primarily useful for memory allocation purposes (destination buffer size).
Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
inputSize : max supported value is LZ4_MAX_INPUT_SIZE
return : maximum output size in a "worst case" scenario
or 0, if input size is incorrect (too large or negative)
*/
LZ4LIB_API int LZ4_compressBound(int inputSize);
/*! LZ4_compress_fast() :
Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
An acceleration value of "1" is the same as regular LZ4_compress_default()
Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c).
*/
LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_compress_fast_extState() :
* Same as LZ4_compress_fast(), using an externally allocated memory space for its state.
* Use LZ4_sizeofState() to know how much memory must be allocated,
* and allocate it on 8-bytes boundaries (using `malloc()` typically).
* Then, provide this buffer as `void* state` to compression function.
*/
LZ4LIB_API int LZ4_sizeofState(void);
LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_compress_destSize() :
* Reverse the logic : compresses as much data as possible from 'src' buffer
* into already allocated buffer 'dst', of size >= 'targetDestSize'.
* This function either compresses the entire 'src' content into 'dst' if it's large enough,
* or fill 'dst' buffer completely with as much data as possible from 'src'.
* note: acceleration parameter is fixed to "default".
*
* *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
* New value is necessarily <= input value.
* @return : Nb bytes written into 'dst' (necessarily <= targetDestSize)
* or 0 if compression fails.
*/
LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize);
/*! LZ4_decompress_safe_partial() :
* Decompress an LZ4 compressed block, of size 'srcSize' at position 'src',
* into destination buffer 'dst' of size 'dstCapacity'.
* Up to 'targetOutputSize' bytes will be decoded.
* The function stops decoding on reaching this objective,
* which can boost performance when only the beginning of a block is required.
*
* @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity)
* If source stream is detected malformed, function returns a negative result.
*
* Note : @return can be < targetOutputSize, if compressed block contains less data.
*
* Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity,
* and expects targetOutputSize <= dstCapacity.
* It effectively stops decoding on reaching targetOutputSize,
* so dstCapacity is kind of redundant.
* This is because in a previous version of this function,
* decoding operation would not "break" a sequence in the middle.
* As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize,
* it could write more bytes, though only up to dstCapacity.
* Some "margin" used to be required for this operation to work properly.
* This is no longer necessary.
* The function nonetheless keeps its signature, in an effort to not break API.
*/
LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
/*-*********************************************
* Streaming Compression Functions
***********************************************/
typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */
LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr);
/*! LZ4_resetStream_fast() : v1.9.0+
* Use this to prepare an LZ4_stream_t for a new chain of dependent blocks
* (e.g., LZ4_compress_fast_continue()).
*
* An LZ4_stream_t must be initialized once before usage.
* This is automatically done when created by LZ4_createStream().
* However, should the LZ4_stream_t be simply declared on stack (for example),
* it's necessary to initialize it first, using LZ4_initStream().
*
* After init, start any new stream with LZ4_resetStream_fast().
* A same LZ4_stream_t can be re-used multiple times consecutively
* and compress multiple streams,
* provided that it starts each new stream with LZ4_resetStream_fast().
*
* LZ4_resetStream_fast() is much faster than LZ4_initStream(),
* but is not compatible with memory regions containing garbage data.
*
* Note: it's only useful to call LZ4_resetStream_fast()
* in the context of streaming compression.
* The *extState* functions perform their own resets.
* Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive.
*/
LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
/*! LZ4_loadDict() :
* Use this function to reference a static dictionary into LZ4_stream_t.
* The dictionary must remain available during compression.
* LZ4_loadDict() triggers a reset, so any previous data will be forgotten.
* The same dictionary will have to be loaded on decompression side for successful decoding.
* Dictionary are useful for better compression of small data (KB range).
* While LZ4 accept any input as dictionary,
* results are generally better when using Zstandard's Dictionary Builder.
* Loading a size of 0 is allowed, and is the same as reset.
* @return : loaded dictionary size, in bytes (necessarily <= 64 KB)
*/
LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
/*! LZ4_compress_fast_continue() :
* Compress 'src' content using data from previously compressed blocks, for better compression ratio.
* 'dst' buffer must be already allocated.
* If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
*
* @return : size of compressed block
* or 0 if there is an error (typically, cannot fit into 'dst').
*
* Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block.
* Each block has precise boundaries.
* Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata.
* It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together.
*
* Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory !
*
* Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB.
* Make sure that buffers are separated, by at least one byte.
* This construction ensures that each block only depends on previous block.
*
* Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
*
* Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed.
*/
LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_saveDict() :
* If last 64KB data cannot be guaranteed to remain available at its current memory location,
* save it into a safer place (char* safeBuffer).
* This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
* but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
* @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
*/
LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
/*-**********************************************
* Streaming Decompression Functions
* Bufferless synchronous API
************************************************/
typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */
/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
* creation / destruction of streaming decompression tracking context.
* A tracking context can be re-used multiple times.
*/
LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
/*! LZ4_setStreamDecode() :
* An LZ4_streamDecode_t context can be allocated once and re-used multiple times.
* Use this function to start decompression of a new stream of blocks.
* A dictionary can optionally be set. Use NULL or size 0 for a reset order.
* Dictionary is presumed stable : it must remain accessible and unmodified during next decompression.
* @return : 1 if OK, 0 if error
*/
LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
/*! LZ4_decoderRingBufferSize() : v1.8.2+
* Note : in a ring buffer scenario (optional),
* blocks are presumed decompressed next to each other
* up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize),
* at which stage it resumes from beginning of ring buffer.
* When setting such a ring buffer for streaming decompression,
* provides the minimum size of this ring buffer
* to be compatible with any source respecting maxBlockSize condition.
* @return : minimum ring buffer size,
* or 0 if there is an error (invalid maxBlockSize).
*/
LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize);
#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */
/*! LZ4_decompress_*_continue() :
* These decoding functions allow decompression of consecutive blocks in "streaming" mode.
* A block is an unsplittable entity, it must be presented entirely to a decompression function.
* Decompression functions only accepts one block at a time.
* The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded.
* If less than 64KB of data has been decoded, all the data must be present.
*
* Special : if decompression side sets a ring buffer, it must respect one of the following conditions :
* - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize).
* maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes.
* In which case, encoding and decoding buffers do not need to be synchronized.
* Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize.
* - Synchronized mode :
* Decompression buffer size is _exactly_ the same as compression buffer size,
* and follows exactly same update rule (block boundaries at same positions),
* and decoding function is provided with exact decompressed size of each block (exception for last block of the stream),
* _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB).
* - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes.
* In which case, encoding and decoding buffers do not need to be synchronized,
* and encoding ring buffer can have any size, including small ones ( < 64 KB).
*
* Whenever these conditions are not possible,
* save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression,
* then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block.
*/
LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity);
/*! LZ4_decompress_*_usingDict() :
* These decoding functions work the same as
* a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue()
* They are stand-alone, and don't need an LZ4_streamDecode_t structure.
* Dictionary is presumed stable : it must remain accessible and unmodified during decompression.
* Performance tip : Decompression speed can be substantially increased
* when dst == dictStart + dictSize.
*/
LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize);
/*^*************************************
* !!!!!! STATIC LINKING ONLY !!!!!!
***************************************/
/*-****************************************************************************
* Experimental section
*
* Symbols declared in this section must be considered unstable. Their
* signatures or semantics may change, or they may be removed altogether in the
* future. They are therefore only safe to depend on when the caller is
* statically linked against the library.
*
* To protect against unsafe usage, not only are the declarations guarded,
* the definitions are hidden by default
* when building LZ4 as a shared/dynamic library.
*
* In order to access these declarations,
* define LZ4_STATIC_LINKING_ONLY in your application
* before including LZ4's headers.
*
* In order to make their implementations accessible dynamically, you must
* define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library.
******************************************************************************/
#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS
#define LZ4LIB_STATIC_API LZ4LIB_API
#else
#define LZ4LIB_STATIC_API
#endif
#ifdef LZ4_STATIC_LINKING_ONLY
/*! LZ4_compress_fast_extState_fastReset() :
* A variant of LZ4_compress_fast_extState().
*
* Using this variant avoids an expensive initialization step.
* It is only safe to call if the state buffer is known to be correctly initialized already
* (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized").
* From a high level, the difference is that
* this function initializes the provided state with a call to something like LZ4_resetStream_fast()
* while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
*/
LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_attach_dictionary() :
* This is an experimental API that allows
* efficient use of a static dictionary many times.
*
* Rather than re-loading the dictionary buffer into a working context before
* each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
* working LZ4_stream_t, this function introduces a no-copy setup mechanism,
* in which the working stream references the dictionary stream in-place.
*
* Several assumptions are made about the state of the dictionary stream.
* Currently, only streams which have been prepared by LZ4_loadDict() should
* be expected to work.
*
* Alternatively, the provided dictionaryStream may be NULL,
* in which case any existing dictionary stream is unset.
*
* If a dictionary is provided, it replaces any pre-existing stream history.
* The dictionary contents are the only history that can be referenced and
* logically immediately precede the data compressed in the first subsequent
* compression call.
*
* The dictionary will only remain attached to the working stream through the
* first compression call, at the end of which it is cleared. The dictionary
* stream (and source buffer) must remain in-place / accessible / unchanged
* through the completion of the first compression call on the stream.
*/
LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream);
#endif
/*-************************************************************
* PRIVATE DEFINITIONS
**************************************************************
* Do not use these definitions directly.
* They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
* Accessing members will expose code to API and/or ABI break in future versions of the library.
**************************************************************/
#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
#include <stdint.h>
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
struct LZ4_stream_t_internal {
uint32_t hashTable[LZ4_HASH_SIZE_U32];
uint32_t currentOffset;
uint16_t dirty;
uint16_t tableType;
const uint8_t* dictionary;
const LZ4_stream_t_internal* dictCtx;
uint32_t dictSize;
};
typedef struct {
const uint8_t* externalDict;
size_t extDictSize;
const uint8_t* prefixEnd;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#else
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
struct LZ4_stream_t_internal {
unsigned int hashTable[LZ4_HASH_SIZE_U32];
unsigned int currentOffset;
unsigned short dirty;
unsigned short tableType;
const unsigned char* dictionary;
const LZ4_stream_t_internal* dictCtx;
unsigned int dictSize;
};
typedef struct {
const unsigned char* externalDict;
const unsigned char* prefixEnd;
size_t extDictSize;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#endif
/*! LZ4_stream_t :
* information structure to track an LZ4 stream.
* LZ4_stream_t can also be created using LZ4_createStream(), which is recommended.
* The structure definition can be convenient for static allocation
* (on stack, or as part of larger structure).
* Init this structure with LZ4_initStream() before first use.
* note : only use this definition in association with static linking !
* this definition is not API/ABI safe, and may change in a future version.
*/
#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4 + ((sizeof(void*)==16) ? 4 : 0) /*AS-400*/ )
#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long))
union LZ4_stream_u {
unsigned long long table[LZ4_STREAMSIZE_U64];
LZ4_stream_t_internal internal_donotuse;
} ; /* previously typedef'd to LZ4_stream_t */
/*! LZ4_initStream() : v1.9.0+
* An LZ4_stream_t structure must be initialized at least once.
* This is automatically done when invoking LZ4_createStream(),
* but it's not when the structure is simply declared on stack (for example).
*
* Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t.
* It can also initialize any arbitrary buffer of sufficient size,
* and will @return a pointer of proper type upon initialization.
*
* Note : initialization fails if size and alignment conditions are not respected.
* In which case, the function will @return NULL.
* Note2: An LZ4_stream_t structure guarantees correct alignment and size.
* Note3: Before v1.9.0, use LZ4_resetStream() instead
*/
LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size);
/*! LZ4_streamDecode_t :
* information structure to track an LZ4 stream during decompression.
* init this structure using LZ4_setStreamDecode() before first use.
* note : only use in association with static linking !
* this definition is not API/ABI safe,
* and may change in a future version !
*/
#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ )
#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long))
union LZ4_streamDecode_u {
unsigned long long table[LZ4_STREAMDECODESIZE_U64];
LZ4_streamDecode_t_internal internal_donotuse;
} ; /* previously typedef'd to LZ4_streamDecode_t */
/*-************************************
* Obsolete Functions
**************************************/
/*! Deprecation warnings
*
* Deprecated functions make the compiler generate a warning when invoked.
* This is meant to invite users to update their source code.
* Should deprecation warnings be a problem, it is generally possible to disable them,
* typically with -Wno-deprecated-declarations for gcc
* or _CRT_SECURE_NO_WARNINGS in Visual.
*
* Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS
* before including the header file.
*/
#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
# define LZ4_DEPRECATED(message) /* disable deprecation warnings */
#else
# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
# define LZ4_DEPRECATED(message) [[deprecated(message)]]
# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__)
# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
# elif (LZ4_GCC_VERSION >= 301)
# define LZ4_DEPRECATED(message) __attribute__((deprecated))
# elif defined(_MSC_VER)
# define LZ4_DEPRECATED(message) __declspec(deprecated(message))
# else
# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler")
# define LZ4_DEPRECATED(message)
# endif
#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
/* Obsolete compression functions */
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* source, char* dest, int sourceSize);
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
/* Obsolete decompression functions */
LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
/* Obsolete streaming functions; degraded functionality; do not use!
*
* In order to perform streaming compression, these functions depended on data
* that is no longer tracked in the state. They have been preserved as well as
* possible: using them will still produce a correct output. However, they don't
* actually retain any history between compression calls. The compression ratio
* achieved will therefore be no better than compressing each chunk
* independently.
*/
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void);
LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
/* Obsolete streaming decoding functions */
LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
/*! LZ4_decompress_fast() : **unsafe!**
* These functions used to be faster than LZ4_decompress_safe(),
* but it has changed, and they are now slower than LZ4_decompress_safe().
* This is because LZ4_decompress_fast() doesn't know the input size,
* and therefore must progress more cautiously in the input buffer to not read beyond the end of block.
* On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability.
* As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated.
*
* The last remaining LZ4_decompress_fast() specificity is that
* it can decompress a block without knowing its compressed size.
* Such functionality could be achieved in a more secure manner,
* by also providing the maximum size of input buffer,
* but it would require new prototypes, and adaptation of the implementation to this new use case.
*
* Parameters:
* originalSize : is the uncompressed size to regenerate.
* `dst` must be already allocated, its size must be >= 'originalSize' bytes.
* @return : number of bytes read from source buffer (== compressed size).
* The function expects to finish at block's end exactly.
* If the source stream is detected malformed, the function stops decoding and returns a negative result.
* note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer.
* However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds.
* Also, since match offsets are not validated, match reads from 'src' may underflow too.
* These issues never happen if input (compressed) data is correct.
* But they may happen if input data is invalid (error or intentional tampering).
* As a consequence, use these functions in trusted environments with trusted data **only**.
*/
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead")
LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead")
LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead")
LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
/*! LZ4_resetStream() :
* An LZ4_stream_t structure must be initialized at least once.
* This is done with LZ4_initStream(), or LZ4_resetStream().
* Consider switching to LZ4_initStream(),
* invoking LZ4_resetStream() will trigger deprecation warnings in the future.
*/
LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
#endif /* LZ4_H_2983827168210 */
#if defined (__cplusplus)
}
#endif

216
source/new/nca.c Normal file
View file

@ -0,0 +1,216 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "nca.h"
#include "keys.h"
#include "utils.h"
size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, bool encrypt)
{
if (!ctx || !dst || !src || !size || (size % NCA_AES_XTS_SECTOR_SIZE) != 0)
{
LOGFILE("Invalid parameters!");
return 0;
}
size_t i, crypt_res = 0;
u64 cur_sector = sector;
u8 *dst_u8 = (u8*)dst;
const u8 *src_u8 = (const u8*)src;
for(i = 0; i < size; i += NCA_AES_XTS_SECTOR_SIZE, cur_sector++)
{
/* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */
aes128XtsContextResetSector(ctx, cur_sector, true);
if (encrypt)
{
crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE);
} else {
crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE);
}
if (crypt_res != NCA_AES_XTS_SECTOR_SIZE) break;
}
return i;
}
bool ncaDecryptKeyArea(NcaContext *ctx)
{
if (!ctx)
{
LOGFILE("Invalid NCA context!");
return false;
}
Result rc = 0;
u8 tmp_kek[0x10] = {0};
const u8 *kek_src = NULL;
kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index);
if (!kek_src)
{
LOGFILE("Unable to retrieve KAEK source for index 0x%02X!", ctx->header.kaek_index);
return false;
}
rc = splCryptoGenerateAesKek(kek_src, ctx->key_generation, 0, tmp_kek);
if (R_FAILED(rc))
{
LOGFILE("splCryptoGenerateAesKek failed! (0x%08X)", rc);
return false;
}
for(u8 i = 0; i < 4; i++)
{
rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
if (R_FAILED(rc))
{
LOGFILE("splCryptoGenerateAesKey failed! (0x%08X)", rc);
return false;
}
}
return true;
}
bool ncaEncryptNcaKeyArea(NcaContext *ctx)
{
if (!ctx)
{
LOGFILE("Invalid NCA context!");
return false;
}
Aes128Context key_area_ctx = {0};
const u8 *kaek = NULL;
kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index);
if (!kaek)
{
LOGFILE("Unable to retrieve KAEK for key generation 0x%02X and KAEK index 0x%02X!", ctx->key_generation, ctx->header.kaek_index);
return false;
}
aes128ContextCreate(&key_area_ctx, kaek, true);
for(u8 i = 0; i < 4; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
return true;
}
bool ncaDecryptHeader(NcaContext *ctx)
{
if (!ctx)
{
LOGFILE("Invalid NCA context!");
return false;
}
u32 i, magic = 0;
size_t crypt_res = 0;
const u8 *header_key = NULL;
Aes128XtsContext hdr_aes_ctx = {0};
header_key = keysGetNcaHeaderKey();
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false);
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, false);
if (crypt_res != NCA_HEADER_LENGTH)
{
LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res);
return false;
}
magic = __builtin_bswap32(ctx->header.magic);
switch(magic)
{
case NCA_NCA3_MAGIC:
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, false);
break;
case NCA_NCA2_MAGIC:
for(i = 0; i < 4; i++)
{
if (ctx->header.fs_entries[i].enable_entry)
{
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), sizeof(NcaFsHeader), 0, false);
if (crypt_res != sizeof(NcaFsHeader)) break;
} else {
memset(&(ctx->header.fs_headers[i]), 0, sizeof(NcaFsHeader));
}
}
break;
case NCA_NCA0_MAGIC:
break;
default:
LOGFILE("Invalid NCA magic word! Wrong header key? (0x%08X)", magic);
return false;
}
}
static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
}
static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
for(u8 i = 0; i < 4; i++)
{
ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF);
ctr_val >>= 8;
}
}

336
source/new/nca.h Normal file
View file

@ -0,0 +1,336 @@
#pragma once
#ifndef __NCA_H__
#define __NCA_H__
#define NCA_HEADER_LENGTH 0x400
#define NCA_FS_HEADER_LENGTH 0x200
#define NCA_FS_HEADER_COUNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT))
#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0" */
#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2" */
#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3" */
#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC" */
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */
#define NCA_FS_ENTRY_BLOCK_SIZE 0x200
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
typedef enum {
NcaVersion_Nca0Beta = 0,
NcaVersion_Nca0 = 1,
NcaVersion_Nca2 = 2,
NcaVersion_Nca3 = 3
} NcaVersion;
typedef enum {
NcaDistributionType_Download = 0,
NcaDistributionType_GameCard = 1
} NcaDistributionType;
typedef enum {
NcaContentType_Program = 0,
NcaContentType_Meta = 1,
NcaContentType_Control = 2,
NcaContentType_Manual = 3,
NcaContentType_Data = 4,
NcaContentType_PublicData = 5
} NcaContentType;
typedef enum {
NcaKeyGenerationOld_100_230 = 0,
NcaKeyGenerationOld_300 = 1
} NcaKeyGenerationOld;
typedef enum {
NcaKeyAreaEncryptionKeyIndex_Application = 0,
NcaKeyAreaEncryptionKeyIndex_Ocean = 1,
NcaKeyAreaEncryptionKeyIndex_System = 2
} NcaKeyAreaEncryptionKeyIndex;
/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value
typedef enum {
NcaKeyGeneration_301_302 = 3,
NcaKeyGeneration_400_410 = 4,
NcaKeyGeneration_500_510 = 5,
NcaKeyGeneration_600_610 = 6,
NcaKeyGeneration_620 = 7,
NcaKeyGeneration_700_801 = 8,
NcaKeyGeneration_810_811 = 9,
NcaKeyGeneration_900_901 = 10,
NcaKeyGeneration_910_920 = 11,
NcaKeyGeneration_Latest = NcaKeyGeneration_910_920
} NcaKeyGeneration;
typedef struct {
u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks
u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks
u8 enable_entry;
u8 reserved[0x7];
} NcaFsEntry;
typedef struct {
u8 hash[SHA256_HASH_SIZE];
} NcaFsHash;
typedef struct {
u8 key[0x10];
} NcaEncryptedKey;
typedef enum {
NcaFsType_RomFs = 0,
NcaFsType_PartitionFs = 1
} NcaFsType;
typedef enum {
NcaHashType_Auto = 0,
NcaHashType_None = 1,
NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs
NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs
} NcaHashType;
typedef enum {
NcaEncryptionType_Auto = 0,
NcaEncryptionType_None = 1,
NcaEncryptionType_AesXts = 2,
NcaEncryptionType_AesCtr = 3,
NcaEncryptionType_AesCtrEx = 4
} NcaEncryptionType;
typedef struct {
u64 offset;
u64 size;
} NcaHierarchicalSha256LayerInfo;
/// Used for NcaFsType_PartitionFs and NCA0 RomFS
typedef struct {
u8 master_hash[SHA256_HASH_SIZE];
u32 hash_block_size;
u32 layer_count;
NcaHierarchicalSha256LayerInfo hash_data_layer_info;
NcaHierarchicalSha256LayerInfo hash_target_layer_info;
} NcaHierarchicalSha256;
typedef struct {
u64 offset;
u64 size;
u32 block_size; ///< Use NCA_IVFC_CALC_BLOCK_SIZE to calculate the actual block size using this value.
u8 reserved[0x4];
} NcaHierarchicalIntegrityLayerInfo;
/// Used for NcaFsType_RomFs
typedef struct {
u32 magic; ///< "IVFC"
u32 version;
u32 master_hash_size;
u32 layer_count;
NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[5];
NcaHierarchicalIntegrityLayerInfo hash_target_layer_info;
u8 signature_salt[0x20];
u8 master_hash[0x20];
} NcaHierarchicalIntegrity;
typedef struct {
union {
struct {
///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs)
NcaHierarchicalSha256 hierarchical_sha256;
u8 reserved_1[0xB0];
};
struct {
///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs)
NcaHierarchicalIntegrity hierarchical_integrity;
u8 reserved_2[0x18];
};
};
} NcaHashInfo;
typedef struct {
u32 magic; ///< "BKTR"
u32 bucket_count;
u32 entry_count;
u8 reserved[0x4];
} NcaBucketTreeHeader;
/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs)
typedef struct {
u64 indirect_offset;
u64 indirect_size;
NcaBucketTreeHeader indirect_header;
u64 aes_ctr_ex_offset;
u64 aes_ctr_ex_size;
NcaBucketTreeHeader aes_ctr_ex_header;
} NcaPatchInfo;
/// Format unknown
typedef struct {
u8 unknown[0x30];
} NcaSparseInfo;
typedef struct {
u16 version;
u8 fs_type; ///< NcaFsType
u8 hash_type; ///< NcaHashType
u8 encryption_type; ///< NcaEncryptionType
u8 reserved_1[0x3];
NcaHashInfo hash_info;
NcaPatchInfo patch_info;
union {
u8 section_ctr[0x8];
struct {
u32 generation;
u32 secure_value;
};
};
NcaSparseInfo sparse_info;
u8 reserved_2[0x88];
} NcaFsHeader;
typedef struct {
u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key.
u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM.
u32 magic; ///< "NCA0" / "NCA2" / "NCA3"
u8 distribution_type; ///< NcaDistributionType
u8 content_type; ///< NcaContentType
u8 key_generation_old; ///< NcaKeyGenerationOld
u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex
u64 content_size;
u64 program_id;
u32 content_index;
union {
u32 sdk_addon_version;
struct {
u8 sdk_addon_revision;
u8 sdk_addon_micro;
u8 sdk_addon_minor;
u8 sdk_addon_major;
};
};
u8 key_generation; ///< NcaKeyGeneration
u8 main_signature_key_generation;
u8 reserved_1[0xE];
FsRightsId rights_id; ///< Used for titlekey crypto.
NcaFsEntry fs_entries[4]; ///< Start and end offsets for each NCA FS section.
NcaFsHash fs_hashes[4]; ///< SHA-256 hashes calculated over each NCA FS section header.
NcaEncryptedKey encrypted_keys[4]; ///< Only the encrypted key at index #2 is used. The other three are zero filled before the key area is encrypted.
u8 reserved_2[0xC0];
NcaFsHeader fs_headers[4]; /// NCA FS section headers.
} NcaHeader;
typedef struct {
u64 offset;
u64 size;
u32 section_num;
NcaFsHeader *fs_header;
u8 ctr
} NcaFsContext;
typedef struct {
u8 storage_id; ///< NcmStorageId
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data.
u64 gamecard_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
NcmContentId id; ///< Also used to read NCA data.
char id_str[0x21];
u8 hash[0x20];
char hash_str[0x41];
u8 type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 size; ///< Retrieved from NcmContentInfo.
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
u8 id_offset; ///< Retrieved from NcmContentInfo.
bool rights_id_available;
NcaHeader header;
NcaEncryptedKey decrypted_keys[4];
NcaFsContext fs_contexts[4];
} NcaContext;
static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out)
{
if (!size || !out) return;
*out = 0;
memcpy(out, size, 6);
}
static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out)
{
if (!size || !out) return;
memcpy(out, size, 6);
}
static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx)
{
if (!ctx) return 0;
return (ctx->header.key_generation > ctx->header.key_generation_old ? ctx->header.key_generation : ctx->header.key_generation_old);
}
static inline void ncaSetDownloadDistributionType(NcaContext *ctx)
{
if (!ctx) return;
ctx->header.distribution_type = NcaDistributionType_Download;
}
static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx)
{
if (!ctx) return;
bool rights_id_available = false;
for(u8 i = 0; i < 0x10; i++)
{
if (ctx->header.rights_id.c[i] != 0)
{
rights_id_available = true;
break;
}
}
return rights_id_available;
}
static inline void ncaWipeRightsId(NcaContext *ctx)
{
if (!ctx) return;
memset(ctx->header.rights_id, 0, sizeof(FsRightsId));
}
bool ncaDecryptKeyArea(NcaContext *nca_ctx);
bool ncaEncryptKeyArea(NcaContext *nca_ctx);
#endif /* __NCA_H__ */

224
source/new/rsa.c Normal file
View file

@ -0,0 +1,224 @@
#include <stdio.h>
#include <string.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/md.h>
#include <mbedtls/rsa.h>
#include <mbedtls/x509.h>
#include "rsa.h"
#include "utils.h"
/* Self-generated private key */
static const char g_rsa2048CustomAcidPrivateKey[] = "-----BEGIN RSA PRIVATE KEY-----\r\n"
"MIIEowIBAAKCAQEAvVRzt+8mE7oE4RkmSh3ws4CGlBj7uhHkfwCpPFsn4TNVdLRo\r\n"
"YYY17jQYWTtcOYPMcHxwUpgJyspGN8QGXEkJqY8jILv2eO0jBGtg7Br2afUBp6/x\r\n"
"BOMT2RlYVX6H4a1UA19Hzmcn+T1hdDwS6oBYpi8rJSm0+q+yB34dueNkVsk4eKbj\r\n"
"CNNKFi+XgyNBi41d57SPCrkcm/9tkagRorE8vLcFPcXcYOjdXH3L4XTXq7sxxytA\r\n"
"I66erfSc4XunkoLifcbfMOB3gjGCoQs6GfaiAU3TwxewQ7hdoqvj5Gm9VyHqzeDF\r\n"
"5mUTlmed2I6m4ELxbV1b0lUguR5ZEzwXwiVWxwIDAQABAoIBADvLYkijFOmCBGx7\r\n"
"HualkhF+9AHt6gKYCAw8Tzaqq2uqZMDZAWZblsjGVzJHVxcrEvQruOW88srDG24d\r\n"
"UMzwnEaa2ENMWclTS43nw9KNqWlJYd5t6LbcaLZWFNnbflq9/RybiPgdCDjlM9Qb\r\n"
"7PV214iUuRGhnHDX8GgBYq4ErPnjQ7+Gv1ducpMYjZencLWCl4fFX86U0/MU0+Qf\r\n"
"jKGegQTnk52aaeScbDOjjx5h+m0hkDNSfsmXTlvJt2c8wy/Yx+leVgCPjMC1nbft\r\n"
"Ob1TlpjuEAKBOGt4+DkWwVmIlxilmx9wCTZnwvPKd7A0e0FGsdHnQienPrMqlgbl\r\n"
"JPYwJuECgYEA6yLZHTfX3ebpzcdQQqmuHZtbOcs+EGRy24gAzd+9vCGKf0VtKSl9\r\n"
"3oA3XBOe2C2TgSgbWFZ7v/2efWRjgwJta0BQlpkzkh6NUQa2LI2M3zgZwHCZ7Ihr\r\n"
"skG73qZsMHOOv7VQz/wDp6AZNasfz21Mcyh4uFzpkb3NKLXqsJ9LeG8CgYEAziEb\r\n"
"yBCuhCKq7YZt/cHlbCbi7HbCYbub0isOCUtV0qPsX+kVZdPS+oGLPq1905JKdAe9\r\n"
"O+4SltCw6qn9RgYnCCVQ47SGHg7KO8Z5vdcNUiDvsQ+jNFlmM5QBuf1UV/Y+DV/Q\r\n"
"fZdA06OeYxkfPuBMtjdS9qMKwm3OsCkiQasWQykCgYAqALieAoq6JfSgALmyntLu\r\n"
"kQDzyv2UOg1Wb+4M2KnxAGDYKVO9pZ7Jb0f0V8DpRwLxcHOqDRDgE/MK3TL1hSp8\r\n"
"nSmILWfL8081KSjDvqlqeoAHI1YrrZbnadyggkQTR6E5V69O5+rTN8MpFh+Bkzmz\r\n"
"3IfsDxTeJvSOECkTUfFOWwKBgQDG/id3yMLxRRaGH5TnuNvmwNOpPC0DdL5E8tOm\r\n"
"HVhI9X8oSDgkCY5Pz+fBJnOmYEAIK8B/rqG7ftSMdnbPtvjPYFbqvEgNlHGfq0e0\r\n"
"AXwWoT1ETbhcvUFw4Z2ZE/rswAe/mZQI6o/mwLoTKRmE9byY3Gf3OgcVFDTI060C\r\n"
"gEwJoQKBgHpOmtGum3JuLpPc+PTXZOe29tdWndkFWktjPoow60d+NO2jpTFuEpmW\r\n"
"XRW35vXI8PqMCmHOQ8YU59aMN9juAnsJmPUxbAW5fZfvVwWUo0cTOenfT6syrEYO\r\n"
"n5NEG+mY4WZaOFRNiZu8+4aJI1yycXMyA22iKcU8+nN/sMAJs3Nx\r\n"
"-----END RSA PRIVATE KEY-----\r\n";
/* Self-generated public key */
static const u8 g_rsa2048CustomAcidPublicKey[] = {
0xBD, 0x54, 0x73, 0xB7, 0xEF, 0x26, 0x13, 0xBA, 0x04, 0xE1, 0x19, 0x26, 0x4A, 0x1D, 0xF0, 0xB3,
0x80, 0x86, 0x94, 0x18, 0xFB, 0xBA, 0x11, 0xE4, 0x7F, 0x00, 0xA9, 0x3C, 0x5B, 0x27, 0xE1, 0x33,
0x55, 0x74, 0xB4, 0x68, 0x61, 0x86, 0x35, 0xEE, 0x34, 0x18, 0x59, 0x3B, 0x5C, 0x39, 0x83, 0xCC,
0x70, 0x7C, 0x70, 0x52, 0x98, 0x09, 0xCA, 0xCA, 0x46, 0x37, 0xC4, 0x06, 0x5C, 0x49, 0x09, 0xA9,
0x8F, 0x23, 0x20, 0xBB, 0xF6, 0x78, 0xED, 0x23, 0x04, 0x6B, 0x60, 0xEC, 0x1A, 0xF6, 0x69, 0xF5,
0x01, 0xA7, 0xAF, 0xF1, 0x04, 0xE3, 0x13, 0xD9, 0x19, 0x58, 0x55, 0x7E, 0x87, 0xE1, 0xAD, 0x54,
0x03, 0x5F, 0x47, 0xCE, 0x67, 0x27, 0xF9, 0x3D, 0x61, 0x74, 0x3C, 0x12, 0xEA, 0x80, 0x58, 0xA6,
0x2F, 0x2B, 0x25, 0x29, 0xB4, 0xFA, 0xAF, 0xB2, 0x07, 0x7E, 0x1D, 0xB9, 0xE3, 0x64, 0x56, 0xC9,
0x38, 0x78, 0xA6, 0xE3, 0x08, 0xD3, 0x4A, 0x16, 0x2F, 0x97, 0x83, 0x23, 0x41, 0x8B, 0x8D, 0x5D,
0xE7, 0xB4, 0x8F, 0x0A, 0xB9, 0x1C, 0x9B, 0xFF, 0x6D, 0x91, 0xA8, 0x11, 0xA2, 0xB1, 0x3C, 0xBC,
0xB7, 0x05, 0x3D, 0xC5, 0xDC, 0x60, 0xE8, 0xDD, 0x5C, 0x7D, 0xCB, 0xE1, 0x74, 0xD7, 0xAB, 0xBB,
0x31, 0xC7, 0x2B, 0x40, 0x23, 0xAE, 0x9E, 0xAD, 0xF4, 0x9C, 0xE1, 0x7B, 0xA7, 0x92, 0x82, 0xE2,
0x7D, 0xC6, 0xDF, 0x30, 0xE0, 0x77, 0x82, 0x31, 0x82, 0xA1, 0x0B, 0x3A, 0x19, 0xF6, 0xA2, 0x01,
0x4D, 0xD3, 0xC3, 0x17, 0xB0, 0x43, 0xB8, 0x5D, 0xA2, 0xAB, 0xE3, 0xE4, 0x69, 0xBD, 0x57, 0x21,
0xEA, 0xCD, 0xE0, 0xC5, 0xE6, 0x65, 0x13, 0x96, 0x67, 0x9D, 0xD8, 0x8E, 0xA6, 0xE0, 0x42, 0xF1,
0x6D, 0x5D, 0x5B, 0xD2, 0x55, 0x20, 0xB9, 0x1E, 0x59, 0x13, 0x3C, 0x17, 0xC2, 0x25, 0x56, 0xC7
};
static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_src, size_t h_src_size);
bool rsa2048GenerateSha256BasedCustomAcidSignature(void *dst, const void *src, size_t size)
{
if (!dst || !src || !size)
{
LOGFILE("Invalid parameters!");
return false;
}
u8 hash[SHA256_HASH_SIZE];
u8 buf[MBEDTLS_MPI_MAX_SIZE];
const char *pers = "rsa_sign_pss";
size_t olen = 0;
int ret;
bool success = false;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_context entropy;
mbedtls_entropy_init(&entropy);
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
/* Calculate SHA-256 checksum for the input data */
sha256CalculateHash(hash, src, size);
/* Seed the random number generator */
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
if (ret != 0)
{
LOGFILE("mbedtls_ctr_drbg_seed failed! (%d)", ret);
goto out;
}
/* Parse private key */
ret = mbedtls_pk_parse_key(&pk, (u8*)g_rsa2048CustomAcidPrivateKey, strlen(g_rsa2048CustomAcidPrivateKey) + 1, NULL, 0);
if (ret != 0)
{
LOGFILE("mbedtls_pk_parse_key failed! (%d)", ret);
goto out;
}
/* Set RSA padding */
mbedtls_rsa_set_padding(mbedtls_pk_rsa(pk), MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
/* Calculate hash signature */
ret = mbedtls_pk_sign(&pk, MBEDTLS_MD_SHA256, hash, 0, buf, &olen, mbedtls_ctr_drbg_random, &ctr_drbg);
if (ret != 0)
{
LOGFILE("mbedtls_pk_sign failed! (%d)", ret);
goto out;
}
/* Copy signature to output buffer */
memcpy(dst, buf, RSA2048_SIGNATURE_SIZE);
success = true;
out:
mbedtls_pk_free(&pk);
mbedtls_entropy_free(&entropy);
mbedtls_ctr_drbg_free(&ctr_drbg);
return success;
}
const u8 rsa2048GetCustomAcidPublicKey(void)
{
return g_rsa2048CustomAcidPublicKey;
}
bool rsa2048OaepDecryptAndVerify(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *exponent, size_t exponent_size, const void *label_hash, size_t *out_size)
{
if (!dst || !dst_size || !signature || !modulus || !exponent || !exponent_size || !label_hash || !out_size)
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
u8 m_buf[RSA2048_SIGNATURE_SIZE] = {0};
rc = splUserExpMod(signature, modulus, exponent, exponent_size, m_buf);
if (R_FAILED(rc))
{
LOGFILE("splUserExpMod failed! (0x%08X)", rc);
return false;
}
if (m_buf[0] != 0)
{
LOGFILE("Invalid PSS!");
return false;
}
/* Unmask salt */
rsaCalculateMgf1AndXor(m_buf + 1, 0x20, m_buf + 0x21, RSA2048_SIGNATURE_SIZE - 0x21);
/* Unmask DB */
rsaCalculateMgf1AndXor(m_buf + 0x21, RSA2048_SIGNATURE_SIZE - 0x21, m_buf + 1, 0x20);
/* Validate label hash */
const u8 *db = (const u8*)(m_buf + 0x21);
if (memcmp(db, label_hash, SHA256_HASH_SIZE) != 0)
{
LOGFILE("Label hash validation failed! Wrong decryption keys?");
return false;
}
/* Validate message prefix */
const u8 *data = (const u8*)(db + 0x20);
size_t remaining = (RSA2048_SIGNATURE_SIZE - 0x41);
while(!*data && remaining)
{
data++;
remaining--;
}
if (!remaining || *data++ != 1)
{
LOGFILE("Message prefix validation failed! Wrong decryption keys?");
return false;
}
remaining--;
*out_size = remaining;
if (remaining > dst_size) remaining = dst_size;
memcpy(dst, data, remaining);
return true;
}
static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_src, size_t h_src_size)
{
if (!data || !data_size || !h_src || !h_src_size || h_src_size > RSA2048_SIGNATURE_SIZE)
{
LOGFILE("Invalid parameters!");
return;
}
u32 seed = 0;
size_t i, offset = 0;
u8 mgf1_buf[SHA256_HASH_SIZE] = {0};
u8 h_buf[RSA2048_SIGNATURE_SIZE] = {0};
memcpy(h_buf, h_src, h_src_size);
while(offset < data_size)
{
for(i = 0; i < 4; i++) h_buf[h_src_size + 3 - i] = ((seed >> (8 * i)) & 0xFF);
sha256CalculateHash(mgf1_buf, h_buf, h_src_size + 4);
for(i = offset; i < data_size && i < (offset + 0x20); i++) data[i] ^= mgf1_buf[i - offset];
seed++;
offset += 0x20;
}
}

15
source/new/rsa.h Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#ifndef __RSA_H__
#define __RSA_H__
#include <switch.h>
#define RSA2048_SIGNATURE_SIZE 0x100
bool rsa2048GenerateSha256BasedCustomAcidSignature(void *dst, const void *src, size_t size);
const u8 rsa2048GetCustomAcidPublicKey(void);
bool rsa2048OaepDecryptAndVerify(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *exponent, size_t exponent_size, const void *label_hash, size_t *out_size);
#endif /* __RSA_H__ */

1803
source/new/save.c Normal file

File diff suppressed because it is too large Load diff

502
source/new/save.h Normal file
View file

@ -0,0 +1,502 @@
/* Savedata container parsing code taken from Lockpick_RCM with some slight modifications */
/* Big thanks to shchmue */
#pragma once
#ifndef __SAVE_H__
#define __SAVE_H__
#include <stddef.h>
#include <stdint.h>
#include <switch.h>
#include "fatfs/ff.h"
#define IVFC_MAX_LEVEL 6
#define SAVE_HEADER_SIZE 0x4000
#define SAVE_FAT_ENTRY_SIZE 8
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
#define MAGIC_DISF 0x46534944
#define MAGIC_DPFS 0x53465044
#define MAGIC_JNGL 0x4C474E4A
#define MAGIC_SAVE 0x45564153
#define MAGIC_RMAP 0x50414D52
#define MAGIC_IVFC 0x43465649
#define ACTION_VERIFY (1<<2)
typedef enum {
VALIDITY_UNCHECKED = 0,
VALIDITY_INVALID,
VALIDITY_VALID
} validity_t;
typedef struct save_ctx_t save_ctx_t;
typedef struct {
u32 magic; /* DISF */
u32 version;
u8 hash[0x20];
u64 file_map_entry_offset;
u64 file_map_entry_size;
u64 meta_map_entry_offset;
u64 meta_map_entry_size;
u64 file_map_data_offset;
u64 file_map_data_size;
u64 duplex_l1_offset_a;
u64 duplex_l1_offset_b;
u64 duplex_l1_size;
u64 duplex_data_offset_a;
u64 duplex_data_offset_b;
u64 duplex_data_size;
u64 journal_data_offset;
u64 journal_data_size_a;
u64 journal_data_size_b;
u64 journal_size;
u64 duplex_master_offset_a;
u64 duplex_master_offset_b;
u64 duplex_master_size;
u64 ivfc_master_hash_offset_a;
u64 ivfc_master_hash_offset_b;
u64 ivfc_master_hash_size;
u64 journal_map_table_offset;
u64 journal_map_table_size;
u64 journal_physical_bitmap_offset;
u64 journal_physical_bitmap_size;
u64 journal_virtual_bitmap_offset;
u64 journal_virtual_bitmap_size;
u64 journal_free_bitmap_offset;
u64 journal_free_bitmap_size;
u64 ivfc_l1_offset;
u64 ivfc_l1_size;
u64 ivfc_l2_offset;
u64 ivfc_l2_size;
u64 ivfc_l3_offset;
u64 ivfc_l3_size;
u64 fat_offset;
u64 fat_size;
u64 duplex_index;
u64 fat_ivfc_master_hash_a;
u64 fat_ivfc_master_hash_b;
u64 fat_ivfc_l1_offset;
u64 fat_ivfc_l1_size;
u64 fat_ivfc_l2_offset;
u64 fat_ivfc_l2_size;
u8 _0x190[0x70];
} fs_layout_t;
#pragma pack(push, 1)
typedef struct {
u64 offset;
u64 length;
u32 block_size_power;
} duplex_info_t;
#pragma pack(pop)
typedef struct {
u32 magic; /* DPFS */
u32 version;
duplex_info_t layers[3];
} duplex_header_t;
typedef struct {
u32 version;
u32 main_data_block_count;
u32 journal_block_count;
u32 _0x0C;
} journal_map_header_t;
typedef struct {
u32 magic; /* JNGL */
u32 version;
u64 total_size;
u64 journal_size;
u64 block_size;
} journal_header_t;
typedef struct {
u32 magic; /* SAVE */
u32 version;
u64 block_count;
u64 block_size;
} save_fs_header_t;
typedef struct {
u64 block_size;
u64 allocation_table_offset;
u32 allocation_table_block_count;
u32 _0x14;
u64 data_offset;
u32 data_block_count;
u32 _0x24;
u32 directory_table_block;
u32 file_table_block;
} fat_header_t;
typedef struct {
u32 magic; /* RMAP */
u32 version;
u32 map_entry_count;
u32 map_segment_count;
u32 segment_bits;
u8 _0x14[0x2C];
} remap_header_t;
typedef struct remap_segment_ctx_t remap_segment_ctx_t;
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
#pragma pack(push, 1)
struct remap_entry_ctx_t {
u64 virtual_offset;
u64 physical_offset;
u64 size;
u32 alignment;
u32 _0x1C;
u64 virtual_offset_end;
u64 physical_offset_end;
remap_segment_ctx_t *segment;
remap_entry_ctx_t *next;
};
#pragma pack(pop)
struct remap_segment_ctx_t{
u64 offset;
u64 length;
remap_entry_ctx_t *entries;
u64 entry_count;
};
typedef struct {
u8 *data;
u8 *bitmap;
} duplex_bitmap_t;
typedef struct {
u32 block_size;
u8 *bitmap_storage;
u8 *data_a;
u8 *data_b;
duplex_bitmap_t bitmap;
u64 _length;
} duplex_storage_ctx_t;
enum base_storage_type {
STORAGE_BYTES = 0,
STORAGE_DUPLEX = 1,
STORAGE_REMAP = 2,
STORAGE_JOURNAL = 3
};
typedef struct {
remap_header_t *header;
remap_entry_ctx_t *map_entries;
remap_segment_ctx_t *segments;
enum base_storage_type type;
u64 base_storage_offset;
duplex_storage_ctx_t *duplex;
FIL *file;
} remap_storage_ctx_t;
typedef struct {
u64 title_id;
u8 user_id[0x10];
u64 save_id;
u8 save_data_type;
u8 _0x21[0x1F];
u64 save_owner_id;
u64 timestamp;
u64 _0x50;
u64 data_size;
u64 journal_size;
u64 commit_id;
} extra_data_t;
typedef struct {
u64 logical_offset;
u64 hash_data_size;
u32 block_size;
u32 reserved;
} ivfc_level_hdr_t;
typedef struct {
u32 magic;
u32 id;
u32 master_hash_size;
u32 num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
u8 salt_source[0x20];
} ivfc_save_hdr_t;
#pragma pack(push, 1)
typedef struct {
u8 cmac[0x10];
u8 _0x10[0xF0];
fs_layout_t layout;
duplex_header_t duplex_header;
ivfc_save_hdr_t data_ivfc_header;
u32 _0x404;
journal_header_t journal_header;
journal_map_header_t map_header;
u8 _0x438[0x1D0];
save_fs_header_t save_header;
fat_header_t fat_header;
remap_header_t main_remap_header, meta_remap_header;
u64 _0x6D0;
extra_data_t extra_data;
u8 _0x748[0x390];
ivfc_save_hdr_t fat_ivfc_header;
u8 _0xB98[0x3468];
} save_header_t;
#pragma pack(pop)
typedef struct {
duplex_storage_ctx_t layers[2];
duplex_storage_ctx_t data_layer;
u64 _length;
} hierarchical_duplex_storage_ctx_t;
typedef struct {
u8 *data_a;
u8 *data_b;
duplex_info_t info;
} duplex_fs_layer_info_t;
typedef struct {
u8 *map_storage;
u8 *physical_block_bitmap;
u8 *virtual_block_bitmap;
u8 *free_block_bitmap;
} journal_map_params_t;
typedef struct {
u32 physical_index;
u32 virtual_index;
} journal_map_entry_t;
typedef struct {
journal_map_header_t *header;
journal_map_entry_t *entries;
u8 *map_storage;
} journal_map_ctx_t;
typedef struct {
journal_map_ctx_t map;
journal_header_t *header;
u32 block_size;
u64 journal_data_offset;
u64 _length;
FIL *file;
} journal_storage_ctx_t;
typedef struct {
u64 data_offset;
u64 data_size;
u64 hash_offset;
u32 hash_block_size;
validity_t hash_validity;
enum base_storage_type type;
save_ctx_t *save_ctx;
} ivfc_level_save_ctx_t;
typedef struct {
ivfc_level_save_ctx_t *data;
u32 block_size;
u8 salt[0x20];
} integrity_verification_info_ctx_t;
typedef struct integrity_verification_storage_ctx_t integrity_verification_storage_ctx_t;
struct integrity_verification_storage_ctx_t {
ivfc_level_save_ctx_t *hash_storage;
ivfc_level_save_ctx_t *base_storage;
validity_t *block_validities;
u8 salt[0x20];
u32 sector_size;
u32 sector_count;
u64 _length;
integrity_verification_storage_ctx_t *next_level;
};
typedef struct {
ivfc_level_save_ctx_t levels[5];
ivfc_level_save_ctx_t *data_level;
validity_t **level_validities;
u64 _length;
integrity_verification_storage_ctx_t integrity_storages[4];
} hierarchical_integrity_verification_storage_ctx_t;
typedef struct {
u32 prev;
u32 next;
} allocation_table_entry_t;
typedef struct {
u32 free_list_entry_index;
void *base_storage;
fat_header_t *header;
} allocation_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
u32 block_size;
u32 initial_block;
allocation_table_ctx_t *fat;
u64 _length;
} allocation_table_storage_ctx_t;
typedef struct {
allocation_table_ctx_t *fat;
u32 virtual_block;
u32 physical_block;
u32 current_segment_size;
u32 next_block;
u32 prev_block;
} allocation_table_iterator_ctx_t;
typedef struct {
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
u32 parent;
} save_entry_key_t;
#pragma pack(push, 1)
typedef struct {
u32 start_block;
u64 length;
u32 _0xC[2];
} save_file_info_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
u32 next_directory;
u32 next_file;
u32 _0x8[3];
} save_find_position_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
u32 next_sibling;
union { /* Save table entry type. Size = 0x14. */
save_file_info_t save_file_info;
save_find_position_t save_find_position;
};
} save_table_entry_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
u32 parent;
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
save_table_entry_t value;
u32 next;
} save_fs_list_entry_t;
#pragma pack(pop)
typedef struct {
u32 free_list_head_index;
u32 used_list_head_index;
allocation_table_storage_ctx_t storage;
u32 capacity;
} save_filesystem_list_ctx_t;
typedef struct {
save_filesystem_list_ctx_t file_table;
save_filesystem_list_ctx_t directory_table;
} hierarchical_save_file_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
allocation_table_ctx_t allocation_table;
save_fs_header_t *header;
hierarchical_save_file_table_ctx_t file_table;
} save_filesystem_ctx_t;
struct save_ctx_t {
save_header_t header;
FIL *file;
struct {
FIL *file;
u32 action;
} tool_ctx;
validity_t header_cmac_validity;
validity_t header_hash_validity;
u8 *data_ivfc_master;
u8 *fat_ivfc_master;
remap_storage_ctx_t data_remap_storage;
remap_storage_ctx_t meta_remap_storage;
duplex_fs_layer_info_t duplex_layers[3];
hierarchical_duplex_storage_ctx_t duplex_storage;
journal_storage_ctx_t journal_storage;
journal_map_params_t journal_map_info;
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
u8 *fat_storage;
save_filesystem_ctx_t save_filesystem_core;
u8 save_mac_key[0x10];
};
static inline u32 allocation_table_entry_index_to_block(u32 entry_index)
{
return (entry_index - 1);
}
static inline u32 allocation_table_block_to_entry_index(u32 block_index)
{
return (block_index + 1);
}
static inline int allocation_table_is_list_end(allocation_table_entry_t *entry)
{
return ((entry->next & 0x7FFFFFFF) == 0);
}
static inline int allocation_table_is_list_start(allocation_table_entry_t *entry)
{
return (entry->prev == 0x80000000);
}
static inline int allocation_table_get_next(allocation_table_entry_t *entry)
{
return (entry->next & 0x7FFFFFFF);
}
static inline int allocation_table_get_prev(allocation_table_entry_t *entry)
{
return (entry->prev & 0x7FFFFFFF);
}
static inline allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, u32 entry_index)
{
return ((allocation_table_entry_t*)((u8*)ctx->base_storage + (entry_index * SAVE_FAT_ENTRY_SIZE)));
}
static inline u32 save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx)
{
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
}
static inline u32 save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx)
{
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
}
bool save_process(save_ctx_t *ctx);
bool save_process_header(save_ctx_t *ctx);
void save_free_contexts(save_ctx_t *ctx);
bool save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, u32 block_index);
u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, u64 offset, size_t count);
bool save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, u32 index, save_fs_list_entry_t *value);
u32 save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, u32 *prev_index);
bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, const char *path);
bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
save_ctx_t *save_open_savefile(const char *path, u32 action);
void save_close_savefile(save_ctx_t *ctx);
bool save_get_fat_storage_from_file_entry_by_path(save_ctx_t *ctx, const char *path, allocation_table_storage_ctx_t *out_fat_storage, u64 *out_file_entry_size);
#endif /* __SAVE_H__ */

View file

@ -0,0 +1,52 @@
#pragma once
#include <switch.h>
typedef struct ServiceGuard {
Mutex mutex;
u32 refCount;
} ServiceGuard;
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
{
mutexLock(&g->mutex);
return (g->refCount++) == 0;
}
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
{
if (R_FAILED(rc)) {
cleanupFunc();
--g->refCount;
}
mutexUnlock(&g->mutex);
return rc;
}
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
{
mutexLock(&g->mutex);
if (g->refCount && (--g->refCount) == 0)
cleanupFunc();
mutexUnlock(&g->mutex);
}
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
\
static ServiceGuard g_##name##Guard; \
NX_INLINE Result _##name##Initialize _paramdecl; \
static void _##name##Cleanup(void); \
\
Result name##Initialize _paramdecl \
{ \
Result rc = 0; \
if (serviceGuardBeginInit(&g_##name##Guard)) \
rc = _##name##Initialize _parampass; \
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
} \
\
void name##Exit(void) \
{ \
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
}
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())

33
source/new/signature.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#ifndef __SIGNATURE_H__
#define __SIGNATURE_H__
typedef enum {
SignatureType_Rsa4096Sha1 = 0x10000,
SignatureType_Rsa2048Sha1 = 0x10001,
SignatureType_Ecsda240Sha1 = 0x10002,
SignatureType_Rsa4096Sha256 = 0x10003,
SignatureType_Rsa2048Sha256 = 0x10004,
SignatureType_Ecsda240Sha256 = 0x10005
} SignatureType;
typedef struct {
u32 sig_type; ///< SignatureType_Rsa4096Sha1, SignatureType_Rsa4096Sha256.
u8 signature[0x200];
u8 padding[0x3C];
} SignatureBlockRsa4096;
typedef struct {
u32 sig_type; ///< SignatureType_Rsa2048Sha1, SignatureType_Rsa2048Sha256.
u8 signature[0x100];
u8 padding[0x3C];
} SignatureBlockRsa2048;
typedef struct {
u32 sig_type; ///< SignatureType_Ecsda240Sha1, SignatureType_Ecsda240Sha256.
u8 signature[0x3C];
u8 padding[0x40];
} SignatureBlockEcsda240;
#endif /* __SIGNATURE_H__ */

571
source/new/tik.c Normal file
View file

@ -0,0 +1,571 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "tik.h"
#include "save.h"
#include "es.h"
#include "keys.h"
#include "rsa.h"
#include "utils.h"
#define TIK_COMMON_SAVEFILE_PATH "sys:/save/80000000000000e1"
#define TIK_PERSONALIZED_SAVEFILE_PATH "sys:/save/80000000000000e2"
#define TIK_SAVEFILE_STORAGE_PATH "/ticket.bin"
#define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001
/// Everything after the AES CTR is encrypted.
typedef struct {
u8 ctr[0x10];
u8 exponent[0x100];
u8 modulus[0x100];
u32 public_exponent; ///< Must match ETICKET_DEVKEY_PUBLIC_EXPONENT. Stored using big endian byte order.
u8 padding[0x14];
u8 device_id[0x8];
u8 ghash[0x10];
} tikEticketDeviceKeyData;
static SetCalRsa2048DeviceKey g_eTicketDeviceKey = {0};
static bool g_eTicketDeviceKeyRetrieved = false;
/* Used during the RSA-OAEP titlekey decryption stage */
static const u8 g_nullHash[0x20] = {
0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55
};
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized);
static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id);
static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size);
static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, const void *n);
static bool tikRetrieveEticketDeviceKey(void);
TikCommonBlock *tikGetTicketCommonBlockFromMemoryBuffer(void *data)
{
if (!data)
{
LOGFILE("Invalid parameters!");
return NULL;
}
u8 *data_u8 = (u8*)data;
TikCommonBlock *tik_common_blk = NULL;
u32 sig_type;
memcpy(&sig_type, data_u8, sizeof(u32));
switch(sig_type)
{
case SignatureType_Rsa4096Sha1:
case SignatureType_Rsa4096Sha256:
tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockRsa4096));
break;
case SignatureType_Rsa2048Sha1:
case SignatureType_Rsa2048Sha256:
tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockRsa2048));
break;
case SignatureType_Ecsda240Sha1:
case SignatureType_Ecsda240Sha256:
tik_common_blk = (TikCommonBlock*)(data_u8 + sizeof(SignatureBlockEcsda240));
break;
default:
LOGFILE("Invalid signature type value! (0x%08X)", sig_type);
return NULL;
}
return tik_common_blk;
}
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id)
{
if (!dst || !id)
{
LOGFILE("Invalid parameters!");
return false;
}
u32 i;
save_ctx_t *save_ctx = NULL;
allocation_table_storage_ctx_t fat_storage = {0};
u64 ticket_bin_size = 0;
u64 buf_size = (TIK_MAX_SIZE * 0x10); /* 0x4000 */
u64 br = buf_size, total_br = 0;
u8 *ticket_bin_buf = NULL;
bool found_tik = false, success = false;
u8 title_key_type = tikGetTitleKeyTypeFromRightsId(id);
if (title_key_type == TikTitleKeyType_Invalid)
{
LOGFILE("Unable to retrieve ticket titlekey type!");
return false;
}
save_ctx = save_open_savefile(title_key_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0);
if (!save_ctx)
{
LOGFILE("Failed to open ES %s ticket system savefile!", title_key_type == TikTitleKeyType_Common ? "common" : "personalized");
return false;
}
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_bin_size))
{
LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, title_key_type == TikTitleKeyType_Common ? "common" : "personalized");
goto out;
}
if (ticket_bin_size < TIK_MIN_SIZE || (ticket_bin_size % TIK_MAX_SIZE) != 0)
{
LOGFILE("Invalid size for \"%s\"! (0x%lX)", TIK_SAVEFILE_STORAGE_PATH, ticket_bin_size);
goto out;
}
ticket_bin_buf = malloc(buf_size);
if (ticket_bin_buf)
{
LOGFILE("Unable to allocate memory for temporary read buffer!");
goto out;
}
while(br == buf_size && total_br < ticket_bin_size)
{
br = save_allocation_table_storage_read(&fat_storage, ticket_bin_buf, total_br, buf_size);
if (br != buf_size)
{
LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, title_key_type == TikTitleKeyType_Common ? "common" : "personalized");
goto out;
}
if (ticket_bin_buf[0] == 0) break;
for(i = 0; i < buf_size; i += TIK_MAX_SIZE)
{
TikCommonBlock *tik_common_blk = tikGetTicketCommonBlockFromMemoryBuffer(ticket_bin_buf + i);
if (!tik_common_blk || memcmp(tik_common_blk->rights_id.c, id->c, 0x10) != 0) continue;
/* Jackpot */
found_tik = true;
break;
}
}
if (!found_tik)
{
LOGFILE("Unable to find a matching ticket entry for the provided Rights ID!");
goto out;
}
if (!tikGetTicketTypeAndSize(ticket_bin_buf + i, TIK_MAX_SIZE, &(dst->type), &(dst->size)))
{
LOGFILE("Unable to determine ticket type and size!");
goto out;
}
memcpy(dst->data, ticket_bin_buf + i, dst->size);
success = true;
out:
if (ticket_bin_buf) free(ticket_bin_buf);
if (save_ctx) save_close_savefile(save_ctx);
return success;
}
bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TicketCommonBlock *tik_common_blk)
{
if (!dst || !tik_common_blk)
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
size_t out_keydata_size = 0;
u8 out_keydata[0x100] = {0};
tikEticketDeviceKeyData *eticket_devkey = NULL;
switch(tik_common_blk->title_key_type)
{
case TikTitleKeyType_Common:
/* No titlekek crypto used */
memcpy(dst, tik_common_blk->title_key_block, 0x10);
break;
case TikTitleKeyType_Personalized:
/* Retrieve eTicket device key */
if (!tikRetrieveEticketDeviceKey())
{
LOGFILE("Unable to retrieve eTicket device key!");
return false;
}
eticket_devkey = (tikEticketDeviceKeyData*)g_eTicketDeviceKey.key;
/* Perform a RSA-OAEP decrypt operation to get the titlekey */
if (!rsa2048OaepDecryptAndVerify(out_keydata, 0x100, tik_common_blk->title_key_block, eticket_devkey->modulus, eticket_devkey->exponent, 0x100, g_nullHash, &out_keydata_size) || out_keydata_size < 0x10)
{
LOGFILE("RSA-OAEP titlekey decryption failed!");
return false;
}
/* Copy decrypted titlekey */
memcpy(dst, out_keydata, 0x10);
break;
default:
LOGFILE("Invalid titlekey type value! (0x%02X)", tik_common_blk->title_key_type);
return false;
}
return true;
}
bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation)
{
if (!dst || !src)
{
LOGFILE("Invalid parameters!");
return false;
}
const u8 *titlekek = NULL;
Aes128Context titlekey_aes_ctx = {0};
titlekek = keysGetTitlekek(key_generation);
if (!titlekek)
{
LOGFILE("Unable to retrieve titlekek for key generation 0x%02X!", key_generation);
return false;
}
aes128ContextCreate(&titlekey_aes_ctx, titlekek, false);
aes128DecryptBlock(&titlekey_aes_ctx, dst, src);
return true;
}
bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik)
{
if (!dst || !tik)
{
LOGFILE("Invalid parameters!");
return false;
}
u8 titlekey[0x10] = {0};
TikCommonBlock *tik_common_blk = NULL;
tik_common_blk = tikGetTicketCommonBlockFromTicket(tik);
if (!tik_common_blk)
{
LOGFILE("Unable to retrieve ticket common block!");
return false;
}
if (!tikGetTitleKeyFromTicketCommonBlock(titlekey, tik_common_blk))
{
LOGFILE("Unable to retrieve titlekey from ticket!");
return false;
}
/* Even though tickets do have a proper key_generation field, we'll default to retrieve it from the rights_id field */
/* Old custom tools used to wipe the key_generation field or save it to a different offset */
if (!tikGetTitleKekDecryptedTitleKey(dst, titlekey, tik_common_blk->rights_id.c[0xF]))
{
LOGFILE("Unable to perform titlekek decryption on titlekey!");
return false;
}
return true;
}
void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, const void *titlekey)
{
if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE || !titlekey) return;
bool dev_cert = false;
TicketCommonBlock *tik_common_blk = NULL;
tik_common_blk = tikGetTicketCommonBlockFromTicket(tik);
if (!tik_common_blk || tik_common_blk->title_key_type != TikTitleKeyType_Personalized) return;
switch(tik->type)
{
case TikType_SigRsa4096:
tik->size = sizeof(TikSigRsa4096);
memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockRsa4096, signature));
break;
case TikType_SigRsa2048:
tik->size = sizeof(TikSigRsa2048);
memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockRsa2048, signature));
break;
case TikType_SigEcsda240:
tik->size = sizeof(TikSigEcsda240);
memset(tik->data + 4, 0xFF, MEMBER_SIZE(SignatureBlockEcsda240, signature));
break;
default:
break;
}
dev_cert = (strstr(tik_common_blk->issuer, "CA00000004") != NULL);
memset(tik_common_blk->issuer, 0, sizeof(tik_common_blk->issuer));
sprintf(tik_common_blk->issuer, "Root-CA%08X-XS00000020", dev_cert ? 4 : 3);
memset(tik_common_blk->title_key_block, 0, sizeof(tik_common_blk->title_key_block));
memcpy(tik_common_blk->title_key_block, titlekey, 0x10);
tik_common_blk->title_key_type = TikTitleKeyType_Common;
tik_common_blk->ticket_id = 0;
tik_common_blk->device_id = 0;
tik_common_blk->account_id = 0;
tik_common_blk->sect_total_size = 0;
tik_common_blk->sect_hdr_offset = (u32)tik->size;
tik_common_blk->sect_hdr_count = 0;
tik_common_blk->sect_hdr_entry_size = 0;
memset(tik->data + tik->size, 0, TIK_MAX_SIZE - tik->size);
}
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized)
{
if (!out || !out_count)
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
u32 count = 0, ids_written = 0;
FsRightsId *rights_ids = NULL;
rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count));
if (R_FAILED(rc))
{
LOGFILE("esCount%sTicket failed! (0x%08X)", personalized ? "Personalized" : "Common", rc);
return false;
}
if (!count)
{
LOGFILE("No %s tickets available!", personalized ? "personalized" : "common");
*out_count = 0;
return true;
}
rights_ids = calloc(count, sizeof(FsRightsId));
if (!rights_ids)
{
LOGFILE("Unable to allocate memory for %s rights IDs!", personalized ? "personalized" : "common");
return false;
}
rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, count * sizeof(FsRightsId)) : esListCommonTicket((s32*)&ids_written, rights_ids, count * sizeof(FsRightsId)));
if (R_FAILED(rc) || ids_written != count)
{
LOGFILE("esList%sTicket failed! (0x%08X) | Wrote %u entries, expected %u entries", personalized ? "Personalized" : "Common", rc, ids_written, count);
free(rights_ids);
return false;
}
*out = rights_ids;
*out_count = count;
return true;
}
static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id)
{
if (!id)
{
LOGFILE("Invalid parameters!");
return TikTitleKeyType_Invalid;
}
u8 type = TikTitleKeyType_Invalid;
u32 count;
FsRightsId *rights_ids;
for(u8 i = 0; i < 2; i++)
{
count = 0;
rights_ids = NULL;
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, (bool)i))
{
LOGFILE("Unable to retrieve %s rights IDs!", i == 0 ? "common" : "personalized");
break;
}
if (!count) continue;
for(u32 j = 0; j < count; j++)
{
if (!memcmp(rights_ids[j].c, id->c, 0x10))
{
type = i; /* TikTitleKeyType_Common or TikTitleKeyType_Personalized */
break;
}
}
free(rights_ids);
if (type != TikTitleKeyType_Invalid) break;
}
return type;
}
static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size)
{
if (!data || data_size < TIK_MIN_SIZE || data_size > TIK_MAX_SIZE || !out_type || !out_size)
{
LOGFILE("Invalid parameters!");
return false;
}
u8 type = TikType_Invalid;
const u8 *data_u8 = (const u8*)data;
const TikCommonBlock *tik_common_blk = NULL;
u32 sig_type;
u64 offset = 0;
memcpy(&sig_type, data_u8, sizeof(u32));
switch(sig_type)
{
case SignatureType_Rsa4096Sha1:
case SignatureType_Rsa4096Sha256:
type = TikType_SigRsa4096;
offset += sizeof(SignatureBlockRsa4096);
break;
case SignatureType_Rsa2048Sha1:
case SignatureType_Rsa2048Sha256:
type = TikType_SigRsa2048;
offset += sizeof(SignatureBlockRsa2048);
break;
case SignatureType_Ecsda240Sha1:
case SignatureType_Ecsda240Sha256:
type = TikType_SigEcsda240;
offset += sizeof(SignatureBlockEcsda240);
break;
default:
LOGFILE("Invalid signature type value! (0x%08X)", sig_type);
return false;
}
tik_common_blk = (const TikCommonBlock*)(data_u8 + offset);
offset += sizeof(TikCommonBlock);
if ((u32)offset != tik_common_blk->sect_hdr_offset)
{
LOGFILE("Calculated ticket common block end offset doesn't match ESv2 section records header offset! 0x%X != 0x%X", (u32)offset, tik_common_blk->sect_hdr_offset);
return false;
}
for(u32 i = 0; i < tik_common_blk->sect_hdr_count; i++)
{
const TikEsv2SectionRecord *rec = (const TikEsv2SectionRecord*)(data_u8 + offset);
offset += sizeof(TikEsv2SectionRecord);
offset += ((u64)rec->record_count * (u64)rec->record_size);
if (offset > data_size)
{
LOGFILE("Offset calculation exceeded input buffer size while counting ESv2 section records! (0x%lX)", offset);
return false;
}
}
*out_type = type;
*out_size = offset;
return true;
}
static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, const void *n)
{
if (!e || !d || !n)
{
LOGFILE("Invalid parameters!");
return false;
}
Result rc = 0;
u8 x[0x100] = {0}, y[0x100] = {0}, z[0x100] = {0};
/* 0xCAFEBABE */
x[0xFC] = 0xCA;
x[0xFD] = 0xFE;
x[0xFE] = 0xBA;
x[0xFF] = 0xBE;
rc = splUserExpMod(x, n, d, 0x100, y);
if (R_FAILED(rc))
{
LOGFILE("splUserExpMod failed! (#1) (0x%08X)", rc);
return false;
}
rc = splUserExpMod(y, n, e, 4, z);
if (R_FAILED(rc))
{
LOGFILE("splUserExpMod failed! (#2) (0x%08X)", rc);
return false;
}
if (memcmp(x, y, 0x100) != 0)
{
LOGFILE("Invalid RSA key pair!");
return false;
}
return true;
}
static bool tikRetrieveEticketDeviceKey(void)
{
if (g_eTicketDeviceKeyRetrieved) return true;
Result rc = 0;
u32 public_exponent = 0;
tikEticketDeviceKeyData *eticket_devkey = NULL;
Aes128CtrContext eticket_aes_ctx = {0};
rc = setcalGetEticketDeviceKey(&g_eTicketDeviceKey);
if (R_FAILED(rc))
{
LOGFILE("setcalGetEticketDeviceKey failed! (0x%08X)", rc);
return false;
}
/* Decrypt eTicket RSA key */
eticket_devkey = (tikEticketDeviceKeyData*)g_eTicketDeviceKey.key;
aes128CtrContextCreate(&eticket_aes_ctx, keysGetEticketRsaKek(), eticket_devkey->ctr);
aes128CtrCrypt(&eticket_aes_ctx, &(eticket_devkey->exponent), &(eticket_devkey->exponent), sizeof(tikEticketDeviceKeyData) - 0x10);
/* Public exponent value must be 0x10001 */
/* It is stored use big endian byte order */
public_exponent = __builtin_bswap32(eticket_devkey->public_exponent);
if (public_exponent != ETICKET_DEVKEY_PUBLIC_EXPONENT)
{
LOGFILE("Invalid public RSA exponent for eTicket device key! Wrong keys? (0x%08X)", public_exponent);
return false;
}
/* Test RSA key pair */
if (!tikTestKeyPairFromEticketDeviceKey(&(eticket_devkey->public_exponent), eticket_devkey->exponent, eticket_devkey->modulus))
{
LOGFILE("RSA key pair test failed! Wrong keys?");
return false;
}
g_eTicketDeviceKeyRetrieved = true;
return true;
}

132
source/new/tik.h Normal file
View file

@ -0,0 +1,132 @@
#pragma once
#ifndef __TIK_H__
#define __TIK_H__
#include "signature.h"
#define TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES system savefiles */
#define TIK_MIN_SIZE 0x200 /* Equivalent to sizeof(TikSigEcsda240) - assuming no ESv2 records are available */
typedef enum {
TikType_SigRsa4096 = 0,
TikType_SigRsa2048 = 1,
TikType_SigEcsda240 = 2,
TikType_Invalid = 255
} TikType;
typedef enum {
TikTitleKeyType_Common = 0,
TikTitleKeyType_Personalized = 1,
TikTitleKeyType_Invalid = 255
} TikTitleKeyType;
typedef enum {
TikLicenseType_Permanent = 0,
TikLicenseType_Demo = 1,
TikLicenseType_Trial = 2,
TikLicenseType_Rental = 3,
TikLicenseType_Subscription = 4,
TikLicenseType_Service = 5
} TikLicenseType;
typedef struct {
u8 preinstallation : 1;
u8 shared_title : 1;
u8 all_contents : 1;
u8 device_link_independent : 1;
u8 _volatile : 1;
u8 elicense_required : 1;
} TikPropertyMask;
typedef enum {
TikSectionType_Permanent = 1,
TikSectionType_Subscription = 2,
TikSectionType_Content = 3,
TikSectionType_ContentConsumption = 4,
TikSectionType_AccessTitle = 5,
TikSectionType_LimitedResource = 6
} TikSectionType;
/// Placed after the ticket signature block.
typedef struct {
char issuer[0x40];
u8 title_key_block[0x100];
u8 format_version;
u8 title_key_type; ///< TikTitleKeyType.
u16 ticket_version;
u8 license_type; ///< TikLicenseType.
u8 key_generation;
TikPropertyMask property_mask;
u8 reserved_1[0x9];
u64 ticket_id;
u64 device_id;
FsRightsId rights_id;
u32 account_id;
u32 sect_total_size;
u32 sect_hdr_offset;
u16 sect_hdr_count;
u16 sect_hdr_entry_size;
} TikCommonBlock;
typedef struct {
SignatureRsa4096Block sig_block;
TikCommonBlock tik_common_blk;
} TikSigRsa4096;
typedef struct {
SignatureRsa2048Block sig_block;
TikCommonBlock tik_common_blk;
} TikSigRsa2048;
typedef struct {
SignatureEcsda240Block sig_block;
TikCommonBlock tik_common_blk;
} TikSigEcsda240;
/// Section records are placed right after the ticket data. These aren't available in TikTitleKeyType_Common tickets.
/// These are only used if the sect_* fields are non-zero (other than 'sect_hdr_offset').
/// Each section record is followed by a 'record_count' number of Esv1 records, each one of 'record_size' size.
typedef struct {
u32 sect_offset;
u32 record_size;
u32 section_size;
u16 record_count;
u16 section_type; ///< TikSectionType.
} TikEsv2SectionRecord;
/// Used to store ticket type, size and raw data.
typedef struct {
u8 type; ///< TikType.
u64 size;
u8 data[TIK_MAX_SIZE];
} Ticket;
TikCommonBlock *tikGetTicketCommonBlockFromMemoryBuffer(void *data);
static inline TikCommonBlock *tikGetTicketCommonBlockFromTicket(Ticket *tik)
{
if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) return NULL;
return tikGetTicketCommonBlockFromMemoryBuffer(tik->data);
}
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id);
bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TicketCommonBlock *tik_common_blk);
static inline bool tikGetTitleKeyFromTicket(void *dst, Ticket *tik)
{
if (!dst || !tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE) return false;
return tikGetTitleKeyFromTicketCommonBlock(dst, tikGetTicketCommonBlockFromTicket(tik));
}
bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation);
bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik);
/// This will convert a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket.
/// Use the output titlekey from tikGetTitleKeyFromTicket() / tikGetTitleKeyFromTicketCommonBlock() as the second parameter for this function.
/// Bear in mind the 'size' member from the Ticket parameter will be updated by this function to remove any possible references to TikEsv2SectionRecord records.
void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, const void *titlekey);
#endif /* __TIK_H__ */

429
source/nso.c Normal file
View file

@ -0,0 +1,429 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "nso.h"
#include "lz4.h"
#include "util.h"
#include "ui.h"
/* Extern variables */
extern int breaks;
extern int font_height;
/* Statically allocated variables */
static u8 *nsoBinaryData = NULL;
static u64 nsoBinaryDataSize = 0;
static u64 nsoBinaryTextSectionOffset = 0;
static u64 nsoBinaryTextSectionSize = 0;
static u64 nsoBinaryRodataSectionOffset = 0;
static u64 nsoBinaryRodataSectionSize = 0;
static u64 nsoBinaryDataSectionOffset = 0;
static u64 nsoBinaryDataSectionSize = 0;
void freeNsoBinaryData()
{
if (nsoBinaryData)
{
free(nsoBinaryData);
nsoBinaryData = NULL;
}
nsoBinaryDataSize = 0;
nsoBinaryTextSectionOffset = 0;
nsoBinaryTextSectionSize = 0;
nsoBinaryRodataSectionOffset = 0;
nsoBinaryRodataSectionSize = 0;
nsoBinaryDataSectionOffset = 0;
nsoBinaryDataSectionSize = 0;
}
bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, u64 nso_base_offset, nso_header_t *nsoHeader)
{
if (!ncmStorage || !ncaId || !aes_ctx || !nso_base_offset || !nsoHeader)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to load .text, .rodata and .data sections from NSO in Program NCA!", __func__);
return false;
}
u8 i;
u8 *nsoTextSection = NULL;
u64 nsoTextSectionSize = 0;
u8 *nsoRodataSection = NULL;
u64 nsoRodataSectionSize = 0;
u8 *nsoDataSection = NULL;
u64 nsoDataSectionSize = 0;
u8 *curCompressedSection;
u64 curCompressedSectionSize;
u64 curCompressedSectionOffset;
u8 curSectionFlag;
u8 *curDecompressedSection;
u64 curDecompressedSectionSize;
bool success = true;
freeNsoBinaryData();
for(i = 0; i < 3; i++)
{
curCompressedSection = NULL;
curCompressedSectionSize = (i == 0 ? (u64)nsoHeader->text_compressed_size : (i == 1 ? (u64)nsoHeader->rodata_compressed_size : (u64)nsoHeader->data_compressed_size));
curCompressedSectionOffset = (nso_base_offset + (i == 0 ? (u64)nsoHeader->text_segment_header.file_offset : (i == 1 ? (u64)nsoHeader->rodata_segment_header.file_offset : (u64)nsoHeader->data_segment_header.file_offset)));
curSectionFlag = (1 << i);
curDecompressedSection = NULL;
curDecompressedSectionSize = (i == 0 ? (u64)nsoHeader->text_segment_header.decompressed_size : (i == 1 ? (u64)nsoHeader->rodata_segment_header.decompressed_size : (u64)nsoHeader->data_segment_header.decompressed_size));
// Load section
curCompressedSection = malloc(curCompressedSectionSize);
if (!curCompressedSection)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the compressed %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data")));
success = false;
break;
}
if (!processNcaCtrSectionBlock(ncmStorage, ncaId, aes_ctx, curCompressedSectionOffset, curCompressedSection, curCompressedSectionSize, false))
{
breaks++;
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes %s section from NSO in Program NCA!", __func__, curCompressedSectionSize, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data")));
free(curCompressedSection);
success = false;
break;
}
if (nsoHeader->flags & curSectionFlag)
{
if (curDecompressedSectionSize <= curCompressedSectionSize)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decompressed size for %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data")));
free(curCompressedSection);
success = false;
break;
}
// Uncompress section
curDecompressedSection = malloc(curDecompressedSectionSize);
if (!curDecompressedSection)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decompressed %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data")));
free(curCompressedSection);
success = false;
break;
}
if (LZ4_decompress_safe((const char*)curCompressedSection, (char*)curDecompressedSection, (int)curCompressedSectionSize, (int)curDecompressedSectionSize) != (int)curDecompressedSectionSize)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to decompress %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data")));
free(curDecompressedSection);
free(curCompressedSection);
success = false;
break;
}
free(curCompressedSection);
switch(i)
{
case 0:
nsoTextSection = curDecompressedSection;
nsoTextSectionSize = curDecompressedSectionSize;
break;
case 1:
nsoRodataSection = curDecompressedSection;
nsoRodataSectionSize = curDecompressedSectionSize;
break;
case 2:
nsoDataSection = curDecompressedSection;
nsoDataSectionSize = curDecompressedSectionSize;
break;
default:
break;
}
} else {
switch(i)
{
case 0:
nsoTextSection = curCompressedSection;
nsoTextSectionSize = curCompressedSectionSize;
break;
case 1:
nsoRodataSection = curCompressedSection;
nsoRodataSectionSize = curCompressedSectionSize;
break;
case 2:
nsoDataSection = curCompressedSection;
nsoDataSectionSize = curCompressedSectionSize;
break;
default:
break;
}
}
}
curCompressedSection = curDecompressedSection = NULL;
if (success)
{
// Calculate full binary size
u64 finalTextSectionSize = nsoTextSectionSize;
u64 finalRodataSectionSize = nsoRodataSectionSize;
nsoBinaryDataSize = nsoTextSectionSize;
if ((u64)nsoHeader->rodata_segment_header.memory_offset > nsoBinaryDataSize)
{
nsoBinaryDataSize += ((u64)nsoHeader->rodata_segment_header.memory_offset - nsoBinaryDataSize);
} else
if ((u64)nsoHeader->rodata_segment_header.memory_offset < nsoBinaryDataSize)
{
finalTextSectionSize -= (nsoBinaryDataSize - (u64)nsoHeader->rodata_segment_header.memory_offset);
nsoBinaryDataSize -= (nsoBinaryDataSize - (u64)nsoHeader->rodata_segment_header.memory_offset);
}
nsoBinaryDataSize += nsoRodataSectionSize;
if ((u64)nsoHeader->data_segment_header.memory_offset > nsoBinaryDataSize)
{
nsoBinaryDataSize += ((u64)nsoHeader->data_segment_header.memory_offset - nsoBinaryDataSize);
} else
if ((u64)nsoHeader->data_segment_header.memory_offset < nsoBinaryDataSize)
{
finalRodataSectionSize -= (nsoBinaryDataSize - (u64)nsoHeader->data_segment_header.memory_offset);
nsoBinaryDataSize -= (nsoBinaryDataSize - (u64)nsoHeader->data_segment_header.memory_offset);
}
nsoBinaryDataSize += nsoDataSectionSize;
nsoBinaryData = calloc(nsoBinaryDataSize, sizeof(u8));
if (nsoBinaryData)
{
memcpy(nsoBinaryData, nsoTextSection, finalTextSectionSize);
memcpy(nsoBinaryData + (u64)nsoHeader->rodata_segment_header.memory_offset, nsoRodataSection, finalRodataSectionSize);
memcpy(nsoBinaryData + (u64)nsoHeader->data_segment_header.memory_offset, nsoDataSection, nsoDataSectionSize);
nsoBinaryTextSectionOffset = 0;
nsoBinaryTextSectionSize = finalTextSectionSize;
nsoBinaryRodataSectionOffset = (u64)nsoHeader->rodata_segment_header.memory_offset;
nsoBinaryRodataSectionSize = finalRodataSectionSize;
nsoBinaryDataSectionOffset = (u64)nsoHeader->data_segment_header.memory_offset;
nsoBinaryDataSectionSize = nsoDataSectionSize;
} else {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate %lu bytes for full decompressed NSO in Program NCA!", __func__, nsoBinaryDataSize);
nsoBinaryDataSize = 0;
success = false;
}
}
if (nsoTextSection) free(nsoTextSection);
if (nsoRodataSection) free(nsoRodataSection);
if (nsoDataSection) free(nsoDataSection);
return success;
}
bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml)
{
if (!ncmStorage || !ncaId || !aes_ctx || !nso_filename || !strlen(nso_filename) || !nso_base_offset || !nsoHeader || !programInfoXml)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve middleware list from NSO in Program NCA!", __func__);
return false;
}
u64 i;
char tmp[NAME_BUF_LEN] = {'\0'};
if (!loadNsoBinaryData(ncmStorage, ncaId, aes_ctx, nso_base_offset, nsoHeader)) return false;
for(i = 0; i < nsoBinaryRodataSectionSize; i++)
{
char *curStr = ((char*)nsoBinaryData + nsoBinaryRodataSectionOffset + i);
if (strncmp(curStr, "SDK MW+", 7) != 0) continue;
// Found a match
char *mwDev = (curStr + 7);
char *mwName = (strchr(mwDev, '+') + 1);
// Filter nnSdk entries
if (!strncasecmp(mwName, "NintendoSdk_nnSdk", 17))
{
i += strlen(curStr);
continue;
}
sprintf(tmp, " <Middleware>\n" \
" <ModuleName>%s</ModuleName>\n" \
" <VenderName>%.*s</VenderName>\n" \
" <NsoName>%s</NsoName>\n" \
" </Middleware>\n", \
mwName, \
(int)(mwName - mwDev - 1), mwDev, \
nso_filename);
strcat(programInfoXml, tmp);
// Update counter
i += strlen(curStr);
}
freeNsoBinaryData();
return true;
}
bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml)
{
if (!ncmStorage || !ncaId || !aes_ctx || !nso_filename || !strlen(nso_filename) || !nso_base_offset || !nsoHeader || !programInfoXml)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve symbols list from NSO in Program NCA!", __func__);
return false;
}
u64 i;
bool success = false;
char tmp[NAME_BUF_LEN] = {'\0'};
u32 mod_magic_offset;
u32 mod_magic;
s32 dynamic_section_offset;
bool armv7;
u64 dynamic_block_size;
u64 dynamic_block_cnt;
bool found_strtab = false;
u64 symbol_str_table_offset = 0;
bool found_symtab = false;
u64 symbol_table_offset = 0;
bool found_strsz = false;
u64 symbol_str_table_size = 0;
char *symbol_str_table = NULL;
u64 cur_symbol_table_offset = 0;
if (!loadNsoBinaryData(ncmStorage, ncaId, aes_ctx, nso_base_offset, nsoHeader)) return false;
mod_magic_offset = *((u32*)(&(nsoBinaryData[0x04])));
mod_magic = *((u32*)(&(nsoBinaryData[mod_magic_offset])));
dynamic_section_offset = ((s32)mod_magic_offset + *((s32*)(&(nsoBinaryData[mod_magic_offset + 0x04]))));
if (__builtin_bswap32(mod_magic) != MOD_MAGIC)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid MOD0 magic word in decompressed NSO from Program NCA! (0x%08X)", __func__, __builtin_bswap32(mod_magic));
goto out;
}
armv7 = (*((u64*)(&(nsoBinaryData[dynamic_section_offset]))) > (u64)0xFFFFFFFF || *((u64*)(&(nsoBinaryData[dynamic_section_offset + 0x10]))) > (u64)0xFFFFFFFF);
// Read dynamic section
dynamic_block_size = (armv7 ? 0x08 : 0x10);
dynamic_block_cnt = ((nsoBinaryDataSize - dynamic_section_offset) / dynamic_block_size);
for(i = 0; i < dynamic_block_cnt; i++)
{
if ((nsoBinaryDataSize - dynamic_section_offset - (i * dynamic_block_size)) < dynamic_block_size) break;
u64 tag = (armv7 ? (u64)(*((u32*)(&(nsoBinaryData[dynamic_section_offset + (i * dynamic_block_size)])))) : *((u64*)(&(nsoBinaryData[dynamic_section_offset + (i * dynamic_block_size)]))));
u64 val = (armv7 ? (u64)(*((u32*)(&(nsoBinaryData[dynamic_section_offset + (i * dynamic_block_size) + 0x04])))) : *((u64*)(&(nsoBinaryData[dynamic_section_offset + (i * dynamic_block_size) + 0x08]))));
if (!tag) break;
if (tag == DT_STRTAB && !found_strtab)
{
// Retrieve symbol string table offset
symbol_str_table_offset = val;
found_strtab = true;
}
if (tag == DT_SYMTAB && !found_symtab)
{
// Retrieve symbol table offset
symbol_table_offset = val;
found_symtab = true;
}
if (tag == DT_STRSZ && !found_strsz)
{
// Retrieve symbol string table size
symbol_str_table_size = val;
found_strsz = true;
}
if (found_strtab && found_symtab && found_strsz) break;
}
if (!found_strtab || !found_symtab || !found_strsz)
{
// Nothing to do here if we can't find what we need
success = true;
goto out;
}
// Point to the symbol string table
symbol_str_table = ((char*)nsoBinaryData + symbol_str_table_offset);
// Retrieve symbol list
cur_symbol_table_offset = symbol_table_offset;
while(true)
{
if (symbol_table_offset < symbol_str_table_offset && cur_symbol_table_offset >= symbol_str_table_offset) break;
u32 st_name = *((u32*)(&(nsoBinaryData[cur_symbol_table_offset])));
u8 st_info = (armv7 ? nsoBinaryData[cur_symbol_table_offset + 0x0C] : nsoBinaryData[cur_symbol_table_offset + 0x04]);
//u8 st_other = (armv7 ? nsoBinaryData[cur_symbol_table_offset + 0x0D] : nsoBinaryData[cur_symbol_table_offset + 0x05]);
u16 st_shndx = (armv7 ? *((u16*)(&(nsoBinaryData[cur_symbol_table_offset + 0x0E]))) : *((u16*)(&(nsoBinaryData[cur_symbol_table_offset + 0x06]))));
u64 st_value = (armv7 ? (u64)(*((u32*)(&(nsoBinaryData[cur_symbol_table_offset + 0x04])))) : *((u64*)(&(nsoBinaryData[cur_symbol_table_offset + 0x08]))));
//u64 st_size = (armv7 ? (u64)(*((u32*)(&(nsoBinaryData[cur_symbol_table_offset + 0x08])))) : *((u64*)(&(nsoBinaryData[cur_symbol_table_offset + 0x10]))));
//u8 st_vis = (st_other & 0x03);
u8 st_type = (st_info & 0x0F);
//u8 st_bind = (st_info >> 0x04);
if (st_name >= symbol_str_table_size) break;
cur_symbol_table_offset += (armv7 ? 0x10 : 0x18);
// TO-DO: Add more filters?
if (!st_shndx && !st_value && st_type != ST_OBJECT)
{
sprintf(tmp, " <UnresolvedApi>\n" \
" <ApiName>%s</ApiName>\n" \
" <NsoName>%s</NsoName>\n" \
" </UnresolvedApi>\n", \
symbol_str_table + st_name, \
nso_filename);
strcat(programInfoXml, tmp);
}
}
success = true;
out:
freeNsoBinaryData();
return success;
}

58
source/nso.h Normal file
View file

@ -0,0 +1,58 @@
#pragma once
#ifndef __NSO_H__
#define __NSO_H__
#include <switch.h>
#define NSO_MAGIC (u32)0x4E534F30 // "NSO0"
#define MOD_MAGIC (u32)0x4D4F4430 // "MOD0"
#define DT_STRTAB 0x05
#define DT_SYMTAB 0x06
#define DT_STRSZ 0x0A
#define ST_OBJECT 0x01
typedef struct {
u32 file_offset;
u32 memory_offset;
u32 decompressed_size;
} PACKED segment_header_t;
typedef struct {
u32 region_offset;
u32 region_size;
} PACKED rodata_extent_t;
typedef struct {
u32 magic;
u32 version;
u32 reserved1;
u32 flags;
segment_header_t text_segment_header;
u32 module_offset;
segment_header_t rodata_segment_header;
u32 module_file_size;
segment_header_t data_segment_header;
u32 bss_size;
u8 elf_note_build_id[0x20];
u32 text_compressed_size;
u32 rodata_compressed_size;
u32 data_compressed_size;
u8 reserved2[0x1C];
rodata_extent_t rodata_api_info;
rodata_extent_t rodata_dynstr;
rodata_extent_t rodata_dynsym;
u8 text_decompressed_hash[0x20];
u8 rodata_decompressed_hash[0x20];
u8 data_decompressed_hash[0x20];
} PACKED nso_header_t;
// Retrieves the middleware list from a NSO stored in a partition from a NCA file
bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml);
// Retrieves the symbols list from a NSO stored in a partition from a NCA file
bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml);
#endif

5276
source/ui.c Normal file

File diff suppressed because it is too large Load diff

206
source/ui.h Normal file
View file

@ -0,0 +1,206 @@
#pragma once
#ifndef __UI_H__
#define __UI_H__
#define FB_WIDTH 1280
#define FB_HEIGHT 720
#define CHAR_PT_SIZE 12
#define SCREEN_DPI_CNT 96
#define LINE_HEIGHT (font_height + (font_height / 4))
#define LINE_STRING_OFFSET (font_height / 8)
#define STRING_DEFAULT_POS 8, 8
#define STRING_X_POS 8
#define STRING_Y_POS(x) (8 + ((x) * LINE_HEIGHT) + ((x) > 0 ? LINE_STRING_OFFSET : 0))
#define BG_COLOR_RGB 50, 50, 50
#define FONT_COLOR_RGB 255, 255, 255
#define HIGHLIGHT_BG_COLOR_RGB 33, 34, 39
#define HIGHLIGHT_FONT_COLOR_RGB 0, 255, 197
#define FONT_COLOR_SUCCESS_RGB 0, 255, 0
#define FONT_COLOR_ERROR_RGB 255, 0, 0
#define FONT_COLOR_TITLE_RGB 115, 115, 255
#define EMPTY_BAR_COLOR_RGB 0, 0, 0
#define COMMON_MAX_ELEMENTS 9
#define HFS0_MAX_ELEMENTS 14
#define ROMFS_MAX_ELEMENTS 12
#define SDCARD_MAX_ELEMENTS 3
#define ORPHAN_MAX_ELEMENTS 12
#define BATCH_MAX_ELEMENTS 14
#define OPTIONS_X_START_POS (35 * CHAR_PT_SIZE)
#define OPTIONS_X_END_POS (OPTIONS_X_START_POS + (6 * CHAR_PT_SIZE))
#define OPTIONS_X_END_POS_NSP (FB_WIDTH - (4 * CHAR_PT_SIZE))
#define TAB_WIDTH 4
#define BROWSER_ICON_DIMENSION 16
// UTF-8 sequences
#define UPWARDS_ARROW "\xE2\x86\x91"
#define DOWNWARDS_ARROW "\xE2\x86\x93"
#define NINTENDO_FONT_A "\xEE\x82\xA0"
#define NINTENDO_FONT_B "\xEE\x82\xA1"
#define NINTENDO_FONT_X "\xEE\x82\xA2"
#define NINTENDO_FONT_Y "\xEE\x82\xA3"
#define NINTENDO_FONT_L "\xEE\x82\xA4"
#define NINTENDO_FONT_R "\xEE\x82\xA5"
#define NINTENDO_FONT_ZL "\xEE\x82\xA6"
#define NINTENDO_FONT_ZR "\xEE\x82\xA7"
#define NINTENDO_FONT_DPAD "\xEE\x82\xAA"
#define NINTENDO_FONT_PLUS "\xEE\x82\xB5"
#define NINTENDO_FONT_HOME "\xEE\x82\xB9"
#define NINTENDO_FONT_LSTICK "\xEE\x83\x81"
#define NINTENDO_FONT_RSTICK "\xEE\x83\x82"
typedef enum {
resultNone,
resultShowMainMenu,
resultShowGameCardMenu,
resultShowXciDumpMenu,
resultDumpXci,
resultShowNspDumpMenu,
resultShowNspAppDumpMenu,
resultShowNspPatchDumpMenu,
resultShowNspAddOnDumpMenu,
resultDumpNsp,
resultShowHfs0Menu,
resultShowRawHfs0PartitionDumpMenu,
resultDumpRawHfs0Partition,
resultShowHfs0PartitionDataDumpMenu,
resultDumpHfs0PartitionData,
resultShowHfs0BrowserMenu,
resultHfs0BrowserGetList,
resultShowHfs0Browser,
resultHfs0BrowserCopyFile,
resultShowExeFsMenu,
resultShowExeFsSectionDataDumpMenu,
resultDumpExeFsSectionData,
resultShowExeFsSectionBrowserMenu,
resultExeFsSectionBrowserGetList,
resultShowExeFsSectionBrowser,
resultExeFsSectionBrowserCopyFile,
resultShowRomFsMenu,
resultShowRomFsSectionDataDumpMenu,
resultDumpRomFsSectionData,
resultShowRomFsSectionBrowserMenu,
resultRomFsSectionBrowserGetEntries,
resultShowRomFsSectionBrowser,
resultRomFsSectionBrowserChangeDir,
resultRomFsSectionBrowserCopyFile,
resultRomFsSectionBrowserCopyDir,
resultDumpGameCardCertificate,
resultShowSdCardEmmcMenu,
resultShowSdCardEmmcTitleMenu,
resultShowSdCardEmmcOrphanPatchAddOnMenu,
resultShowSdCardEmmcBatchModeMenu,
resultSdCardEmmcBatchDump,
resultShowTicketMenu,
resultDumpTicket,
resultShowUpdateMenu,
resultUpdateNSWDBXml,
resultUpdateApplication,
resultExit
} UIResult;
typedef enum {
stateMainMenu,
stateGameCardMenu,
stateXciDumpMenu,
stateDumpXci,
stateNspDumpMenu,
stateNspAppDumpMenu,
stateNspPatchDumpMenu,
stateNspAddOnDumpMenu,
stateDumpNsp,
stateHfs0Menu,
stateRawHfs0PartitionDumpMenu,
stateDumpRawHfs0Partition,
stateHfs0PartitionDataDumpMenu,
stateDumpHfs0PartitionData,
stateHfs0BrowserMenu,
stateHfs0BrowserGetList,
stateHfs0Browser,
stateHfs0BrowserCopyFile,
stateExeFsMenu,
stateExeFsSectionDataDumpMenu,
stateDumpExeFsSectionData,
stateExeFsSectionBrowserMenu,
stateExeFsSectionBrowserGetList,
stateExeFsSectionBrowser,
stateExeFsSectionBrowserCopyFile,
stateRomFsMenu,
stateRomFsSectionDataDumpMenu,
stateDumpRomFsSectionData,
stateRomFsSectionBrowserMenu,
stateRomFsSectionBrowserGetEntries,
stateRomFsSectionBrowser,
stateRomFsSectionBrowserChangeDir,
stateRomFsSectionBrowserCopyFile,
stateRomFsSectionBrowserCopyDir,
stateDumpGameCardCertificate,
stateSdCardEmmcMenu,
stateSdCardEmmcTitleMenu,
stateSdCardEmmcOrphanPatchAddOnMenu,
stateSdCardEmmcBatchModeMenu,
stateSdCardEmmcBatchDump,
stateTicketMenu,
stateDumpTicket,
stateUpdateMenu,
stateUpdateNSWDBXml,
stateUpdateApplication
} UIState;
typedef enum {
MENUTYPE_MAIN = 0,
MENUTYPE_GAMECARD,
MENUTYPE_SDCARD_EMMC
} curMenuType;
void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b);
void uiDrawIcon(const u8 *icon, int width, int height, int x, int y);
bool uiLoadJpgFromMem(u8 *rawJpg, size_t rawJpgSize, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
void uiDrawString(int x, int y, u8 r, u8 g, u8 b, const char *fmt, ...);
u32 uiGetStrWidth(const char *fmt, ...);
void uiRefreshDisplay();
void uiStatusMsg(const char *fmt, ...);
void uiUpdateStatusMsg();
void uiClearStatusMsg();
void uiPleaseWait(u8 wait);
void uiClearScreen();
void uiPrintHeadline();
bool uiInit();
void uiDeinit();
void uiSetState(UIState state);
UIState uiGetState();
UIResult uiProcess();
#endif

4695
source/util.c Normal file

File diff suppressed because it is too large Load diff

486
source/util.h Normal file
View file

@ -0,0 +1,486 @@
#pragma once
#ifndef __UTIL_H__
#define __UTIL_H__
#include <time.h>
#include <switch.h>
#include "nca.h"
#define HBLOADER_BASE_PATH "sdmc:/switch/"
#define APP_BASE_PATH HBLOADER_BASE_PATH APP_TITLE "/"
#define XCI_DUMP_PATH APP_BASE_PATH "XCI/"
#define NSP_DUMP_PATH APP_BASE_PATH "NSP/"
#define HFS0_DUMP_PATH APP_BASE_PATH "HFS0/"
#define EXEFS_DUMP_PATH APP_BASE_PATH "ExeFS/"
#define ROMFS_DUMP_PATH APP_BASE_PATH "RomFS/"
#define CERT_DUMP_PATH APP_BASE_PATH "Certificate/"
#define BATCH_OVERRIDES_PATH NSP_DUMP_PATH "BatchOverrides/"
#define TICKET_PATH APP_BASE_PATH "Ticket/"
void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
{
if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return;
va_list args;
FILE *logfile = NULL;
char str[FS_MAX_PATH] = {0};
logfile = fopen(APP_BASE_PATH "log.txt", "a+");
if (!logfile) return;
time_t now = time(NULL);
struct tm *ts = localtime(&now);
fprintf(logfile, "%d-%d-%d %d:%d:%d -> %s: ", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, func_name);
va_start(args, fmt);
vfprintf(logfile, fmt, args);
va_end(args);
fprintf(logfile, "\r\n");
fclose(logfile);
}
#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__)
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
#define CONFIG_PATH APP_BASE_PATH "config.bin"
#define NRO_NAME APP_TITLE ".nro"
#define NRO_PATH APP_BASE_PATH NRO_NAME
#define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml"
#define KEYS_FILE_PATH HBLOADER_BASE_PATH "prod.keys"
#define CFW_PATH_ATMOSPHERE "sdmc:/atmosphere/contents/"
#define CFW_PATH_SXOS "sdmc:/sxos/titles/"
#define CFW_PATH_REINX "sdmc:/ReiNX/titles/"
#define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)"
#define GITHUB_API_URL "https://api.github.com/repos/DarkMatterCore/nxdumptool/releases/latest"
#define GITHUB_API_JSON_RELEASE_NAME "name"
#define GITHUB_API_JSON_ASSETS "assets"
#define GITHUB_API_JSON_ASSETS_NAME "name"
#define GITHUB_API_JSON_ASSETS_DL_URL "browser_download_url"
#define NOINTRO_DOM_CHECK_URL "https://datomatic.no-intro.org/qchknsw.php"
#define NSWDB_XML_URL "http://nswdb.com/xml.php"
#define NSWDB_XML_ROOT "releases"
#define NSWDB_XML_CHILD "release"
#define NSWDB_XML_CHILD_TITLEID "titleid"
#define NSWDB_XML_CHILD_IMGCRC "imgcrc"
#define NSWDB_XML_CHILD_RELEASENAME "releasename"
#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM"
#define KiB (1024.0)
#define MiB (1024.0 * KiB)
#define GiB (1024.0 * MiB)
#define NAME_BUF_LEN 2048
#define DUMP_BUFFER_SIZE (u64)0x400000 // 4 MiB (4194304 bytes)
#define GAMECARD_READ_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes)
#define NCA_CTR_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes)
#define NSP_XML_BUFFER_SIZE (u64)0xA00000 // 10 MiB (10485760 bytes)
#define APPLICATION_PATCH_BITMASK (u64)0x800
#define APPLICATION_ADDON_BITMASK (u64)0xFFFFFFFFFFFF0000
#define NACP_APPNAME_LEN 0x200
#define NACP_AUTHOR_LEN 0x100
#define VERSION_STR_LEN 0x40
#define MEDIA_UNIT_SIZE 0x200
#define ISTORAGE_PARTITION_CNT 2
#define GAMECARD_WAIT_TIME 3 // 3 seconds
#define GAMECARD_HEADER_MAGIC (u32)0x48454144 // "HEAD"
#define GAMECARD_SIZE_1GiB (u64)0x40000000
#define GAMECARD_SIZE_2GiB (u64)0x80000000
#define GAMECARD_SIZE_4GiB (u64)0x100000000
#define GAMECARD_SIZE_8GiB (u64)0x200000000
#define GAMECARD_SIZE_16GiB (u64)0x400000000
#define GAMECARD_SIZE_32GiB (u64)0x800000000
#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816
#define GAMECARD_ECC_BLOCK_SIZE (u64)0x200 // 512 bytes
#define GAMECARD_ECC_DATA_SIZE (u64)0x24 // 36 bytes
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3)
#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))
#define HFS0_MAGIC (u32)0x48465330 // "HFS0"
#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < (GAMECARD_TYPE1_PARTITION_CNT - 1) ? 0 : 1) : ((y) < (GAMECARD_TYPE2_PARTITION_CNT - 1) ? 0 : 1))
#define NACP_ICON_SQUARE_DIMENSION 256
#define NACP_ICON_DOWNSCALED 96
#define round_up(x, y) ((x) + (((y) - ((x) % (y))) % (y))) // Aligns 'x' bytes to a 'y' bytes boundary
#define ORPHAN_ENTRY_TYPE_PATCH 1
#define ORPHAN_ENTRY_TYPE_ADDON 2
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) // Returns the max number of elements that can be stored in an array
#define MAX_CHARACTERS(x) (MAX_ELEMENTS((x)) - 1) // Returns the max number of characters that can be stored in char array while also leaving space for a NULL terminator
#define BIS_MOUNT_NAME "sys:"
#define BIS_CERT_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e0"
#define BIS_COMMON_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e1"
#define BIS_PERSONALIZED_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e2"
#define SMOOTHING_FACTOR (double)0.1
#define CANCEL_BTN_SEC_HOLD 2 // The cancel button must be held for at least CANCEL_BTN_SEC_HOLD seconds to cancel an ongoing operation
typedef struct {
u8 signature[0x100];
u32 magic;
u32 secureAreaStartAddr;
u32 backupAreaStartAddr;
u8 titleKeyIndex;
u8 size;
u8 headerVersion;
u8 flags;
u64 packageId;
u64 validDataEndAddr;
u8 iv[0x10];
u64 rootHfs0HeaderOffset;
u64 rootHfs0HeaderSize;
u8 rootHfs0HeaderHash[SHA256_HASH_SIZE];
u8 initialDataHash[SHA256_HASH_SIZE];
u32 securityMode;
u32 t1KeyIndex;
u32 keyIndex;
u32 normalAreaEndAddr;
u8 encryptedInfoBlock[0x70];
} PACKED gamecard_header_t;
typedef struct {
u64 offset;
u64 size;
u8 *header;
u64 header_size;
u32 file_cnt;
u32 str_table_size;
} PACKED hfs0_partition_info;
typedef enum {
ISTORAGE_PARTITION_NONE = 0,
ISTORAGE_PARTITION_NORMAL,
ISTORAGE_PARTITION_SECURE,
ISTORAGE_PARTITION_INVALID
} openIStoragePartition;
typedef struct {
FsDeviceOperator fsOperatorInstance;
FsEventNotifier fsGameCardEventNotifier;
Event fsGameCardKernelEvent;
FsGameCardHandle fsGameCardHandle;
FsStorage fsGameCardStorage;
openIStoragePartition curIStorageIndex;
volatile bool isInserted;
gamecard_header_t header;
u8 *rootHfs0Header;
u32 hfs0PartitionCnt;
hfs0_partition_info *hfs0Partitions;
u64 size;
char sizeStr[32];
u64 trimmedSize;
char trimmedSizeStr[32];
u64 IStoragePartitionSizes[ISTORAGE_PARTITION_CNT];
u64 updateTitleId;
u32 updateVersion;
char updateVersionStr[64];
} gamecard_ctx_t;
typedef struct {
u64 titleId;
u32 version;
u32 ncmIndex;
NcmStorageId storageId;
char name[NACP_APPNAME_LEN];
char fixedName[NACP_APPNAME_LEN];
char author[NACP_AUTHOR_LEN];
char versionStr[VERSION_STR_LEN];
u8 *icon;
u64 contentSize;
char contentSizeStr[32];
} base_app_ctx_t;
typedef struct {
u64 titleId;
u32 version;
u32 ncmIndex;
NcmStorageId storageId;
char versionStr[VERSION_STR_LEN];
u64 contentSize;
char contentSizeStr[32];
} patch_addon_ctx_t;
typedef struct {
u32 index;
u8 type; // 1 = Patch, 2 = AddOn
char name[NACP_APPNAME_LEN];
char fixedName[NACP_APPNAME_LEN];
char orphanListStr[NACP_APPNAME_LEN * 2];
} orphan_patch_addon_entry;
typedef struct {
u32 magic;
u32 file_cnt;
u32 str_table_size;
u32 reserved;
} PACKED hfs0_header;
typedef struct {
u64 file_offset;
u64 file_size;
u32 filename_offset;
u32 hashed_region_size;
u64 reserved;
u8 hashed_region_sha256[0x20];
} PACKED hfs0_file_entry;
typedef struct {
int line_offset;
u64 totalSize;
char totalSizeStr[32];
u64 curOffset;
char curOffsetStr[32];
u64 seqDumpCurOffset;
u8 progress;
u64 start;
u64 now;
u64 remainingTime;
char etaInfo[32];
double lastSpeed;
double averageSpeed;
u32 cancelBtnState;
u32 cancelBtnStatePrev;
u64 cancelStartTmr;
u64 cancelEndTmr;
} progress_ctx_t;
typedef enum {
ROMFS_TYPE_APP = 0,
ROMFS_TYPE_PATCH,
ROMFS_TYPE_ADDON
} selectedRomFsType;
typedef enum {
TICKET_TYPE_APP = 0,
TICKET_TYPE_PATCH,
TICKET_TYPE_ADDON
} selectedTicketType;
typedef struct {
bool isFat32;
bool setXciArchiveBit;
bool keepCert;
bool trimDump;
bool calcCrc;
bool useNoIntroLookup;
bool useBrackets;
} PACKED xciOptions;
typedef struct {
bool isFat32;
bool useNoIntroLookup;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
bool dumpDeltaFragments;
bool useBrackets;
} PACKED nspOptions;
typedef enum {
BATCH_SOURCE_ALL = 0,
BATCH_SOURCE_SDCARD,
BATCH_SOURCE_EMMC,
BATCH_SOURCE_CNT
} batchModeSourceStorage;
typedef struct {
bool dumpAppTitles;
bool dumpPatchTitles;
bool dumpAddOnTitles;
bool isFat32;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
bool dumpDeltaFragments;
bool skipDumpedTitles;
bool rememberDumpedTitles;
bool haltOnErrors;
bool useBrackets;
batchModeSourceStorage batchModeSrc;
} PACKED batchOptions;
typedef struct {
bool removeConsoleData;
} PACKED ticketOptions;
typedef struct {
bool isFat32;
bool useLayeredFSDir;
} PACKED ncaFsOptions;
typedef struct {
xciOptions xciDumpCfg;
nspOptions nspDumpCfg;
batchOptions batchDumpCfg;
ticketOptions tikDumpCfg;
ncaFsOptions exeFsDumpCfg;
ncaFsOptions romFsDumpCfg;
} PACKED dumpOptions;
void loadConfig();
void saveConfig();
void closeGameCardStoragePartition();
Result openGameCardStoragePartition(openIStoragePartition partitionIndex);
Result readGameCardStoragePartition(u64 off, void *buf, size_t len);
void delay(u8 seconds);
void convertSize(u64 size, char *out, size_t outSize);
void updateFreeSpace();
void freeFilenameBuffer(void);
void initExeFsContext();
void freeExeFsContext();
void initRomFsContext();
void freeRomFsContext();
void initBktrContext();
void freeBktrContext();
void freeRomFsBrowserEntries();
void freeHfs0ExeFsEntriesSizes();
u64 hidKeysAllDown();
u64 hidKeysAllHeld();
void consoleErrorScreen(const char *fmt, ...);
bool initApplicationResources(int argc, char **argv);
void deinitApplicationResources();
bool appletModeCheck();
void appletModeOperationWarning();
void changeHomeButtonBlockStatus(bool block);
void formatETAString(u64 curTime, char *out, size_t outSize);
void generateSdCardEmmcTitleList();
bool loadTitlesFromSdCardAndEmmc(NcmContentMetaType metaType);
void freeTitlesFromSdCardAndEmmc(NcmContentMetaType metaType);
void removeIllegalCharacters(char *name);
void strtrim(char *str);
void loadTitleInfo();
void truncateBrowserEntryName(char *str);
bool getHfs0FileList(u32 partition);
bool readFileFromSecureHfs0PartitionByName(const char *filename, u64 offset, void *outBuf, size_t bufSize);
bool calculateExeFsExtractedDataSize(u64 *out);
bool calculateRomFsFullExtractedSize(bool usePatch, u64 *out);
bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out);
bool retrieveContentInfosFromTitle(NcmStorageId storageId, NcmContentMetaType metaType, u32 titleCount, u32 titleIndex, NcmContentInfo **outContentInfos, u32 *outContentInfoCnt);
void removeConsoleDataFromTicket(title_rights_ctx *rights_info);
bool readNcaExeFsSection(u32 titleIndex, bool usePatch);
bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, int desiredIdOffset);
bool getExeFsFileList();
bool getRomFsFileList(u32 dir_offset, bool usePatch);
char *generateGameCardDumpName(bool useBrackets);
char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex, bool useBrackets);
void retrieveDescriptionForPatchOrAddOn(u32 titleIndex, bool addOn, bool addAppName, const char *prefix, char *outBuf, size_t outBufSize);
u32 calculateOrphanPatchOrAddOnCount(bool addOn);
void generateOrphanPatchOrAddOnList();
bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn);
bool checkIfPatchOrAddOnBelongsToBaseApplication(u32 titleIndex, u32 appIndex, bool addOn);
u32 retrieveFirstPatchOrAddOnIndexFromBaseApplication(u32 appIndex, bool addOn);
u32 retrievePreviousPatchOrAddOnIndexFromBaseApplication(u32 startTitleIndex, u32 appIndex, bool addOn);
u32 retrieveNextPatchOrAddOnIndexFromBaseApplication(u32 startTitleIndex, u32 appIndex, bool addOn);
u32 retrieveLastPatchOrAddOnIndexFromBaseApplication(u32 appIndex, bool addOn);
void waitForButtonPress();
void printProgressBar(progress_ctx_t *progressCtx, bool calcData, u64 chunkSize);
void setProgressBarError(progress_ctx_t *progressCtx);
bool cancelProcessCheck(progress_ctx_t *progressCtx);
void convertDataToHexString(const u8 *data, const u32 dataSize, char *outBuf, const u32 outBufSize);
bool checkIfFileExists(const char *path);
bool yesNoPrompt(const char *message);
bool checkIfDumpedXciContainsCertificate(const char *xciPath);
bool checkIfDumpedNspContainsConsoleData(const char *nspPath);
void removeDirectoryWithVerbose(const char *path, const char *msg);
void gameCardDumpNSWDBCheck(u32 crc);
void noIntroDumpCheck(bool isDigital, u32 crc);
void updateNSWDBXml();
bool updateApplication();
#endif