diff --git a/LICENSE b/LICENSE
index 1ccef1f1..f3642129 100644
--- a/LICENSE
+++ b/LICENSE
@@ -4,8 +4,9 @@
- Tinymembench is under MIT license, which is compatible with GPL v2.
-- Although "sys-clk" uses permissive license, all modifications towards it in this repo ("hoc-sys") are licensed under GPL v2.
+- Although "sys-clk" uses a permissive license, all modifications towards it in this repo ("hoc-sys") are licensed under GPL v2.
+- Hekate Power BDK is made by CtCaer and licensed under GPLv2
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
diff --git a/README.md b/README.md
index 73219487..29a408f8 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
---
-
+

[](https://discord.com/invite/S3eX47dHsB)

@@ -13,99 +13,53 @@

---
+
-
+### DISCLAIMER: THIS TOOL CAN BE DANGEROUS IF MISUSED. PROCEED WITH CAUTION
+* Due to the design of Horizon OS, overclocking RAM can cause **NAND DAMAGE**. Ensure to have a NAND Backup
-## ⚠️ Disclaimer
+A open source overclocking tool for Nintendo Switch consoles running Atmosphere custom firmware
-> **THIS TOOL CAN BE DANGEROUS IF MISUSED. PROCEED WITH CAUTION.**
-> Due to the design of Horizon OS, **overclocking RAM can cause NAND DAMAGE.**
-> Ensure you have a **full NAND backup** before proceeding.
----
+## Features:
+CPU overclock up to 2397MHz on Mariko units, 2091MHz on Erista units
+GPU up to 1305MHz on Mariko units, 998MHz on Erista units
+RAM up to 3200MHz on Mariko units, 2360MHz on Erista units
+Over/undervolting
+Configurator
+Works with most homebrew
-## 🌀 About
+*Higher (potentially dangerous) frequencies are unlockable*
+*The exact maximum overclock varies per console*
+## Installation
+Ensure you have the latest version of [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere) and [Ultrahand](https://github.com/ppkantorski/Ultrahand-Overlay) installed before continuing
+Grab latest hoc.kip from releases tab
+If using hekate, edit hekate_ipl.ini to include "kip1=atmosphere/kips/*". No need for editing if using fusee
+Download latest Horizon OC sysmodule from releases tab
+Extract sysmodule into root of SD card
-**Horizon OC** is an open-source overclocking tool for Nintendo Switch consoles running **Atmosphere custom firmware**.
-It enables advanced CPU, GPU, and RAM tuning with user-friendly configuration tools.
+Alternatively, you can download the configurator and click the two install buttons
----
-## 🚀 Features
+## Configuration
+Download the latest configurator on your computer
+Run the file
+Select the drive your SD card or UMS device is mounted as
+Configure the kip to your liking, and in the end, save it
-* **CPU:** Up to `2397MHz` (Mariko) / `2091MHz` (Erista)
-* **GPU:** Up to `1305MHz` (Mariko) / `998MHz` (Erista)
-* **RAM:** Up to `3200MHz` (Mariko) / `2360MHz` (Erista)
-* Over/undervolting support
-* Built-in configurator
-* Compatible with most homebrew
+## Building
+Set up a development enviorment ready to compile Atmosphere
+Git clone Atmosphere, and move the cloned folder into build/
+Insert Source/stratosphere folder into build/
+Run build.sh
-> ⚡ *Higher (potentially dangerous) frequencies are unlockable.*
-> ⚙️ *Exact maximum values vary per console.*
+To build the configurator, cd into Source/Configurator
+Run build.bat or run "python -m PyInstaller --onefile --add-data "assets;assets" --icon=assets/icon.ico --noconsole src/main.py"
----
-
-## 🧩 Installation
-
-1. Ensure you have the latest versions of
-
- * [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere)
- * [Ultrahand Overlay](https://github.com/ppkantorski/Ultrahand-Overlay)
-2. Download the latest **hoc.kip** file from the [Releases](../../releases) tab.
-3. If using **Hekate**, edit `hekate_ipl.ini` to include:
-
- ```
- kip1=atmosphere/kips/*
- ```
-
- *(No changes needed if using fusee.)*
-4. Download and extract the **Horizon OC sysmodule** to the root of your SD card.
-5. Alternatively, use the **Configurator** and click the **Install** buttons for automatic setup.
-
----
-
-## ⚙️ Configuration
-
-1. Download the latest **Configurator** on your computer.
-2. Run the executable.
-3. Select your SD card or UMS drive.
-4. Adjust overclocking settings as desired.
-5. Click **Save** to apply your configuration.
-
----
-
-## 🧱 Building from Source
-
-1. Set up a development environment for compiling **Atmosphere**.
-2. Clone Atmosphere:
-
- ```bash
- git clone https://github.com/Atmosphere-NX/Atmosphere.git
- ```
-3. Move the cloned folder into `build/`.
-4. Insert your `Source/stratosphere` folder into `build/`.
-5. Run:
-
- ```bash
- ./build.sh
- ```
-
-To build the Configurator:
-
-```bash
-cd Source/Configurator
-build.bat
-# or
-python -m PyInstaller --onefile --add-data "assets;assets" --icon=assets/icon.ico --noconsole src/main.py
-```
-
----
-
-## 💎 Credits
-
-* **Lightos** & **Dominatorul** – RAM timings
-* **KazushiMe** & **meha** – Switch-OC-Suite
-* **sys-clk team** – sys-clk
-* **b0rd2death** – Ultrahand sys-clk fork
-* **Lightos** & **Sammybigio2011** – Early testing
+## Credits
+Lightos for RAM timings
+KazushiMe and meha for Switch-Oc-Suite
+sys-clk team for sys-clk
+b0rd2death for Ultrahand sys-clk fork
+Lightos and Sammybigio2011 for early testing
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
index 749b2d6e..c7866186 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,8 +4,7 @@
| Version | Supported |
| ------- | ------------------ |
-| 2.0.x | :white_check_mark: |
-| 1.0.x | :x: |
+| 0.x.x | :white_check_mark: |
## Reporting a Vulnerability
diff --git a/Source/Atmosphere-MTC-Unlock/.gitattributes b/Source/Atmosphere-MTC-Unlock/.gitattributes
new file mode 100644
index 00000000..ef554e79
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/.gitattributes
@@ -0,0 +1,14 @@
+config_templates/hbl_html/accessible-urls/accessible-urls.txt text eol=lf
+
+# Mark C++ "include" files as C++
+*.inc linguist-language=C++
+
+# Mark RapidJSON include as vendored
+libraries/include/stratosphere/rapidjson/** linguist-vendored
+
+# Mark emummc as vendored
+emummc/** linguist-vendored
+
+# Mark fatfs as vendored
+exosphere/mariko_fatal/source/fatfs/** linguist-vendored
+fusee/program/source/fatfs/** linguist-vendored
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/.gitignore b/Source/Atmosphere-MTC-Unlock/.gitignore
new file mode 100644
index 00000000..a4a9c261
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/.gitignore
@@ -0,0 +1,110 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+*.lst
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.lz4
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Deko3d shaders
+*.dksh
+
+# Switch Executables
+*.nso
+*.nro
+*.nacp
+*.npdm
+*.pfs0
+*.nsp
+*.kip
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# Distribution files
+*.tgz
+*.zip
+*.bz2
+
+# IDA binaries
+*.id0
+*.id1
+*.id2
+*.idb
+*.i64
+*.nam
+*.til
+
+# Compiled python files.
+*.pyc
+
+.**/
+
+# macOS horseshittery
+.DS_Store
+
+# NOTE: make sure to make exceptions to this pattern when needed!
+*.bin
+*.enc
+
+**/out
+**/build
+**/lib
+**/build_nintendo_nx_arm64
+**/build_nintendo_nx_arm64_armv8a
+**/build_nintendo_nx_arm
+**/build_nintendo_nx_arm_armv8a
+**/build_nintendo_nx_arm_armv7a
+**/build_nintendo_nx_arm_armv4t
+**/build_nintendo_nx_x64
+**/build_nintendo_nx_x86
+
+tools/*/
+
+package3
+
+stratosphere/test/
diff --git a/Source/sys-clk/sysmodule/src/emc.cpp b/Source/Atmosphere-MTC-Unlock/.gitmodules
similarity index 100%
rename from Source/sys-clk/sysmodule/src/emc.cpp
rename to Source/Atmosphere-MTC-Unlock/.gitmodules
diff --git a/Source/Atmosphere-MTC-Unlock/LICENSE b/Source/Atmosphere-MTC-Unlock/LICENSE
new file mode 100644
index 00000000..ecbc0593
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/LICENSE
@@ -0,0 +1,339 @@
+ 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 Lesser 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.
+
+ GNU GENERAL PUBLIC LICENSE
+ 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.
+
+
+ Copyright (C)
+
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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.
+
+ , 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 Lesser General
+Public License instead of this License.
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/Makefile b/Source/Atmosphere-MTC-Unlock/Makefile
new file mode 100644
index 00000000..d85056f7
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/Makefile
@@ -0,0 +1,42 @@
+ATMOSPHERE_BUILD_CONFIGS :=
+all: nx_release
+clean: clean-nx_release
+
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+
+define ATMOSPHERE_ADD_TARGET
+
+ATMOSPHERE_BUILD_CONFIGS += $(strip $1)
+
+$(strip $1):
+ @echo "Building $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/atmosphere.mk ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+clean-$(strip $1):
+ @echo "Cleaning $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/atmosphere.mk clean ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+endef
+
+define ATMOSPHERE_ADD_TARGETS
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_release, release, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4)" \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, debug, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, audit, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 \
+))
+
+endef
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, nx-hac-001, arm-cortex-a57,))
+
+clean-all: $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS),clean-$(config))
+
+.PHONY: all clean clean-all $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS), $(config) clean-$(config))
diff --git a/Source/Atmosphere-MTC-Unlock/README.md b/Source/Atmosphere-MTC-Unlock/README.md
new file mode 100644
index 00000000..aee0ff97
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/README.md
@@ -0,0 +1,51 @@
+
+
+=====
+
+
+[](https://discordapp.com/invite/ZdqEhed)
+
+
+Atmosphère is a work-in-progress customized firmware for the Nintendo Switch.
+
+Components
+=====
+
+Atmosphère consists of multiple components, each of which replaces/modifies a different component of the system:
+
+* Fusée: First-stage Loader, responsible for loading and validating stage 2 (custom TrustZone) plus package2 (Kernel/FIRM sysmodules), and patching them as needed. This replaces all functionality normally in Package1loader/NX Bootloader.
+* Exosphère: Customized TrustZone, to run a customized Secure Monitor
+* Thermosphère: EL2 EmuNAND support, i.e. backing up and using virtualized/redirected NAND images
+* Stratosphère: Custom Sysmodule(s), both Rosalina style to extend the kernel/provide new features, and of the loader reimplementation style to hook important system actions
+* Troposphère: Application-level Horizon OS patches, used to implement desirable CFW features
+
+Licensing
+=====
+
+This software is licensed under the terms of the GPLv2, with exemptions for specific projects noted below.
+
+You can find a copy of the license in the [LICENSE file](LICENSE).
+
+Exemptions:
+* [Nintendo](https://github.com/Nintendo) is exempt from GPLv2 licensing and may (at its option) instead license any source code authored for the Atmosphère project under the Zero-Clause BSD license.
+
+Credits
+=====
+
+Atmosphère is currently being developed and maintained by __SciresM__, __TuxSH__, __hexkyz__, and __fincs__.
+In no particular order, we credit the following for their invaluable contributions:
+
+* __switchbrew__ for the [libnx](https://github.com/switchbrew/libnx) project and the extensive [documentation, research and tool development](http://switchbrew.org) pertaining to the Nintendo Switch.
+* __devkitPro__ for the [devkitA64](https://devkitpro.org/) toolchain and libnx support.
+* __ReSwitched Team__ for additional [documentation, research and tool development](https://reswitched.github.io/) pertaining to the Nintendo Switch.
+* __ChaN__ for the [FatFs](http://elm-chan.org/fsw/ff/00index_e.html) module.
+* __Marcus Geelnard__ for the [bcl-1.2.0](https://sourceforge.net/projects/bcl/files/bcl/bcl-1.2.0) library.
+* __naehrwert__ and __st4rk__ for the original [hekate](https://github.com/nwert/hekate) project and its hwinit code base.
+* __CTCaer__ for the continued [hekate](https://github.com/CTCaer/hekate) project's fork and the [minerva_tc](https://github.com/CTCaer/minerva_tc) project.
+* __m4xw__ for development of the [emuMMC](https://github.com/m4xw/emummc) project.
+* __Riley__ for suggesting "Atmosphere" as a Horizon OS reimplementation+customization project name.
+* __hedgeberg__ for research and hardware testing.
+* __lioncash__ for code cleanup and general improvements.
+* __jaames__ for designing and providing Atmosphère's graphical resources.
+* Everyone who submitted entries for Atmosphère's [splash design contest](https://github.com/Atmosphere-NX/Atmosphere-splashes).
+* _All those who actively contribute to the Atmosphère repository._
diff --git a/Source/Atmosphere-MTC-Unlock/atmosphere.mk b/Source/Atmosphere-MTC-Unlock/atmosphere.mk
new file mode 100644
index 00000000..7d80c94b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/atmosphere.mk
@@ -0,0 +1,173 @@
+#---------------------------------------------------------------------------------
+# pull in common atmosphere configuration
+#---------------------------------------------------------------------------------
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+include $(CURRENT_DIRECTORY)/libraries/config/common.mk
+
+# Get Atmosphere version fields
+ATMOSPHERE_MAJOR_VERSION := $(shell grep 'define ATMOSPHERE_RELEASE_VERSION_MAJOR\b' $(ATMOSPHERE_LIBRARIES_DIR)/libvapours/include/vapours/ams/ams_api_version.h | tr -s [:blank:] | cut -d' ' -f3)
+ATMOSPHERE_MINOR_VERSION := $(shell grep 'define ATMOSPHERE_RELEASE_VERSION_MINOR\b' $(ATMOSPHERE_LIBRARIES_DIR)/libvapours/include/vapours/ams/ams_api_version.h | tr -s [:blank:] | cut -d' ' -f3)
+ATMOSPHERE_MICRO_VERSION := $(shell grep 'define ATMOSPHERE_RELEASE_VERSION_MICRO\b' $(ATMOSPHERE_LIBRARIES_DIR)/libvapours/include/vapours/ams/ams_api_version.h | tr -s [:blank:] | cut -d' ' -f3)
+ATMOSPHERE_SUPPORTED_HOS_MAJOR_VERSION := $(shell grep 'define ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR\b' $(ATMOSPHERE_LIBRARIES_DIR)/libvapours/include/vapours/ams/ams_api_version.h | tr -s [:blank:] | cut -d' ' -f3)
+ATMOSPHERE_SUPPORTED_HOS_MINOR_VERSION := $(shell grep 'define ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR\b' $(ATMOSPHERE_LIBRARIES_DIR)/libvapours/include/vapours/ams/ams_api_version.h | tr -s [:blank:] | cut -d' ' -f3)
+ATMOSPHERE_SUPPORTED_HOS_MICRO_VERSION := $(shell grep 'define ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO\b' $(ATMOSPHERE_LIBRARIES_DIR)/libvapours/include/vapours/ams/ams_api_version.h | tr -s [:blank:] | cut -d' ' -f3)
+
+ATMOSPHERE_VERSION := $(ATMOSPHERE_MAJOR_VERSION).$(ATMOSPHERE_MINOR_VERSION).$(ATMOSPHERE_MICRO_VERSION)-$(ATMOSPHERE_GIT_REVISION)
+
+dist: dist-no-debug
+ $(eval DIST_DIR = $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/atmosphere-$(ATMOSPHERE_VERSION)-debug)
+ rm -rf $(DIST_DIR)
+ mkdir $(DIST_DIR)
+ cp $(CURRENT_DIRECTORY)/fusee/loader_stub/$(ATMOSPHERE_BOOT_OUT_DIR)/loader_stub.elf $(DIST_DIR)/fusee-loader-stub.elf
+ cp $(CURRENT_DIRECTORY)/fusee/program/$(ATMOSPHERE_BOOT_OUT_DIR)/program.elf $(DIST_DIR)/fusee-program.elf
+ cp $(CURRENT_DIRECTORY)/exosphere/loader_stub/$(ATMOSPHERE_OUT_DIR)/loader_stub.elf $(DIST_DIR)/exosphere-loader-stub.elf
+ cp $(CURRENT_DIRECTORY)/exosphere/program/$(ATMOSPHERE_OUT_DIR)/program.elf $(DIST_DIR)/exosphere-program.elf
+ cp $(CURRENT_DIRECTORY)/exosphere/warmboot/$(ATMOSPHERE_BOOT_OUT_DIR)/warmboot.elf $(DIST_DIR)/exosphere-warmboot.elf
+ cp $(CURRENT_DIRECTORY)/exosphere/mariko_fatal/$(ATMOSPHERE_OUT_DIR)/mariko_fatal.elf $(DIST_DIR)/exosphere-mariko-fatal.elf
+ cp $(CURRENT_DIRECTORY)/exosphere/program/sc7fw/$(ATMOSPHERE_BOOT_OUT_DIR)/sc7fw.elf $(DIST_DIR)/exosphere-sc7fw.elf
+ cp $(CURRENT_DIRECTORY)/exosphere/program/rebootstub/$(ATMOSPHERE_BOOT_OUT_DIR)/rebootstub.elf $(DIST_DIR)/exosphere-rebootstub.elf
+ cp $(CURRENT_DIRECTORY)/mesosphere/kernel_ldr/$(ATMOSPHERE_OUT_DIR)/kernel_ldr.elf $(DIST_DIR)/kernel_ldr.elf
+ cp $(CURRENT_DIRECTORY)/mesosphere/kernel/$(ATMOSPHERE_OUT_DIR)/kernel.elf $(DIST_DIR)/kernel.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/ams_mitm/$(ATMOSPHERE_OUT_DIR)/ams_mitm.elf $(DIST_DIR)/ams_mitm.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/boot/$(ATMOSPHERE_OUT_DIR)/boot.elf $(DIST_DIR)/boot.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/boot2/$(ATMOSPHERE_OUT_DIR)/boot2.elf $(DIST_DIR)/boot2.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/creport/$(ATMOSPHERE_OUT_DIR)/creport.elf $(DIST_DIR)/creport.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/cs/$(ATMOSPHERE_OUT_DIR)/cs.elf $(DIST_DIR)/cs.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/dmnt/$(ATMOSPHERE_OUT_DIR)/dmnt.elf $(DIST_DIR)/dmnt.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/dmnt.gen2/$(ATMOSPHERE_OUT_DIR)/dmnt.gen2.elf $(DIST_DIR)/dmnt.gen2.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/eclct.stub/$(ATMOSPHERE_OUT_DIR)/eclct.stub.elf $(DIST_DIR)/eclct.stub.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/erpt/$(ATMOSPHERE_OUT_DIR)/erpt.elf $(DIST_DIR)/erpt.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/fatal/$(ATMOSPHERE_OUT_DIR)/fatal.elf $(DIST_DIR)/fatal.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/htc/$(ATMOSPHERE_OUT_DIR)/htc.elf $(DIST_DIR)/htc.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/jpegdec/$(ATMOSPHERE_OUT_DIR)/jpegdec.elf $(DIST_DIR)/jpegdec.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/loader/$(ATMOSPHERE_OUT_DIR)/loader.elf $(DIST_DIR)/loader.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/LogManager/$(ATMOSPHERE_OUT_DIR)/LogManager.elf $(DIST_DIR)/LogManager.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/ncm/$(ATMOSPHERE_OUT_DIR)/ncm.elf $(DIST_DIR)/ncm.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/pgl/$(ATMOSPHERE_OUT_DIR)/pgl.elf $(DIST_DIR)/pgl.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/pm/$(ATMOSPHERE_OUT_DIR)/pm.elf $(DIST_DIR)/pm.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/ro/$(ATMOSPHERE_OUT_DIR)/ro.elf $(DIST_DIR)/ro.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/sm/$(ATMOSPHERE_OUT_DIR)/sm.elf $(DIST_DIR)/sm.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/spl/$(ATMOSPHERE_OUT_DIR)/spl.elf $(DIST_DIR)/spl.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/TioServer/$(ATMOSPHERE_OUT_DIR)/TioServer.elf $(DIST_DIR)/TioServer.elf
+ cp $(CURRENT_DIRECTORY)/stratosphere/memlet/$(ATMOSPHERE_OUT_DIR)/memlet.elf $(DIST_DIR)/memlet.elf
+ cp $(CURRENT_DIRECTORY)/troposphere/daybreak/daybreak.elf $(DIST_DIR)/daybreak.elf
+ cp $(CURRENT_DIRECTORY)/troposphere/haze/haze.elf $(DIST_DIR)/haze.elf
+ cp $(CURRENT_DIRECTORY)/troposphere/reboot_to_payload/reboot_to_payload.elf $(DIST_DIR)/reboot_to_payload.elf
+ cd $(DIST_DIR); zip -r ../atmosphere-$(ATMOSPHERE_VERSION)-debug.zip ./*; cd ../;
+ rm -rf $(DIST_DIR)
+
+dist-no-debug: package3 $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)
+ $(eval DIST_DIR = $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/atmosphere-$(ATMOSPHERE_VERSION))
+ rm -rf $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/*
+ rm -rf $(DIST_DIR)
+ mkdir $(DIST_DIR)
+ mkdir $(DIST_DIR)/atmosphere
+ mkdir $(DIST_DIR)/switch
+ mkdir -p $(DIST_DIR)/atmosphere/fatal_errors
+ mkdir -p $(DIST_DIR)/atmosphere/config_templates
+ mkdir -p $(DIST_DIR)/atmosphere/config
+ mkdir -p $(DIST_DIR)/atmosphere/flags
+ cp fusee/$(ATMOSPHERE_BOOT_OUT_DIR)/fusee.bin $(DIST_DIR)/atmosphere/reboot_payload.bin
+ cp fusee/$(ATMOSPHERE_BOOT_OUT_DIR)/package3 $(DIST_DIR)/atmosphere/package3
+ cp config_templates/stratosphere.ini $(DIST_DIR)/atmosphere/config_templates/stratosphere.ini
+ cp config_templates/override_config.ini $(DIST_DIR)/atmosphere/config_templates/override_config.ini
+ cp config_templates/system_settings.ini $(DIST_DIR)/atmosphere/config_templates/system_settings.ini
+ cp config_templates/exosphere.ini $(DIST_DIR)/atmosphere/config_templates/exosphere.ini
+ mkdir -p config_templates/kip_patches
+ cp -r config_templates/kip_patches $(DIST_DIR)/atmosphere/kip_patches
+ cp -r config_templates/hbl_html $(DIST_DIR)/atmosphere/hbl_html
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000008
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000000d
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000017
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000002b
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000032
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000034
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000036
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000037
+ #mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000003c
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000042
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000420
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000421
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000b240
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000d609
+ mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000d623
+ cp stratosphere/boot2/$(ATMOSPHERE_OUT_DIR)/boot2.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000008/exefs.nsp
+ cp stratosphere/dmnt/$(ATMOSPHERE_OUT_DIR)/dmnt.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000000d/exefs.nsp
+ cp stratosphere/cs/$(ATMOSPHERE_OUT_DIR)/cs.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000017/exefs.nsp
+ cp stratosphere/erpt/$(ATMOSPHERE_OUT_DIR)/erpt.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000002b/exefs.nsp
+ cp stratosphere/eclct.stub/$(ATMOSPHERE_OUT_DIR)/eclct.stub.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000032/exefs.nsp
+ cp stratosphere/fatal/$(ATMOSPHERE_OUT_DIR)/fatal.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000034/exefs.nsp
+ cp stratosphere/creport/$(ATMOSPHERE_OUT_DIR)/creport.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000036/exefs.nsp
+ cp stratosphere/ro/$(ATMOSPHERE_OUT_DIR)/ro.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000037/exefs.nsp
+ #cp stratosphere/jpegdec/$(ATMOSPHERE_OUT_DIR)/jpegdec.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000003c/exefs.nsp
+ cp stratosphere/pgl/$(ATMOSPHERE_OUT_DIR)/pgl.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000042/exefs.nsp
+ cp stratosphere/LogManager/$(ATMOSPHERE_OUT_DIR)/LogManager.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000420/exefs.nsp
+ cp stratosphere/htc/$(ATMOSPHERE_OUT_DIR)/htc.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000b240/exefs.nsp
+ cp stratosphere/dmnt.gen2/$(ATMOSPHERE_OUT_DIR)/dmnt.gen2.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000d609/exefs.nsp
+ cp stratosphere/TioServer/$(ATMOSPHERE_OUT_DIR)/TioServer.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000d623/exefs.nsp
+ cp stratosphere/memlet/$(ATMOSPHERE_OUT_DIR)/memlet.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000421/exefs.nsp
+ @PATH="$(DEVKITPRO)/tools/bin:$$PATH" build_romfs $(DIST_DIR)/stratosphere_romfs $(DIST_DIR)/atmosphere/stratosphere.romfs
+ rm -r $(DIST_DIR)/stratosphere_romfs
+ cp troposphere/reboot_to_payload/reboot_to_payload.nro $(DIST_DIR)/switch/reboot_to_payload.nro
+ cp troposphere/daybreak/daybreak.nro $(DIST_DIR)/switch/daybreak.nro
+ cp troposphere/haze/haze.nro $(DIST_DIR)/switch/haze.nro
+ cd $(DIST_DIR); zip -r ../atmosphere-$(ATMOSPHERE_VERSION).zip ./*; cd ../;
+ rm -rf $(DIST_DIR)
+ cp fusee/$(ATMOSPHERE_BOOT_OUT_DIR)/fusee.bin $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/fusee.bin
+
+package3: emummc fusee stratosphere mesosphere exosphere troposphere
+ $(SILENTCMD)$(PYTHON) fusee/build_package3.py $(CURRENT_DIRECTORY) $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BOOT_OUT_DIR) $(ATMOSPHERE_GIT_HASH) $(ATMOSPHERE_MAJOR_VERSION) $(ATMOSPHERE_MINOR_VERSION) $(ATMOSPHERE_MICRO_VERSION) 0 $(ATMOSPHERE_SUPPORTED_HOS_MAJOR_VERSION) $(ATMOSPHERE_SUPPORTED_HOS_MINOR_VERSION) $(ATMOSPHERE_SUPPORTED_HOS_MICRO_VERSION) 0
+ @echo "Built package3!"
+
+emummc:
+ $(MAKE) -C $(CURRENT_DIRECTORY)/emummc all
+
+fusee: libexosphere_boot
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/fusee -f $(CURRENT_DIRECTORY)/fusee/fusee.mk ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+
+exosphere: libexosphere libexosphere_boot
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/exosphere -f $(CURRENT_DIRECTORY)/exosphere/exosphere.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE=1
+
+stratosphere: fusee libstratosphere
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/stratosphere -f $(CURRENT_DIRECTORY)/stratosphere/stratosphere.mk ATMOSPHERE_CHECKED_LIBSTRATOSPHERE=1 ATMOSPHERE_CHECKED_FUSEE=1
+
+mesosphere: libmesosphere
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/mesosphere -f $(CURRENT_DIRECTORY)/mesosphere/mesosphere.mk ATMOSPHERE_CHECKED_LIBMESOSPHERE=1
+
+troposphere:
+ $(MAKE) -C $(CURRENT_DIRECTORY)/troposphere all
+
+libexosphere:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk
+
+ifneq ($(strip $(ATMOSPHERE_LIBRARY_DIR)),$(strip $(ATMOSPHERE_BOOT_LIBRARY_DIR)))
+libexosphere_boot:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+else
+libexosphere_boot: libexosphere
+endif
+
+libmesosphere:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/libmesosphere.mk
+
+libstratosphere:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk
+
+clean:
+ $(MAKE) -C $(CURRENT_DIRECTORY)/fusee -f $(CURRENT_DIRECTORY)/fusee/fusee.mk clean ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+ $(MAKE) -C $(CURRENT_DIRECTORY)/emummc clean
+ $(MAKE) -C $(CURRENT_DIRECTORY)/exosphere -f $(CURRENT_DIRECTORY)/exosphere/exosphere.mk clean
+ $(MAKE) -C $(CURRENT_DIRECTORY)/mesosphere -f $(CURRENT_DIRECTORY)/mesosphere/mesosphere.mk clean
+ $(MAKE) -C $(CURRENT_DIRECTORY)/stratosphere -f $(CURRENT_DIRECTORY)/stratosphere/stratosphere.mk clean
+ $(MAKE) -C $(CURRENT_DIRECTORY)/troposphere clean
+ $(MAKE) -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk clean
+ $(MAKE) -C $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/libmesosphere.mk clean
+ $(MAKE) -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk clean
+ $(MAKE) -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk clean ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+ rm -rf $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)
+
+$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR) $(CURRENT_DIRECTORY)/$(ATMOSPHERE_BUILD_DIR):
+ @[ -d $@ ] || mkdir -p $@
+
+.PHONY: dist dist-no-debug clean package3 emummc fusee stratosphere mesosphere exosphere troposphere
diff --git a/Source/Atmosphere-MTC-Unlock/build.sh b/Source/Atmosphere-MTC-Unlock/build.sh
new file mode 100644
index 00000000..ce171e32
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/build.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+make -j"$(nproc)"
diff --git a/Source/Atmosphere-MTC-Unlock/config_templates/exosphere.ini b/Source/Atmosphere-MTC-Unlock/config_templates/exosphere.ini
new file mode 100644
index 00000000..8df772cc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/config_templates/exosphere.ini
@@ -0,0 +1,59 @@
+# Key: debugmode, default: 1.
+# Desc: Controls whether kernel is debug mode.
+# Disabling this will break Atmosphere.
+
+# Key: debugmode_user, default: 0.
+# Desc: Controls whether userland is debug mode.
+
+# Key: disable_user_exception_handlers, default: 0.
+# Desc: Controls whether user exception handlers are executed on error.
+# NOTE: This will cause atmosphere to not fail gracefully.
+# Support may not be provided to users tho disable these.
+# If you do not know what you are doing, leave them on.
+
+# Key: enable_user_pmu_access, default: 0.
+# Desc: Controls whether userland has access to the PMU registers.
+# NOTE: It is unknown what effects this has on official code.
+
+# Key: blank_prodinfo_sysmmc, default: 0.
+# Desc: Controls whether PRODINFO should be blanked in sysmmc.
+# This will cause the system to see dummied out keys and
+# serial number information.
+# NOTE: This is not known to be safe, as data may be
+# cached elsewhere in the system. Usage is not encouraged.
+
+# Key: blank_prodinfo_emummc, default: 0.
+# Desc: Controls whether PRODINFO should be blanked in emummc.
+# NOTE: This is not known to be safe, as data may be
+# cached elsewhere in the system. Usage is not encouraged.
+
+# Key: allow_writing_to_cal_sysmmc, default: 0.
+# Desc: Controls whether PRODINFO can be written by homebrew in sysmmc.
+# NOTE: Usage of this setting is strongly discouraged without
+# a safe backup elsewhere. Turning this on will also cause Atmosphere
+# to ensure a safe backup of calibration data is stored in unused
+# mmc space, encrypted to prevent detection. This backup can be used
+# to prevent unrecoverable edits in emergencies.
+
+# Key: log_port, default: 0.
+# Desc: Controls what uart port exosphere will set up for logging.
+# NOTE: 0 = UART-A, 1 = UART-B, 2 = UART-C, 3 = UART-D
+
+# Key: log_baud_rate, default: 115200
+# Desc: Controls the baud rate exosphere will set up for logging.
+# NOTE: 0 is treated as equivalent to 115200.
+
+# Key: log_inverted, default: 0.
+# Desc: Controls whether the logging uart port is inverted.
+
+[exosphere]
+debugmode=1
+debugmode_user=0
+disable_user_exception_handlers=0
+enable_user_pmu_access=0
+blank_prodinfo_sysmmc=0
+blank_prodinfo_emummc=0
+allow_writing_to_cal_sysmmc=0
+log_port=0
+log_baud_rate=115200
+log_inverted=0
diff --git a/Source/Atmosphere-MTC-Unlock/config_templates/hbl_html/accessible-urls/accessible-urls.txt b/Source/Atmosphere-MTC-Unlock/config_templates/hbl_html/accessible-urls/accessible-urls.txt
new file mode 100644
index 00000000..c13735f7
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/config_templates/hbl_html/accessible-urls/accessible-urls.txt
@@ -0,0 +1 @@
+^http*
diff --git a/Source/Atmosphere-MTC-Unlock/config_templates/override_config.ini b/Source/Atmosphere-MTC-Unlock/config_templates/override_config.ini
new file mode 100644
index 00000000..fa86f1ef
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/config_templates/override_config.ini
@@ -0,0 +1,21 @@
+[hbl_config]
+; Program Specific Config
+; Up to 8 program-specific configurations can be set.
+; These use `program_id_#`, `override_address_space_#`, and `override_key_#`
+; where # is in range [0,7].
+; program_id_0=010000000000100D
+; override_address_space=39_bit
+; override_key_0=!R
+
+; Any Application Config
+; Note that this will only apply to program IDs that
+; are both applications and not specified above
+; by a program specific config.
+; override_any_app=true
+; override_any_app_key=R
+; override_any_app_address_space=39_bit
+; path=atmosphere/hbl.nsp
+
+[default_config]
+; override_key=!L
+; cheat_enable_key=!L
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/config_templates/stratosphere.ini b/Source/Atmosphere-MTC-Unlock/config_templates/stratosphere.ini
new file mode 100644
index 00000000..9042a3e4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/config_templates/stratosphere.ini
@@ -0,0 +1,4 @@
+[stratosphere]
+; To force-enable nogc, add nogc = 1
+; To force-disable nogc, add nogc = 0
+
diff --git a/Source/Atmosphere-MTC-Unlock/config_templates/system_settings.ini b/Source/Atmosphere-MTC-Unlock/config_templates/system_settings.ini
new file mode 100644
index 00000000..f9c7f61b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/config_templates/system_settings.ini
@@ -0,0 +1,82 @@
+[eupld]
+; Disable uploading error reports to Nintendo
+; upload_enabled = u8!0x0
+[usb]
+; Enable USB 3.0 superspeed for homebrew
+; 0 = USB 3.0 support is system default (usually disabled), 1 = USB 3.0 support is enabled.
+; usb30_force_enabled = u8!0x0
+[ro]
+; Control whether RO should ease its validation of NROs.
+; (note: this is normally not necessary, and ips patches can be used.)
+; ease_nro_restriction = u8!0x1
+[lm]
+; Control whether lm should log to the SD card.
+; Note that this setting does nothing when log manager is not enabled.
+; enable_sd_card_logging = u8!0x1
+; Control the output directory for SD card logs.
+; Note that this setting does nothing when log manager is not enabled/sd card logging is not enabled.
+; sd_card_log_output_directory = str!atmosphere/binlogs
+; Atmosphere custom settings
+[erpt]
+; Control whether erpt reports should always be preserved, instead of automatically cleaning periodically.
+; disable_automatic_report_cleanup = u8!0x0
+[atmosphere]
+; Reboot from fatal automatically after some number of milliseconds.
+; If field is not present or 0, fatal will wait indefinitely for user input.
+; fatal_auto_reboot_interval = u64!0x0
+; Make the power menu's "reboot" button reboot to payload.
+; Set to "normal" for normal reboot, "rcm" for rcm reboot.
+; power_menu_reboot_function = str!payload
+; Controls whether dmnt cheats should be toggled on or off by
+; default. 1 = toggled on by default, 0 = toggled off by default.
+; dmnt_cheats_enabled_by_default = u8!0x1
+; Controls whether dmnt should always save cheat toggle state
+; for restoration on new game launch. 1 = always save toggles,
+; 0 = only save toggles if toggle file exists.
+; dmnt_always_save_cheat_toggles = u8!0x0
+; Enable writing to BIS partitions for HBL.
+; This is probably undesirable for normal usage.
+; enable_hbl_bis_write = u8!0x0
+; Enable reading the CAL0 partition for HBL.
+; This is probably undesirable for normal usage.
+; enable_hbl_cal_read = u8!0x0
+; Controls whether fs.mitm should redirect save files
+; to directories on the sd card.
+; 0 = Do not redirect, 1 = Redirect.
+; NOTE: EXPERIMENTAL
+; If you do not know what you are doing, do not touch this yet.
+; fsmitm_redirect_saves_to_sd = u8!0x0
+; Controls whether am sees system settings "DebugModeFlag" as
+; enabled or disabled.
+; 0 = Disabled (not debug mode), 1 = Enabled (debug mode)
+; enable_am_debug_mode = u8!0x0
+; Controls whether dns.mitm is enabled
+; 0 = Disabled, 1 = Enabled
+; enable_dns_mitm = u8!0x1
+; Controls whether dns.mitm uses the default redirections in addition to
+; whatever is specified in the user's hosts file.
+; 0 = Disabled (use hosts file contents), 1 = Enabled (use defaults and hosts file contents)
+; add_defaults_to_dns_hosts = u8!0x1
+; Controls whether dns.mitm logs to the sd card for debugging
+; 0 = Disabled, 1 = Enabled
+; enable_dns_mitm_debug_log = u8!0x0
+; Controls whether htc is enabled
+; 0 = Disabled, 1 = Enabled
+; enable_htc = u8!0x0
+; Controls whether atmosphere's log manager is enabled
+; Note that this setting is ignored (and treated as 1) when htc is enabled.
+; 0 = Disabled, 1 = Enabled
+; enable_log_manager = u8!0x0
+; Controls whether the bluetooth pairing database is redirected to the SD card (shared across sysmmc/all emummcs)
+; NOTE: On <13.0.0, the database size was 10 instead of 20; booting pre-13.0.0 will truncate the database.
+; 0 = Disabled, 1 = Enabled
+; enable_external_bluetooth_db = u8!0x0
+[hbloader]
+; Controls the size of the homebrew heap when running as applet.
+; If set to zero, all available applet memory is used as heap.
+; The default is zero.
+; applet_heap_size = u64!0x0
+; Controls the amount of memory to reserve when running as applet
+; for usage by other applets. This setting has no effect if
+; applet_heap_size is non-zero. The default is 0x8600000.
+; applet_heap_reservation_size = u64!0x8600000
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/docs/building.md b/Source/Atmosphere-MTC-Unlock/docs/building.md
new file mode 100644
index 00000000..4f000acf
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/building.md
@@ -0,0 +1,26 @@
+# Building Atmosphère
+Building Atmosphère is a very straightforward process that relies almost exclusively on tools provided by the [devkitPro](https://devkitpro.org) organization.
+
+## Dependencies
++ [devkitA64](https://devkitpro.org)
++ [devkitARM](https://devkitpro.org)
++ [Python 2](https://www.python.org) (Python 3 may work as well, but this is not guaranteed)
++ [LZ4](https://pypi.org/project/lz4)
++ [PyCryptodome](https://pypi.org/project/pycryptodome) (optional)
++ [hactool](https://github.com/SciresM/hactool)
+
+## Instructions
+1. Follow the guide located [here](https://devkitpro.org/wiki/Getting_Started) to install and configure all the tools necessary for the build process.
+
+2. Install the following packages via (dkp-)pacman:
+ + `switch-dev`
+ + `switch-glm`
+ + `switch-libjpeg-turbo`
+ + `devkitARM`
+ + `devkitarm-rules`
+ + `hactool`
+
+3. Install the following library via python's package manager `pip`, required by [exosphere](components/exosphere.md):
+ + `lz4`
+
+4. Finally, clone the Atmosphère repository and run `make` under its root directory.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/changelog.md b/Source/Atmosphere-MTC-Unlock/docs/changelog.md
new file mode 100644
index 00000000..51974cc4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/changelog.md
@@ -0,0 +1,1447 @@
+# Changelog
+## 1.9.5
++ Basic support was added for 20.5.0.
++ General system stability improvements to enhance the user's experience.
+## 1.9.4
++ Basic support was added for 20.4.0.
++ An issue was fixed in `exosphère`'s register accessilibity tables (thanks @CTCaer).
+ + I believe this had no impact on official code, though it would have prevented some homebrew from interacting correctly with the MC0/MC1 registers.
++ An issue was fixed that could cause a deadlock when building multiple romfs images simultaneously (thanks @Ereza).
+ + This fixes support for certain mods, e.g. system language translations overriding content for both overlayDisp and qlaunch.
++ General system stability improvements to enhance the user's experience.
+## 1.9.3
++ Basic support was added for 20.3.0.
++ Compatibility was fixed for loading mods with KOTOR 2 (star wars).
++ General system stability improvements to enhance the user's experience.
+## 1.9.2
++ Basic support was added for 20.2.0.
++ USB 3.0 support force-enable was fixed for 20.1.0+.
++ General system stability improvements to enhance the user's experience.
+## 1.9.1
++ Basic support was added for 20.1.0.
++ General system stability improvements to enhance the user's experience.
+## 1.9.0
++ Basic support was added for 20.0.0.
+ + The console should boot and atmosphère should be fully functional. However, not all modules have been fully updated to reflect the latest changes.
+ + There shouldn't be anything user visible resulting from this, but it will be addressed in a future atmosphère update.
+ + The same action item from 18.0.0 remains, and I believe in my heart of hearts that it will be addressed eventually. Someone has told me they're working on it.
+ + There aren't (to my knowledge) outstanding 19.0.0 items any more.
+ + **Please note**: As a result of changes made to nintendo's software in 20.0.0, there is roughly 10MB less memory available for custom system modules.
+ + We can only steal a maximum of 14MB from the applet pool, down from 40MB.
+ + To compensate for this, `ams.mitm`'s heap usage has been reduced by 20MB.
+ + To facilitate this, a new helper module (`memlet`) was added, so that memory may be temporarily stolen during the romfs building process.
+ + Hopefully, this results in relatively little breakage, however it is possible that user mods which replace extremely large numbers of files in The Legend of Zelda: Tears of the Kingdom may no longer function.
+ + If you are affected by this, you will see "Data abort (0x101)" when trying to launch the game with mods.
+ + Please reach out to `sciresm` on discord if this occurs to share your error report binary. However, some issues may be impossible to fix.
+ + I apologize sincerely if the issue is impossible to resolve, but I have been forced unavoidably to make compromises here, and I think this is the best balance to be struck.
+ + `exosphère` was updated to reflect the latest official secure monitor behavior.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `loader` was updated to reflect the latest official behavior.
+ + `pm` was updated to reflect the latest official behavior.
+ + `ncm` was partially updated to reflect the latest official behavior.
+ + `erpt` was updated to reflect the latest official behavior.
++ Atmosphère was updated to use GCC 15/newlib (latest devkitA64/devkitARM releases).
++ A number of improvements were made to the dmnt cheat engine.
+ + New instructions were added, and instructions were updated for improved/new functionality.
+ + Please see the documents for details -- thanks @tomvita!
++ General system stability improvements to enhance the user's experience.
+## 1.8.0
++ Basic support was added for 19.0.0.
+ + The console should boot and atmosphère should be fully functional. However, not all modules have been fully updated to reflect the latest changes.
+ + There shouldn't be anything user visible resulting from this, but it will be addressed in a future atmosphère update. There is still one action item from 18.0.0 to be addressed, as well.
+ + `exosphère` was updated to reflect the latest official secure monitor behavior.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `loader` was updated to reflect the latest official behavior.
+ + `pm` was updated to reflect the latest official behavior.
+ + `ro` was updated to reflect the latest official behavior.
++ `creport`'s file acces patterns were optimized, greatly improving performance when generating a crash report.
++ Atmosphère now uses `relr` relocations where possible.
+ + This reduces the filesize of a number of atmosphère's modules.
++ A number of minor issues were fixed and improvements were made, including:
+ + Support was fixed for running Atmosphère on newer units with specific Hynix/Micron DRAM chips.
++ General system stability improvements to enhance the user's experience.
+## 1.7.1
++ Support was added for 18.1.0.
++ Atmosphère was updated to use GCC 14/newlib (latest devkitA64/devkitARM releases).
++ Further changes were for 18.0.0:
+ + `loader` was updated to reflect the latest official behavior.
++ General system stability improvements to enhance the user's experience.
+## 1.7.0
++ Basic support was added for 18.0.0.
+ + The console should boot and atmosphère should be fully functional. However, not all modules have been fully updated to reflect the latest changes.
+ + There shouldn't be anything user visible resulting from this, but it will be addressed in a future atmosphère update, once I am not traveling so much.
+ + `exosphère` was updated to reflect the latest official secure monitor behavior.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `spl` was updated to reflect the latest official behavior.
++ `fusee`'s no longer supports applying IPS patches to KIPs.
+ + The only KIPs that are ever present are a) atmosphère modules, b) custom system modules, or c) FS.
+ + The IPS subsystem was originally designed to make nogc patches work for FS, but these are now internal, and it appears the literal only kip patches that exist are for piracy.
+ + I could not find any kip patches posted anywhere made for any other purpose.
+ + It fundamentally does not make sense to slow down boot for every normal user for a feature that has no actual use-case, especially when `fusee` seeks to be a minimal bootloader.
++ Minor improvements were made to atmosphere's gdbstub, including:
+ + Support was added for QStartNoAckMode.
+ + An issue was fixed that could cause a fatal error when creating too many breakpoints.
++ A number of minor issues were fixed and improvements were made, including:
+ + `pt-BR` (`PortugueseBr`) is now accepted as a valid language when overriding game locales.
+ + A bug was fixed that could cause atmosphere to incorrectly serialize output object IDs over IPC when using domain objects.
+ + A bug was fixed in `pm`'s resource limit boost logic that could potentially cause legitimate boosts to fail in certain circumstances.
+ + `loader`/`ro` will now throw a fatal error when using invalid IPS patches that go out of bounds, instead of corrupting memory.
+ + Support was fixed for booting using a memory configuration of half of the true available memory (e.g. forcing a 4GB configuration on an 8GB board).
++ General system stability improvements to enhance the user's experience.
+## 1.6.2
++ Support was finished for 17.0.0.
+ + `erpt` was updated to support the latest official behavior.
+ + `jpegdec` was updated to support the latest official behavior.
+ + `pm` was updated to support the latest official behavior.
++ General system stability improvements to enhance the user's experience.
+## 1.6.1
++ An improved solution to [the problem that would cause consoles which had previously re-built their SYSTEM partition to brick on update-to-17.0.0](https://gist.github.com/SciresM/2ddb708c812ed585c4d99f54e25205ff) was added.
+ + In particular, booting atmosphère will now automatically detect the problem and unbrick any consoles which have fallen into this state.
++ Some improvements were made to `haze`, including:
+ + Performance was greatly improved:
+ + Support was added for GetObjectPropList, which decreases the amount of requests made by ~8x.
+ + Haze now performs rendering on the GPU, freeing up the CPU to respond to requests in a more timely manner.
+ + An issue was fixed with how `haze` configures `bMaxPacketSize0` which improves support for USB3.
++ General system stability improvements to enhance the user's experience.
+## 1.6.0
++ Basic support was added for 17.0.0.
+ + The console should boot and atmosphère should be fully functional. However, not all modules have been fully updated to reflect the latest changes.
+ + There shouldn't be anything user visible resulting from this, but it will be addressed in a soon-to-come atmosphère update.
+ + `exosphère` was updated to reflect the latest official secure monitor behavior.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `ncm` was updated to reflect the latest official behavior.
+ + `erpt` was partially updated to support the latest official behavior.
++ Atmosphere's gdbstub now supports waiting to attach to a specific program id on launch (as opposed to any application).
+ + The monitor command for this is `monitor wait `, where program id can optionally have an `0x` prefix.
++ Support was added to `haze` for editing files in-place and performing 64-bit transfers (files larger than 4 GB).
++ `bpc.mitm` was enabled on Mariko units, and now triggers pmic-based shutdowns/reboots (thanks @CTCaer).
+ + This should cause the console to no longer wake ~15 seconds after shutdown on Mariko.
++ A number of minor issues were fixed and improvements were made, including:
+ + A workaround was added for a change in 17.0.0 that would cause consoles which had previously re-built their SYSTEM partition to brick on update-to-17.0.0.
++ General system stability improvements to enhance the user's experience.
+## 1.5.5
++ Support was added for 16.1.0.
++ General system stability improvements to enhance the user's experience.
+## 1.5.4
++ Experimental new functionality was implemented to prevent crashing when building romfs for certain games with obscene file counts.
+ + This includes both Fire Emblem: Engage (~190000 files), and The Legend of Zelda: Tears of the Kingdom (~300000) files.
+ + The solution involved adding functionality to ams.mitm/pm to dynamically steal memory from the application (and system) pool as needed when the games have romfs mods.
+ + No memory is taken, and there is no cost to this functionality when playing without mods (or with overrides disabled).
+ + The Legend of Zelda: Tears of the Kingdom is currently the absolute worst case game, requiring ~48 MB of memory to build a romfs image to play with mods.
+ + Right now, the memory is sourced as follows: 32 MB (base ams.mitm heap), 10 MB (stolen from application pool), 8 MB (dynamically stolen from system pool).
+ + This is 50 MB, which allows a little overhead in the worst case (prevents crashing due to exhausting the heap for other allocations in ams.mitm).
+ + Zelda is remarkably sensitive to memory being stolen from the application pool, tolerating no more than 16 MB on 1.0.0 and 12 MB on 1.1.0. I have chosen to steal 10 MB, to be safe, for now.
+ + This may break on a future game update, but I will fix it if and when that happens. There is no perfect solution; the game simply requires too much memory to support mods flawlessly, and I am forced to compromise.
+ + As usual, if you encounter a game that exhausts ams.mitm's memory (crashing it) when loading layeredfs mods, please contact `SciresM#0524`.
+ "I am jinxing myself by saying this, but it's really hard to imagine any game being worse than The Legend of Zelda: Tears of the Kingdom, but if it happens again I will drop everything to fix it as usual".
++ General system stability improvements to enhance the user's experience.
+## 1.5.3
++ Support was added for 16.0.3.
++ Atmosphère was updated to use GCC 13/newlib (latest devkitA64/devkitARM releases).
+ + **Please note**: This introduces a known issue, which is currently being worked on.
+ + As you may recall from the 1.4.1 changelog, Fire Emblem: Engage requires enormous amounts of memory to support using layeredfs mods with the game.
+ + Latest GCC/newlib slightly increases malloc overhead size, which makes the previous memory increase insufficient.
+ + A general-case solution to this is in the works, which should hopefully fix the problem in a way that doesn't jinx me for the future.
++ A number of minor issues were fixed and improvements were made, including:
+ + An issue was fixed that caused system font replacement to not work on 16.0.0+.
+ + An minor accuracy issue was addressed in mesosphere's management of certain memory ranges; this issue would have had zero visible impact to the end-user.
++ General system stability improvements to enhance the user's experience.
+## 1.5.2
++ A homebrew application (`haze`) was added for performing USB file transfer (with thanks to @liamwhite for both design and implementation).
+ + `haze` is included with atmosphère, and provides access to the SD card via the PTP/MTP protocol.
+ + **Please note**: haze will show inside the homebrew menu under the name "USB File Transfer".
+ + **Please note**: Atmosphère cannot be updated at runtime, and trying to install an atmosphère update via haze will fail as usual.
++ General system stability improvements to enhance the user's experience.
+## 1.5.1
++ `fatal` was updated to reduce memory footprint.
+ + Starting in 16.0.0, official `fatal` has no framebuffer or rendering logic, and instead calls other system service commands to draw the screen.
+ + However, these commands aren't usable by atmosphère (too small rendering window, bad color support).
+ + To reduce the relative memory footprint differential between atmosphère and official code, the framebuffer (2 MB) is now dynamically allocated when needed.
+ + This will try to allocate from multiple pools (preferring System > System_NonSecure > Application).
+ + This technically requires that 2 MB be available in at least one of these pools for the fatal screen to render (otherwise, a reboot-to-black-and-white-fatal will occur), but this should be a non-issue in almost all cases.
++ A feature was added to optionally mirror the bluetooth pairing database to the SD card (thanks @ndeadly).
+ + This allows device pairings to be automatically kept in-sync across sysmmc/all emummcs.
+ + This is opt-in, and can be controlled by setting `atmosphere!enable_external_bluetooth_db = u8!0x1`.
+ + When enabled, the pairing database will be synchronized to `/atmosphere/bluetooth_devices.db`.
++ General system stability improvements to enhance the user's experience.
+## 1.5.0
++ Support was added for 16.0.0
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `ncm` was updated to reflect the latest official behavior.
+ + Many FS apis were updated under the hood to reflect the latest official behavior.
+ + **Please Note**: 16.0.0 made breaking changes to a number of system APIs, including in FS/NCM/Shared Font commands that some homebrew programs may use.
+ + These programs may encounter strange errors, and may need to be recompiled with a libnx updated to support 16.0.0's changes to function properly.
++ A number of minor issues were fixed and improvements were made, including:
+ + An issue was fixed that could cause GPIO outputs to be misconfigured under certain circumstances.
++ General system stability improvements to enhance the user's experience.
+## 1.4.1
++ A number of minor issues were fixed and improvements were made, including:
+ + `dmnt` cheat toggle files are no longer ignored when they are missing a trailing newline.
+ + The mechanism for automatically cleaning up `erpt_reports` added in 1.3.0 was fixed.
+ + This was actually just very fundamentally broken and has never worked, but it is verified working now.
+ + Minor fixes were made in `mesosphère` to match official kernel behavior (spin lock assembly was corrected, wrong result on failure in in GetProcessId was corrected).
+ + A missing call to GetSdStatus when initializing SD cards at non uhs-i mode was added in the sdmmc driver.
++ `ams.mitm`'s memory usage was increased by 16 MB, to prevent crashing when building romfs for games with obscene file counts.
+ + To quote the changelog for 1.2.3: "Animal Crossing's 2.0.0 update contains >99000 files [...] It's really hard to imagine any game being worse than Animal Crossing".
+ + As it turns out, Fire Emblem: Engage has ~186000 files, and is approximately twice as bad as animal crossing.
+ + The additional memory here is taken from the applet pool; no issues are expected to arise from this, but please report anything you may run into.
+ + As usual, if you encounter a game that exhausts ams.mitm's memory (crashing it) when loading layeredfs mods, please contact `SciresM#0524`.
+ + I am jinxing myself by saying this, but it's really hard to imagine any game being worse than Fire Emblem: Engage, but if it happens again I will drop everything to fix it as usual.
++ General system stability improvements to enhance the user's experience.
+## 1.4.0
++ Support was added for 15.0.0.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `ncm` was updated to reflect the latest official behavior.
++ A number of minor issues were fixed and improvements were made, including:
+ + The capacity limit on registered add-on contents was fixed in NCM to reflect the increase that occurred in 12.0.0.
+ + An off-by-one was fixed in mesosphere when computing the new value for an address arbiter signaled with ModifyByWaitingCountIfEqual.
+ + dmnt.gen2's gdbstub now sanitizes thread names to prevent invalid characters from breaking gdb.
+ + dmnt.gen2's gdbstub now reports the architecture tag correctly when attached to 32-bit processes.
+ + Support for program-specific html manual content overrides was added for non-hbl takeover context.
+ + A bug was fixed in how emummc constructed the alternate Nintendo directory path.
+ + Previously, this was using `/*/Nintendo/Nintendo` instead of `/*/Nintendo`.
+ + Code was added to automatically move the old folders to the new ones when booting into emummc.
+ + A bug was fixed in boot that caused an incorrectly low input voltage limit to be set.
++ General system stability improvements to enhance the user's experience.
+## 1.3.2
++ Support was improved for 14.0.0+.
+ + `loader` was updated to reflect the latest official behaviors.
+ + `ro` was updated to reflect the latest official behaviors.
++ A number of minor issues were fixed and improvements were made, including:
+ + A memory leak was fixed in filesystem path management; this could cause a crash when launching games ~100 times, or when deleting/re-downloading games.
+ + A bug was fixed that could cause threads to not see a newly signaled semaphore.
+ + A number of minor inaccuracies were fixed in the updated FileSystem APIs.
++ General system stability improvements to enhance the user's experience.
+## 1.3.1
++ Support was added for 14.1.0.
++ A number of minor under the hood improvements to accuracy were made to better reflect latest official system module behavior, particularly around FS apis.
++ General system stability improvements to enhance the user's experience.
+## 1.3.0
++ Support was added for 14.0.0.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `erpt` was updated to reflect the latest official behaviors.
+ + `pm` was updated to reflect the latest official behaviors.
+ + `fatal` was updated to reflect the latest official behaviors.
++ A mechanism for automatically cleaning up `erpt_reports` was added.
+ + When booting, if the console has more than 1000 reports inside `/atmosphere/erpt_reports`, the folder will be cleaned to empty.
+ + This behavior can be disabled by setting `erpt!disable_automatic_report_cleanup` = u8!0x1 in system_settings.ini.
++ Atmosphère's build system was re-written, and now allows globally building for various builds/configs.
+ + All boards now automatically support release/debugging/auditing targets; it is now possible to build a full debugging/auditing build of atmosphère for the first time.
++ Support was added for compiling libstratosphère to run on PC.
+ + The currently implemented/tested targets are Windows (x64), Linux (x64, arm64), macOS (x64, arm64).
+ + If you are a developer interested in adding support for another target, please reach out to `SciresM#0524` on discord.
+ + This is intended to finally allow sanely testing Atmosphère's code, by allowing most of it to run on a PC (with access to a debugger) instead of on game console hardware.
+ + In addition, this will allow making PC tools which reuse code written for Atmosphère directly..
+ + **Please Note**: This has no relation to interacting with official software on PC whatsoever. This really allows for making tests and self-contained atmosphère-based command-line tools; the Atmosphère project continues to have zero interest in attempting to run official software of any kind.
+ + In the course of adding this support (and working on tooling using it), a number of fairly major revisions were made to stratosphere (particularly surrounding filesystem code).
+ + **Please Note**: A number of changes made for this (and ones necessary in the process of adding support for 14.0.0) are api-breaking.
+ + If you're a developer and any of this caused your code to break, please feel free to contact `SciresM#0524` for help updating your program.
++ General system stability improvements to enhance the user's experience.
+## 1.2.6
++ Support was added for 13.2.1.
++ A number of minor issues were fixed and improvements were made, including:
+ + A minor performance improvement was implemented in service table dispatch by sorting and binary-searching the service command table instead of using linear search.
+ + Static initialization logic in Atmosphere was made much more regular.
++ General system stability improvements to enhance the user's experience.
+## 1.2.5
++ Support was added for 13.2.0.
++ A number of minor issues were fixed and improvements were made, including:
+ + A bug was fixed that caused `mesosphère` to underreport the total memory size by 8MB for certain games which use newer system-resource-size memory management.
+ + This caused FIFA 19 to crash, and possibly other issues.
+ + Memory management changes were made to `sm` that save 0x5000 of memory.
+ + A microoptimization was made to the way `mesosphère` manages updating the debug register for hardware single-step support.
+ + Support was fixed for enabling `usb!usb30_force_enabled` on 13.0.0+.
+ + The work-in-progress unit testing framework was updated to use doctest instead of catch2.
++ General system stability improvements to enhance the user's experience.
+## 1.2.4
++ Changes were made to the way fs.mitm builds images when providing a layeredfs romfs.
+ + Cache management (to avoid unnecessary rebuild) was revised, to add a grace period of ~500ms-1s between process closing romfs image and ams.mitm needing to rebuild if romfs is re-opened.
+ + This makes our cache much more effective, previously we were re-building romfs several times.
+ + RomFS image ownership was overhauled, with a new reference-counting implementation added (used to implement the above grace period).
+ + Certain games (e.g. Puyo Puyo Tetris 2, probably others) were sensitive to this timing, and could use access patterns which would trigger creation of romfs image while previous romfs image was in the middle of destructor.
+ + This could cause a fatal error, because the destructor for the old image could run simultaneously with building the new image.
+ + This also provides a speedup versus the 1.2.3 code, with Animal Crossing now taking ~8 fewer seconds to get past the Nintendo Switch logo.
++ General system stability improvements to enhance the user's experience.
+## 1.2.3
++ Because ams.TMA is taking longer to develop than expected, experimental support for Atmosphère's gdbstub as a standalone is now available.
+ + To enable it, set `atmosphere!enable_standalone_gdbstub` = u8!0x1 in system_settings.ini.
+ + The standalone also requires `atmosphere!enable_htc` = u8!0x0, but this should be the case for everyone since ams.TMA isn't actually usable yet.
+ + Once enabled, open the devkitPro provided-gdb (`aarch64-none-elf-gdb` for 64-bit or `arm-none-eabi-gdb` for 32-bit).
+ + The standalone stub exposes itself on port 22225 -- so the command to connect is `target extended-remote :22225`.
+ + Type `info os processes` to get a list of process IDs that can be attached to.
+ + The stub should work on both system programs, games, and homebrew -- but please note that debugging certain processes (like sockets) can cause hang due to the stub using them itself.
+ + Software break-points, hardware break-points, hardware watch-points, and hardware single-step are all supported/implemented.
+ + The following monitor commands are currently supported:
+ + `monitor get info`: Get process info, address space layout, and information on modules.
+ + `monitor get mappings`: Get all memory mappings.
+ + `monitor get mapping `: Get the memory mapping for a specific address.
+ + `monitor wait application`: Causes the stub to wait for an application to be launched. The next application will be started suspended.
+ + User is expected to send `attach ` after launching, which will cause attach-on-first-instruction. Failure to attach may cause system instability, this probably needs work.
+ + **Please Note**: The GDBstub is new and may have bugs/need work. If you find issues, please report them to SciresM#0524 -- all help finding/fixing bugs is appreciated, here.
+ + Generally speaking, if you would like to report information about fixes needed/discuss development of the gdbstub, join ReSwitched's #dev-support channel.
++ Changes were made to the way fs.mitm builds images when providing a layeredfs romfs.
+ + Animal Crossing's 2.0.0 update contains >99000 files, and has tables so big that we ran out of memory even after the optimizations made in 0.10.5.
+ + Previously, we used fixed-sized 0x40000 work buffers for file/directory tables and simultaneously built hash/content tables in one loop over files/directories.
+ + We now iterate over the file/directory tables multiple times, first once to determine the hash table indices, then repeatedly to build hash tables, then once to build content tables.
+ + We also now allow smaller-than-0x40000 work buffers, trying half-as-big buffers until allocation succeeds (or work buffer would be <0x4000, which is a safeguard against truly horrible performance).
+ + There is a slight speed penalty to these changes, but it's on the order of seconds for the worst case (Animal Crossing) and trivial for most games with reasonable tables.
+ + If you encounter a game that exhausts ams.mitm's memory (crashing it) when loading layeredfs mods, please contact `SciresM#0524`.
+ + It's really hard to imagine any game being worse than Animal Crossing, but if it happens again I will drop everything to fix it as usual.
++ `creport` now attempts to parse symbol tables if present.
+ + If a game executable has a symbol for a given address, the function-relative-offset will now be printed after the module-relative-offset.
++ General system stability improvements to enhance the user's experience.
+## 1.2.2
++ A number of fixes were made to Atmosphère's implementation of the new "sprofile" service added in 13.0.0.
+ + Nintendo is finally transmitting data over the internet to certain consoles, which has allowed for validating our service implementation.
+ + Unfortunately, there were several problems, and if your console began trying to use the new services atmosphere would show a fatal error with code 0xCAF6 (sprofile::ResultInvalidState()).
+ + With actual test data in hand, a test program was written and it was verified that our implementation can successfully import/access profile data now.
+ + Hopefully there are no more issues, and I sincerely apologize for anyone who got an 0xCAF6 fatal due to this.
++ A number of minor improvements were made to `mesosphère`, including:
+ + KThread::GetContextForSchedulerLoop was implemented in assembly (using static assertions to verify offset-of-context-in-struct is correct).
+ + This saves an unnecessary function call in the middle of the scheduler hot loop, replacing it with an addition instruction, which should improve microperformance.
+ + Mesosphere's hardware maintenance instructions were audited via a script and now directly match Nintendo's kernels.
+ + Notably, this inserts a missing instruction synchronization barrier when validating that slab heaps may be constructed.
+ + This missing ISB could cause an abort on certain (see: particularly sensitive) hardware on boot if the relevant codepath was speculatively executed (it normally only executes on game launch...)
+ + The SVC handlers for performing light IPC (normally unused) from 32-bit process were fixed in Mesosphere.
+ + A bug was fixed that would cause the register x27 to be overwritten with the contents of x26 when returning from a user exception handler.
+ + A bug was fixed that would cause the kernel to use the userland stack pointer instead of the kernel stack pointer while generating an error report for a kernel abort.
++ General system stability improvements to enhance the user's experience.
+## 1.2.1
++ Support was implemented for 13.1.0.
+ + `mesosphère` was updated to reflect the kernel behavioral changes made in 13.1.0.
+ + KScheduler now issues a data memory barrier when unlocking the scheduler lock and when early-returning due to top-thread-is-current during scheduling.
+ + `erpt` was updated to reflect the latest official behaviors.
+ + The new service added in 13.0.0 ("sprofile") was revised, and the data formats it expects was changed.
+ + This still appears to be (possibly(?)) untestable due to data not being transmitted yet, but I have greater confidence things will go smoothly than I did when 1.1.0 released.
++ A number of improvements were made to `mesosphère`, including:
+ + A build target was created to build targeting the qemu `virt` board.
+ + This facilitates writing unit tests for the kernel (and other atmosphere components) and running them under PC.
+ + **Please Note**: Official system software will not work at all under this, and the Atmosphère project has zero interest in attempting to run official software of any kind. This is unit testing machinery, and explicitly not more than that.
+ + This should hopefully allow us to have greater confidence that all of atmosphere's components work the way they're theoretically supposed to in the future.
+ + **Please Note**: If you are a developer who is familiar with the Horizon operating system (or capable of becoming familiar), I would greatly appreciate help writing tests and improving the testing framework.
+ + Please contact `SciresM#0524` if you are capable and interested.
+ + Really, if you are actually a developer who would like to help me get this off the ground, I would deeply appreciate it.
+ + That said, if you are not a developer but want to be one, this probably isn't the best opportunity; I expect it to be highly technical.
+ + Consider the ReSwitched discord's #hack-n-all channel for your educational purposes.
+ + We are (at least for now) using [catch2](https://github.com/catchorg/Catch2) for unit tests.
+ + Almost all virtual calls in the kernel are now resolved statically.
+ + This eliminates substantial virtual call overhead, and should lead to improved kernel microperformance in pretty much every function.
+ + The remaining red black tree find operations which weren't using the optimized "find key" variant are now using the optimized version.
+ + Custom assembly was written to improve tick-to-timespan conversion.
+ + This works around gcc emitting suboptimal assembly at -Os (it emits good assembly at -O3, clang is fine at both -O3 and -Os).
+ + KThread and KSession structures were updated to optimize member layout, saving 0x10 bytes per KThread/KSession object.
+ + Rather than unnecessarily zero-ing all data in kernel objects only to overwrite members later, we now only initialize the members we need to in kernel object constructors.
+ + This is what Nintendo was doing already.
+ + A set of custom optimized atomic primitives were implemented and are used in place of std::atomic<>
+ + This works around a gcc bug which downgrades specified memory order to seq_cst, and introduces clrex in places where it is appropriate.
+ + This should strictly improve microperformance of many system calls.
+ + An compile-time toggleable extension was added to support 40-bit physical addresses in MapRange capabilities (using currently reserved bits).
+ + A number of minor bugs were fixed, including:
+ + Initial cache management now better reflects official behavior.
+ + This fixes an issue that caused certain hardware with cache sensitivity to produce cryptic kernel panics during boot.
+ + Incorrect logic when checking thread priority capabilities was fixed to reflect official behavior.
+ + The scheduler was updated to reflect latest official behavior, and a number of minor bugs involving clz/ctz were fixed.
+ + Accesses to the processes local region were fixed to properly use kernel linear region, not userland pointers.
+ + The cache SVCs exposed for 32-bit processes now better reflect official core mask request semantics.
+ + A bug was fixed that could cause a kernel panic if SvcArbitrateLock was called on a thread with exactly one reference in the middle of handling a user-mode exception.
++ General system stability improvements to enhance the user's experience.
+## 1.2.0
++ `boot` was updated to reflect the latest official behavior for display/battery management.
+ + This should fix any issues that might result from running older releases on the OLED model, if you're somehow in a position to do so.
++ The "target firmware" system was changed to allow the bootloader to specify an approximation, rather than the true target firmware.
+ + Previously we expected compliant bootloaders to inspect SYSTEM:/ to determine the specific target firmware.
+ + Now, we only require an approximate version, with major version == true major version and approximate version <= true version.
+ + This greatly simplifies bootloader requirements, and correspondingly all code for accessing SYSTEM has been removed from fusee.
+ + This should result in a substantial speedup when booting emummc with fusee, as SYSTEM accesses were the most expensive thing done previously.
+ + This should resolve any inconsistency in firmware detection when booting via fusee vs hekate.
+ + This should also improve our compatibility with micro firmware releases, making it more likely that atmosphere "just works" if nothing important has changed.
++ Dynamic resource limit determination logic was implemented in `pm` to match latest official behavior.
+ + This greatly simplifies/makes consistent the resource limits on older firmwares, as well.
++ An enormous amount of refactoring was performed under the hood, including:
+ + **Please Note**: If you are a developer who uses Atmosphere-libs, a number of changes here are breaking.
+ + Feel free to contact SciresM#0524 for help updating your program.
+ + The OS namespace had many primitives implemented/made more accurate.
+ + Since mesosphere is now always-on, os::LightEvent (which required newer SVCs) is now globally usable (and used by stratosphere where relevant).
+ + Assertions are now true no-ops when building for release.
+ + Stratosphere is now built with -Wextra/-Werror.
+ + Most "common" logic in system module main.cpp files was moved into libstratosphere.
+ + **Please Note**: main.cpp files for prior atmosphere-libs will no longer work, for a really large number of reasons.
+ + A number of longstanding code style issues were corrected.
+ + Mesosphere now uses util::BitFlagSet for SVC permissions.
+ + Mesosphere now puts its relocation table inside .bss, which allows that memory to be reclaimed after relocations are performed.
+ + These changes save ~16KB of memory in the kernel, all said and done.
+ + A number of locations in stratosphere where memory could be saved were spotted and taken advantage of, leading to ~150-200KB of saved memory.
+ + The `spl` and `loader` system module was refactored to better reflect official logic.
+ + `sf` ipc server code was updated to only emit mitm/defer logic when that logic is actually required somewhere in process.
+ + `tipc` ipc server code was updated to reflect changes to official logic made in 13.0.0.
+ + Many, many other minor changes, please talk to SciresM#0524 or read the relevant commits if you want to know more.
++ A number of minor issues were fixed, including:
+ + Mesosphere's handling of SVC permissions on thread pin/unpin was updated to reflect official kernel behavior.
+ + util::CountTrailingZeros() was fixed to calculate the correct value when used at compile-time.
++ General system stability improvements to enhance the user's experience.
+## 1.1.1
++ A bug was fixed which caused some memory to leak when launching a game with mods enabled, eventually causing a crash after enough game launches without rebooting.
++ General system stability improvements to enhance the user's experience.
+## 1.1.0
++ Support was implemented for 13.0.0.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `ncm` was updated to reflect the latest official behaviors.
+ + `erpt` was updated to reflect the latest official behaviors.
+ + Two new services ("sprofile") were added to `erpt`, and have been fully reimplemented.
+ + **Please Note**: These services provide a way for settings to be pushed to consoles over the internet without system update.
+ + Because there appear to be no settings pushed out yet, this implementation fundamentally cannot be fully tested right now, but hopefully there are no issues once settings begin being distributed.
++ The `LogManager` system module was reimplemented.
+ + This system module provides services that some games use for logging.
+ + Atmosphere's reimplementation supports logging to the SD card (if `lm!enable_sd_card_logging` is true) and to ams.TMA.
+ + To control the directory where logs are saved, modify the `lm!sd_card_log_output_directory` setting.
+ + Atmosphere's reimplementation is disabled by default (in order to save memory), but can be enabled by setting `lm!enable_log_manager` to true.
+ + This will allow reading over logs from games which use the services (or potentially logging from homebrew in the future), which can be useful to developers.
+ + Please note that when TMA is fully implemented in the future, enabling TMA will forcibly enable `LogManager`.
++ General system stability improvements to enhance the user's experience.
+## 1.0.0
++ `fusee` was completely re-written in C++ to use the same atmosphere-libs APIs as the rest of atmosphere's code.
+ + The rewrite was performed with a big emphasis on ensuring a good boot speed, and generally boot should be much faster than it was previously.
+ + Depending on SD card/environment, boot speed may now be slightly faster than, roughly the same as, or slightly slower than when booting with hekate.
+ + The obvious low-hanging fruit for performance improvements has been picked, so hopefully the improved performance is to everybody's liking.
+ + SD card compatibility was improved: fusee should now have SD card compatibility identical to the official OS driver.
+ + **Please Note**: various components were renamed (fusee-primary.bin -> fusee.bin, fusee-secondary.bin -> package3).
+ + If you use another bootloader (like hekate), you may need to update your configuration to use the new layout.
+ + **Please Note**: BCT.ini no longer exists, nogc configuration has been moved to `/atmosphere/stratosphere.ini`.
+ + If you rely on custom nogc configuration, please be sure to update accordingly.
+ + Custom splash screen BMP parsing is no longer supported (as it slows down boot for 99% of users).
+ + To compensate for this, a script to insert a custom splash screen into a `package3` binary has been added to the `utilities` folder of the atmosphere repository.
+ + The release build should be equivalent to running the following command from the root of the atmosphere repository: `python utilities/insert_splash_screen.py img/splash.png fusee/package3`
++ A number of pending changes were made, following the end of the relevant testing periods:
+ + `mesosphere` is no longer opt-out, and stratosphere code will begin depending on its being present/in use.
+ + `NCM` is no longer opt-out.
+ + The cleanup to ease the transition from < 0.19.0 to 0.19.0 has been removed.
++ General system stability improvements to enhance the user's experience.
+## 0.20.1
++ An issue was fixed that caused severely degraded performance after wake-from-sleep on Mariko hardware.
+ + This was due to Mariko MTC resulting in a frequency of 1599.999MHz instead of 1600MHz.
+ + Due to this off-by-one, Nintendo's EMC management code failed to initialize/take over, and after wake from sleep RAM would be in a strange state.
++ General system stability improvements to enhance the user's experience.
+## 0.20.0
++ DRAM training (MTC) was implemented for Mariko hardware, increasing RAM speed from 204MHz to 1600MHz.
+ + This significantly optimizes Mariko boot speed, cutting boot time roughly in half.
+ + Typical boot time reductions (measured as "select fusee" to "home menu visible"):
+ + Normal (Iowa): ~35 seconds -> ~18 seconds.
+ + Lite (Hoag): ~65 seconds -> ~30 seconds.
+ + NOTE: Work is being started on a re-written `fusee` component, with an eye specifically towards ensuring a good boot speed.
+ + With any luck, boot will be much much faster on all units (Mariko and Erista) in an upcoming release.
++ Sept was replaced, and deleted from the repository.
+ + Erista units now use a custom TSEC firmware to manage key derivation.
+ + For more details, contact SciresM#0524 on discord.
+ + This has a number of benefits, including:
+ + This greatly simplifies key derivation logic by making it consistent on all firmwares.
+ + Fusee no longer accesses/uses keyblobs at all, so units which have accidentally destroyed/lost keyblobs can boot without them.
+ + This greatly increases stability (sept was the biggest source of boot failures).
+ + This improves boot speed (sept rebooted multiple times, performed hardware init multiple times, and was generally very slow).
+ + Atmosphère build process is now much saner.
++ A number of improvements were made to the dmnt cheat engine.
+ + Cheats which take in a memory region operand may now use types "2" or "3" to perform accesses relative to the alias/aslr regions, respectively.
+ + Support was added for an "else" opcode in the cheat engine, to make writing certain conditional logic more natural.
+ + Support was added for a cheat orchestrator homebrew (like edizon) to detach from a cheat process/set the master cheat programmatically.
++ Daybreak now provides a warning when attempting to install a firmware newer than the highest version atmosphère knows it supports.
+ + To facilitate this, exosphere now exposes the supported HOS version via an extension ConfigItem.
++ A number of minor issues were fixed, including:
+ + Several mesosphere debug SVC implementations were updated to reflect the semantics of the latest kernel.
+ + Support was fixed for deriving BIS encryption keys on certain prototype hardware.
++ General system stability improvements to enhance the user's experience.
+## 0.19.5
++ Support was added for 12.1.0.
++ LayeredFS support was added for OpenDataStorageWithProgramIndex commands.
+ + Certain games using newer (7.0.0+ APIs) which include multiple programs under a single title previously could not be modified.
+ + These are now supported as normal, and LayeredFS should have 100% compatibility again.
++ A number of minor issues were fixed, including:
+ + The Reboot to Payload NRO was updated to allow the OS to save state prior to rebooting (thanks @AuroraWright)!
+ + An issue was fixed that could cause dns.mitm to fail when games requested resolution of an empty string.
+ + An issue was fixed that caused a memory leak in the erpt system module.
+ + This would eventually cause a system crash after ~540 reports were generated without rebooting.
++ A number of minor improvements were made to improve mesosphere's accuracy.
++ General system stability improvements to enhance the user's experience.
+## 0.19.4
++ Support was added for 12.0.3.
++ A number of minor issues were fixed, including:
+ + An issue was fixed that could cause heap memory corruption when allocation was highly contended.
+ + An issue was fixed that could cause sleep to fail under certain conditions.
+ + An issue was fixed that could cause a scheduler slow path to be taken more often than necessary.
++ General system stability improvements to enhance the user's experience.
+## 0.19.3
++ Support was added for 12.0.2.
++ A number of minor issues were fixed, including:
+ + An issue was fixed in dns.mitm that caused a crash when games attempted to resolve the IP address of nullptr.
+ + An issue was fixed in erpt that would cause an abort when booting without having ever booted stock previously.
+ + An issue was fixed in (file-based) emummc that caused an error on system format/downloading certain games.
++ General system stability improvements to enhance the user's experience.
+## 0.19.2
++ Atmosphère's components were further updated to reflect latest official behaviors as of 12.0.0.
+ + Notably, `erpt` was updated to implement the new forced shutdown detection feature.
+ + When a forced-shutdown occurs, an erpt_report will be generated and saved to the SD card on the next boot.
++ Atmosphere-libs was updated to use GCC 11 (latest devkitA64/devkitARM releases).
+ + Initial inspections show mild-to-moderate optimizer improvements in several important places (kernel is 0x3000 smaller).
+ + General system stability improvements to enhance the user's experience.
++ A number of minor issues were fixed, including:
+ + A bug was fixed that caused a black screen when attempting to boot firmware versions 2.0.0-4.1.0.
+ + A bug was fixed that caused sm to abort when at the session limit, rather than returning error codes.
+ + A bug was fixed that allowed for resource exhaustion on 12.0.0, under certain circumstances.
++ Several issues were fixed, and usability and stability were improved.
+## 0.19.1
++ An issue was fixed that caused a fatal error when using official `migration` services to transfer data between consoles.
++ An issue was fixed in `ncm` that caused an error when the OS tried to enumerate installed SD card content.
++ Several issues were fixed, and usability and stability were improved.
+## 0.19.0
++ Support was added for 12.0.0.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `sm`, `boot2`, `pgl` were updated to reflect the latest official behaviors.
+ + **Please Note**: 12.0.0 added a new protocol for IPC ("tipc"), which has been freshly reimplemented in its entirety.
+ + It is possible there may be as of yet unfound issues; if there are, please send the appropriate crash reports to SciresM (SciresM#0524 on discord).
+ + Homebrew which uses atmosphere extensions (including the mitm API) will need to be re-compiled in order to function on 0.19.0.
+ + I apologize for this, but it's unavoidable for technical reasons. If you're affected by this and mad about it, please contact SciresM to complain.
+ + `erpt` was partially updated to reflect the latest official behaviors.
+ + New features were added to erpt to track the activity of running applets, and to detect when a forced shutdown occurs.
+ + These behaviors have been temporarily stubbed, as they are not necessary for 12.0.0 to run (and their outputs won't be saved anywhere).
+ + A future atmosphère update will implement these behaviors, in the interest of reflecting official logic as faithfully as we can.
++ Atmosphère no longer uses the /contents/ folder for its own programs.
+ + Atmosphère's system modules are now bundled together in the single file "stratosphere.romfs".
+ + For those working on developing for atmosphère, executables inside the /contents/ directory will be preferred to those in "stratosphere.romfs".
+ + **Please Note**: In order to facilitate this change (and the desired behavior), the first time you boot after extracting a release zip, atmosphère system modules inside /contents/ will be deleted.
+ + This will have no impact on user programs (it only removes programs with specific program ids).
++ Improvements were made to mesosphere, including:
+ + An extension InfoType was added for getting the current process handle, without having to spawn a thread and do IPC with oneself.
+ + An issue was fixed in SvcSetDebugThreadContext.
+ + An issue was fixed when doing IPC with user buffers.
++ Support was fixed for toggling the custom setting `usb!usb30_force_enabled` on 9.0.0+.
+ + This was broken by Nintendo's introducing a dependency that made USB a requirement to launch before custom settings are parsed.
+ + Since the fix, you can now toggle the setting (as you could prior to atmosphère 0.9.4), and it will work as expected.
+ + **Please Note**: Enabling USB 3.0 often severely impacts wireless communications.
+ + Because of this, the setting will default to off. If you experience issues with it enabled, consider disabling it.
++ A warning was added to daybreak when resetting the console to factory settings.
++ Substantial work was completed towards atmosphere's upcoming implementation of the host target connection protocol.
+ + Once completed, users will be able to interact with a Switch running atmosphère via a PC application ("Starlink") currently under development.
+ + Planned eventual features for connected consoles include a gdbstub, interacting with memory (for cheat development), streaming gameplay audio and video, and accessing the Switch's SD card filesystem.
+ + Switch homebrew will also have access to a (configurable and sandboxed) filesystem on the host PC, while connected.
+ + Towards this end, the following was accomplished:
+ + The "htc" system module was reimplemented completely.
+ + The system module which provides remote access to the SD card was reimplemented completely.
+ + This is currently the active focus of atmosphère's development.
+ + **Please Note**: Support is not yet completed, and users are disadvised from interacting with the related settings for the time being, unless they particularly know what they're doing.
++ A number of minor issues were fixed, including:
+ + A bug was fixed in `dmnt` that could cause a fatal when launching certain games with cheats active.
+ + An issue was fixed that could cause an abort in `sm` when using a large number of custom system modules.
+ + An issue was fixed that prevented launching gamecards on 1.0.0.
+ + Minor issues were fixed in the cheat virtual machine's behavior.
++ Several issues were fixed, and usability and stability were improved.
+## 0.18.1
++ A number of minor issues were fixed, including:
+ + The new `dns.mitm` module added in 0.18.0 no longer fatal errors when receiving port=nullptr.
+ + This fixes youtube ad-blocking, and possibly other usecases.
+ + A bug was fixed that caused ams.mitm to incorrectly cache data storages.
+ + This potentially broke DLC when using romfs mods, and could have caused other issues (e.g. with custom themes, and maybe other cases).
+ + A bug was fixed in power state control module registration.
+ + This might fix a weird edge case with system module dependencies on sleep/wake, but probably nobody should notice any differences.
+ + A bug was fixed where mesosphere sometimes treated virtual core IDs as though they were physical core IDs.
+ + This had zero impact, because for Switch virtual core == physical core, but it could have affected future platforms if it had remained unresolved.
++ Several issues were fixed, and usability and stability were improved.
+## 0.18.0
++ A new mitm module was added (`dns.mitm`).
+ + This provides a highly configurable mechanism for redirecting DNS resolution requests.
+ + By default atmosphère redirects resolution requests for official telemetry servers to a loopback address.
+ + Documentation on how to configure `dns.mitm` to meet your more specific needs may be found [here](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/features/dns_mitm.md).
++ The service framework API (`sf`) was refactored to be more accurate to official logic and greatly reduce memory requirements.
+ + The comparison of atmosphère module memory usage versus Nintendo's found [here](https://github.com/Atmosphere-NX/Atmosphere/wiki/Memory-Comparisons) was updated to reflect this.
+ + **Please Note**: If you are a developer using the libstratosphere service APIs, some updating may be required. Contact SciresM#0524 on discord for assistance if required.
++ A number of deprecations were removed, following a general codebase cleanup:
+ + The `sm` extension to not unregister services on connection close was superseded by official opt-in logic in 11.0.0, and has been removed in favor of official logic.
+ + This should have zero impact on users.
+ + The temporary `hid-mitm` added in 0.9.0 has finally been removed, following over a year of deprecation.
+ + There shouldn't be any homebrew in use still affected by this, but the situation will be monitored.
+ + If this is somehow still a real issue, an unaffiliated hid mitm sysmodule providing the same functionality can be created and released, separate from atmosphère itself.
++ Several issues were fixed, and usability and stability were improved.
+## 0.17.1
++ A number of atmosphère's modules were using more memory than their Nintendo equivalent's in 0.17.0; a number of code generatio tweaks have been applied to fix this across the board.
+ + A detailed comparison of atmosphère module memory usage versus Nintendo's was made and can be found [here](https://github.com/Atmosphere-NX/Atmosphere/wiki/Memory-Comparisons).
++ Several minor bugs were fixed, including:
+ + A bug was fixed in mesosphère that caused games which attempt to map more memory than the Switch has to fail.
+ + This affected "Piczle Lines DX 500 More Puzzles!", and possibly other games.
+ + Enabling configuration to "blank" PRODINFO no longer causes a hang on Mariko devices (or any devices with newer format).
++ Several issues were fixed, and usability and stability were improved.
+## 0.17.0
++ fusee was heavily rewritten in order to add support for Mariko hardware.
+ + **Please Note**: Mariko hardware currently has no (and may not ever have any) software exploits; fusee works when loaded from bootloader context with the right keys in the security engine. No means of getting the system into this state is provided.
++ An issue was fixed in the way shutdown was performed on Erista hardware.
+ + This fixes an issue that caused OFW to black screen on boot after power off from atmosphere without first doing a reboot.
+ + This also substantially improves power drain when the system is shut off; consoles powered off from Atmosphere should now drain battery at the same reduced rate as original firmware.
++ A number of minor changes were made, including:
+ + A number of inconsistencies in the build system were fixed.
+ + For those building atmosphère at home, the `boot` sysmodule will no longer rebuild every time make is invoked.
+ + This substantially improves build times during development iteration.
+ + `sm` was updated to more accurately reflect how official code manages request deferral.
+ + `mesosphère` was updated to more accurately reflect official kernel management of the trace buffer.
+ + `mesosphère` was updated to improve kernel loader's logic by taking advantage of the assumption that we only boot our kernel, not Nintendo's.
++ As it has been a few months with zero reported issues, `mesosphère` is now opt-out.
+ + Users who wish to begin using or continue using mesosphere should use the standard/cool kids zip ("atmosphere-").
+ + Users who wish to opt-out of mesosphere should download and extract the opt-out zip ("atmosphere-WITHOUT_MESOSPHERE-").
++ Several issues were fixed, and usability and stability were improved.
+## 0.16.2
++ Atmosphère release zips no longer bundle BCT.ini, instead relying on defaults in code.
+ + This means atmosphere updates should no longer overwrite any user configuration at all.
+ + If you wish to modify BCT.ini config, copy the template from /config_templates/ as with other configuration.
++ `pgl` and `creport` were further updated to reflect differences in official behavior in 11.0.0.
++ An issue was fixed that caused creport to be launched multiple times on process crash.
+ + This fixes the "duplicate reports" issue that sometimes plagued people.
++ A new system setting (`atmosphere!enable_am_debug_mode`) configuring am to use debug mode.
+ + If you are not a developer or don't see a clear use for this, leave it configured to the default (off).
++ Reboot to payload NRO was updated to fix support with certain payloads.
++ Support was fixed for atmosphere's extension to support homebrew use of new (8.0.0+) kernel mappings.
+ + In particular, when running tracing debug builds of `mesosphère`, hbloader now has access to the kernel trace buffer.
++ Several issues were fixed, and usability and stability were improved.
+## 0.16.1
++ Support was added for 11.0.1.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + A new svc::InfoType added in 11.0.0 was implemented (it wasn't discovered before 0.16.0 released).
+ + The new Control Flow Integrity (CFI) logic added in 11.0.0 kernel was implemented.
++ `fs` logic was refactored and cleaned up to reflect some newer sysmodule behavioral and structural changes.
++ `exosphère` was updated to allow dynamic control of what uart port is used for logging.
+ + This can be controlled by editing the `log_port`, `log_baud_rate`, and `log_inverted` fields in `exosphere.ini`.
++ `mesosphère` was updated to improve debugging capabilities.
+ + This is still a work in progress, but developers may be interested.
++ A bug was fixed that caused `fatal` to fatal error if the fatal process was already being debugged.
++ Several issues were fixed, and usability and stability were improved.
+## 0.16.0
++ Support was added for 11.0.0.
+ + `exosphère` was updated to reflect the latest official secure monitor behavior.
+ + `mesosphère` was updated to reflect the latest official kernel behavior.
+ + `loader`, `sm`, `boot`, `pgl` were updated to reflect the latest official behaviors.
+ + **Please Note**: 11.0.0 implements an opt-in version of the atmosphère `sm` extension that allows for closing session without unregistering services.
+ + Correspondingly, the extension will be deprecated in favor of the new official opt-in command. In 0.17.0, it will be removed entirely.
+ + If your custom system module relies on this extension (however unlikely that seems to me), please update it accordingly.
+ + `erpt` was partially updated to provide compatibility with 11.0.0.
+ + The latest firmware attaches additional fields and context information to logs.
+ + A future atmosphère update will implement this logic, so that users who are interested can also get the new information when examining their logs.
+ + **Please Note**: 11.0.0 introduced breaking changes to the `usb` system module's `usb:ds` API.
+ + Homebrew which uses the `usb:ds` service should rebuild with the latest libnx version to support running on 11.0.0.
++ The `boot` system module was rewritten to reflect the huge driver changes introduced in 8.0.0.
+ + This includes a number of improvements to both logo display and battery management logic.
++ Support was added for configuring the address space width for `hbl`.
+ + The `hbl_config!override_address_space_(#)` and `hbl_config!override_any_app_address_space` can now be set to `39_bit`, `36_bit`, or `32_bit` to control the address space for hbl on a per-override basis.
+ + If a configuration has not been set, hbl will now default to 39-bit address space.
+ + Previously, a legacy 36-bit address space was always used to maintain compatibility with 1.0.0.
+ + A new loader extension was added to support 39-bit whenever possible (including mesosphere-on-1.0.0), with fallback to 36-bit when unavailable.
++ Support was added to a number of components for running on Mariko hardware.
+ + The `boot` system module can now safely be run on mariko hardware, performing correct hardware initialization.
+ + Daybreak (and generally, system update logic) were updated to be usable on Mariko.
+ + Boot0 protection/management logic was updated to perform correct actions on Mariko.
+ + Reboot to payload does not and cannot work on Mariko. Correspondingly, A "fatal error" handler was written, to display and save fatal errors from within TrustZone.
+ + **Please Note:** Atmosphere is still not properly usable on Mariko hardware.
+ + In particular, wake-from-sleep will not properly function (the magic numbers aren't set correctly), among a few other minor issues.
++ `exosphère` received support for building under debug configuration.
+ + A small (otherwise unused) portion of IRAM is now reserved for debug-only exosphere code (this region is unused/untouched under release config).
+ + This enables logging (including printf) to uart from the secure monitor, for those interested.
++ A number of bugs were fixed, including:
+ + Minor issues in a number of filesystem related code were fixed.
+ + An issue was fixed that could cause NCM to abort on consoles which came with 3.0.x and were never updated.
++ Several issues were fixed, and usability and stability were improved.
+## 0.15.0
++ fusee-primary's panic display was updated to automatically identify and give suggestions to resolve many of the most common errors users encounter.
++ Having been tested as well as I can alone, `mesosphere` (atmosphère's reimplementation of the Nintendo Switch kernel) is now available for users interested in trying it.
+ + Beginning in this release and until it is stable and well-tested, atmosphère will distribute two zips.
+ + Users who wish to opt-in to mesosphere should download and extract the "cool kids" zip ("atmosphere-EXPERIMENTAL-").
+ + Users who do not wish to use mesosphere should continue using the normal zip ("atmosphere-").
+ + Users may detect whether mesosphere is active in system settings.
+ + When mesosphere is active, the system version string will display "M.15.0" rather than "0.15.0", and so on for future releases.
+ + Crash reports and the like will contain information on whether or not the user is using mesosphere, as well.
+ + There are "probably" no material user-facing benefits to using mesosphere at this time.
+ + Developers may be interested in the fact that mesosphere provides many newer SVC APIs even when on lower firmware versions.
+ + The primary benefit to using mesosphere is that any issues you may encounter and report to me will be fixed.
+ + All users who choose to opt in to using mesosphere have my deepest gratitude.
+ + **Note:** If using hekate instead of fusee-primary, you will have to wait for the next hekate release for mesosphere to function, as hekate's support has not yet been included in an official release build.
+ + This will be updated in the release notes when hekate provides a new release.
+ + As mentioned in previous release notes, when mesosphere is stable and well-tested, it will be enabled by default and atmosphère's version will transition to 1.0.0.
++ Having been tested sufficiently over the last half-year, Atmosphere's NCM implementation is now opt-out, rather than opt in.
+ + In the unlikely event that any issues are encountered, please report them to @SciresM.
+ + Users interested in opting out of using our implementation should set `stratosphere!disable_ncm = 1` in BCT.ini.
+ + The NCM implementation will stop being opt-out in a future update, probably around the same time that mesosphere becomes opt-out instead of opt-in.
++ Several bugs were fixed, including:
+ + Loader now sets HBL's thread priority to a higher value when loading it in applet mode.
+ + This fixes an extremely-slow launch ("hang") when using applet-HBL with certain games that do not suspend while inactive (e.g. Super Mario Sunshine).
+ + set.mitm now caches user language configuration much more heavily.
+ + This severely reduces lag in certain games which misuse the "nn::oe::GetDesiredLanguage()" API.
+ + A bug was fixed that could cause erpt to fatal when loading an official save file that had error report attachments in it.
++ General system stability improvements to enhance the user's experience.
+## 0.14.4
++ Several bugs were fixed involving the official jit sysmodule added in 10.0.0.
+ + A Process handle leak was fixed when JitPlugin NRRs were registered with the `ro` sysmodule.
+ + This prevented processes using jit from being able to exit, causing a full system freeze.
+ + The `sm` atmosphere extension to not unregister services when the server's connection is closed was special-case disabled for `jit:u`.
+ + This extension is normally desirable in order to allow more concurrent processes to exist (as only 0x40 sm connections may ever be concurrently open), but official jit sysmodule relies on the behavior.
+ + This would cause crashes on attempts to launch a program using jit services more than once per reboot.
++ General system stability improvements to enhance the user's experience.
+## 0.14.3
++ Support was added for 10.2.0.
++ General system stability improvements to enhance the user's experience.
+## 0.14.2
++ A bug was fixed that could cause a deadlock when installing mitm services.
+ + Fixing this required a breaking change to the client behavior when installing a mitm service, and so custom sysmodules which use mitm will need to be re-compiled to function properly.
++ A bug was fixed that caused atmosphere sysmodules to respond incorrectly when receiving invalid messages.
++ A bug was fixed that caused fatal auto-reboot timing to work improperly.
++ Support was added to fusee for loading binaries for `mesosphere`, atmosphère's reimplementation of the Nintendo Switch kernel.
+ + 0.14.2 does not include mesosphere, but those who are especially interested can build and test mesosphere themselves.
+ + In the future, to enable a sufficient testing period Atmosphère releases will distribute two zips for some time.
+ + One zip will use mesosphere, and the other will not.
+ + This will allow users who are interested to opt-in to mesosphere usage before it has been tested to be stable.
+ + When mesosphere is stable and well-tested, it will be enabled by default and Atmosphère's version will transition to 1.0.0.
++ General system stability improvements to enhance the user's experience.
+## 0.14.1
++ An issue was fixed in 0.14.0 that would cause a black screen on boot when the INI1's size was not aligned to 8 bytes.
++ General system stability improvements to enhance the user's experience.
+## 0.14.0
++ An API (`ams:su`) was added to allow homebrew to safely install system upgrades or downgrades.
+ + This is a re-implementation of the logic that `ns` uses to install gamecard system updates.
+ + Nintendo (and now atmosphère) uses an installation process that can recover no matter where a failure occurs, which should significantly improve the safety of custom system update installation.
++ Support was added to `exosphère` for running on Mariko hardware.
+ + **Please note**: Atmosphère still does not support Mariko, and should not be run on Mariko yet.
+ + Certain stratosphere components do not handle mariko-specific logic fully correctly yet, and may initialize or interact with hardware incorrectly.
+ + This will be fixed and support will be added over the remainder of the Summer.
++ A homebrew application (`daybreak`) was added that uses the system updater API (with thanks to @Adubbz for both design and implementation).
+ + `daybreak` is included with atmosphère, and functions as a safer/more accurate equivalent to e.g. ChoiDujourNX.
+ + Upgrades/downgrades can be installed from a folder containing the update NCAs on the SD card.
+ + Because the update logic functions identically to Nintendo's, `daybreak` will be safe to use on Mariko when the rest of atmosphère has support.
+ + **Please note**: Daybreak requires that meta (.cnmt) NCAs have the correct extension `.cnmt.nca`.
+ + This is because gamecard system update logic uses extension to determine whether to mount the content.
+ + [Several](https://gist.github.com/HookedBehemoth/df36b5970e1c5b1b512ec7bdd9043c6e) [scripts](https://gist.github.com/antiKk/279966c27fdfd9c7fe63b4ae410f89c4) have been made by community members to automatically rename folders with incorrect extensions.
++ A bug was fixed that would cause file-based emummc to throw an error (showing a hexdump) on boot.
+ + Major thanks to @hexkyz for tracking down and resolving this.
++ A number of minor issues were resolved, including:
+ + fusee now prints information to the screen when an error occurs, instead of getting stuck trying to initialize the display.
+ + A race condition in Horizon was worked around that could prevent boot under certain circumstances.
+ + A bug was fixed that would cause atmosphère modules to open ten copies of certain filesystems instead of one.
+ + This could cause object exhaustion under certain circumstances.
++ For those interested in atmosphère's future development plans, the project's [roadmap](https://github.com/Atmosphere-NX/Atmosphere/blob/ac9832c5ce7be5832f6d29f6564a9c03e7efd22f/docs/roadmap.md) was updated.
++ General system stability improvements to enhance the user's experience.
+## 0.13.0
++ `exosphère`, atmosphère's secure monitor re-implementation, was completely re-written.
+ + `exosphère` was the first component authored for the project in early 2018. It is written in C, and in a style very different from the rest of atmosphère's code.
+ + This has made the codebase difficult to maintain as time has gone on.
+ + `exosphère` was also written to conform to constraints and assumptions that simply no longer apply when cfw is not launched from the web browser, and when warmboothax is possible.
+ + Even beyond these issues, `exosphère` used all but 1KB of the 64KB of space available to it. This was a problem for a few reasons:
+ + Each new system update added requires additional space to support (to add new keys and reflect various changes); 10.0.0 support used up 3 of the 4KB we had left.
+ + atmosphère will want to have software support for mariko hardware, and this is not possible to fit in 1 KB.
+ + The `exosphère` rewrite (which was codenamed `exosphère2` during development) solves these problems.
+ + The new codebase is C++20 written in atmosphère's style.
+ + This solves the maintainability problem, and should make understanding how the secure monitor works *much* easier for those interested in using the code as a reference implementation.
+ + In addition, the new implementation currently uses ~59.5 of the 64KB available.
+ + Several potential code changes are planned that can save/grant access to an additional ~2-3 KB if needed.
+ + Unlike the first codebase, the new `exosphère` actually already has space allocated for future keys/etc. It is currently expected that the reserved space will never be required.
+ + The previous implementation chose not to implement a number of "unimportant" secure monitor functions due to space concerns. The new code has enough breathing room that it can implement them without worries. :)
+ + Finally, the groundwork for mariko support has been laid -- there are only a few minor changes needed for the new secure monitor implementation to work on both erista and mariko hardware.
+ + **Please note**: `exosphère` is only one of many components, and many more need changes to support running on mariko hardware.
+ + Software-side support for executing on mariko hardware is expected some time during Summer 2020, though it should also be noted that this is not a hard deadline.
+ + **Please note**: The new `exosphère` binary is not abi-compatible with the old one. Users who boot using hekate should wait for it to update before running 0.13.0 (or boot fusee-primary via hekate).
++ atmosphère's api for target firmware was changed. All minor/micro system versions are now recognized, instead of only major versions.
+ + This was required in order to support firmware version 5.1.0, which made breaking changes to certain IPC APIs that caused atmosphère 0.12.0 to abort.
+ + **Please note**: this is (unavoidably) a breaking change. System modules using atmosphere-libs will need to update to understand what firmware version they are running.
++ `emummc` was updated to include the new changes.
+ + `emummc` now uses an updated/improved/faster SDMMC driver.
+ + File-based emummc is now almost as fast as raw partition-based emummc.
++ For those interested in atmosphère's future development plans, the project's [roadmap](https://github.com/Atmosphere-NX/Atmosphere/blob/f68d33b70aed8954cc2c539e5934bcaf37ba51da/docs/roadmap.md) was updated.
++ General system stability improvements to enhance the user's experience.
+
+## 0.12.0
++ Configuration for exosphere was moved to sd:/exosphere.ini.
+ + This is to facilitate BIS protection changes described below.
+ + Hopefully having this outside of the Atmosphere folder will prevent accidental deletion, since this now contains important settings.
++ Atmosphere's bis protection policy for the PRODINFO partition was substantially reworked.
+ + Support was added for "automatically" performing a "blanking" operation to PRODINFO without actually modifying NAND.
+ + This is equivalent to using the "incognito" homebrew tool, but NAND is never actually modified.
+ + This can be turned on in sysmmc by setting `blank_prodinfo_sysmmc=1` in exosphere.ini, and in emummc by setting `blank_prodinfo_emummc=1` in exosphere.ini.
+ + **Please note**: This is not known to be safe. There is a lack of research on whether the information blanked out is cached elsewhere in the system.
+ + Usage of this option is not encouraged for this reason.
+ + Support was added for writing to the PRODINFO partition, if a verified encrypted backup has been made.
+ + PRODINFO is the only system data that cannot be recovered if not backed up, and thus Atmosphere has backed it up to the SD card on boot for some time now.
+ + Users who wish to modify their calibration data may now do so unconditionally in emummc, and in sysmmc if `allow_writing_to_cal_sysmmc=1` is set in exosphere.ini.
+ + **Please note**: This is heavily discouraged, and the typical user will almost never want to do this.
+ + Setting this option will cause Atmosphere to attempt to verify (or create) an encrypted backup of the PRODINFO data to an unused region in the partition.
+ + The backup is encrypted with per-console keys that Atmosphere's developers do not know.
+ + If the backup is not verified or created, writes will not work. Users who have corrupted their PRODINFO in the past are encouraged to flash a good backup to allow use of this setting.
+ + Reads and writes to the region used for the securely encrypted backup will appear to succeed, but will actually read/write from a buffer filled with garbage in memory.
+ + Support will be investigated in the future for supporting booting with fully blanked calibration.
+ + This is desirable to allow boot to succeed for users who lost their calibration data due to bricking homebrew before bis protection was implemented.
++ `creport` has been updated to use the new screenshot APIs added in 9.0.0+.
+ + On 10.0.0+, if a crash occurs in an application (not applet or sysmodule) a screenshot will now be automatically saved to the SD card.
+ + If the user applies a patch to vi on 9.0.0 (as the command this uses was previously for dev-units only), this can also work on 9.0.0.
++ The new sysmodule `pgl` added in 10.0.0 was reimplemented.
+ + `pgl` ("Program Launcher", probably) is responsible for managing launched user-processes, previously this was handled by NS.
+ + The most exciting thing about pgl is that it finally provides an API for multiple clients to subscribe to process events.
+ + Using these new APIs, system modules / other homebrew can subscribe to be notified whenever a process event occurs.
+ + This means action can be taken on process launch, process exit, process crash, etc.
+ + A slight concern with Nintendo's implementation is that each subscriber object uses 0x448 bytes of memory, and N only reserves 8KB for all allocations in pgl.
+ + Atmosphere's implementation uses a 32KB heap, which should not be exhaustible.
+ + Atmosphere's implementation has a total memory footprint roughly 0x28000 bytes smaller than Nintendo's.
++ A reimplementation was added for the `jpegdec` system module (thanks @HookedBehemoth)!
+ + This allows two sessions instead of 1, so homebrew can now use it for software jpeg decoding in addition to the OS itself.
+ + As usual the implementation has a very slightly smaller memory footprint than Nintendo's.
++ `dmnt`'s Cheat VM was extended to add three new opcodes.
+ + The first new opcode, "ReadWriteStaticRegister", allows for cheats to read from a bank of 128 read-only static registers, and write to a bank of 128 write-only static registers.
+ + This can be used in concert with new IPC commands that allow a cheat manager to read or write the value of these static registers to have "dynamic" cheats.
+ + As an example, a cheat manager could write a value to a static register that a cheat to control how many of an item to give in a game.
+ + As another example, a cheat manager could read a static register that a cheat writes to to learn how many items a player has.
++ The second and third opcodes are a pair, "PauseProcess" and "ResumeProcess".
+ + Executing pause process in a cheat will pause the game (it will be frozen) until a resume process opcode is used.
+ + These are also available over IPC, for cheat managers or system modules that want to pause or resume the attached cheat process.
+ + This allows a cheat to know that the game won't modify or access data the cheat is accessing.
+ + For example, this can be used to prevent Pokemon from seeing a pokemon a cheat is in the middle of injecting and turning it into a bad egg.
++ A bug was fixed that would cause the console to crash when connected to Wi-Fi on versions between 3.0.0 and 4.1.0 inclusive.
++ A bug was fixed that could cause boot to fail sporadically due to cache/tlb mismanagement when doing physical ASLR of the kernel.
++ A number of other minor issues were addressed (and more of Atmosphere was updated to reflect other changes in 10.0.x).
++ General system stability improvements to enhance the user's experience.
+
+## 0.11.1
++ A bug was fixed that could cause owls to flicker under certain circumstances.
+ + For those interested in technical details, in 10.0.0 kernelldr/kernel no longer set cpuactlr_el1, assuming that it was set correctly by the secure monitor.
+ + However, exosphere did not set cpuactlr_el1. This meant that the register held the reset value going into boot.
+ + This caused a variety of highly erratic symptoms, including causing basically any game to crash seemingly randomly.
++ A number of other major inaccuracies in exosphere were corrected.
++ General system stability improvements to enhance the user's experience.
+
+## 0.11.0
++ Support was added for 10.0.0.
+ + Exosphere has been updated to reflect the new key import semantics in 10.0.0.
+ + kernel_ldr now implements physical ASLR for the kernel's backing pages.
+ + Loader, NCM, and PM have been updated to reflect the changes Nintendo made in 10.0.0.
+ + Creport was updated to use the new `pgl` service to terminate processes instead of `ns:dev`.
++ A reimplementation of the `erpt` (error reports) system module was added.
+ + In previous versions of Atmosphere, a majority of error reports were prevented via a combination of custom creport, fatal, and stubbed eclct.
+ + However, error reports were still generated via some system actions.
+ + Most notably, any time the error applet appeared, an error report was generated.
+ + By default, atmosphere disabled the *uploading* of error reports, but going online in OFW after an error report occurred in Atmosphere could lead to undesirable telemetry.
+ + Atmosphere's `erpt` reimplementation allows the system to interact with existing error reports as expected.
+ + However, all new error reports are instead saved to the sd card (`/atmosphere/erpt_reports`), and are not committed to the system savegame.
+ + Users curious about what kind of telemetry is being prevented can view the reports as they're generated in there.
+ + Reports are saved as msgpack (as this is what Nintendo uses).
+ + Please note, not all telemetry is disabled. Play reports and System reports will continue to function unmodified.
+ + With atmosphere's `erpt` implementation, homebrew can now use the native error applet to display errors without worrying about generating undesirable telemetry.
++ libstratosphere and libvapours received a number of improvements.
+ + With thanks to @Adubbz for his work, the NCM namespace now has client code.
+ + This lays the groundwork for first-class system update/downgrade homebrew support in the near future.
+ + In particular, code implementing the os namespace is significantly more accurate.
+ + In addition, Nintendo's allocators were implemented, allowing for identical memory efficiency versus Nintendo's implementations.
++ General system stability improvements to enhance the user's experience.
+
+## 0.10.5
++ Changes were made to the way fs.mitm builds images when providing a layeredfs romfs.
+ + Building romfs metadata previously had a memory cost of about ~4-5x the file table size.
+ + This caused games that have particularly enormous file metadata tables (> 4 MB) to exhaust fs.mitm's 16 MB memory pool.
+ + The code that creates romfs images was thus changed to be significantly more memory efficient, again.
+ + Memory requirements have been lowered from ~4x file table size to ~2x file table size + 0.5 MB.
+ + There is a slight speed penalty to this, but testing on Football Manager 2020 only took an extra ~1.5 seconds for the game to boot with many modded files.
+ + This shouldn't be noticeable thanks to the async changes made in 0.10.2.
+ + If you encounter a game that exhausts ams.mitm's memory (crashing it) when loading layeredfs mods, please contact SciresM.
+ + Romfs building can be made even more memory efficient, but unless games show up with even more absurdly huge file tables it seems not worth the speed trade-off.
++ A bug was fixed that caused Atmosphere's fatal error context to not dump TLS for certain processes.
++ General system stability improvements to enhance the user's experience.
+
+## 0.10.4
++ With major thanks to @Adubbz for his work, the NCM system module has now been re-implemented.
+ + This was a major stepping stone towards the goal of having implementations everything in the Switch's package1/package2 firmware.
+ + This also lays the groundwork for libstratosphere providing bindings for changing the installed version of the Switch's OS.
+ + **Please Note**: The NCM implementation will initially be opt-in.
+ + The Atmosphere team is confident in our NCM implementation (and we have tested it on every firmware version).
+ + That said, this is our first system module that manages NAND savegames -- and caution is a habit.
+ + We do not anticipate any issues that didn't come up in testing, so this is just our being particularly careful.
+ + Users interested in opting in to using our implementation should set `stratosphere!ncm_enabled = 1` in BCT.ini.
+ + In the unlikely event that any issues are encountered, please report them to @SciresM.
+ + The NCM implementation will stop being opt-in in a future update, after thorough testing has been done in practice.
++ A bug was fixed in emummc that caused Nintendo path to be corrupted on 1.0.0.
+ + This manifested as the emummc folder being created inside the virtual NAND instead of the SD card.
+ + It's unlikely there are any negative consequences to this in practice.
+ + If you want to be truly sure, you can re-clone sysmmc before updating a 1.0.0 emummc to latest firmware.
++ Stratosphere system modules now use new Nintendo-style FS bindings instead of stdio.
+ + This saves a modest amount of memory due to leaner code, and greatly increases the accuracy of several components.
+ + These bindings will make it easier for other system modules using libstratosphere to interact with the filesystem.
+ + This also lays the groundwork for changes necessary to support per-emummc Atmosphere folders in a future update.
++ Atmosphere's fatal error context now dumps 0x100 of TLS.
+ + This will make it much easier to fix bugs when an error report is dumped for whatever caused the crash.
++ General system stability improvements to enhance the user's experience.
+
+## 0.10.3
++ Support was added for 9.2.0.
++ Support was added for redirecting manual html content for games.
+ + This works like normal layeredfs, replacing content placed in `/atmosphere/contents//manual_html/`.
+ + This allows for game mods/translations to provide custom manual content, if they so choose.
++ A number of improvements were made to Atmosphere's memory usage, including:
+ + `fatal` now uses STB instead of freetype for rendering.
+ + This saves around 1 MB of memory, and makes our fatal substantially leaner than Nintendo's.
+ + `sm` no longer wastes 2 MiB unnecessarily.
++ fusee/sept's sdmmc access now better matches official behavior.
+ + This improves compatibility with some SD cards.
++ `ro` has been updated to reflect changes made in 9.1.0.
++ The temporary auto-migration added in 0.10.0 has been removed, since the transitionary period is well over.
++ General system stability improvements to enhance the user's experience.
+
+## 0.10.2
++ hbl configuration was made more flexible.
+ + Up to eight specific program ids can now be specified to have their own override keys.
+ + This allows designating both the album applet and a specific game as hbl by default as desired.
+ + Configuration targeting a specific program is now mutually exclusive with override-any-app for that program.
+ + This fixes unintuitive behavior when override key differed for an application specific program.
++ Loader's external content fileystem support was fixed (thanks @misson20000!).
++ KernelLdr was reimplemented.
+ + This is the first step towards developing mesosphere, Atmosphere's planned reimplementation of the Switch's Kernel.
+ + The typical user won't notice anything different, as there are no extensions, but a lot of groundwork was laid for future development.
++ Improvements were made to the way Atmosphere's buildsystem detects source code files.
+ + This significantly reduces compilation time (saving >30 seconds) on the machine that builds official releases.
++ Certain device code was cleaned up and made more correct in fusee/sept/exosphere (thanks @hexkyz!).
++ A number of changes were made to the way fs.mitm builds images when providing a layeredfs romfs.
+ + Some games (Resident Evil 6, Football Manager 2020 Touch, possibly others) have enormous numbers of files.
+ + Attempting to create a layeredfs mod for these games actually caused fs.mitm to run out of memory, causing a fatal error.
+ + The code that creates these images was changed to be significantly more memory efficient.
+ + However, these changes also cause a significant slowdown in the romfs building code (~2-5x).
+ + This introduced a noticeable stutter when launching a game, because the UI thread would block on the romfs creation.
+ + To solve this, fs.mitm now lazily initializes the image in a background thread.
+ + This fixes stutter issues, however some games may be slightly slower (~1-2s in the worst cases) to transition from the "loading" GIF to gameplay now.
+ + Please note: the slowdown is not noticeable in the common case where games don't have tons of files (typical is ~0.1-0.2 seconds).
+ + Once the image has been built, there is no further speed penalty at runtime -- only when the game is launched.
++ A number of other bugs were fixed, including:
+ + Several minor logic inversions that could have caused fatal errors when modding games.
+ + Atmosphere's new-ipc code did not handle "automatic" recvlist buffers correctly, so some non-libnx homebrew could crash.
+ + fs.mitm now correctly mitms sdb, which makes redirection of certain system data archives work again.
+ + In 0.10.0/0.10.1, changing the system font/language did not work correctly due to this.
+ + A bug was fixed in process cleanup that caused the system to hang on < 5.0.0.
++ The temporary hid-mitm added in Atmosphere 0.9.0 was disabled by default.
+ + Please ensure your homebrew is updated.
+ + For now, users may re-enable this mitm by use of a custom setting (`atmosphere!enable_deprecated_hid_mitm`) to ease the transition process some.
+ + Please note: support for this setting may be removed to save memory in a future atmosphere release.
++ General system stability improvements to enhance the user's experience.
+
+## 0.10.1
++ A bug was fixed that caused memory reallocation to the system pool to work improperly on firmware 5.0.0 and above.
+ + Atmosphere was always trying to deallocate memory away from the applet pool and towards the system pool.
+ + The intent of this is to facilitate running more custom sysmodules/atmosphere binaries.
+ + However, while memory was always successfully taken away from the applet pool, on 5.0.0+ granting it to the system pool did not work for technical reasons.
+ + If you are interested in the technical details, talk to SciresM.
+ + This has now been fixed by adding new kernel patches, and memory is correctly granted to the system pool as intended.
++ Atmosphere's library system has been overhauled:
+ + libstratosphere's repository has been rebranded, more generally, to "Atmosphere-libs".
+ + In addition to libstratosphere, a new general library for not-stratosphere-specific code has been added.
+ + This is currently named `libvapours`.
+ + In the future, kernel functionality will be available as `libmesosphere`.
+ + The build system for stratosphere system modules has been similarly overhauled.
++ A number of other bugs were fixed, including:
+ + A bug was fixed that could cause memory corruption when redirecting certain Romfs content.
+ + A bug was fixed that could cause an infinite loop when redirecting certain Romfs content.
+ + A bug was fixed that could cause certain NROs to fail to load.
+ + This caused the latest version of Super Smash Bros to display "An error has occurred" on launch.
+ + A bug was fixed that caused input/output array sizes for certain circumstances to be calculated incorrectly.
+ + This caused cheats to fail to function properly.
+ + C++ exception code is now more thoroughly removed from stratosphere executables.
+ + This saves a minor amount of memory.
+ + A number of minor logic inversions were fixed in libstratosphere.
+ + These did not affect any code currently used by released Atmosphere binaries.
++ *Please note**: Because this update is releasing so soon after 0.10.0, the removal of the temporary hid-mitm has been postponed to 0.10.2.
+ + Please ensure your homebrew is updated.
++ Random number generation now uses TinyMT instead of XorShift.
++ General system stability improvements to enhance the user's experience.
+
+## 0.10.0
++ Support was added for 9.1.0
+ + **Please note**: The temporary hid-mitm added in Atmosphere 0.9.0 will be removed in Atmosphere 0.10.1.
+ + Please ensure your homebrew is updated.
++ The Stratosphere rewrite was completed.
+ + libstratosphere was rewritten as part of Stratosphere's refactor.
+ + Code responsible for providing and managing IPC services was greatly improved.
+ + The new code is significantly more accurate (it is bug-for-bug compatible with Nintendo's code), and significantly faster.
+ + ams.mitm was rewritten as part of Stratosphere's refactor.
+ + Saves redirected to the SD card are now separated for sysmmc vs emummc.
+ + **Please note**: If you find any bugs, please report them so they can be fixed.
++ Thanks to the rewrite, Atmosphere now uses significantly less memory.
+ + Roughly 10 additional megabytes are now available for custom system modules to use.
+ + This means you can potentially run more custom system modules simultaneously.
+ + If system modules are incompatible, please ask their authors to reduce their memory footprints.
++ Atmosphere's configuration layout has had major changes.
+ + Configuration now lives inside /atmosphere/config/.
+ + Atmosphere code now knows what default values should be, and includes them in code.
+ + It is no longer an error if configuration inis are not present.
+ + Correspondingly, Atmosphere no longer distributes default configuration inis.
+ + Templates are provided in /atmosphere/config_templates.
+ + loader.ini was renamed to override_config.ini.
+ + This fixes the longstanding problem that atmosphere updates overwrote user configuration when extracted.
++ Atmosphere's process override layout was changed.
+ + Atmosphere now uses the /atmosphere/contents directory, instead of /atmosphere/titles.
+ + This goes along with a refactoring to remove all reference to "title id" from code, as Nintendo does not use the term.
+ + To make this transition easier, a temporary functionality has been added that migrates folders to the new directory.
+ + When booting into 0.10.0, Atmosphere will rename /atmosphere/titles/`` to /atmosphere/contents/``.
+ + This functionality may or may not be removed in some future update.
+ + This should solve any transition difficulties for the typical user.
+ + Please make sure that any future mods you install extract to the correct directory.
++ Support for configuring override keys for hbl was improved.
+ + The key used to override applications versus a specific program can now be different.
+ + The key to override a specific program can be managed via override_key.
+ + The key to override any app can be managed via override_any_app_key.
+ + Default override behavior was changed.
+ + By default, atmosphere will now override the album applet with hbl unless R is held.
+ + By default, atmosphere will now override any application with hbl only if R is held.
++ The default amount of applet memory reserved has been slightly increased.
+ + This allows the profile selector applet to work by default in applet mode.
++ The way process override status is captured was changed.
+ + Process override keys are now captured exactly once, when the process is created.
+ + This fixes the longstanding issue where letting go of the override button partway into the process launch could cause problems.
+ + The Mitm API was changed to pass around override status.
+ + Mitm services now know what keys were held when the client was created, as well as whether the client is HBL/should override contents.
+ + An extension was added to pm:info to allow querying a process's override status.
++ Thanks to process override capture improvements, hbl html behavior has been greatly improved.
+ + Web applets launched by hbl will now always see the /atmosphere/hbl_html filesystem
++ Support was added to exosphere for enabling usermode access to the PMU registers.
+ + This can be controlled via exosphere!enable_user_pmu_access in BCT.ini.
++ An enormous number of minor bugs were fixed.
+ + dmnt's cheat VM had a fix for an inversion in opcode behavior.
+ + An issue was fixed in fs.mitm's management of domain object IDs that could lead to system corruption in rare cases.
+ + The Mitm API no longer silently fails when attempting to handle commands passing C descriptors.
+ + On previous atmosphere versions, certain commands to FS would silently fail due to this...
+ + No users reported any visible errors, but it was definitely a problem behind the scenes.
+ + These commands are now handled correctly.
+ + Atmosphere can now display a fatal error screen significantly earlier in the boot process, if things go wrong early on.
+ + The temporary hid mitm will no longer sometimes cause games to fail to detect input.
+ + Mitm Domain object ID management no longer desynchronizes from the host process.
+ + An issue was fixed that could cause service acquisition to hang forever if certain sm commands were called in a precise order.
+ + An off-by-one was fixed that could cause memory corruption in server memory management.
+ + ... and too many more bugs fixed to reasonably list them all :)
++ General system stability improvements to enhance the user's experience.
+
+## 0.9.4
++ Support was added for 9.0.0.
+ + **Please note**: 9.0.0 made a number of changes that may cause some issues with homebrew. Details:
+ + 9.0.0 changed HID in a way that causes libnx to be unable to detect button input.
+ + Homebrew should be recompiled with newest libnx to fix this.
+ + Atmosphere now provides a temporary hid-mitm that will cause homebrew to continue to work as expected.
+ + This mitm will be removed in a future Atmosphere revision once homebrew has been updated, to allow users to use a custom hid mitm again if they desire.
+ + 9.0.0 introduced an dependency in FS on the USB system module in order to launch the SD card.
+ + This means the USB system module must now be launched before the SD card is initialized.
+ + Correspondingly, the USB system module can no longer be IPS patched, and its settings cannot be reliably mitm'd.
+ + We know this is frustrating, so we'll be looking into whether there is some way of addressing this in the future.
++ An off-by-one error was fixed in `boot` system module's pinmux initialization.
+ + This could theoretically have caused issues with HdmiCec communication.
+ + No users reported issues, so it's unclear if this was a problem in practice.
++ A bug was fixed that could cause webapplet launching homebrew to improperly set the accessible url whitelist.
++ BIS key generation has been fixed for newer hardware.
+ + Newer hardware uses new, per-firmware device key to generate BIS keys instead of the first device key, so previously the wrong keys were generated as backup.
+ + This only affects units manufactured after ~5.0.0.
++ General system stability improvements to enhance the user's experience.
+
+## 0.9.3
++ Thanks to hexkyz, fusee's boot sequence has been greatly optimized.
+ + Memory training is now managed by a separate binary (`fusee-mtc`, loaded by fusee-primary before fusee-secondary).
+ + Unnecessarily long splash screen display times were reduced.
+ + The end result is that Atmosphere now boots *significantly* faster. :)
+ + **Note:** This means fusee-primary must be updated for Atmosphere to boot successfully.
++ The version string was adjusted, and now informs users whether or not they are using emummc.
++ Atmosphere now automatically backs up the user's BIS keys on boot.
+ + This should prevent a user from corrupting nand without access to a copy of the keys needed to fix it.
+ + This is especially relevant on ipatched units, where the RCM vulnerability is not an option for addressing bricks.
++ The `pm` system module was rewritten as part of Stratosphere's ongoing refactor.
+ + Support was added for forward-declaring a mitm'd service before a custom user sysmodule is launched.
+ + This should help resolve dependency issues with service registration times.
+ + SM is now informed of every process's title id, including built-in system modules.
++ The `creport` system module was rewritten as part of Stratosphere's ongoing refactor.
+ + creport now dumps up to 0x100 of stack from each thread in the target process.
+ + A few bugs were fixed, including one that caused creport to incorrectly dump process dying messages.
++ Defaults were added to `system_settings.ini` for controlling hbloader's memory usage in applet mode.
+ + These defaults reserve enough memory so that homebrew can launch swkbd while in applet mode.
++ The `fatal` system module was rewritten as part of Stratosphere's ongoing refactor.
+ + Incorrect display output ("2000-0000") has been fixed. Fatal will now correctly show 2162-0002 when this occurs.
+ + A longstanding bug in how fatal manages the displays has been fixed, and official display init behavior is now matched precisely.
++ General system stability improvements to enhance the user's experience.
+
+## 0.9.2
++ A number of emummc bugfixes were added (all thanks to @m4xw's hard work). The following is a summary of emummc changes:
+ + Support for file-based emummc instances was fixed.
+ + Please note: file-based emummc is still unoptimized, and so may be much slower than partition-based.
+ + This speed differential should hopefully be made better in a future emummc update.
+ + The way emummc handles power management was completely overhauled.
+ + Emummc now properly handles init/de-init, and now supports low voltage mode.
+ + Much better support for shutdown was added, which should assuage corruption/synchronization problems.
+ + This should also improve support for more types of SD cards.
+ + A bug was fixed that caused emummc to not work on lower system versions due to missing SVC access.
+ + **Please note**: The configuration entries used for emummc have been changed.
+ + `emummc_` prefixes have been removed, since they are superfluous given the `emummc` category they are under.
+ + As an example, `emummc!emummc_enabled` is now `emummc!enabled`.
+ + INI configurations made by @CTCaer's [tool](https://github.com/ctcaer/hekate/releases/latest) (which is the recommended way to manage emummc) should automatically work as expected/be corrected.
+ + **If you do not wish to use the above, you will need to manually correct your configuration file**.
+ + General system stability improvements to enhance the user's experience.
++ Stratosphere is currently in the process of being re-written/refactored.
+ + Stratosphere was my (SciresM's) first C++ project, ever -- the code written for it a year ago when I was learning C++ is/was of much lower quality than code written more recently.
+ + Code is thus being re-rwitten for clarity/stlye/to de-duplicate functionality, with much being moved into libstratosphere.
+ + Stratosphere will, after the rewrite, globally use the `sts::` namespace -- this should greatly enhancing libstratosphere's ability to provide functionality for system modules.
+ + The rewritten modules consistently have lower memory footprints, and should be easier to maintain going forwards.
+ + The `sm`, `boot`, `spl`, `ro`, and `loader` modules have been tackled so far.
++ General system stability improvements to enhance the user's experience.
+
+## 0.9.1
++ Support was added for 8.1.0.
++ Please note, emummc is still considered **beta/experimental** -- this is not the inevitable bugfix update for it, although some number of bugs have been fixed. :)
++ General system stability improvements to enhance the user's experience.
+
+## 0.9.0
++ Creport output was improved significantly.
+ + Thread names are now dumped on crash in addition to 0x100 of TLS from each thread.
+ + This significantly aids debugging efforts for crashes.
+ + Support was added for 32-bit stackframes, so reports can now be generated for 32-bit games.
++ `dmnt`'s Cheat VM was extended to add a new debug opcode.
++ With thanks to/collaboration with @m4xw and @CTCaer, support was added for redirecting NAND to the SD card (emummc).
+ + Please note, this support is very much **beta/experimental**.
+ + It is quite likely we have not identified all bugs -- those will be fixed as they are reported over the next few days/weeks.
+ + In addition, some niceties (e.g. having a separate Atmosphere folder per emummc instance) still need some thought put in before they can be implemented in a way that makes everyone happy.
+ + If you are not an advanced user, you may wish to think about waiting for the inevitable 0.9.1 bugfix update before using emummc as your default boot option.
+ + You may especially wish to consider waiting if you are using Atmosphere on a unit with the RCM bug patched.
+ + Emummc is managed by editing the emummc section of "emummc/emummc.ini".
+ + To enable emummc, set `emummc!emummc_enabled` = 1.
+ + Support is included for redirecting NAND to a partition on the SD card.
+ + This can be done by setting `emummc!emummc_sector` to the start sector of your partition (e.g., `emummc_sector = 0x1A010000`).
+ + Support is also included for redirecting NAND to a collection of loose files on the SD card.
+ + This can be done by setting `emummc!emummc_path` to the folder (with archive bit set) containing the NAND boot partitions' files "boot0" and "boot1", and the raw NAND image files "00", "01", "02", etc. (single "00" file with the whole NAND image requires exFAT mode while multipart NAND can be used in both exFAT and FAT32 modes).
+ + The `Nintendo` contents directory can be redirected arbitrarily.
+ + By default, it will be redirected to `emummc/Nintendo_XXXX`, where `XXXX` is the hexadecimal representation of the emummc's ID.
+ + The current emummc ID may be selected by changing `emummc!emummc_id` in emummc.ini.
+ + This can be set to any arbitrary directory by setting `emummc!emummc_nintendo_path`.
+ + To create a backup usable for emummc, users may use tools provided by the [hekate](https://github.com/CTCaer/hekate) project.
+ + If, when using emummc, you encounter a bug, *please be sure to report it* -- that's the only way we can fix it. :)
+
+## 0.8.10
++ A bug was fixed that could cause incorrect system memory allocation on 5.0.0.
+ + 5.0.0 should now correctly have an additional 12 MiB allocated for sysmodules.
++ Atmosphère features which check button presses now consider all controllers, isntead of just P1.
++ Support was added for configuring language/region on a per-game basis.
+ + This is managed by editing `atmosphere/titles//config.ini` for the game.
+ + To edit title language, edit `override_config!override_language`.
+ + The languages supported are `ja`, `en-US`, `fr`, `de`, `it`, `es`, `zh-CN`, `ko`, `nl`, `pt`, `ru`, `zh-TW`, `en-GB`, `fr-CA`, `es-419`, `zh-Hans`, `zh-Hant`.
+ + To edit title region, edit `override_config!override_region`.
+ + The regions supported are `jpn`, `usa`, `eur`, `aus`, `chn`, `kor`, `twn`.
++ Atmosphère now provides a reimplementation of the `boot` system module.
+ + `boot` is responsible for performing hardware initialization, showing the Nintendo logo, and repairing NAND on system update failure.
+ + Atmosphère's `boot` implementation preserves AutoRCM during NAND repair.
+ + NAND repair occurs when an unexpected shutdown or error happens during a system update.
+ + This fixes a final edge case where AutoRCM might be removed by HOS, which could cause a user to burn fuses.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.9
++ A number of bugs were fixed, including:
+ + A data abort was fixed when mounting certain partitions on NAND.
+ + All Stratosphère system modules now only maintain a connection to `sm` when actively using it.
+ + This helps mitigate the scenario where sm hits the limit of 64 active connections and crashes.
+ + This sometimes caused crashes when custom non-Atmosphère sysmodules were active and the user played certain games (ex: Smash's Stage Builder).
+ + fatal now uses the 8.0.0 clkrst API, instead of silently failing to adjust clock rates on that firmware version.
+ + A wait loop is now performed when trying to get a session to `sm`, in the case where `sm:` is not yet registered.
+ + This fixes a race condition that could cause a failure to boot under certain circumstances.
+ + libstratosphere's handling of domain object closing has been improved.
+ + Previously, this code could cause crashes/extremely odd behavior (misinterpreting what object a service is) under certain circumstances.
++ An optional automatic reboot timer was added to fatal.
+ + By setting the system setting `atmosphere!fatal_auto_reboot_interval` to a non-zero u64 value, fatal can be made to automatically reboot after a certain number of milliseconds.
+ + If the setting is zero or not present, fatal will wait for user input as usual.
++ Atmosphère now provides a reimplementation of the `ro` system module.
+ + `ro` is responsible for loading dynamic libraries (NROs) on 3.0.0+.
+ + On 1.0.0-2.3.0, this is handled by `loader`.
+ + Atmosphere's `ro` provides this functionality (`ldr:ro`, `ro:dmnt`) on all firmware versions.
+ + An extension was implemented to provide support for applying IPS patches to NROs.
+ + All patches at paths like /atmosphere/nro_patches//.ips will be applied, allowing for easy distribution of patches.
+ + Both the IPS and IPS32 formats are supported.
++ Atmosphère now provides a reimplementation of the `spl` system module.
+ + `spl` (Secure Platform Services) is responsible for cryptographic operations, including all communications with the secure monitor (exosphère).
+ + In the future, this may be used to provide extensions to the API for interacting with exosphère from userland.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.8
++ Support was added for firmware version 8.0.0.
++ Custom exception handlers were added to stratosphere modules.
+ + If a crash happens in a core atmosphere module now, instead of silently failing a reboot will occur to log the information to the SD card.
++ A bug was fixed in creport that caused games to hang when crashing under certain circumstances.
++ A bug was fixed that prevented maintenance mode from booting on 7.0.0+.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.7
++ A few bugs were fixed that could cause fatal to fail to show an error under certain circumstances.
++ A bug was fixed that caused an error when launching certain games (e.g. Hellblade: Senua's Sacrifice).
+ + Loader had support added in ams-0.8.4 for a new (7.0.0+) flag bit in NPDMs during process creation, but forgot to allow this bit to be set when validating the NPDM.
++ dmnt's cheat virtual machine received new instructions.
+ + These allow for saving, restoring, or clearing registers to a secondary bank, effectively doubling the number of values that can be stored.
++ SHA256 code has been swapped from linux code to libnx's new hw-accelerated cryptography API.
++ Extensions were added to smcGetInfo:
+ + A ConfigItem was added to detect whether the current unit has the RCM bug patched.
+ + A ConfigItem was added to retrieve the current Atmosphère build hash.
++ Exosphère now tells the kernel to enable user-mode exception handlers, which should allow for better crash reporting/detection from Atmosphère's modules in the future..
++ Opt-in support was added for redirecting game save files to directories on the SD card.
+ + Please note, this feature is **experimental**, and may cause problems. Please use at your own risk (and back up your saves before enabling it), as it still needs testing.
+ + This can be enabled by setting `atmosphere!fsmitm_redirect_saves_to_sd` to 1 in `system_settings.ini`.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.6
++ A number of bugs were fixed, including:
+ + A case of inverted logic was fixed in fs.mitm which prevented the flags system from working correctly.
+ + Time service access was corrected in both creport/fatal.
+ + This fixes the timestamps used in fatal/crash report filenames.
+ + A coherency issue was fixed in exosphère's Security Engine driver.
+ + This fixes some instability issues encountered when overclocking the CPU.
+ + Loader now unmaps NROs correctly, when ldr:ro is used.
+ + This fixes a crash when repeatedly launching the web applet on < 3.0.0.
+ + Usage of hidKeysDown was corrected to hidKeysHeld in several modules.
+ + This fixes a rare issue where keypresses may have been incorrectly detected.
+ + An issue with code filesystem unmounting was fixed in loader.
+ + This issue could occasionally cause a fatal error 0x1015 to be thrown on boot.
+ + Two bugs were fixed in the implementations of dmnt's cheat virtual machine.
+ + These could cause cheats to work incorrectly under certain circumstances.
+ + PM now uses a static buffer instead of a dynamically allocated one during process launch.
+ + This fixes a memory exhaustion problem when building with gcc 8.3.0.
+ + A workaround for a deadlock bug in Horizon's kernel on >= 6.0.0 was added in dmnt.
+ + This prevents a system hang when booting certain titles with cheats enabled (ex: Mario Kart 8 Deluxe).
+ + set.mitm now reads the system firmware version directly from the system version archive, instead of calling into set:sys.
+ + This fixes compatibility with 1.0.0, which now successfully boots again.
++ dmnt's cheat virtual machine had some instruction set changes.
+ + A new opcode was added for beginning conditional blocks based on register contents.
+ + More addressing modes were added to the StoreRegisterToAddress opcode.
+ + These should allow for more complex cheats to be implemented.
++ A new system for saving the state of cheat toggles between game boots was added.
+ + Toggles are now saved to `atmosphere/titles//cheats/toggles.txt` when either toggles were successfully loaded from that file or the system setting `atmosphere!dmnt_always_save_cheat_toggles` is non-zero.
+ + This removes the need for manually setting cheats from all-on or all-off to the desired state on each game boot.
++ The default behavior for loader's HBL support was changed.
+ + Instead of launching HBL when album is launched without R held, loader now launches HBL when album or any game is launched with R held.
+ + Loader will now override any app in addition to a specific title id when `hbl_config!override_any_app` is true in `loader.ini`.
+ + Accordingly, the `hbl_config!title_id=app` setting was deprecated. Support will be removed in Atmosphère 0.9.0.
++ First-class support was added to loader and fs.mitm for enabling homebrew to launch web applets.
+ + Loader will now cause the "HtmlDocument" NCA path to resolve for whatever title HBL is taking over, even if it would not normally do so.
+ + fs.mitm will also now cause requests to mount the HtmlDocument content for HBL's title to open the `sdmc:/atmosphere/hbl_html` folder.
+ + By default, this just contains a URL whitelist.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.5
++ Support was added for overriding content on a per-title basis, separate from HBL override.
+ + This allows for using mods on the same title that one uses to launch HBL.
+ + By default, `!L` is used for title content override (this is configurable by editing `default_config!override_key` in `loader.ini`)
+ + This key combination can be set on a per-title basis by creating a `atmosphere/titles//config.ini`, and editing `override_config!override_key`.
++ Content headers were added for the embedded files inside of fusee-secondary.
+ + This will allow non-fusee bootloaders (like `hekate`) to extract the components bundled inside release binaries.
+ + This should greatly simplify the update process in the future, for users who do not launch Atmosphère using fusee.
++ Support for cheat codes was added.
+ + These are handled by a new `dmnt` sysmodule, which will also reimplement Nintendo's Debug Monitor in the future.
+ + Cheat codes can be enabled/disabled at application launch via a per-title key combination.
+ + For details, please see the [cheat loading documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/cheats.md#cheat-loating-process).
+ + Cheat codes are fully backwards compatible with the pre-existing format, although a number of bugs have been fixed and some new features have been added.
+ + For details, please see [the compatibility documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/cheats.md#cheat-code-compatibility).
+ + An HIPC service API was added (`dmnt:cht`), that will allow user homebrew to interface with and control Atmosphère's cheat manager.
+ + Please see [the relevant documentation](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/modules/dmnt.md).
+ + Full client code can be found in [libstratosphere](https://github.com/Atmosphere-NX/libstratosphere/blob/master/include/stratosphere/services/dmntcht.h).
+ + Users interested in interfacing should see [EdiZon](https://github.com/WerWolv/EdiZon), which should have support for interfacing with Atmosphère's API shortly after 0.8.5 releases.
++ A bug was fixed that would cause Atmosphère's fatal screen to not show on 1.0.0-2.3.0.
++ A bug was fixed that caused Atmosphère's automatic ProdInfo backups to be corrupt.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.4
++ Support for 7.0.0/7.0.1 was added.
+ + This is facilitated through a new payload, `sept`, which can be signed, encrypted, and then loaded by Nintendo's TSEC firmware.
+ + `sept` will derive the keys needed to boot new firmware, and then load `sept/payload.bin` off the SD card and jump to it.
++ Recognition of applications for override/mitm has been improved.
+ + Nintendo's official Title ID range (`0x0100000000000000`-`0x01FFFFFFFFFFFFFF`) is now enforced.
++ A deadlock condition was fixed involving libstratosphere mitm sysmodules.
++ Kernel patches for JIT support were added (Thanks, @m4xw!).
+ + These loosen restrictions on caller process in svcControlCodeMemory.
++ `set.mitm` and `fs.mitm` were merged into a single `ams_mitm` sysmodule.
+ + This saves a process ID, allowing users to run one additional process up to the 0x40 process limit.
++ A `bpc.mitm` component was added, performing custom behavior on shutdown/reboot requests from `am` or applications.
+ + Performing a reboot from the reboot menu now reboots to atmosphere. This can be configured via `system_settings.ini`.
+ + Performing a shutdown from the reboot menu now works properly with AutoRCM, and does a real shutdown.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.3
++ A custom warmboot firmware was implemented, which does not perform anti-downgrade fuse checks.
+ + This fixes sleep mode when using a downgraded NAND.
+ + This also removes Atmosphère's final dependency on Nintendo's encrypted PK11 binary; all components are now re-implemented.
++ The ExternalContentSource API was changed to not clear on failure.
++ Content override now supports an "app" setting, that causes all applications to be overridden with HBL instead of a specific title.
+ + Note: because override keys are system-wide, using this setting will prevent using mods in games (as every game will be HBL).
++ A bug was fixed causing incorrect fatal-error output when svcBreak was called on 5.0.0+.
++ An extension was added to set.mitm to support customization of system settings.
+ + These are controlled by `atmosphere/system_settings.ini`, see [here](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/modules/set_mitm.md) for documentation.
++ An extension was added to sm, adding a new `sm:dmnt` service.
+ + This can be used by a debug monitor in order to debug the registration state of various other services.
++ A bug was fixed in the MitM API that could sometimes cause a system hang during boot.
++ A change was made to the MitM API: in cases where sm would have returned 0xE15 when installing a mitm service, it now defers the result (following GetService semantics).
++ Support for booting into maintenance mode by holding +/- was added to PM.
++ An extension was added to exosphere, adding a custom SMC that allows for DMA to IRAM.
++ In addition, smcGetConfig was extended to reboot to a payload in IRAM at 0x40010000 when ConfigItem 65001 is set to 2.
+ + Fatal will now use this to reboot to sdmc:/atmosphere/reboot_payload.bin if present, when a vol button is pressed.
+ + An example homebrew ("reboot_to_payload") was also written and is now included with Atmosphère.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.2
++ A number of bugs were fixed causing users to sometimes see `Key Derivation Failed!`.
+ + KFUSE clock enable timings have been adjusted to allow time to stabilize before TSEC is granted access.
+ + A race condition was fixed that could cause wrong key data to be used on 6.2.0
+ + The TSEC firmware is now retried on failure, fixing a failure affecting ~1/50 boots on 6.2.0.
++ A bug was fixed causing some modules to not work on firmware 1.0.0.
++ A bug was fixed causing sleep mode to not work with debugmode enabled.
+ + As a result, debugmode is now enabled in the default BCT.ini.
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.1
++ A bug was fixed causing users to see `Failed to enable SMMU!` if fusee had previously rebooted.
+ + This message will still occur sporadically if fusee is not launched from coldboot, but it can never happen twice in a row.
++ A race condition was fixed in Atmosphere `bis_protect` functionality that could cause NS to be able to overwrite BCT public keys.
+ + This sometimes broke AutoRCM protection, the current fix has been tested on hardware and verified to work.
++ Support was added for enabling `debugmode` based on the `exosphere` section of `BCT.ini`:
+ + Setting `debugmode = 1` will cause exosphere to tell the kernel that debugmode is active.
+ + Setting `debugmode_user = 1` will cause exosphere to tell userland that debugmode is active.
+ + These are completely independent of one another, allowing fine control of system behavior.
++ Support was added for `nogc` functionality; thanks to @rajkosto for the patches.
+ + By default, `nogc` patches will automatically apply if the user is booting into 4.0.0+ with fuses from <= 3.0.2.
+ + Users can override this functionality via the `nogc` entry in the `stratosphere` section of `BCT.ini`:
+ + Setting `nogc = 1` will force enable `nogc` patches.
+ + Setting `nogc = 0` will force disable `nogc` patches.
+ + If patches are enabled but not found for the booting system, a fatal error will be thrown.
+ + This should prevent running FS without `nogc` patches after updating to an unsupported system version.
++ An extension was added to `exosphere` allowing userland applications to cause the system to reboot into RCM:
+ + This is done by calling smcSetConfig(id=65001, value=); user homebrew can use splSetConfig for this.
++ On fatal error, the user can now choose to perform a standard reboot via the power button, or a reboot into RCM via either volume button.
++ A custom message was added to `fatal` for when an Atmosphère API version mismatch is detected (2495-1623).
++ General system stability improvements to enhance the user's experience.
+
+## 0.8.0
++ A custom `fatal` system module was added.
+ + This re-implements and extends Nintendo's fatal module, with the following features:
+ + Atmosphère's `fatal` does not create error reports.
+ + Atmosphère's `fatal` draws a custom error screen, showing registers and a backtrace.
+ + Atmosphère's `fatal` attempts to gather debugging info for all crashes, and not just ones that include info.
+ + Atmosphère's `fatal` will attempt saving reports to the SD, if a crash report was not generated by `creport`.
++ Title flag handling was changed to prevent folder clutter.
+ + Instead of living in `atmosphere/titles//%s.flag`, flags are now located in `atmosphere/titles//flags/%s.flag`
+ + The old format will continue to be supported for some time, but is deprecated.
+ + Flags can now be applied to HBL by placing them at `atmosphere/flags/hbl_%s.flag`.
++ Changes were made to the mitm API, greatly improving caller semantics.
+ + `sm` now informs mitm services of a new session's process id, enabling custom handling based on title id/process id.
++ smhax is no longer enabled, because it is no longer needed and breaks significant functionality.
+ + Users with updated HBL/homebrew should see no observable differences due to this change.
++ Functionality was added implementing basic protections for NAND from userland homebrew:
+ + BOOT0 now has write protection for the BCT public key and keyblob regions.
+ + The `ns` sysmodule is no longer allowed to write the BCT public keys; all other processes can.
+ + This should prevent system updates from removing AutoRCM.
+ + No processes should be allowed to write to the keyblob region.
+ + By default, BIS partitions other than BOOT0 are now read-only, and CAL0 is neither readable nor writable.
+ + Adding a `bis_write` flag for a title will allow it to write to BIS.
+ + Adding a `cal_read` flag for a title will allow it to read CAL0.
+ + An automatic backup is now made of CAL0 on boot.
+ + `fs.mitm` maintains a file handle to this backup, so userland software cannot read it.
+ + To facilitate this, `fs.mitm` now mitms all sessions for non-system modules; content overriding has been made separate from service interception.
+ + Please note: these protections are basic, and sufficiently malicious homebrew ++can defeat them++.
+ + Please be careful to only run homebrew software from sources that you trust.
++ A bug involving HDCP titles crashing on newer firmwares was fixed.
++ Support was added for system version 6.2.0; our thanks to @motezazer for his invaluable help.
+ + By default, new keys will automatically be derived without user input.
+ + Support is also present for loading new keys from `atmosphere/prod.keys` or `atmosphere/dev.keys`
++ General system stability improvements to enhance the user's experience.
+
+## 0.7.5
++ DRAM training was added to fusee-secondary, courtesy @hexkyz.
+ + This greatly improves the speed of memory accesses during boot, resulting in a boot time that is ~200-400% faster.
++ creport has had its code region detection improved.
+ + Instead of only checking one of the crashing thread's PC/LR for code region presence, creport now checks both + every address in the stacktrace. This is also now done for every thread.
+ + This matches the improvement Nintendo added to official creport in 6.1.0.
+ + The code region detection heuristic was further improved by checking whether an address points to .rodata or .rwdata, instead of just .text.
+ + This means that a crash appears in a loaded NRO (or otherwise discontiguous) code region, creport will be able to detect all active code regions, and not just that one.
+
+## 0.7.4
++ [libstratosphere](https://github.com/Atmosphere-NX/libstratosphere) has been completely refactored/rewritten, and split into its own, separate submodule.
+ + While this is mostly "under the hood" for end-users, the refactor is faster (improving both boot-time and runtime performance), more accurate (many of the internal IPC structures are now bug-for-bug compatible with Nintendo's implementations), and significantly more stable (it fixes a large number of bugs present in the old library).
+ + The refactored API is significantly cleaner and easier to write system module code for, which should improve/speed up development of stratosphere.
+ + Developers looking to write their own custom system modules for the Switch can now easily include libstratosphere as a submodule in their projects.
++ Loader was extended to add a new generic way to redirect content (ExternalContentSources), courtesy @misson20000:
+ + A new command was added to ldr:shel, taking in a tid to redirect and returning a session handle.
+ + When the requested TID is loading, Loader will query the handle as though it were an IFileSystem.
+ + This allows clients to generically define their own filesystems, and override content with them in loader.
++ fs.mitm has gotten several optimizations that should improve its performance and stability:
+ + RomFS redirection now only occurs when there is content to redirect, even if the title is being mitm'd elsewhere.
+ + A cache is now maintained of the active data storage, if any, for all opened title IDs. This means if two processes both try to open the same archive, fs.mitm won't duplicate any of its work.
+ + RomFS metadata is now cached to the SD card on build instead of being persisted in memory -- this greatly reduces memory footprint and allows fs.mitm to redirect more titles simultaneously than before.
++ A number of bugs were fixed, including:
+ + A resource leak was fixed in process creation. This fixes crashes that occur when a large number (>32) games have been launched since the last reboot.
+ + fs.mitm no longer errors when receiving a zero-sized buffer. This fixes crashes in some games, including The Messenger.
+ + Multi-threaded server semantics should no longer cause deadlocks in certain circumstances. This fixes crashes in some games, including NES Classics.
+ + PM now only gives full FS permissions to the active KIPs. This fixes a potential crash where new processes might be unable to be registered with FS.
++ The `make dist` target now includes the branch in the generated zip name.
++ General system stability improvements to enhance the user's experience.
+
+## 0.7.3
++ Loader and fs.mitm now try to reload loader.ini before reading it. This allows for changing the override button combination/HBL title id at runtime.
++ Added a MitM between set:sys and qlaunch, used to override the system version string displayed in system settings.
+ + The displayed system version will now display ` (AMS ..)`.
++ General system stability improvements to enhance the user's experience.
+
+## 0.7.2
++ Fixed a bug in fs.mitm's LayeredFS read implementation that caused some games to crash when trying to read files.
++ Fixed a bug affecting 1.0.0 that caused games to crash with fatal error 2001-0106 on boot.
++ Improved filenames output by the make dist target.
++ General system stability improvements to enhance the user's experience.
+
+## 0.7.1
++ Fixed a bug preventing consoles on 4.0.0-4.1.0 from going to sleep and waking back up.
++ Fixed a bug preventing consoles on < 4.0.0 from booting without specific KIPs on the SD card.
++ An API was added to Atmosphère's Service Manager for deferring acquisition of all handles for specific services until after early initialization is completed.
++ General system stability improvements to enhance the user's experience.
+
+## 0.7.0
++ First official release of Atmosphère.
++ Supports the following featureset:
+ + Fusée, a custom bootloader.
+ + Supports loading/customizing of arbitrary KIPs from the SD card.
+ + Supports loading a custom kernel from the SD card ("/atmosphere/kernel.bin").
+ + Supports compile-time defined kernel patches on a per-firmware basis.
+ + All patches at paths like /atmosphere/kip_patches//.ips will be applied to the relevant KIPs, allowing for easy distribution of patches supporting multiple versions.
+ + Both the IPS and IPS32 formats are supported.
+ + All patches at paths like /atmosphere/kernel_patches//.ips will be applied to the kernel, allowing for easy distribution of patches supporting multiple versions.
+ + Both the IPS and IPS32 formats are supported.
+ + Configurable by editing BCT.ini on the SD card.
+ + Atmosphère should also be launchable by the alternative hekate bootloader, for those who prefer it.
+ + Exosphère, a fully-featured custom secure monitor.
+ + Exosphere is a re-implementation of Nintendo's TrustZone firmware, fully replicating all of its features.
+ + In addition, it has been extended to provide information on current Atmosphere API version, for homebrew wishing to make use of it.
+ + Stratosphère, a set of custom system modules. This includes:
+ + A loader system module.
+ + Reimplementation of Nintendo's loader, fully replicating all original functionality.
+ + Configurable by editing /atmosphere/loader.ini
+ + First class support for the Homebrew Loader.
+ + An exefs NSP (default "/atmosphere/hbl.nsp") will be used in place of the victim title's exefs.
+ + By default, HBL will replace the album applet, but any application should also be supported.
+ + Extended to support arbitrary redirection of executable content to the SD card.
+ + Files will be preferentially loaded from /atmosphere/titles//exefs/, if present.
+ + Files present in the original exefs a user wants to mark as not present may be "stubbed" by creating a .stub file on the SD.
+ + If present, a PFS0 at /atmosphere/titles//exefs.nsp will fully replace the original exefs.
+ + Redirection is optionally toggleable by holding down certain buttons (by default, holding R disables redirection).
+ + Full support for patching NSO content is implemented.
+ + All patches at paths like /atmosphere/exefs_patches//.ips will be applied, allowing for easy distribution of patches supporting multiple firmware versions and/or titles.
+ + Both the IPS and IPS32 formats are supported.
+ + Extended to support launching content from loose executable files on the SD card, without requiring any official installation.
+ + This is done by specifying FsStorageId_None on launch.
+ + A service manager system module.
+ + Reimplementation of Nintendo's service manager, fully replicating all original functionality.
+ + Compile-time support for reintroduction of "smhax", allowing clients to optionally skip service access verification by skipping initialization.
+ + Extended to allow homebrew to acquire more handles to privileged services than Nintendo natively allows.
+ + Extended to add a new API for installing Man-In-The-Middle listeners for arbitrary services.
+ + API can additionally be used to safely detect whether a service has been registered in a non-blocking way with no side-effects.
+ + Full API documentation to come.
+ + A process manager system module.
+ + Reimplementation of Nintendo's process manager, fully replicating all original functionality.
+ + Extended to allow homebrew to acquire handles to arbitrary processes, and thus read/modify system memory without blocking execution.
+ + Extended to allow homebrew to retrieve information about system resource limits.
+ + Extended by embedding a full, extended implementation of Nintendo's boot2 system module.
+ + Title launch order has been optimized in order to grant access to the SD card faster.
+ + The error-collection system module is intentionally not launched, preventing many system telemetry error reports from being generated at all.
+ + Users may place their own custom sysmodules on the SD card and flag them for automatic boot2 launch by creating a /atmosphere/titles//boot2.flag file on their SD card.
+ + A custom fs.mitm system module.
+ + Uses Atmosphère's MitM API in order to provide an easy means for users to modify game content.
+ + Intercepts all FS commands sent by games, with special handling for commands used to mount RomFS/DLC content to enable easy creation and distribution of game/DLC mods.
+ + fs.mitm will parse the base RomFS image for a game, a RomFS image located at /atmosphere/titles//romfs.bin, and all loose files in /atmosphere/titles//romfs/, and merge them together into a single RomFS image.
+ + When merging, loose files are preferred to content in the SD card romfs.bin image, and files from the SD card image are preferred to those in the base image.
+ + Can additionally be used to intercept commands sent by arbitrary system titles (excepting those launched before SD card is active), by creating a /atmosphere/titles//fsmitm.flag file on the SD card.
+ + Can be forcibly disabled for any title, by creating a /atmosphere/titles//fsmitm_disable.flag file on the SD card.
+ + Redirection is optionally toggleable by holding down certain buttons (by default, holding R disables redirection).
+ + A custom crash report system module.
+ + Serves as a drop-in replacement for Nintendo's own creport system module.
+ + Generates detailed, human-readable reports on system crashes, saving to /atmosphere/crash_reports/_.log.
+ + Because reports are not sent to the erpt sysmodule, this disables all crash report related telemetry.
+ + General system stability improvements to enhance the user's experience.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/detail/exosphere_memory_layout.txt b/Source/Atmosphere-MTC-Unlock/docs/components/detail/exosphere_memory_layout.txt
new file mode 100644
index 00000000..e5ebfcc0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/detail/exosphere_memory_layout.txt
@@ -0,0 +1,103 @@
+exosphere, storage requirements:
+
+Nonvolatile memory:
+0xE000
+
+Volatile memory: 0x2000
+
+Physical Address Space:
+-0x7C010000-0x7C012000 - boot code/volatile memory
+-0x7C012000-0x7C01E000 - program region
+-0x7C01E000-0x7C01F000 - global data/context
+-0x7C01F000-0x7C020000 - L2/L3 page table
+
+-0x7C020000-0x7C040000 - Mariko-only program region
+-0x7C040000-0x7C048000 - Mariko-only program stack
+-0x7C048000-0x7C050000 - Reserved Mariko TZRAM (SE context carveouts, etc)
+
+Virtual Address Space:
+
+L1: 0x40 bytes.
+
+L1 Entries:
+ -0 (0x000000000-0x040000000): Empty
+ -1 (0x040000000-0x080000000): Identity Mapping/Empty
+ -2 (0x080000000-0x0C0000000): DRAM Mapping/Empty
+ -3 (0x0C0000000-0x100000000): DRAM Mapping/Empty
+ -4 (0x100000000-0x140000000): Empty
+ -5 (0x140000000-0x180000000): Empty
+ -6 (0x180000000-0x1C0000000): Empty
+ -7 (0x1C0000000-0x200000000): Virtual Region
+
+L2 Page and L3 page are both0x7C01F000
+
+L2 Entries:
+ -0x040000000 (Identity IRAM Table)
+ - Entry Used: 0x000
+ -0x07C000000 (Identity TZRAM Table)
+ - Entry Used: 0x1E0
+ -0x1F0000000 (Virtual Region Table)
+ - Entry Used: 0x180
+
+L3 Entries:
+ - Identity TZRAM mapping (0x7C010000-0x7C020000)
+ - Entry Used: 0x010-0x01F
+ - Identity IRAM mapping (0x40020000-0x40040000)
+ - Entry Used: 0x020-0x03F
+ - Virtual Device region (0x1F0040000-0x1F0080000)
+ - Entry Used: 0x040-0x07F
+ - Read Only TZRAM Alias (0x1F00A0000-0x1F00B0000)
+ - Entry Used: 0x0A0-0x0AF
+ - Program region (0x1F00C0000-0x1F00CC000)
+ - Entry Used: 0x0C0-0x0CB
+ - Mariko Program region (0x1F00D0000-0x1F00F0000)
+ - Entry Used: 0x0D0-0x0EF
+ - Mariko Program stack (0x1F00F4000-0x1F00FC000)
+ - Entry Used: 0x0F4-0x0FB
+ - Secure DRAM Storage (0x1F0100000-0x1F0110000)
+ - Entry Used: 0x100-0x10F
+ - Debug DRAM Storage (0x1F0110000-0x1F0114000)
+ - Entry Used: 0x110-0x113
+ - SC7 IRAM Work Space (0x1F0120000-0x1F0130000)
+ - Entry Used: 0x120-0x12F
+ - SC7 IRAM Firmware (0x1F0140000-0x1F0141000)
+ - Entry Used: 0x140-0x140
+ - Debug Code (0x1F0150000-0x1F0154000)
+ - Entry Used: 0x150-0x153
+ - Reserved For Debug (0x1F0160000-0x1F0170000)
+ - Entry Used: 0x160-0x16F
+ - Boot Code (0x1F01C0000-0x1F01C2000)
+ - Entry Used: 0x1C0-0x1C1
+ - AMS IRAM Page (0x1F01F2000-0x1F01F2000)
+ - Entry Used: 0x1F2-0x1F2
+ - AMS User Page (0x1F01F4000-0x1F01F4000)
+ - Entry Used: 0x1F4-0x1F4
+ - SMC User Page (0x1F01F6000-0x1F01F6000)
+ - Entry Used: 0x1F6-0x1F6
+ - Volatile (Data) (0x1F01F8000-0x1F01F9000)
+ - Entry Used: 0x1F8-0x1F8
+ - Volatile (Stacks) (0x1F01FA000-0x1F01FB000)
+ - Entry Used: 0x1FA-0x1FA
+ - Global Data (0x1F01FC000-0x1F01FD000)
+
+
+NV Global Data needs:
+ Exosphere + Emummc Config (<=0x200)
+ Boot Config[0x400]
+ RSA Context(0x100)
+ Old Device Keys[0x20][0x10];
+ Old Master Keys[0x20][0x10];
+ Imported Rsa Keys[4][0x200];
+ CPU Ctx[4][0x100];
+
+Total: 0x1700
+
+Global Data Page (accessible via X18):
+ 0x000-0x200: Exosphere Config
+ 0x200-0x400: Emummc Config
+ 0x400-0x800: Sealed AES Keys
+ 0x800-0xC00: Boot Config
+ 0xC00-0xFFF: CPU contexts. Can be replaced, but this fits exactly so minimizes program space waste.
+
+Volatile Global Data needs:
+ Random Cache 0x400 bytes
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/emummc.md b/Source/Atmosphere-MTC-Unlock/docs/components/emummc.md
new file mode 100644
index 00000000..6ae69b9c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/emummc.md
@@ -0,0 +1,4 @@
+# emummc
+emummc is a collaborative project that provides eMMC storage emulation.
+
+Please refer to the project's repository [here](https://github.com/m4xw/emuMMC) for detailed instructions and documentation.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/exosphere.md b/Source/Atmosphere-MTC-Unlock/docs/components/exosphere.md
new file mode 100644
index 00000000..1b7f963d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/exosphere.md
@@ -0,0 +1,76 @@
+# exosphère
+exosphère is a customized reimplementation of the Horizon OS's Secure Monitor.
+The Secure Monitor follows the same design principle as Arm's TrustZone and both terms can be used interchangeably in this context. It runs at the highest privilege mode (EL3) available to the main processor and is responsible for all the sensitive cryptographic operations needed by the system as well as power management for each CPU.
+
+## Extensions
+exosphère expands the original Secure Monitor design by providing custom SMCs (Secure Monitor Calls) necessary to the homebrew ecosystem. Currently, these are:
+```
+uint32_t smc_ams_iram_copy(smc_args_t *args);
+uint32_t smc_ams_write_address(smc_args_t *args);
+uint32_t smc_ams_get_emummc_config(smc_args_t *args);
+```
+
+Additionally, exosphère expands the functionality of two SMCs provided by the Horizon OS for getting/setting configuration items. The following custom configuration items are provided by exosphère:
+```
+CONFIGITEM_EXOSPHERE_VERSION = 65000,
+CONFIGITEM_NEEDS_REBOOT = 65001,
+CONFIGITEM_NEEDS_SHUTDOWN = 65002,
+CONFIGITEM_EXOSPHERE_VERHASH = 65003,
+CONFIGITEM_HAS_RCM_BUG_PATCH = 65004,
+CONFIGITEM_SHOULD_BLANK_PRODINFO = 65005,
+CONFIGITEM_ALLOW_CAL_WRITES = 65006,
+```
+
+### smc_ams_iram_copy
+This function implements a copy of up to one page between DRAM and IRAM. Its arguments are:
+```
+args->X[1] = DRAM address (translated by kernel), must be 4-byte aligned.
+args->X[2] = IRAM address, must be 4-byte aligned.
+args->X[3] = Size (must be <= 0x1000 and 4-byte aligned).
+args->X[4] = 0 for read, 1 for write.
+```
+
+### smc_ams_write_address
+This function implements a write to a DRAM page. Its arguments are:
+```
+args->X[1] = Virtual address, must be size-bytes aligned and readable by EL0.
+args->X[2] = Value.
+args->X[3] = Size (must be 1, 2, 4, or 8).
+```
+
+### smc_ams_get_emummc_config
+This function retrieves configuration for the current [emummc](emummc.md) context. Its arguments are:
+```
+args->X[1] = MMC id, must be size-bytes aligned and readable by EL0.
+args->X[2] = Pointer to output (for paths for filebased + nintendo dir), must be at least 0x100 bytes.
+```
+
+### CONFIGITEM_EXOSPHERE_VERSION
+This custom configuration item gets information about the current exosphere version.
+
+### CONFIGITEM_NEEDS_REBOOT
+This custom configuration item is used to issue a system reboot into RCM or into a warmboot payload leveraging a secondary vulnerability to achieve code execution from warm booting.
+
+### CONFIGITEM_NEEDS_SHUTDOWN
+This custom configuration item is used to issue a system shutdown with a warmboot payload leveraging a secondary vulnerability to achieve code execution from warm booting.
+
+### CONFIGITEM_EXOSPHERE_VERHASH
+This custom configuration item gets information about the current exosphere git commit hash.
+
+### CONFIGITEM_HAS_RCM_BUG_PATCH
+This custom configuration item gets whether the unit has the CVE-2018-6242 vulnerability patched.
+
+### CONFIGITEM_SHOULD_BLANK_PRODINFO
+This custom configuration item gets whether the unit should simulate a "blanked" PRODINFO. See [here](../features/configurations.md) for more information.
+
+### CONFIGITEM_ALLOW_CAL_WRITES
+This custom configuration item gets whether the unit should allow writing to the calibration partition.
+
+## lp0fw
+This is a small, built-in payload that is responsible for waking up the system during a warm boot.
+
+## sc7fw
+This is a small, built-in payload that is responsible for putting the system to sleep during a warm boot.
+
+## rebootstub
+This is a small, built-in payload that provides functionality to reboot the system into any payload of choice.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/fusee.md b/Source/Atmosphere-MTC-Unlock/docs/components/fusee.md
new file mode 100644
index 00000000..709facd6
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/fusee.md
@@ -0,0 +1,8 @@
+# fusée
+fusée is a custom bootloader used to start the Atmosphère environment.
+
+## fusée
+fusée is the first piece of Atmosphère's code that runs on the hardware.
+It is distributed as a standalone payload designed to be launched via RCM by abusing the CVE-2018-6242 vulnerability.
+
+This payload is responsible for all the low-level hardware initialization required by the Nintendo Switch, setting up the cryptosystem, mounting/emulating the eMMC, injecting/patching system modules, and launching the exosphère component.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/libraries.md b/Source/Atmosphere-MTC-Unlock/docs/components/libraries.md
new file mode 100644
index 00000000..c3840a68
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/libraries.md
@@ -0,0 +1,11 @@
+# libraries
+This is a collection of libraries for doing operating system development for the Nintendo Switch.
+
+## libmesosphere
+libmesosphere is a work-in-progress C++ library implementing functionality for the Horizon Kernel.
+
+## libstratosphere
+libstratosphere is a work-in-progress C++ library for development of system modules for the Nintendo Switch.
+
+## libvapours
+Common boilerplate code for various purposes.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/mesosphere.md b/Source/Atmosphere-MTC-Unlock/docs/components/mesosphere.md
new file mode 100644
index 00000000..a56af4ba
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/mesosphere.md
@@ -0,0 +1,3 @@
+# mesosphère
+mesosphère is a work in progress customized kernel reimplementation.
+The Horizon OS's kernel follows microkernel design principles and runs at the EL1 level. It is currently subdivided into a loader (kernel_ldr) and the main kernel code.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/ams_mitm.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/ams_mitm.md
new file mode 100644
index 00000000..743f4542
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/ams_mitm.md
@@ -0,0 +1,40 @@
+# ams_mitm
+This module provides methods to intercept services provided by other system modules. It is further sub-divided according to the service it targets.
+
+## bpc_mitm
+bpc_mitm enables intercepting requests to power control services. It currently intercepts:
++ `am` system module (to intercept the Reboot/Power buttons in the overlay menu)
++ `fatal` system module (to simplify payload reboot logic significantly)
++ [nx-hbloader](https://github.com/switchbrew/nx-hbloader) (to allow homebrew to take advantage of the feature)
+
+## fs_mitm
+fs_mitm enables intercepting file system operations. It can deny, delay, replace, or redirect any request made to the file system. It enables LayeredFS to function, which allows for replacement of game assets.
+
+## hid_mitm
+hid_mitm enables intercepting requests to controller device services. It is currently disabled by default. If enabled, it intercepts:
++ [nx-hbloader](https://github.com/switchbrew/nx-hbloader) (to help homebrew not need to be recompiled due to a breaking change introduced in the past)
+
+Note that hid_mitm is currently deprecated and might be removed entirely in the future.
+
+## ns_mitm
+ns_mitm enables intercepting requests to application control services. It currently intercepts:
++ Web Applets (to facilitate nx-hbloader web browser launching)
+
+## set_mitm
+set_mitm enables intercepting requests to the system settings service. It currently intercepts:
++ `ns` system module and games (to allow for overriding game locales)
++ All firmware debug settings requests (to allow modification of system settings not directly exposed to the user)
+
+### Firmware Version
+set_mitm intercepts the `GetFirmwareVersion` command, if the requester is `qlaunch` or `maintenance`.
+It modifies the `display_version` field of the returned system version, causing the version to display
+in settings as `#.#.#|AMS #.#.#|?` with `? = S` when running under system eMMC or `? = E` when running under emulated eMMC. This allows users to easily verify what version of Atmosphère and what eMMC environment they are running.
+
+### System Settings
+set_mitm intercepts the `GetSettingsItemValueSize` and `GetSettingsItemValue` commands for all requesters.
+It does so in order to enable user configuration of system settings, which are parsed from `/atmosphere/system_settings.ini` on boot. See [here](../../features/configurations.md) for more information on the system settings format.
+
+## dns_mitm
+dns_mitm enables intercepting requests to dns resolution services, to enable redirecting requests for specified hostnames.
+
+For documentation, see [here](../../features/dns_mitm.md).
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/boot.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/boot.md
new file mode 100644
index 00000000..c0005584
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/boot.md
@@ -0,0 +1,4 @@
+# boot
+This module is a reimplementation of the Horizon OS's `boot` system module, which is responsible for initializing and configuring hardware.
+
+Atmosphère's reimplementation displays its own black and white splash screen and battery icons as replacements for the original assets used during display initialization.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/boot2.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/boot2.md
new file mode 100644
index 00000000..4b7e6f99
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/boot2.md
@@ -0,0 +1,4 @@
+# boot2
+This module is a reimplementation of the Horizon OS's `boot2` system module, which is responsible for launching all the other necessary system modules.
+
+Atmosphère's reimplementation allows launching user provided system modules from the SD card. See [here](../../features/configurations.md) for more information.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/creport.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/creport.md
new file mode 100644
index 00000000..0517840d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/creport.md
@@ -0,0 +1,4 @@
+# creport
+This module is a reimplementation of the Horizon OS's `creport` system module, which is responsible for managing crash reports.
+
+Atmosphère's reimplementation redirects writing of generated crash reports to the SD card under the folder `/atmosphere/crash_reports/`. It also prevents the automatic uploading of said crash reports.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/dmnt.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/dmnt.md
new file mode 100644
index 00000000..aa9f5fa7
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/dmnt.md
@@ -0,0 +1,42 @@
+# dmnt
+This module is a reimplementation of the Horizon OS's `dmnt` system module, which provides a debug monitor.
+
+## Extensions
+Atmosphère implements an extension to provide cheat code functionality.
+
+### Cheat Service
+A HIPC service API is provided for interacting with the cheat code manager through the service `dmnt:cht`. See [here](../../features/cheats.md) for more information on the cheat code format.
+
+The SwIPC definition for `dmnt:cht` follows:
+```
+interface ams::dmnt::cheat::CheatService is dmnt:cht {
+ [65000] HasCheatProcess() -> sf::Out out;
+ [65001] GetCheatProcessEvent() -> sf::OutCopyHandle out_event;
+ [65002] GetCheatProcessMetadata() -> sf::Out out_metadata;
+ [65003] ForceOpenCheatProcess();
+ [65004] PauseCheatProcess();
+ [65005] ResumeCheatProcess();
+
+ [65100] GetCheatProcessMappingCount() -> sf::Out out_count;
+ [65101] GetCheatProcessMappings(u64 offset) -> sf::OutArray &mappings, sf::Out out_count;
+ [65102] ReadCheatProcessMemory(u64 address, u64 out_size) -> sf::OutBuffer &buffer;
+ [65103] WriteCheatProcessMemory(sf::InBuffer &buffer, u64 address, u64 in_size);
+ [65104] QueryCheatProcessMemory(u64 address) -> sf::Out mapping;
+
+ [65200] GetCheatCount() -> sf::Out out_count;
+ [65201] GetCheats(u64 offset) -> sf::OutArray &cheats, sf::Out out_count;
+ [65202] GetCheatById(u32 cheat_id) -> sf::Out cheat;
+ [65203] ToggleCheat(u32 cheat_id);
+ [65204] AddCheat(CheatDefinition &cheat, bool enabled) -> sf::Out out_cheat_id;
+ [65205] RemoveCheat(u32 cheat_id);
+ [65206] ReadStaticRegister(u8 which) -> sf::Out out;
+ [65207] WriteStaticRegister(u8 which, u64 value);
+ [65208] ResetStaticRegisters();
+
+ [65300] GetFrozenAddressCount() -> sf::Out out_count;
+ [65301] GetFrozenAddresses(u64 offset) ->sf::OutArray &addresses, sf::Out out_count;
+ [65302] GetFrozenAddress(u64 address) -> sf::Out entry;
+ [65303] EnableFrozenAddress(u64 address, u64 width) -> sf::Out out_value;
+ [65304] DisableFrozenAddress(u64 address);
+}
+```
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/eclct.stub.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/eclct.stub.md
new file mode 100644
index 00000000..c33df9e3
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/eclct.stub.md
@@ -0,0 +1,4 @@
+# eclct.stub
+This module is a reimplementation of the Horizon OS's `eclct` system module, which collects error reports.
+
+Atmosphère's reimplementation is a stub to remove any and all functionality pertaining to error report collection.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/erpt.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/erpt.md
new file mode 100644
index 00000000..dd8e459f
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/erpt.md
@@ -0,0 +1,4 @@
+# erpt
+This module is a reimplementation of the Horizon OS's `erpt` system module, which is responsible for managing error reports.
+
+Atmosphère's reimplementation redirects writing of generated error reports to the SD card under the folder `/atmosphere/erpt_reports/`.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/fatal.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/fatal.md
new file mode 100644
index 00000000..63e8ed1a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/fatal.md
@@ -0,0 +1,4 @@
+# fatal
+This module is a reimplementation of the Horizon OS's `fatal` system module, which is responsible for managing fatal reports.
+
+Atmosphère's reimplementation prevents error report creation and draws a custom error screen, showing registers and a backtrace. It also attempts to gather debugging info for any and all crashes and tries to save reports to the SD card under the folder `/atmosphere/fatal_reports/`.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/jpegdec.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/jpegdec.md
new file mode 100644
index 00000000..fcc0d3ab
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/jpegdec.md
@@ -0,0 +1,4 @@
+# jpegdec
+This module is a reimplementation of the Horizon OS's `jpegdec` system module, which is responsible for JPEG format decoding.
+
+Atmosphère's reimplementation allows two sessions instead of 1, so homebrew can use it for software JPEG decoding in addition to the OS itself.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/loader.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/loader.md
new file mode 100644
index 00000000..7d3800d8
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/loader.md
@@ -0,0 +1,106 @@
+# loader
+This module is a reimplementation of the Horizon OS's `ldr` system module, which is responsible for creating processes from executable NSO images and registering their access control.
+
+## Extensions
+Atmosphère extends this module to allow executables to be replaced or patched by files stored on the SD card. Note that a few services are required for SD card access and therefore cannot be replaced or patched in this manner.
+
+### Exefs Replacement
+Atmosphère's reimplementation allows replacing executable files in the file system.
+
+#### Partition Replacement
+It is possible to replace the full exefs partition at once with a PFS0 file. In that case, Atmosphère will load the following file:
+```
+/atmosphere/contents//exefs.nsp
+```
+
+#### File Replacement
+When a process is created, loader will search for several NSO filenames in the program's exefs directory.
+These filenames are, in this order:
+ - rtld
+ - main
+ - subsdk0
+ - subsdk1
+ - ...
+ - subsdk9
+ - sdk
+
+Each NSO that is found will be loaded into the process contiguously. The process's entrypoint is at the first NSO to be loaded, usually `rtld` or `main`.
+
+Additionally, when a process is loaded, loader will search for a `main.npdm` file in the exefs directory specifying the program's permissions.
+
+Atmosphère extends this functionality by also searching for these files on the SD card. When searching for a file, loader will first check if it exists on the SD card. If it does, that file will be used instead. Otherwise, it will use the copy located in the exefs, if that is present. The following directory will be searched:
+```
+/atmosphere/contents//exefs/
+```
+
+This allows the replacement of applets, system modules, or even games with homebrew versions.
+
+##### File Stubbing
+In order to prevent an NSO from being loaded even if it exists in the exefs, loader will also check if a stub file exists. If such a file exists, the NSO will not be loaded. The files should be named like `rtld.stub`, `main.stub`, etc. and may be empty.
+
+##### Technical Semantics
+
+loader's semantics for content override can (as you may observe from reading the above) be complicated to understand. The following is an abbreviated description of the very technical semantics by which loader decides what content to read when trying to read a file for a program id.
+
+* If an external content filesystem exists for the program id, the external content filesystem is used directly with no further redirection.
+* Otherwise, if the program ID is being overridden with [nx-hbloader](https://github.com/switchbrew/nx-hbloader/releases) (see Homebrew Support below), the nsp filesystem for hbl is used directly with no further redirection.
+* Otherwise, if content redirection is enabled for the program ID (controlled by a configurable button combination) and a loose file exists on the SD card, the loose file is used.
+* Otherwise, if a stub file exists, a "Not Found" error is returned.
+* Otherwise, if an SD card executable filesystem ("exefs.nsp") exists, it is used without further redirection.
+* Finally, the "real"/base code file system is used without further redirection.
+
+In addition, there are a few other technical details relevant to Atmosphere's redirection:
+* When overriding with nx-hbloader, the real code filesystem must exist. When "main.npdm" (a program capabilities descriptor file) is read, the content from the real code filesystem is read in order to determine whether an applet or an application is being overridden. This allows nx-hbloader to automatically support both applet and application environments.
+* When overriding applications, the real code filesystem must exist and contain valid content. This is required to perform accurate-to-Nintendo content verification procedures.
+* When programs are launched, both a program id and a "storage id" are specified by the launch requester. When the storage id specified is "none" (normally always invalid), Atmosphere assumes that a custom system module is attempting to be launched. This removes the aforementioned requirement on base content validity; the above procedure is still used to determine how to redirect content, however reads to the "real"/base code file system may return "Not Found" errors if the real/base code file system does not exist.
+
+### NSO Patching
+When an NSO is loaded, Atmosphère's reimplementation will search for IPS patch files on the SD card in the following locations.
+```
+/atmosphere/exefs_patches//.ips
+```
+
+This organization allows patch sets affecting multiple NSOs to be distributed as a single directory and also allows patches from multiple patch sets to be stacked. Patches will be searched for in each patch set directory. The name of each patch file should match the hexadecimal build ID of the NSO to affect, except that trailing zero bytes may be left off. Because the NSO build ID is unique for every NSO, this means patches will only apply to the files they are meant to apply to.
+
+Patch files are accepted in either IPS format or IPS32 format.
+
+Because NSO files are compressed, patch files are not made between the original version of a compressed NSO and the modified version of such an NSO. Instead, they are made between the uncompressed version of an NSO and the modified (and still uncompressed) version of that NSO. This also means that a patch file cannot be manually applied to the compressed version of an NSO; it must be applied to the uncompressed version. Atmosphère's reimplementation will correctly apply these patches while loading the process regardless of whether the NSO it finds is compressed or not.
+
+When authoring patches, [hactool](https://github.com/SciresM/hactool) can be used to find an NSO's build ID and to uncompress NSOs. Recent versions of the [ReSwitched IDA loaders](https://github.com/reswitched/loaders) can be used to load uncompressed NSOs into IDA in such a way that you can [apply patches to the input file](https://www.hex-rays.com/products/ida/support/idadoc/1618.shtml). From there, any IPS tool can be used to create the patch between the original NSO and the patched NSO. Note that if the NSO you are patching is larger than 16 MiB, you will have to use a tool that supports IPS32.
+
+### Homebrew Support
+Atmosphère provides first class support for [nx-hbloader](https://github.com/switchbrew/nx-hbloader/releases) and [nx-hbmenu](https://github.com/switchbrew/nx-hbmenu/releases).
+
+Launching of the nx-hbloader process is controlled by configurable button inputs. See [here](../../features/configurations.md) for more detailed information.
+
+In addition, loader has extensions to enable homebrew to launch web applets. This normally requires the application launching the applet to have HTML Manual content inside an installed NCA. Atmosphère's reimplementation will automatically ensure that the commands used to check this succeed, and will redirect the relevant file system to the `/atmosphere/hbl_html/` subdirectory.
+
+### IPC Commands
+Atmosphère's reimplementation extends the HIPC loader services' API with several custom commands.
+
+The SwIPC definition for the `ldr:pm` extension commands follows:
+```
+interface ams::ldr::pm::ProcessManagerInterface is ldr:pm {
+ ...
+ [65000] AtmosphereHasLaunchedProgram(ncm::ProgramId program_id) -> sf::Out out;
+ [65001] AtmosphereGetProgramInfo(ncm::ProgramLocation &loc) -> sf::Out out_program_info, sf::Out out_status;
+ [65002] AtmospherePinProgram(ncm::ProgramLocation &loc, cfg::OverrideStatus &override_status) -> sf::Out out_id;
+}
+```
+
+The SwIPC definition for the `ldr:dmnt` extension commands follows:
+```
+interface ams::ldr::dmnt::DebugMonitorInterface is ldr:dmnt {
+ ...
+ [65000] AtmosphereHasLaunchedProgram(ncm::ProgramId program_id) -> sf::Out out;
+}
+```
+
+The SwIPC definition for the `ldr:shel` extension commands follows:
+```
+interface ams::ldr::shell::ShellInterface is ldr:shel {
+ ...
+ [65000] AtmosphereRegisterExternalCode(ncm::ProgramId program_id) -> sf::OutMoveHandle out;
+ [65001] AtmosphereUnregisterExternalCode(ncm::ProgramId program_id);
+}
+```
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/ncm.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/ncm.md
new file mode 100644
index 00000000..89bfab95
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/ncm.md
@@ -0,0 +1,4 @@
+# ncm
+This module is a reimplementation of the Horizon OS's `ncm` system module, which is responsible for content management.
+
+Atmosphère's reimplementation is currently opt-in only. See [here](../../features/configurations.md) for more information.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/pgl.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/pgl.md
new file mode 100644
index 00000000..305594b2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/pgl.md
@@ -0,0 +1,4 @@
+# pgl
+This module is a reimplementation of the Horizon OS's `pgl` system module, which is responsible for launching programs and was introduced by firmware version `10.0.0`.
+
+Currently, Atmosphère's reimplementation doesn't backport this module's functionalities to firmware versions lower than `10.0.0`.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/pm.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/pm.md
new file mode 100644
index 00000000..cc52a460
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/pm.md
@@ -0,0 +1,30 @@
+# pm
+This module is a reimplementation of the Horizon OS's `pm` system module, which is responsible for tracking running processes on the system, and managing resource limits.
+
+## Extensions
+Atmosphère extends this module with extra IPC commands and memory restriction changes.
+
+### IPC Commands
+Atmosphère's reimplementation extends the HIPC loader services' API with several custom commands.
+
+The SwIPC definition for the `pm:dmnt` extension commands follows:
+```
+interface ams::pm::dmnt::DebugMonitorServiceBase is pm:dmnt {
+ ...
+ [65000] AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out out_loc, sf::Out out_status;
+ [65001] AtmosphereGetCurrentLimitInfo(u32 group, u32 resource) -> sf::Out out_cur_val, sf::Out out_lim_val;
+}
+```
+
+The SwIPC definition for the `pm:info` extension commands follows:
+```
+interface ams::pm::info::InformationService is pm:info {
+ ...
+ [65000] AtmosphereGetProcessId(ncm::ProgramId program_id) -> sf::Out out;
+ [65001] AtmosphereHasLaunchedProgram(ncm::ProgramId program_id) -> sf::Out out;
+ [65002] AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::Out out_loc, sf::Out out_status;
+}
+```
+
+### Extra System Memory
+Atmosphère's reimplementation shrinks the APPLET memory pool by 24 MiB by default, giving this memory to the SYSTEM pool. This allows custom system modules to use more memory without hitting the SYSTEM memory limit.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/ro.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/ro.md
new file mode 100644
index 00000000..25d967aa
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/ro.md
@@ -0,0 +1,16 @@
+# ro
+This module is a reimplementation of the Horizon OS's `ro` system module, which is responsible for loading dynamic libraries and was introduced by firmware version `3.0.0`.
+
+Atmosphère's reimplementation backports this module's functionalities to firmware versions lower than `3.0.0` where said functionalities were provided by the `ldr` system module instead.
+
+## Extensions
+Atmosphère extends this module to allow libraries to be patched by files stored on the SD card.
+
+### NRO Patching
+When an NRO is loaded, Atmosphère's reimplementation will search for IPS patch files on the SD card in the following locations.
+```
+/atmosphere/nro_patches//.ips
+```
+This organization allows patch sets affecting multiple NROs to be distributed as a single directory. Patches will be searched for in each patch set directory. The name of each patch file should match the hexadecimal build ID of the NRO to affect, except that trailing zero bytes may be left off. Because the NRO build ID is unique for every NRO, this means patches will only apply to the files they are meant to apply to.
+
+Patch files are accepted in either IPS format or IPS32 format.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/sm.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/sm.md
new file mode 100644
index 00000000..bba1712d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/sm.md
@@ -0,0 +1,47 @@
+# sm
+This module is a reimplementation of the Horizon OS's `sm` system module, which is responsible for service management.
+
+## Extensions
+Atmosphère extends this module with extra IPC commands and new services.
+
+### Debug Monitor
+Atmosphère's reimplementation provides an interface `sm:dmnt` to allow a debug monitor to query the service manager's state.
+
+The SwIPC definition for `sm:dmnt` follows:
+```
+interface ams::sm::DmntService is sm:dmnt {
+ [65000] AtmosphereGetRecord(ServiceName service) -> sf::Out record;
+ [65001] AtmosphereListRecords(u64 offset) -> sf::OutArray &records, sf::Out out_count;
+ [65002] AtmosphereGetRecordSize() -> sf::Out record_size;
+}
+```
+
+### IPC Commands
+Atmosphère's reimplementation extends the HIPC loader services' API with several custom commands.
+
+The SwIPC definition for the `sm:` extension commands follows:
+```
+interface ams::sm::UserService is sm: {
+ ...
+ [65000] AtmosphereInstallMitm(ServiceName service) -> sf::OutMoveHandle srv_h, sf::OutMoveHandle qry_h;
+ [65001] AtmosphereUninstallMitm(ServiceName service);
+ [65002] Deprecated_AtmosphereAssociatePidTidForMitm();
+ [65003] AtmosphereAcknowledgeMitmSession(ServiceName service) -> sf::Out client_info, sf::OutMoveHandle fwd_h;
+ [65004] AtmosphereHasMitm(ServiceName service) -> sf::Out out;
+ [65005] AtmosphereWaitMitm(ServiceName service);
+ [65006] AtmosphereDeclareFutureMitm(ServiceName service);
+
+ [65100] AtmosphereHasService(ServiceName service) -> sf::Out out;
+ [65101] AtmosphereWaitService(ServiceName service);
+}
+```
+
+The SwIPC definition for the `sm:m` extension commands follows:
+```
+interface ams::sm::ManagerService is sm:m {
+ ...
+ [65000] AtmosphereEndInitDefers(os::ProcessId process_id, sf::InBuffer &acid_sac, sf::InBuffer &aci_sac);
+ [65001] AtmosphereHasMitm(ServiceName service) -> sf::Out out;
+ [65002] AtmosphereRegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, sf::InBuffer &acid_sac, sf::InBuffer &aci_sac);
+}
+```
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/modules/spl.md b/Source/Atmosphere-MTC-Unlock/docs/components/modules/spl.md
new file mode 100644
index 00000000..ec1605a4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/modules/spl.md
@@ -0,0 +1,2 @@
+# spl
+This module is a reimplementation of the Horizon OS's `spl` system module, which is responsible for providing secure platform services such as cryptographic operations.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/stratosphere.md b/Source/Atmosphere-MTC-Unlock/docs/components/stratosphere.md
new file mode 100644
index 00000000..8eafc141
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/stratosphere.md
@@ -0,0 +1,21 @@
+# stratosphère
+stratosphère provides customization of the Horizon OS at the system level. This includes a reimplementation of several system modules and additional, custom system modules that extend or add a variety of features.
+
+## Modules
+The modules currently provided by stratosphère are:
++ [ams_mitm](modules/ams_mitm.md)
++ [boot](modules/boot.md)
++ [boot2](modules/boot2.md)
++ [creport](modules/creport.md)
++ [dmnt](modules/dmnt.md)
++ [eclct.stub](modules/eclct.stub.md)
++ [erpt](modules/erpt.md)
++ [fatal](modules/fatal.md)
++ [jpegdec](modules/jpegdec.md)
++ [loader](modules/loader.md)
++ [ncm](modules/ncm.md)
++ [pgl](modules/pgl.md)
++ [pm](modules/pm.md)
++ [ro](modules/ro.md)
++ [sm](modules/sm.md)
++ [spl](modules/spl.md)
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/thermosphere.md b/Source/Atmosphere-MTC-Unlock/docs/components/thermosphere.md
new file mode 100644
index 00000000..ad75fb25
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/thermosphere.md
@@ -0,0 +1,3 @@
+# thermosphère
+thermosphère is a work in progress hypervisor implementation.
+This aims to provide functionality at the EL2 level which remains unused by the Horizon OS.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/components/troposphere.md b/Source/Atmosphere-MTC-Unlock/docs/components/troposphere.md
new file mode 100644
index 00000000..91b62afc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/components/troposphere.md
@@ -0,0 +1,5 @@
+# troposphère
+troposphère provides customization of the Horizon OS at the application level.
+
+## reboot_to_payload
+Sample application to perform a system reboot into a payload of choice.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/faq.md b/Source/Atmosphere-MTC-Unlock/docs/faq.md
new file mode 100644
index 00000000..b2906bdc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/faq.md
@@ -0,0 +1,18 @@
+# Frequently Asked Questions
+
+This document serves as a place to store answers for common questions received about Atmosphère.
+
+## What does "June 15th" mean?
+When Atmosphère began development in February 2018, "June 15" was given as the estimate/target date for a first release, to coincide with the planned disclosure of a vulnerability.
+
+This deadline was missed, hard.
+
+People made rather a lot of fun of me (SciresM) for this.
+
+Several months later, when the first Atmosphère release occurred, I captioned it "Happy June 15th!" and pretended like I hadn't missed the first deadline.
+
+This amused me a lot, and so the practice has been kept up for every single release since.
+
+Depending on who you ask, you may be told that this is a dumb joke and it is not funny.
+
+This is incorrect. It is definitely a dumb joke, but it is also hilarious.
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/docs/features/cheats.md b/Source/Atmosphere-MTC-Unlock/docs/features/cheats.md
new file mode 100644
index 00000000..465ded94
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/features/cheats.md
@@ -0,0 +1,512 @@
+# Cheats
+Atmosphère supports Action-Replay style cheat codes, with cheats loaded off of the SD card.
+
+## Cheat Loading Process
+By default, Atmosphère will do the following when deciding whether to attach to a new application process:
+
++ Retrieve information about the new application process from `pm` and `loader`.
++ Check whether a user-defined key combination is held, and stop if not.
+ + This defaults to "L is not held", but can be configured with override keys.
+ + The ini key to configure this is `cheat_enable_key`.
++ Check whether the process is a real application, and stop if not.
+ + This guards against applying cheat codes to the Homebrew Loader.
++ Attempt to load cheats from `/atmosphere/contents//cheats/.txt`, where `build_id` is the hexadecimal representation of the first 8 bytes of the application's main executable's build id.
+ + If no cheats are found, then the cheat manager will stop.
++ Open a kernel debug session for the new application process.
++ Signal to a system event that a new cheat process has been attached to.
+
+This behavior ensures that cheat codes are only loaded when the user would want them to.
+
+In cases where `dmnt` has not activated the cheat manager, but the user wants to make it do so anyway, the cheat manager's service API provides a `ForceOpenCheatProcess` command that homebrew can use. This command will cause the cheat manager to try to force itself to attach to the process.
+
+In cases where `dmnt` has activated the cheat manager, but the user wants to use an alternate debugger, the cheat manager's service API provides a `ForceCloseCheatProcess` command that homebrew can use. This command will cause the cheat manager to detach itself from the process.
+
+By default, all cheat codes listed in the loaded .txt file will be toggled on. This is configurable by the user by editing the `atmosphere!dmnt_cheats_enabled_by_default` [system setting](configurations.md).
+
+Users may use homebrew programs to toggle cheats on and off at runtime via the cheat manager's service API.
+
+## Cheat Code Compatibility
+Atmosphère manages cheat code through the execution of a small, custom virtual machine. Care has been taken to ensure that Atmosphère's cheat code format is fully backwards compatible with the pre-existing cheat code format, though new features have been added and bugs in the pre-existing cheat code applier have been fixed. Here is a short summary of the changes from the pre-existing format:
+
++ A number of bugs were fixed in the processing of conditional instructions.
+ + The pre-existing implementation was fundamentally broken, and checked for the wrong value when detecting the end of a conditional block.
+ + The pre-existing implementation also did not properly decode instructions, and instead linearly scanned for the terminator value. This caused problems if an instruction happened to encode a terminator inside its immediate values.
+ + The pre-existing implementation did not bounds check, and thus certain conditional cheat codes could cause it to read out-of-bounds memory, and potentially crash due to a data abort.
++ Support was added for nesting conditional blocks.
++ An instruction was added to perform much more complex arbitrary arithmetic on two registers.
++ An instruction was added to allow writing the contents of register to a memory address specified by another register.
++ The pre-existing implementation did not correctly synchronize with the application process, and thus would cause heavy lag under certain circumstances (especially around loading screens). This has been fixed in Atmosphère's implementation.
+
+## Cheat Code Format
+The following provides documentation of the instruction format for the virtual machine used to manage cheat codes.
+
+Typically, instruction type is encoded in the upper nybble of the first instruction u32.
+
+### Code Type 0x0: Store Static Value to Memory
+Code type 0x0 allows writing a static value to a memory address.
+
+#### Encoding
+`0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV)`
+
++ T: Width of memory write (1, 2, 4, or 8 bytes).
++ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
++ R: Register to use as an offset from memory region base.
++ A: Immediate offset to use from memory region base.
++ V: Value to write.
+
+---
+
+### Code Type 0x1: Begin Conditional Block
+Code type 0x1 performs a comparison of the contents of memory to a static value.
+
+If the condition is not met, all instructions until the appropriate End or Else conditional block terminator are skipped.
+
+#### Encoding
+`1TMCXrAA AAAAAAAA VVVVVVVV (VVVVVVVV)`
+
++ T: Width of memory read (1, 2, 4, or 8 bytes).
++ M: Memory region to read from (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
++ C: Condition to use, see below.
++ X: Operand Type, see below.
++ r: Offset Register (operand types 1).
++ A: Immediate offset to use from memory region base.
++ V: Value to compare to.
+
+#### Conditions
++ 1: >
++ 2: >=
++ 3: <
++ 4: <=
++ 5: ==
++ 6: !=
+
+#### Operand Type
++ 0: Memory Base + Relative Offset
++ 1: Memory Base + Offset Register + Relative Offset
+---
+
+### Code Type 0x2: End Conditional Block
+Code type 0x2 marks the end of a conditional block (started by Code Type 0x1 or Code Type 0x8).
+
+When an Else is executed, all instructions until the appropriate End conditional block terminator are skipped.
+
+#### Encoding
+`2X000000`
+
++ X: End type (0 = End, 1 = Else).
+
+---
+
+### Code Type 0x3: Start/End Loop
+Code type 0x3 allows for iterating in a loop a fixed number of times.
+
+#### Start Loop Encoding
+`300R0000 VVVVVVVV`
+
++ R: Register to use as loop counter.
++ V: Number of iterations to loop.
+
+#### End Loop Encoding
+`310R0000`
+
++ R: Register to use as loop counter.
+
+---
+
+### Code Type 0x4: Load Register with Static Value
+Code type 0x4 allows setting a register to a constant value.
+
+#### Encoding
+`400R0000 VVVVVVVV VVVVVVVV`
+
++ R: Register to use.
++ V: Value to load.
+
+---
+
+### Code Type 0x5: Load Register with Memory Value
+Code type 0x5 allows loading a value from memory into a register, either using a fixed address or by dereferencing the destination register.
+
+#### Load From Fixed Address Encoding
+`5TMR00AA AAAAAAAA`
+
++ T: Width of memory read (1, 2, 4, or 8 bytes).
++ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
++ R: Register to load value into.
++ A: Immediate offset to use from memory region base.
+
+#### Load from Register Address Encoding
+`5T0R10AA AAAAAAAA`
+
++ T: Width of memory read (1, 2, 4, or 8 bytes).
++ R: Register to load value into. (This register is also used as the base memory address).
++ A: Immediate offset to use from register R.
+
+#### Load from Register Address Encoding
+`5T0R2SAA AAAAAAAA`
+
++ T: Width of memory read (1, 2, 4, or 8 bytes).
++ R: Register to load value into.
++ S: Register to use as the base memory address.
++ A: Immediate offset to use from register R.
+
+#### Load From Fixed Address Encoding with offset register
+`5TMR3SAA AAAAAAAA`
+
++ T: Width of memory read (1, 2, 4, or 8 bytes).
++ M: Memory region to write to (0 = Main NSO, 1 = Heap, 2 = Alias, 3 = Aslr, 4 = non-relative).
++ R: Register to load value into.
++ S: Register to use as offset register.
++ A: Immediate offset to use from memory region base.
+---
+
+### Code Type 0x6: Store Static Value to Register Memory Address
+Code type 0x6 allows writing a fixed value to a memory address specified by a register.
+
+#### Encoding
+`6T0RIor0 VVVVVVVV VVVVVVVV`
+
++ T: Width of memory write (1, 2, 4, or 8 bytes).
++ R: Register used as base memory address.
++ I: Increment register flag (0 = do not increment R, 1 = increment R by T).
++ o: Offset register enable flag (0 = do not add r to address, 1 = add r to address).
++ r: Register used as offset when o is 1.
++ V: Value to write to memory.
+
+---
+
+### Code Type 0x7: Legacy Arithmetic
+Code type 0x7 allows performing arithmetic on registers.
+
+However, it has been deprecated by Code type 0x9, and is only kept for backwards compatibility.
+
+#### Encoding
+`7T0RC000 VVVVVVVV`
+
++ T: Width of arithmetic operation (1, 2, 4, or 8 bytes).
++ R: Register to apply arithmetic to.
++ C: Arithmetic operation to apply, see below.
++ V: Value to use for arithmetic operation.
+
+#### Arithmetic Types
++ 0: Addition
++ 1: Subtraction
++ 2: Multiplication
++ 3: Left Shift
++ 4: Right Shift
+
+---
+
+### Code Type 0x8: Begin Keypress Conditional Block
+Code type 0x8 enters or skips a conditional block based on whether a key combination is pressed.
+
+#### Encoding
+`8kkkkkkk`
+
++ k: Keypad mask to check against, see below.
+
+Note that for multiple button combinations, the bitmasks should be ORd together.
+
+#### Keypad Values
+Note: This is the direct output of `hidKeysDown()`.
+
++ 0000001: A
++ 0000002: B
++ 0000004: X
++ 0000008: Y
++ 0000010: Left Stick Pressed
++ 0000020: Right Stick Pressed
++ 0000040: L
++ 0000080: R
++ 0000100: ZL
++ 0000200: ZR
++ 0000400: Plus
++ 0000800: Minus
++ 0001000: Left
++ 0002000: Up
++ 0004000: Right
++ 0008000: Down
++ 0010000: Left Stick Left
++ 0020000: Left Stick Up
++ 0040000: Left Stick Right
++ 0080000: Left Stick Down
++ 0100000: Right Stick Left
++ 0200000: Right Stick Up
++ 0400000: Right Stick Right
++ 0800000: Right Stick Down
++ 1000000: SL
++ 2000000: SR
+
+---
+
+### Code Type 0x9: Perform Arithmetic
+Code type 0x9 allows performing arithmetic on registers.
+
+#### Register Arithmetic Encoding
+`9TCRS0s0`
+
++ T: Width of arithmetic operation (1, 2, 4, or 8 bytes).
++ C: Arithmetic operation to apply, see below.
++ R: Register to store result in.
++ S: Register to use as left-hand operand.
++ s: Register to use as right-hand operand.
+
+#### Immediate Value Arithmetic Encoding
+`9TCRS100 VVVVVVVV (VVVVVVVV)`
+
++ T: Width of arithmetic operation (1, 2, 4, or 8 bytes).
++ C: Arithmetic operation to apply, see below.
++ R: Register to store result in.
++ S: Register to use as left-hand operand.
++ V: Value to use as right-hand operand.
+
+#### Arithmetic Types
++ 0: Addition
++ 1: Subtraction
++ 2: Multiplication
++ 3: Left Shift
++ 4: Right Shift
++ 5: Logical And
++ 6: Logical Or
++ 7: Logical Not (discards right-hand operand)
++ 8: Logical Xor
++ 9: None/Move (discards right-hand operand)
++ 10: Float Addition, T==4 single T==8 double
++ 11: Float Subtraction, T==4 single T==8 double
++ 12: Float Multiplication, T==4 single T==8 double
++ 13: Float Division, T==4 single T==8 double
+---
+
+### Code Type 0xA: Store Register to Memory Address
+Code type 0xA allows writing a register to memory.
+
+#### Encoding
+`ATSRIOxa (aaaaaaaa)`
+
++ T: Width of memory write (1, 2, 4, or 8 bytes).
++ S: Register to write to memory.
++ R: Register to use as base address.
++ I: Increment register flag (0 = do not increment R, 1 = increment R by T).
++ O: Offset type, see below.
++ x: Register used as offset when O is 1, Memory type when O is 3, 4 or 5.
++ a: Value used as offset when O is 2, 4 or 5.
+
+#### Offset Types
++ 0: No Offset
++ 1: Use Offset Register
++ 2: Use Fixed Offset
++ 3: Memory Region + Base Register
++ 4: Memory Region + Relative Address (ignore address register)
++ 5: Memory Region + Relative Address + Offset Register
+
+---
+
+### Code Type 0xB: Reserved
+Code Type 0xB is currently reserved for future use.
+
+---
+
+### Code Type 0xC-0xF: Extended-Width Instruction
+Code Types 0xC-0xF signal to the VM to treat the upper two nybbles of the first dword as instruction type, instead of just the upper nybble.
+
+This reserves an additional 64 opcodes for future use.
+
+---
+
+### Code Type 0xC0: Begin Register Conditional Block
+Code type 0xC0 performs a comparison of the contents of a register and another value. This code support multiple operand types, see below.
+
+If the condition is not met, all instructions until the appropriate conditional block terminator are skipped.
+
+#### Encoding
+```
+C0TcSX##
+C0TcS0Ma aaaaaaaa
+C0TcS1Mr
+C0TcS2Ra aaaaaaaa
+C0TcS3Rr
+C0TcS400 VVVVVVVV (VVVVVVVV)
+C0TcS5X0
+```
+
++ T: Width of memory write (1, 2, 4, or 8 bytes).
++ c: Condition to use, see below.
++ S: Source Register.
++ X: Operand Type, see below.
++ M: Memory Type (operand types 0 and 1).
++ R: Address Register (operand types 2 and 3).
++ a: Relative Address (operand types 0 and 2).
++ r: Offset Register (operand types 1 and 3).
++ X: Other Register (operand type 5).
++ V: Value to compare to (operand type 4).
+
+#### Operand Type
++ 0: Memory Base + Relative Offset
++ 1: Memory Base + Offset Register
++ 2: Register + Relative Offset
++ 3: Register + Offset Register
++ 4: Static Value
++ 5: Other Register
+
+#### Conditions
++ 1: >
++ 2: >=
++ 3: <
++ 4: <=
++ 5: ==
++ 6: !=
+
+---
+
+### Code Type 0xC1: Save or Restore Register
+Code type 0xC1 performs saving or restoring of registers.
+
+#### Encoding
+`C10D0Sx0`
+
++ D: Destination index.
++ S: Source index.
++ x: Operand Type, see below.
+
+#### Operand Type
++ 0: Restore register
++ 1: Save register
++ 2: Clear saved value
++ 3: Clear register
+
+---
+
+### Code Type 0xC2: Save or Restore Register with Mask
+Code type 0xC2 performs saving or restoring of multiple registers using a bitmask.
+
+#### Encoding
+`C2x0XXXX`
+
++ x: Operand Type, see below.
++ X: 16-bit bitmask, bit i == save or restore register i.
+
+#### Operand Type
++ 0: Restore register
++ 1: Save register
++ 2: Clear saved value
++ 3: Clear register
+
+---
+
+### Code Type 0xC3: Read or Write Static Register
+Code type 0xC3 reads or writes a static register with a given register.
+
+#### Encoding
+`C3000XXx`
+
++ XX: Static register index, 0x00 to 0x7F for reading or 0x80 to 0xFF for writing.
++ x: Register index.
+
+---
+
+### Code Type 0xC4: Begin Extended Keypress Conditional Block
+Code type 0xC4 enters or skips a conditional block based on whether a key combination is pressed.
+
+#### Encoding
+`C4r00000 kkkkkkkk kkkkkkkk`
+
++ r: Auto-repeat, see below.
++ kkkkkkkkkk: Keypad mask to check against output of `hidKeysDown()`.
+
+Note that for multiple button combinations, the bitmasks should be OR'd together.
+
+#### Auto-repeat
+
++ 0: The conditional block executes only once when the keypad mask matches. The mask must stop matching to reset for the next trigger.
++ 1: The conditional block executes as long as the keypad mask matches.
+
+#### Keypad Values
+Note: This is the direct output of `hidKeysDown()`.
+
++ 00000000 00000001: A
++ 00000000 00000002: B
++ 00000000 00000004: X
++ 00000000 00000008: Y
++ 00000000 00000010: Left Stick Pressed
++ 00000000 00000020: Right Stick Pressed
++ 00000000 00000040: L
++ 00000000 00000080: R
++ 00000000 00000100: ZL
++ 00000000 00000200: ZR
++ 00000000 00000400: Plus
++ 00000000 00000800: Minus
++ 00000000 00001000: Left
++ 00000000 00002000: Up
++ 00000000 00004000: Right
++ 00000000 00008000: Down
++ 00000000 00010000: Left Stick Left
++ 00000000 00020000: Left Stick Up
++ 00000000 00040000: Left Stick Right
++ 00000000 00080000: Left Stick Down
++ 00000000 00100000: Right Stick Left
++ 00000000 00200000: Right Stick Up
++ 00000000 00400000: Right Stick Right
++ 00000000 00800000: Right Stick Down
++ 00000000 01000000: SL Left Joy-Con
++ 00000000 02000000: SR Left Joy-Con
++ 00000000 04000000: SL Right Joy-Con
++ 00000000 08000000: SR Right Joy-Con
++ 00000000 10000000: Top button on Poké Ball Plus (Palma) controller
++ 00000000 20000000: Verification
++ 00000000 40000000: B button on Left NES/HVC controller in Handheld mode
++ 00000000 80000000: Left C button in N64 controller
++ 00000001 00000000: Up C button in N64 controller
++ 00000002 00000000: Right C button in N64 controller
++ 00000004 00000000: Down C button in N64 controller
+
+### Code Type 0xF0: Double Extended-Width Instruction
+Code Type 0xF0 signals to the VM to treat the upper three nybbles of the first dword as instruction type, instead of just the upper nybble.
+
+This reserves an additional 16 opcodes for future use.
+
+---
+
+### Code Type 0xFF0: Pause Process
+Code type 0xFF0 pauses the current process.
+
+#### Encoding
+`FF0?????`
+
+---
+
+### Code Type 0xFF1: Resume Process
+Code type 0xFF1 resumes the current process.
+
+#### Encoding
+`FF1?????`
+
+---
+
+### Code Type 0xFFF: Debug Log
+Code type 0xFFF writes a debug log to the SD card under the folder `/atmosphere/cheat_vm_logs/`.
+
+#### Encoding
+```
+FFFTIX##
+FFFTI0Ma aaaaaaaa
+FFFTI1Mr
+FFFTI2Ra aaaaaaaa
+FFFTI3Rr
+FFFTI4X0
+```
+
++ T: Width of memory write (1, 2, 4, or 8 bytes).
++ I: Log id.
++ X: Operand Type, see below.
++ M: Memory Type (operand types 0 and 1).
++ R: Address Register (operand types 2 and 3).
++ a: Relative Address (operand types 0 and 2).
++ r: Offset Register (operand types 1 and 3).
++ X: Value Register (operand type 4).
+
+#### Operand Type
++ 0: Memory Base + Relative Offset
++ 1: Memory Base + Offset Register
++ 2: Register + Relative Offset
++ 3: Register + Offset Register
++ 4: Register Value
diff --git a/Source/Atmosphere-MTC-Unlock/docs/features/configurations.md b/Source/Atmosphere-MTC-Unlock/docs/features/configurations.md
new file mode 100644
index 00000000..084a11a1
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/features/configurations.md
@@ -0,0 +1,125 @@
+# Configurations
+Atmosphère provides a variety of customizable configurations to better adjust to users' needs.
+
+## stratosphere.ini
+This is the configuration file used by fusée for configuring user-space system modules.
+This file is located under the `/atmosphere/config/` folder on your SD card and a default template can be found inside the `/atmosphere/config_templates/` folder.
+
+### Configuring "nogc" Protection
+"nogc" is a feature provided by fusée-secondary which disables the Nintendo Switch's Game Card reader. Its purpose is to prevent the reader from being updated when the console has been updated, without burning fuses, from a lower firmware version. More specifically, from firmware versions 4.0.0 or 9.0.0 which introduced updates to the Game Card reader's firmware. By default, Atmosphère will protect the Game Card reader automatically, but you are free to change it.
+
+To change its functionality, add the following line to the `stratosphere` section and change the value of `X` according to the following list:
+```
+[stratosphere]
+nogc = X
+```
+```
+1 = force-enable nogc, so Atmosphère will always disable the Game Card reader.
+0 = force-disable nogc, so Atmosphère will always enable the Game Card reader.
+```
+
+## Adding a Custom Boot Splashscreen
+Atmosphère provides its own default splashscreen which is displayed at boot time. However, this can be replaced at will.
+
+Boot splash screens must be 1280x720 resolution.
+
+A script can be found inside the source tree (`/utilities/insert_splash_screen.py`) for inserting a custom splash screen into a release binary.
+
+To do so, execute the following command on the script:
+`python insert_splash_screen.py `
+
+## emummc.ini
+This is the configuration file used for the [emummc](../components/emummc.md) component.
+This file is located under the `/emuMMC/` folder on your SD card.
+
+Please refer to the project's repository [here](https://github.com/m4xw/emuMMC) for detailed instructions and documentation.
+
+## exosphere.ini
+This is the configuration file used by exosphère.
+This file is located in the root of your SD card and a default template can be found inside the `/atmosphere/config_templates/` folder.
+
+### Configuring Debugging Modes
+By default, Atmosphère signals to the Horizon kernel that debugging is enabled while leaving usermode debugging disabled, but this can cause undesirable side-effects. If you wish to change this behavior, go to the `exosphere` section and change the value of `X` according to the following list.
+```
+[exosphere]
+debugmode = X
+debugmode_user = X
+```
+```
+1 = enable
+0 = disable
+```
+
+### Blanking PRODINFO
+Atmosphère provides a way for users to blank their factory installed calibration data (known as PRODINFO) in either emulated or system eMMC environments. You can find more detailed information on this inside the respective template file. Usage of this configuration is not encouraged.
+
+## override_config.ini
+This file is located under the `/atmosphere/config/` folder on your SD card and a default template can be found inside the `/atmosphere/config_templates/` folder.
+
+### Overrides Format
+Overrides are parsed from the `/atmosphere/config/override_config.ini` file during the boot process.
+
+By default `override_config.ini` is not configured. It can be used to select the behavior of certain buttons and bind them to functionalities such as launching the Homebrew Menu or enabling the cheat code manager.
+
+You can modify the override_key entries in `override_config.ini` with this list of valid buttons:
+| Formal Name | .ini Name |
+| ----------- | --------- |
+| A Button | A |
+| B Button | B |
+| X Button | X |
+| Y Button | Y |
+| Left Stick | LS |
+| Right Stick | RS |
+| L Button | L |
+| R Button | R |
+| ZL Button | ZL |
+| ZR Button | ZR |
+| + Button | PLUS |
+| - Button | MINUS |
+| Left Dpad | DLEFT |
+| Up Dpad | DUP |
+| Right Dpad | DRIGHT |
+| Down Dpad | DDOWN |
+| SL Button | SL |
+| SR Button | SR |
+
+To invert the behavior of the override key, place an exclamation point in front of whatever button you wish to use. It will launch the actual game while holding down that button, instead of going into the Homebrew Menu. For example, `override_key=!R` will run the game only while holding down R when launching it, otherwise it will boot into the Homebrew Menu. Afterwards you may reinsert your SD card into your Switch and boot into Atmosphère as you normally would. You should now be able to boot into the Homebrew Menu by launching your designated program of choice.
+
+## system_settings.ini
+This file is located under the `/atmosphere/config/` folder on your SD card and a default template can be found inside the `/atmosphere/config_templates/` folder.
+
+### Settings Format
+Atmosphère provides a way to override the firmware debug settings used by the system. These can be parsed from the `/atmosphere/config/system_settings.ini` file during the boot process. This file is a normal ini file, with some specific interpretations.
+
+The standard representation of a setting's identifier takes the form `name!key`. This is represented within `system_settings.ini` as a section `name`, with an entry `key`. For example:
+```
+[name]
+key = ...
+```
+
+Settings can have variable types (strings, integral values, byte arrays, etc). To accommodate this, `system_settings.ini` must store values as a `type_identifier!value_store` pair. A number of different types are supported, with identifiers detailed below.
+Please note that a malformed value string will cause a fatal error to occur on boot. A full example of a custom setting is given below (setting `eupld!upload_enabled = 0`), for posterity:
+```
+[eupld]
+upload_enabled = u8!0x0
+```
+
+#### Supported Types
+* Strings
+ * Type identifiers: `str`, `string`
+ * The value string is used directly as the setting, with null terminator appended.
+* Integral types
+ * Type identifiers: `u8`, `u16`, `u32`, `u64`
+ * The value string is parsed via a call to `strtoul(value, NULL, 0)`.
+ * Setting bitwidth is determined by the identifier (8 for 1 byte, 16 for 2 bytes, and so on).
+* Raw bytes
+ * Type identifiers: `hex`, `bytes`
+ * The value string is parsed as a hexadecimal string.
+ * The value string must be of even length, or a fatal error will be thrown on parse.
+
+## Content Specific Flags
+Atmosphère supports customizing CFW behavior based on the presence of `flags` on the SD card.
+
+The following flags are supported on a per-program basis, by placing `.flag` inside `/atmosphere/contents//flags/`:
++ `boot2`, which indicates that the program should be launched during the `boot2` process.
++ `redirect_save`, which indicates that the program wants its savedata to be redirected to the SD card.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/features/dns_mitm.md b/Source/Atmosphere-MTC-Unlock/docs/features/dns_mitm.md
new file mode 100644
index 00000000..e61f499c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/features/dns_mitm.md
@@ -0,0 +1,53 @@
+# DNS.mitm
+As of 0.18.0, atmosphère provides a mechanism for redirecting DNS resolution requests.
+
+By default, atmosphère redirects resolution requests for official telemetry servers, redirecting them to a loopback address.
+
+## Hosts files
+
+DNS.mitm can be configured through the usage of a slightly-extended `hosts` file format, which is parsed only once on system startup.
+
+In particular, hosts files parsed by DNS.mitm have the following extensions to the usual format:
++ `*` is treated as a wildcard character, matching any collection of 0 or more characters wherever it occurs in a hostname.
++ `%` is treated as a stand-in for the value of `nsd!environment_identifier`. This is always `lp1`, on production devices.
+
+If multiple entries in a host file match a domain, the last-defined match is used.
+
+Please note that homebrew may trigger a hosts file re-parse by sending the extension IPC command 65000 ("AtmosphereReloadHostsFile") to a connected `sfdnsres` session.
+
+### Hosts file selection
+
+Atmosphère will try to read hosts from the following file paths, in order, stopping once it successfully performs a file read:
+
++ (emummc only) `/atmosphere/hosts/emummc_%04lx.txt`, formatted with the emummc's id number (see `emummc.ini`).
++ (emummc only) `/atmosphere/hosts/emummc.txt`.
++ (sysmmc only) `/atmosphere/hosts/sysmmc.txt`.
++ `/atmosphere/hosts/default.txt`
+
+If `/atmosphere/hosts/default.txt` does not exist, atmosphère will create it to contain the defaults.
+
+### Atmosphère defaults
+
+By default, atmosphère's default redirections are parsed **in addition to** the contents of the loaded hosts file.
+
+This is equivalent to thinking of the loaded hosts file as having the atmosphère defaults prepended to it.
+
+This setting is considered desirable, because it minimizes the telemetry risks if a user forgets to update a custom hosts file on a system update which changes the telemetry servers.
+
+This behavior can be opted-out from by setting `atmosphere!add_defaults_to_dns_hosts = u8!0x0` in `system_settings.ini`.
+
+The current default redirections are:
+
+```
+# Nintendo telemetry servers
+127.0.0.1 receive-%.dg.srv.nintendo.net receive-%.er.srv.nintendo.net
+```
+
+## Debugging
+
+On startup (or on hosts file re-parse), DNS.mitm will log both what hosts file it selected and the contents of all redirections it parses to `/atmosphere/logs/dns_mitm_startup.log`.
+
+In addition, if the user sets `atmosphere!enable_dns_mitm_debug_log = u8!0x1` in `system_settings.ini`, DNS.mitm will log all requests to GetHostByName/GetAddrInfo to `/atmosphere/logs/dns_mitm_debug.log`. All redirections will be noted when they occur.
+
+## Opting-out of DNS.mitm entirely
+If you wish to disable DNS.mitm entirely, `system_settings.ini` can be edited to set `atmosphere!enable_dns_mitm = u8!0x0`.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/licensing_exemptions/MIT_LICENSE b/Source/Atmosphere-MTC-Unlock/docs/licensing_exemptions/MIT_LICENSE
new file mode 100644
index 00000000..ef737504
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/licensing_exemptions/MIT_LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) Atmosphère-NX
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Source/Atmosphere-MTC-Unlock/docs/main.md b/Source/Atmosphere-MTC-Unlock/docs/main.md
new file mode 100644
index 00000000..853cf1f0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/main.md
@@ -0,0 +1,32 @@
+# Atmosphère
+Atmosphère is a work-in-progress customized firmware for the Nintendo Switch. Its design principle consists of a multi-layered approach where each layer replaces/modifies a different component of the Nintendo Switch's system.
+
+## Components
+Atmosphère provides six core components, mimicking to some degree the various layers of the Earth's atmosphere:
++ [fusée](components/fusee.md)
++ [exosphère](components/exosphere.md)
++ [thermosphère](components/thermosphere.md)
++ [mesosphère](components/mesosphere.md)
++ [stratosphère](components/stratosphere.md)
++ [troposphère](components/troposphere.md)
+
+Additionally, Atmosphère also provides the following secondary components:
++ [emummc](components/emummc.md)
++ [libraries](components/libraries.md)
+
+## Features
+Atmosphère provides several original features which add or expand functionalities for the customized firmware environment:
++ [Cheats](features/cheats.md)
++ [Configurations](features/configurations.md)
+
+## Building Atmosphère
+A guide to building Atmosphère can be found [here](building.md).
+
+## Upcoming Features
+A list of planned features for Atmosphère can be found [here](roadmap.md).
+
+## Release History
+A changelog of previous versions of Atmosphère can be found [here](changelog.md).
+
+## Frequently Asked Questions
+Answers to one or more frequently asked questions may be found [here](faq.md).
diff --git a/Source/Atmosphere-MTC-Unlock/docs/roadmap.md b/Source/Atmosphere-MTC-Unlock/docs/roadmap.md
new file mode 100644
index 00000000..7b27dfdd
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/docs/roadmap.md
@@ -0,0 +1,60 @@
+# Planned Features
+atmosphère has a number of features that are either works-in-progress or planned. Please note that while time-estimates are given, they are loose, and things may be completed sooner or later than advertised.
+
+The following descriptions were last updated on January 14th, 2021
+
+## tma reimplementation
+* **Description** tma ("target manager agent") is a system module that manages communication between the Switch and a client PC. Atmosphere's implementation will allow homebrew on the switch to communicate with a connected PC to do various operations such as exchanging data or interacting with files. It will also serve as the communicator for Atmosphère's planned debugger. This will also include PC-side software for interacting with the Switch.
+* **Development Status**: Planned. Switch-side code is fully implemented but needs heavy refactoring/rebasing, as the code was originally authored in 2018.
+* **Estimated Time**: 2021-2022.
+
+## dmnt.gen2 reimplementation
+* **Description**: A reimplementation of the Switch's debug monitor, dmnt will provide an interface for debugging applications or system modules running on the Switch. This will include a gdbstub for debugging actively-running system components or applications.
+* **Development Status**: Planned
+* **Estimated Time**: 2021-2022
+
+## fs reimplementation
+* **Description**: Following mesosphère's completion, atmosphère will have reimplemented all components of the BootImagePackage firmware except for the filesystem services system module. Reimplementing fs will allow for fixing Nintendo bugs (such as corruption when using exFAT filesystems and encoding inconsistencies with UTF-8 and Shift-JIS).
+* **Development Status**: Planned.
+* **Estimated Time**: 2021-2022.
+
+## settings reimplementation
+* **Description**: A planned reimplementation of the settings system module, and with it a removal of the settings mitm. This will greatly simplify atmosphère's boot process, and will allow much more flexible control over the various system settings.
+* **Development Status**: Pending development by Adubbz.
+* **Estimated Time**: Unclear, pending developer availability.
+
+## thermosphère
+* **Description**: A general-purpose hypervisor, thermosphère will enable the virtualization of the Switch's operating system; this is planned to enable debugging of the Switch's kernel.
+* **Development Status**: Pending development by TuxSH.
+* **Estimated Time**: Unclear, pending developer availability.
+
+## other planned features
+* **Description**: General system stability improvements to enhance the user's experience.
+* **Development Status**: Undergoing active development by all members of the atmosphère team.
+* **Estimated Time**: June 15th.
+
+# Completed features
+
+The following features were previously included under the planned features section and are now complete.
+
+Please note that this is not an exhaustive list of features present in atmosphère, and only serves to indicate what from the above has been completed.
+
+## system updater homebrew
+* **Description**: A user homebrew making use of the new system updater api, so that users can actually use the new api in practice.
+* **Completion Time**: July 2020
+
+## system updater api
+* **Description**: A planned extension api for stratosphere (tenatively `ams:su`), this will provide an interface for homebrew to safely install system upgrades or downgrades. This will allow for much more easily transitioning safely between different versions of the operating system.
+* **Completion Time**: June 2020
+
+## exosphere re-write
+* **Description**: exosphère, atmosphère's reimplementation of Horizon's Secure Monitor, was the first component authored for the project in early 2018. It is written in C, and in a style very different from the rest of atmosphère's code. In addition, exosphère was written to conform to constraints that no longer apply in an environment where it is not launched from the web browser, and where using a custom firmware image to orchestrate wake-from-sleep is possible. exosphère currently uses all but 1 KB of the space available to it, putting it at risk of breaking as future firmware updates are supported. A re-write will solve these issues.
+* **Completion Time**: June 2020
+
+## mesosphere
+* **Description**: mesosphère is a reimplementation of the Horizon operating system's Kernel. It aims to provide an open-source reference for Nintendo's code.
+* **Estimated Time**: September 2020
+
+## ams-on-mariko
+* **Description**: Atmosphere cannot run as-is on Mariko hardware. A large number of changes are needed in many components. Although secure monitor support is complete in exosphere, additional work is needed on the bootloader and stratosphere sides as well. Mariko support will also require further design thought; atmosphere's debugging design heavily relies on reboot-to-payload and (more generally) the ability to perform warmboot bootrom hax at will. This is not possible on Mariko, and will require a new design/software support for whatever solution is chosen.
+* **Completion Time**: January 2021
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/.gitignore b/Source/Atmosphere-MTC-Unlock/emummc/.gitignore
new file mode 100644
index 00000000..3c00ac63
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/.gitignore
@@ -0,0 +1,9 @@
+*.kip
+*.data
+*.elf
+build
+.vscode/ipch
+.vscode/settings.json
+*.exe
+*.kip*
+emummc.caps
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/.gitrepo b/Source/Atmosphere-MTC-Unlock/emummc/.gitrepo
new file mode 100644
index 00000000..1aad635a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/.gitrepo
@@ -0,0 +1,12 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://github.com/m4xw/emummc
+ branch = develop
+ commit = a8e5f1a184aeb8ba884166a1e4f386088d4a6cf1
+ parent = 409c3cf9e190dbb65fe76570954939cbe8a5eca0
+ method = merge
+ cmdver = 0.4.1
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/LICENSE b/Source/Atmosphere-MTC-Unlock/emummc/LICENSE
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/LICENSE
@@ -0,0 +1,339 @@
+ 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 Lesser 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.
+
+ GNU GENERAL PUBLIC LICENSE
+ 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.
+
+
+ Copyright (C)
+
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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.
+
+ , 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 Lesser General
+Public License instead of this License.
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/Makefile b/Source/Atmosphere-MTC-Unlock/emummc/Makefile
new file mode 100644
index 00000000..7af45eaa
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/Makefile
@@ -0,0 +1,118 @@
+.SUFFIXES:
+
+ifeq ($(strip $(DEVKITPRO)),)
+$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro")
+endif
+
+TARGET := emummc
+BUILD := build
+SOURCES := source/nx source source/utils source/emmc source/soc source/power source/emuMMC source/FS source/libs/fatfs
+DATA := data
+INCLUDES := include
+EXEFS_SRC := exefs_src
+
+ifneq ($(BUILD),$(notdir $(CURDIR)))
+EMUMMCDIR ?= $(CURDIR)
+else
+EMUMMCDIR ?= $(CURDIR)/../
+endif
+
+include $(DEVKITPRO)/libnx/switch_rules
+
+ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
+
+# Current max usage is 0x4600. (512 * 34 FatFS file objects + 1 fsync buffer).
+DEFINES := -DINNER_HEAP_SIZE=0x8000
+
+CFLAGS := -Wall -O2 -ffunction-sections -fdata-sections -Wno-unused-function \
+ $(ARCH) $(DEFINES)
+
+CFLAGS += $(INCLUDE) -D__SWITCH__
+
+CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
+
+ASFLAGS := -g $(ARCH)
+LDFLAGS = -specs=$(EMUMMCDIR)/emummc.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
+
+ifneq ($(BUILD),$(notdir $(CURDIR)))
+
+export OUTPUT := $(CURDIR)/$(TARGET)
+export TOPDIR := $(EMUMMCDIR)
+
+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)/*.*)))
+
+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 $(CONFIG_JSON)),)
+ jsons := $(wildcard *.json)
+ ifneq (,$(findstring $(TARGET).json,$(jsons)))
+ export APP_JSON := $(TOPDIR)/$(TARGET).json
+ else
+ ifneq (,$(findstring config.json,$(jsons)))
+ export APP_JSON := $(TOPDIR)/config.json
+ endif
+ endif
+else
+ export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
+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).elf $(TARGET).kip
+
+else
+.PHONY: all
+
+DEPENDS := $(OFILES:.o=.d)
+
+all : $(OUTPUT)_unpacked.kip
+
+$(OUTPUT)_unpacked.kip : $(OUTPUT).kip
+ @hactool -t kip --uncompressed=$(OUTPUT)_unpacked.kip $(OUTPUT).kip
+
+$(OUTPUT).kip : $(OUTPUT).elf
+
+$(OUTPUT).elf : $(OFILES)
+
+$(OFILES_SRC) : $(HFILES_BIN)
+
+%.bin.o %_bin.h : %.bin
+ @echo $(notdir $<)
+ @$(bin2o)
+
+-include $(DEPENDS)
+
+endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/README.md b/Source/Atmosphere-MTC-Unlock/emummc/README.md
new file mode 100644
index 00000000..aa9736d2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/README.md
@@ -0,0 +1,36 @@
+# emuMMC
+*A SDMMC driver replacement for Nintendo's Filesystem Services, by **m4xw***
+
+### Supported Horizon Versions
+**1.0.0 - 20.1.0**
+
+## Features
+* Arbitrary SDMMC backend selection
+ **This allows loading eMMC from SD or even SD from eMMC**
+* On the fly hooking / patching, fully self-infesting
+ **Only one payload required for all versions!**
+* File-based SDMMC backend support (from SD)
+ **This allows loading eMMC images from hekate-backups (split or not)**
+* SDMMC device based sector offset (*currently eMMC only*)
+ **Raw partition support for eMMC from SD with less performance overhead**
+* Full support for `/Nintendo` folder redirection to a arbitrary path
+ **No 8 char length restriction!**
+* exosphere based context configuration
+ **This includes full support for multiple emuMMC images**
+
+## Compiling
+### hekate
+Run `./build.sh` and copy the produced kipm (Kernel Initial Process Modification) file to `/bootloader/sys/`
+
+### Atmosphere
+Run `make`, the resulting kip can be used for code injection via fusee (place at `/atmosphere/emummc.kip`)
+
+## License
+**emuMMC is released as GPLv2**
+
+## Credits
+* **CTCaer** - The CTCaer hekate fork, file-based emuMMC support, SDMMC driver fixes among other things
+* **SciresM, hexkyz** - The Atmosphere project, FS offsets, additional research related to newer FS versions
+* **naehrwert** - The hekate project, its SDMMC driver and being very helpful in the early research phase
+* **jakibaki** - KIP Inject PoC, used in the early dev phase
+* **switchbrew/devkitPro** - devkitA64 and libnx sources
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/build.sh b/Source/Atmosphere-MTC-Unlock/emummc/build.sh
new file mode 100644
index 00000000..b3389537
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+make clean
+make -j
+./hactool.exe -t kip emummc.kip --uncompressed emummc_unpacked.kip
+python2.7 tools/kip1converter.py emummc_unpacked.kip emummc.data
+cat emummc.caps emummc.data > emummc.kipm
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/emummc.json b/Source/Atmosphere-MTC-Unlock/emummc/emummc.json
new file mode 100644
index 00000000..19471c53
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/emummc.json
@@ -0,0 +1,137 @@
+{
+ "name": "FS",
+ "title_id": "0x0100000000000000",
+ "main_thread_stack_size": "0x00008000",
+ "main_thread_priority": 45,
+ "default_cpu_id": 3,
+ "process_category": 1,
+ "kernel_capabilities": [
+ {
+ "type": "map_page",
+ "value": "0x60006000"
+ },
+ {
+ "type": "map",
+ "value": {
+ "address": "0x6000D000",
+ "size": "0x1000",
+ "is_ro": false,
+ "is_io": true
+ }
+ },
+ {
+ "type": "map",
+ "value": {
+ "address": "0x700b0000",
+ "is_ro": false,
+ "size": "0x00005000",
+ "is_io": true
+ }
+ },
+ {
+ "type": "map",
+ "value": {
+ "address": "0x7000C000",
+ "is_ro": false,
+ "size": "0x00002000",
+ "is_io": true
+ }
+ },
+ {
+ "type": "map",
+ "value": {
+ "address": "0x70000000",
+ "is_ro": false,
+ "size": "0x00004000",
+ "is_io": true
+ }
+ },
+ {
+ "type": "handle_table_size",
+ "value": 256
+ },
+ {
+ "type": "irq_pair",
+ "value": [
+ 46,
+ 47
+ ]
+ },
+ {
+ "type": "irq_pair",
+ "value": [
+ 51,
+ 63
+ ]
+ },
+ {
+ "type": "syscalls",
+ "value": {
+ "svcSetHeapSize": "0x01",
+ "svcSetMemoryPermission": "0x02",
+ "svcSetMemoryAttribute": "0x03",
+ "svcMapMemory": "0x04",
+ "svcUnmapMemory": "0x05",
+ "svcQueryMemory": "0x06",
+ "svcExitProcess": "0x07",
+ "svcCreateThread": "0x08",
+ "svcStartThread": "0x09",
+ "svcExitThread": "0x0a",
+ "svcSleepThread": "0x0b",
+ "svcGetThreadPriority": "0x0c",
+ "svcSetThreadPriority": "0x0d",
+ "svcGetThreadCoreMask": "0x0e",
+ "svcSetThreadCoreMask": "0x0f",
+ "svcGetCurrentProcessorNumber": "0x10",
+ "svcSignalEvent": "0x11",
+ "svcClearEvent": "0x12",
+ "svcMapSharedMemory": "0x13",
+ "svcUnmapSharedMemory": "0x14",
+ "svcCreateTransferMemory": "0x15",
+ "svcCloseHandle": "0x16",
+ "svcResetSignal": "0x17",
+ "svcWaitSynchronization": "0x18",
+ "svcCancelSynchronization": "0x19",
+ "svcArbitrateLock": "0x1a",
+ "svcArbitrateUnlock": "0x1b",
+ "svcWaitProcessWideKeyAtomic": "0x1c",
+ "svcSignalProcessWideKey": "0x1d",
+ "svcGetSystemTick": "0x1e",
+ "svcConnectToNamedPort": "0x1f",
+ "svcSendSyncRequestLight": "0x20",
+ "svcSendSyncRequest": "0x21",
+ "svcSendSyncRequestWithUserBuffer": "0x22",
+ "svcSendAsyncRequestWithUserBuffer": "0x23",
+ "svcGetProcessId": "0x24",
+ "svcGetThreadId": "0x25",
+ "svcBreak": "0x26",
+ "svcOutputDebugString": "0x27",
+ "svcReturnFromException": "0x28",
+ "svcGetInfo": "0x29",
+ "svcWaitForAddress": "0x34",
+ "svcSignalToAddress": "0x35",
+ "svcCreateSession": "0x40",
+ "svcAcceptSession": "0x41",
+ "svcReplyAndReceiveLight": "0x42",
+ "svcReplyAndReceive": "0x43",
+ "svcReplyAndReceiveWithUserBuffer": "0x44",
+ "svcCreateEvent": "0x45",
+ "svcReadWriteRegister": "0x4E",
+ "svcMapTransferMemory": "0x51",
+ "svcUnmapTransferMemory": "0x52",
+ "svcCreateInterruptEvent": "0x53",
+ "svcQueryIoMapping": "0x55",
+ "svcCreateDeviceAddressSpace": "0x56",
+ "svcAttachDeviceAddressSpace": "0x57",
+ "svcDetachDeviceAddressSpace": "0x58",
+ "svcMapDeviceAddressSpaceAligned": "0x5a",
+ "svcUnmapDeviceAddressSpace": "0x5c",
+ "svcGetSystemInfo": "0x6f",
+ "svcSetProcessMemoryPermission": "0x73",
+ "svcMapProcessMemory": "0x74",
+ "svcUnmapProcessMemory": "0x75",
+ "svcCallSecureMonitor": "0x7f"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/emummc.ld b/Source/Atmosphere-MTC-Unlock/emummc/emummc.ld
new file mode 100644
index 00000000..48bf24b9
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/emummc.ld
@@ -0,0 +1,201 @@
+OUTPUT_ARCH(aarch64)
+ENTRY(_start)
+
+PHDRS
+{
+ code PT_LOAD FLAGS(5) /* Read | Execute */;
+ rodata PT_LOAD FLAGS(4) /* Read */;
+ data PT_LOAD FLAGS(6) /* Read | Write */;
+ dyn PT_DYNAMIC;
+}
+
+SECTIONS
+{
+ /* =========== CODE section =========== */
+ PROVIDE(__start__ = 0x0);
+ . = __start__;
+ __code_start = . ;
+
+ .crt0 :
+ {
+ KEEP (*(.crt0))
+ . = ALIGN(8);
+ } :code
+
+ .init :
+ {
+ KEEP( *(.init) )
+ . = ALIGN(8);
+ } :code
+
+ .plt :
+ {
+ *(.plt)
+ *(.iplt)
+ . = ALIGN(8);
+ } :code
+
+ .text :
+ {
+ *(.text.unlikely .text.*_unlikely .text.unlikely.*)
+ *(.text.exit .text.exit.*)
+ *(.text.startup .text.startup.*)
+ *(.text.hot .text.hot.*)
+ *(.text .stub .text.* .gnu.linkonce.t.*)
+ . = ALIGN(8);
+ } :code
+
+ .fini :
+ {
+ KEEP( *(.fini) )
+ . = ALIGN(8);
+ } :code
+
+ /* =========== RODATA section =========== */
+ . = ALIGN(0x1000);
+ __rodata_start = . ;
+
+ .nx-module-name : { KEEP (*(.nx-module-name)) } :rodata
+
+ .rodata :
+ {
+ *(.rodata .rodata.* .gnu.linkonce.r.*)
+ . = ALIGN(8);
+ } :rodata
+
+ .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } :rodata
+ .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } :rodata
+ .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } :rodata
+ .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } : rodata
+
+ .dynamic : { *(.dynamic) } :rodata :dyn
+ .dynsym : { *(.dynsym) } :rodata
+ .dynstr : { *(.dynstr) } :rodata
+ .rela.dyn : { *(.rela.*) } :rodata
+ .interp : { *(.interp) } :rodata
+ .hash : { *(.hash) } :rodata
+ .gnu.hash : { *(.gnu.hash) } :rodata
+ .gnu.version : { *(.gnu.version) } :rodata
+ .gnu.version_d : { *(.gnu.version_d) } :rodata
+ .gnu.version_r : { *(.gnu.version_r) } :rodata
+ .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata
+
+ /* =========== DATA section =========== */
+ . = ALIGN(0x1000);
+ __data_start = . ;
+
+ .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } :data
+ .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } :data
+ .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } : data
+ .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } :data
+
+ .tdata ALIGN(8) :
+ {
+ __tdata_lma = .;
+ *(.tdata .tdata.* .gnu.linkonce.td.*)
+ . = ALIGN(8);
+ __tdata_lma_end = .;
+ } :data
+
+ .tbss ALIGN(8) :
+ {
+ *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
+ . = ALIGN(8);
+ } :data
+
+ .preinit_array ALIGN(8) :
+ {
+ PROVIDE (__preinit_array_start = .);
+ KEEP (*(.preinit_array))
+ PROVIDE (__preinit_array_end = .);
+ } :data
+
+ .init_array ALIGN(8) :
+ {
+ PROVIDE (__init_array_start = .);
+ KEEP( *(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)) )
+ KEEP( *(.init_array .ctors) )
+ PROVIDE (__init_array_end = .);
+ } :data
+
+ .fini_array ALIGN(8) :
+ {
+ PROVIDE (__fini_array_start = .);
+ KEEP( *(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)) )
+ KEEP( *(.fini_array .dtors) )
+ PROVIDE (__fini_array_end = .);
+ } :data
+
+ __got_start__ = .;
+
+ .got : { *(.got) *(.igot) } :data
+ .got.plt : { *(.got.plt) *(.igot.plt) } :data
+
+ __got_end__ = .;
+
+ .data ALIGN(8) :
+ {
+ *(.data .data.* .gnu.linkonce.d.*)
+ SORT(CONSTRUCTORS)
+ } :data
+
+ __bss_start__ = .;
+ .bss ALIGN(8) :
+ {
+ *(.dynbss)
+ *(.bss .bss.* .gnu.linkonce.b.*)
+ *(COMMON)
+ . = ALIGN(8);
+
+ /* Reserve space for the TLS segment of the main thread */
+ __tls_start = .;
+ . += + SIZEOF(.tdata) + SIZEOF(.tbss);
+ __tls_end = .;
+ } : data
+ __bss_end__ = .;
+
+ __end__ = ABSOLUTE(.) ;
+
+ . = ALIGN(0x1000);
+ __argdata__ = ABSOLUTE(.) ;
+
+ /* ==================
+ ==== Metadata ====
+ ================== */
+
+ /* Discard sections that difficult post-processing */
+ /DISCARD/ : { *(.group .comment .note) }
+
+ /* Stabs debugging sections. */
+ .stab 0 : { *(.stab) }
+ .stabstr 0 : { *(.stabstr) }
+ .stab.excl 0 : { *(.stab.excl) }
+ .stab.exclstr 0 : { *(.stab.exclstr) }
+ .stab.index 0 : { *(.stab.index) }
+ .stab.indexstr 0 : { *(.stab.indexstr) }
+
+ /* DWARF debug sections.
+ Symbols in the DWARF debugging sections are relative to the beginning
+ of the section so we begin them at 0. */
+
+ /* DWARF 1 */
+ .debug 0 : { *(.debug) }
+ .line 0 : { *(.line) }
+
+ /* GNU DWARF 1 extensions */
+ .debug_srcinfo 0 : { *(.debug_srcinfo) }
+ .debug_sfnames 0 : { *(.debug_sfnames) }
+
+ /* DWARF 1.1 and DWARF 2 */
+ .debug_aranges 0 : { *(.debug_aranges) }
+ .debug_pubnames 0 : { *(.debug_pubnames) }
+
+ /* DWARF 2 */
+ .debug_info 0 : { *(.debug_info) }
+ .debug_abbrev 0 : { *(.debug_abbrev) }
+ .debug_line 0 : { *(.debug_line) }
+ .debug_frame 0 : { *(.debug_frame) }
+ .debug_str 0 : { *(.debug_str) }
+ .debug_loc 0 : { *(.debug_loc) }
+ .debug_macinfo 0 : { *(.debug_macinfo) }
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/emummc.specs b/Source/Atmosphere-MTC-Unlock/emummc/emummc.specs
new file mode 100644
index 00000000..828da3d2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/emummc.specs
@@ -0,0 +1,8 @@
+%rename link old_link
+
+*link:
+%(old_link) -T %:getenv(TOPDIR /emummc.ld) -pie --no-dynamic-linker --spare-dynamic-tags=0 --gc-sections -z text -z nodynamic-undefined-weak --build-id=sha1 --nx-module-name
+
+*startfile:
+crti%O%s crtbegin%O%s
+
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS.h
new file mode 100644
index 00000000..2d92a98a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef __FS_H__
+#define __FS_H__
+
+// TODO
+#include "../emmc/sdmmc_t210.h"
+
+#include "FS_versions.h"
+#include "FS_offsets.h"
+#include "FS_structs.h"
+
+#define FS_SDMMC_EMMC 0
+#define FS_SDMMC_SD 1
+#define FS_SDMMC_GC 2
+
+#define FS_EMMC_PARTITION_GPP 0
+#define FS_EMMC_PARTITION_BOOT0 1
+#define FS_EMMC_PARTITION_BOOT1 2
+#define FS_EMMC_PARTITION_INVALID 3
+
+#define BOOT_PARTITION_SIZE 0x2000
+#define FS_READ_WRITE_ERROR 1048
+
+#define NAND_PATROL_SECTOR 0xC20
+#define NAND_PATROL_OFFSET 0x184000
+
+typedef struct _fs_nand_patrol_t
+{
+ uint8_t hmac[0x20];
+ unsigned int offset;
+ unsigned int count;
+ uint8_t rsvd[0x1D8];
+} fs_nand_patrol_t;
+
+#endif /* __FS_H__ */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_offsets.c b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_offsets.c
new file mode 100644
index 00000000..0db07fe8
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_offsets.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include "FS_offsets.h"
+#include "offsets/100.h"
+#include "offsets/200.h"
+#include "offsets/200_exfat.h"
+#include "offsets/210.h"
+#include "offsets/210_exfat.h"
+#include "offsets/300.h"
+#include "offsets/300_exfat.h"
+#include "offsets/301.h"
+#include "offsets/301_exfat.h"
+#include "offsets/400.h"
+#include "offsets/400_exfat.h"
+#include "offsets/410.h"
+#include "offsets/410_exfat.h"
+#include "offsets/500.h"
+#include "offsets/500_exfat.h"
+#include "offsets/510.h"
+#include "offsets/510_exfat.h"
+#include "offsets/600.h"
+#include "offsets/600_exfat.h"
+#include "offsets/700.h"
+#include "offsets/700_exfat.h"
+#include "offsets/800.h"
+#include "offsets/800_exfat.h"
+#include "offsets/810.h"
+#include "offsets/810_exfat.h"
+#include "offsets/900.h"
+#include "offsets/900_exfat.h"
+#include "offsets/910.h"
+#include "offsets/910_exfat.h"
+#include "offsets/1000.h"
+#include "offsets/1000_exfat.h"
+#include "offsets/1020.h"
+#include "offsets/1020_exfat.h"
+#include "offsets/1100.h"
+#include "offsets/1100_exfat.h"
+#include "offsets/1200.h"
+#include "offsets/1200_exfat.h"
+#include "offsets/1203.h"
+#include "offsets/1203_exfat.h"
+#include "offsets/1300.h"
+#include "offsets/1300_exfat.h"
+#include "offsets/1310.h"
+#include "offsets/1310_exfat.h"
+#include "offsets/1400.h"
+#include "offsets/1400_exfat.h"
+#include "offsets/1500.h"
+#include "offsets/1500_exfat.h"
+#include "offsets/1600.h"
+#include "offsets/1600_exfat.h"
+#include "offsets/1603.h"
+#include "offsets/1603_exfat.h"
+#include "offsets/1700.h"
+#include "offsets/1700_exfat.h"
+#include "offsets/1800.h"
+#include "offsets/1800_exfat.h"
+#include "offsets/1810.h"
+#include "offsets/1810_exfat.h"
+#include "offsets/1900.h"
+#include "offsets/1900_exfat.h"
+#include "offsets/2000.h"
+#include "offsets/2000_exfat.h"
+#include "offsets/2010.h"
+#include "offsets/2010_exfat.h"
+#include "../utils/fatal.h"
+
+#define GET_OFFSET_STRUCT_NAME(vers) g_offsets##vers
+
+#define DEFINE_OFFSET_STRUCT(vers) \
+static const fs_offsets_t GET_OFFSET_STRUCT_NAME(vers) = { \
+ .sdmmc_accessor_gc = FS_OFFSET##vers##_SDMMC_ACCESSOR_GC, \
+ .sdmmc_accessor_sd = FS_OFFSET##vers##_SDMMC_ACCESSOR_SD, \
+ .sdmmc_accessor_nand = FS_OFFSET##vers##_SDMMC_ACCESSOR_NAND, \
+ .sdmmc_wrapper_read = FS_OFFSET##vers##_SDMMC_WRAPPER_READ, \
+ .sdmmc_wrapper_write = FS_OFFSET##vers##_SDMMC_WRAPPER_WRITE, \
+ .clkrst_set_min_v_clock_rate = FS_OFFSET##vers##_CLKRST_SET_MIN_V_CLK_RATE, \
+ .rtld = FS_OFFSET##vers##_RTLD, \
+ .rtld_destination = FS_OFFSET##vers##_RTLD_DESTINATION, \
+ .lock_mutex = FS_OFFSET##vers##_LOCK_MUTEX, \
+ .unlock_mutex = FS_OFFSET##vers##_UNLOCK_MUTEX, \
+ .sd_mutex = FS_OFFSET##vers##_SD_MUTEX, \
+ .nand_mutex = FS_OFFSET##vers##_NAND_MUTEX, \
+ .active_partition = FS_OFFSET##vers##_ACTIVE_PARTITION, \
+ .sdmmc_das_handle = FS_OFFSET##vers##_SDMMC_DAS_HANDLE, \
+ .sdmmc_accessor_controller_open = FS_OFFSET##vers##_SDMMC_WRAPPER_CONTROLLER_OPEN, \
+ .sdmmc_accessor_controller_close = FS_OFFSET##vers##_SDMMC_WRAPPER_CONTROLLER_CLOSE, \
+ .sd_das_init = FS_OFFSET##vers##_SD_DAS_INIT, \
+ .nintendo_paths = FS_OFFSET##vers##_NINTENDO_PATHS, \
+}
+
+// Actually define offset structs
+DEFINE_OFFSET_STRUCT(_100);
+DEFINE_OFFSET_STRUCT(_200);
+DEFINE_OFFSET_STRUCT(_200_EXFAT);
+DEFINE_OFFSET_STRUCT(_210);
+DEFINE_OFFSET_STRUCT(_210_EXFAT);
+DEFINE_OFFSET_STRUCT(_300);
+DEFINE_OFFSET_STRUCT(_300_EXFAT);
+DEFINE_OFFSET_STRUCT(_301);
+DEFINE_OFFSET_STRUCT(_301_EXFAT);
+DEFINE_OFFSET_STRUCT(_400);
+DEFINE_OFFSET_STRUCT(_400_EXFAT);
+DEFINE_OFFSET_STRUCT(_410);
+DEFINE_OFFSET_STRUCT(_410_EXFAT);
+DEFINE_OFFSET_STRUCT(_500);
+DEFINE_OFFSET_STRUCT(_500_EXFAT);
+DEFINE_OFFSET_STRUCT(_510);
+DEFINE_OFFSET_STRUCT(_510_EXFAT);
+DEFINE_OFFSET_STRUCT(_600);
+DEFINE_OFFSET_STRUCT(_600_EXFAT);
+DEFINE_OFFSET_STRUCT(_700);
+DEFINE_OFFSET_STRUCT(_700_EXFAT);
+DEFINE_OFFSET_STRUCT(_800);
+DEFINE_OFFSET_STRUCT(_800_EXFAT);
+DEFINE_OFFSET_STRUCT(_810);
+DEFINE_OFFSET_STRUCT(_810_EXFAT);
+DEFINE_OFFSET_STRUCT(_900);
+DEFINE_OFFSET_STRUCT(_900_EXFAT);
+DEFINE_OFFSET_STRUCT(_910);
+DEFINE_OFFSET_STRUCT(_910_EXFAT);
+DEFINE_OFFSET_STRUCT(_1000);
+DEFINE_OFFSET_STRUCT(_1000_EXFAT);
+DEFINE_OFFSET_STRUCT(_1020);
+DEFINE_OFFSET_STRUCT(_1020_EXFAT);
+DEFINE_OFFSET_STRUCT(_1100);
+DEFINE_OFFSET_STRUCT(_1100_EXFAT);
+DEFINE_OFFSET_STRUCT(_1200);
+DEFINE_OFFSET_STRUCT(_1200_EXFAT);
+DEFINE_OFFSET_STRUCT(_1203);
+DEFINE_OFFSET_STRUCT(_1203_EXFAT);
+DEFINE_OFFSET_STRUCT(_1300);
+DEFINE_OFFSET_STRUCT(_1300_EXFAT);
+DEFINE_OFFSET_STRUCT(_1310);
+DEFINE_OFFSET_STRUCT(_1310_EXFAT);
+DEFINE_OFFSET_STRUCT(_1400);
+DEFINE_OFFSET_STRUCT(_1400_EXFAT);
+DEFINE_OFFSET_STRUCT(_1500);
+DEFINE_OFFSET_STRUCT(_1500_EXFAT);
+DEFINE_OFFSET_STRUCT(_1600);
+DEFINE_OFFSET_STRUCT(_1600_EXFAT);
+DEFINE_OFFSET_STRUCT(_1603);
+DEFINE_OFFSET_STRUCT(_1603_EXFAT);
+DEFINE_OFFSET_STRUCT(_1700);
+DEFINE_OFFSET_STRUCT(_1700_EXFAT);
+DEFINE_OFFSET_STRUCT(_1800);
+DEFINE_OFFSET_STRUCT(_1800_EXFAT);
+DEFINE_OFFSET_STRUCT(_1810);
+DEFINE_OFFSET_STRUCT(_1810_EXFAT);
+DEFINE_OFFSET_STRUCT(_1900);
+DEFINE_OFFSET_STRUCT(_1900_EXFAT);
+DEFINE_OFFSET_STRUCT(_2000);
+DEFINE_OFFSET_STRUCT(_2000_EXFAT);
+DEFINE_OFFSET_STRUCT(_2010);
+DEFINE_OFFSET_STRUCT(_2010_EXFAT);
+
+const fs_offsets_t *get_fs_offsets(enum FS_VER version) {
+ switch (version) {
+ case FS_VER_1_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_100));
+ case FS_VER_2_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_200));
+ case FS_VER_2_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_200_EXFAT));
+ case FS_VER_2_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_210));
+ case FS_VER_2_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_210_EXFAT));
+ case FS_VER_3_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_300));
+ case FS_VER_3_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_300_EXFAT));
+ case FS_VER_3_0_1:
+ return &(GET_OFFSET_STRUCT_NAME(_301));
+ case FS_VER_3_0_1_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_301_EXFAT));
+ case FS_VER_4_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_400));
+ case FS_VER_4_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_400_EXFAT));
+ case FS_VER_4_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_410));
+ case FS_VER_4_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_410_EXFAT));
+ case FS_VER_5_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_500));
+ case FS_VER_5_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_500_EXFAT));
+ case FS_VER_5_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_510));
+ case FS_VER_5_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_510_EXFAT));
+ case FS_VER_6_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_600));
+ case FS_VER_6_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_600_EXFAT));
+ case FS_VER_7_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_700));
+ case FS_VER_7_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_700_EXFAT));
+ case FS_VER_8_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_800));
+ case FS_VER_8_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_800_EXFAT));
+ case FS_VER_8_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_810));
+ case FS_VER_8_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_810_EXFAT));
+ case FS_VER_9_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_900));
+ case FS_VER_9_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_900_EXFAT));
+ case FS_VER_9_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_910));
+ case FS_VER_9_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_910_EXFAT));
+ case FS_VER_10_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1000));
+ case FS_VER_10_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1000_EXFAT));
+ case FS_VER_10_2_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1020));
+ case FS_VER_10_2_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1020_EXFAT));
+ case FS_VER_11_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1100));
+ case FS_VER_11_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1100_EXFAT));
+ case FS_VER_12_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1200));
+ case FS_VER_12_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1200_EXFAT));
+ case FS_VER_12_0_3:
+ return &(GET_OFFSET_STRUCT_NAME(_1203));
+ case FS_VER_12_0_3_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1203_EXFAT));
+ case FS_VER_13_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1300));
+ case FS_VER_13_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1300_EXFAT));
+ case FS_VER_13_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1310));
+ case FS_VER_13_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1310_EXFAT));
+ case FS_VER_14_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1400));
+ case FS_VER_14_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1400_EXFAT));
+ case FS_VER_15_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1500));
+ case FS_VER_15_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1500_EXFAT));
+ case FS_VER_16_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1600));
+ case FS_VER_16_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1600_EXFAT));
+ case FS_VER_16_0_3:
+ return &(GET_OFFSET_STRUCT_NAME(_1603));
+ case FS_VER_16_0_3_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1603_EXFAT));
+ case FS_VER_17_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1700));
+ case FS_VER_17_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1700_EXFAT));
+ case FS_VER_18_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1800));
+ case FS_VER_18_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1800_EXFAT));
+ case FS_VER_18_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1810));
+ case FS_VER_18_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1810_EXFAT));
+ case FS_VER_19_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_1900));
+ case FS_VER_19_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_1900_EXFAT));
+ case FS_VER_20_0_0:
+ return &(GET_OFFSET_STRUCT_NAME(_2000));
+ case FS_VER_20_0_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_2000_EXFAT));
+ case FS_VER_20_1_0:
+ return &(GET_OFFSET_STRUCT_NAME(_2010));
+ case FS_VER_20_1_0_EXFAT:
+ return &(GET_OFFSET_STRUCT_NAME(_2010_EXFAT));
+ default:
+ fatal_abort(Fatal_UnknownVersion);
+ }
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_offsets.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_offsets.h
new file mode 100644
index 00000000..8507d85d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_offsets.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef __FS_OFFSETS_H__
+#define __FS_OFFSETS_H__
+
+#include
+#include "FS_versions.h"
+
+typedef struct {
+ int opcode_reg;
+ uint32_t adrp_offset;
+ uint32_t add_rel_offset;
+} fs_offsets_nintendo_path_t;
+
+typedef struct {
+ // Accessor vtable getters
+ uintptr_t sdmmc_accessor_gc;
+ uintptr_t sdmmc_accessor_sd;
+ uintptr_t sdmmc_accessor_nand;
+ // Hooks
+ uintptr_t sdmmc_wrapper_read;
+ uintptr_t sdmmc_wrapper_write;
+ uintptr_t rtld;
+ uintptr_t rtld_destination;
+ uintptr_t clkrst_set_min_v_clock_rate;
+ // Misc funcs
+ uintptr_t lock_mutex;
+ uintptr_t unlock_mutex;
+ uintptr_t sdmmc_accessor_controller_open;
+ uintptr_t sdmmc_accessor_controller_close;
+ // Misc data
+ uintptr_t sd_mutex;
+ uintptr_t nand_mutex;
+ uintptr_t active_partition;
+ uintptr_t sdmmc_das_handle;
+ // NOPs
+ uintptr_t sd_das_init;
+ // Nintendo Paths
+ fs_offsets_nintendo_path_t nintendo_paths[];
+} fs_offsets_t;
+
+const fs_offsets_t *get_fs_offsets(enum FS_VER version);
+
+#endif // __FS_OFFSETS_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_structs.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_structs.h
new file mode 100644
index 00000000..81db84f8
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_structs.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef __FS_STRUCTS_H__
+#define __FS_STRUCTS_H__
+
+#include "../utils/types.h"
+
+typedef struct
+{
+ char *device_addr_buffer;
+ uint64_t device_addr_buffer_size;
+ char *device_addr_buffer_masked;
+} sdmmc_dma_buffer_t;
+
+_Static_assert(__alignof(sdmmc_dma_buffer_t) == 8, "sdmmc_dma_buffer_t definition");
+
+typedef struct sdmmc_accessor_vt
+{
+ void *ctor;
+ void *dtor;
+ void *map_device_addr_space;
+ void *unmap_device_addr_space;
+ uint64_t (*sdmmc_accessor_controller_open)(void *);
+ uint64_t (*sdmmc_accessor_controller_close)(void *);
+ uint64_t (*read_write)(void *, uint64_t, uint64_t, void *, uint64_t, uint64_t);
+ // More not included because we don't use it.
+} sdmmc_accessor_vt_t;
+
+_Static_assert(__alignof(sdmmc_accessor_vt_t) == 8, "sdmmc_accessor_vt_t definition");
+
+typedef struct
+{
+ void *vtab;
+ t210_sdmmc_t *io_map;
+ sdmmc_dma_buffer_t dmaBuffers[3];
+ // More not included because we don't use it.
+} mmc_obj_t;
+
+typedef struct
+{
+ sdmmc_accessor_vt_t *vtab;
+ mmc_obj_t *parent;
+ // More not included because we don't use it.
+} sdmmc_accessor_t;
+
+#endif // __FS_STRUCTS_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_versions.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_versions.h
new file mode 100644
index 00000000..312f6322
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/FS_versions.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef __FS_VERSIONS_H__
+#define __FS_VERSIONS_H__
+
+// FS Version enum
+enum FS_VER
+{
+ FS_VER_1_0_0 = 0,
+
+ FS_VER_2_0_0,
+ FS_VER_2_0_0_EXFAT,
+
+ FS_VER_2_1_0,
+ FS_VER_2_1_0_EXFAT,
+
+ FS_VER_3_0_0,
+ FS_VER_3_0_0_EXFAT,
+
+ FS_VER_3_0_1,
+ FS_VER_3_0_1_EXFAT,
+
+ FS_VER_4_0_0,
+ FS_VER_4_0_0_EXFAT,
+
+ FS_VER_4_1_0,
+ FS_VER_4_1_0_EXFAT,
+
+ FS_VER_5_0_0,
+ FS_VER_5_0_0_EXFAT,
+
+ FS_VER_5_1_0,
+ FS_VER_5_1_0_EXFAT,
+
+ FS_VER_6_0_0,
+ FS_VER_6_0_0_EXFAT,
+
+ FS_VER_7_0_0,
+ FS_VER_7_0_0_EXFAT,
+
+ FS_VER_8_0_0,
+ FS_VER_8_0_0_EXFAT,
+
+ FS_VER_8_1_0,
+ FS_VER_8_1_0_EXFAT,
+
+ FS_VER_9_0_0,
+ FS_VER_9_0_0_EXFAT,
+
+ FS_VER_9_1_0,
+ FS_VER_9_1_0_EXFAT,
+
+ FS_VER_10_0_0,
+ FS_VER_10_0_0_EXFAT,
+
+ FS_VER_10_2_0,
+ FS_VER_10_2_0_EXFAT,
+
+ FS_VER_11_0_0,
+ FS_VER_11_0_0_EXFAT,
+
+ FS_VER_12_0_0,
+ FS_VER_12_0_0_EXFAT,
+
+ FS_VER_12_0_3,
+ FS_VER_12_0_3_EXFAT,
+
+ FS_VER_13_0_0,
+ FS_VER_13_0_0_EXFAT,
+
+ FS_VER_13_1_0,
+ FS_VER_13_1_0_EXFAT,
+
+ FS_VER_14_0_0,
+ FS_VER_14_0_0_EXFAT,
+
+ FS_VER_15_0_0,
+ FS_VER_15_0_0_EXFAT,
+
+ FS_VER_16_0_0,
+ FS_VER_16_0_0_EXFAT,
+
+ FS_VER_16_0_3,
+ FS_VER_16_0_3_EXFAT,
+
+ FS_VER_17_0_0,
+ FS_VER_17_0_0_EXFAT,
+
+ FS_VER_18_0_0,
+ FS_VER_18_0_0_EXFAT,
+
+ FS_VER_18_1_0,
+ FS_VER_18_1_0_EXFAT,
+
+ FS_VER_19_0_0,
+ FS_VER_19_0_0_EXFAT,
+
+ FS_VER_20_0_0,
+ FS_VER_20_0_0_EXFAT,
+
+ FS_VER_20_1_0,
+ FS_VER_20_1_0_EXFAT,
+
+ FS_VER_MAX,
+};
+
+#endif // __FS_VERSIONS_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/100.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/100.h
new file mode 100644
index 00000000..0f94f19f
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/100.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_100_H__
+#define __FS_100_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_100_SDMMC_ACCESSOR_GC 0x6F850
+#define FS_OFFSET_100_SDMMC_ACCESSOR_SD 0x6F65C
+#define FS_OFFSET_100_SDMMC_ACCESSOR_NAND 0x6F230
+
+// Hooks
+#define FS_OFFSET_100_SDMMC_WRAPPER_READ 0x6A930
+#define FS_OFFSET_100_SDMMC_WRAPPER_WRITE 0x6A9F0
+#define FS_OFFSET_100_RTLD 0x534
+#define FS_OFFSET_100_RTLD_DESTINATION 0xA0
+
+#define FS_OFFSET_100_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_100_LOCK_MUTEX 0x2884
+#define FS_OFFSET_100_UNLOCK_MUTEX 0x28F0
+
+#define FS_OFFSET_100_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_100_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x6A8AC
+
+// Misc Data
+#define FS_OFFSET_100_SD_MUTEX 0xE36058
+#define FS_OFFSET_100_NAND_MUTEX 0xE30610
+#define FS_OFFSET_100_ACTIVE_PARTITION 0xE30650
+#define FS_OFFSET_100_SDMMC_DAS_HANDLE 0xE2F730
+
+// NOPs
+#define FS_OFFSET_100_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_100_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 8, .adrp_offset = 0x00032C58, .add_rel_offset = 8}, \
+ {.opcode_reg = 9, .adrp_offset = 0x00032F40, .add_rel_offset = 8}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_100_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1000.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1000.h
new file mode 100644
index 00000000..1471eb93
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1000.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1000_H__
+#define __FS_1000_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1000_SDMMC_ACCESSOR_GC 0x14DC90
+#define FS_OFFSET_1000_SDMMC_ACCESSOR_SD 0x14BDA0
+#define FS_OFFSET_1000_SDMMC_ACCESSOR_NAND 0x146C20
+
+// Hooks
+#define FS_OFFSET_1000_SDMMC_WRAPPER_READ 0x142380
+#define FS_OFFSET_1000_SDMMC_WRAPPER_WRITE 0x142460
+#define FS_OFFSET_1000_RTLD 0x634
+#define FS_OFFSET_1000_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_1000_CLKRST_SET_MIN_V_CLK_RATE 0x1415A0
+
+// Misc funcs
+#define FS_OFFSET_1000_LOCK_MUTEX 0x28910
+#define FS_OFFSET_1000_UNLOCK_MUTEX 0x28960
+
+#define FS_OFFSET_1000_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_1000_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1422E0
+
+// Misc Data
+#define FS_OFFSET_1000_SD_MUTEX 0xE273E8
+#define FS_OFFSET_1000_NAND_MUTEX 0xE22DA0
+#define FS_OFFSET_1000_ACTIVE_PARTITION 0xE22DE0
+#define FS_OFFSET_1000_SDMMC_DAS_HANDLE 0xE0AB90
+
+// NOPs
+#define FS_OFFSET_1000_SD_DAS_INIT 0x151CEC
+
+// Nintendo Paths
+#define FS_OFFSET_1000_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006BBA4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00078520, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007ED0C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009115C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1000_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1000_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1000_exfat.h
new file mode 100644
index 00000000..6dcfe6a0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1000_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1000_EXFAT_H__
+#define __FS_1000_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1000_EXFAT_SDMMC_ACCESSOR_GC 0x14DC90
+#define FS_OFFSET_1000_EXFAT_SDMMC_ACCESSOR_SD 0x14BDA0
+#define FS_OFFSET_1000_EXFAT_SDMMC_ACCESSOR_NAND 0x146C20
+
+// Hooks
+#define FS_OFFSET_1000_EXFAT_SDMMC_WRAPPER_READ 0x142380
+#define FS_OFFSET_1000_EXFAT_SDMMC_WRAPPER_WRITE 0x142460
+#define FS_OFFSET_1000_EXFAT_RTLD 0x634
+#define FS_OFFSET_1000_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_1000_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1415A0
+
+// Misc funcs
+#define FS_OFFSET_1000_EXFAT_LOCK_MUTEX 0x28910
+#define FS_OFFSET_1000_EXFAT_UNLOCK_MUTEX 0x28960
+
+#define FS_OFFSET_1000_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_1000_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1422E0
+
+// Misc Data
+#define FS_OFFSET_1000_EXFAT_SD_MUTEX 0xE353E8
+#define FS_OFFSET_1000_EXFAT_NAND_MUTEX 0xE30DA0
+#define FS_OFFSET_1000_EXFAT_ACTIVE_PARTITION 0xE30DE0
+#define FS_OFFSET_1000_EXFAT_SDMMC_DAS_HANDLE 0xE18B90
+
+// NOPs
+#define FS_OFFSET_1000_EXFAT_SD_DAS_INIT 0x151CEC
+
+// Nintendo Paths
+#define FS_OFFSET_1000_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006BBA4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00078520, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007ED0C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009115C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1000_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1020.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1020.h
new file mode 100644
index 00000000..65d5b48c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1020.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1020_H__
+#define __FS_1020_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1020_SDMMC_ACCESSOR_GC 0x14E0F0
+#define FS_OFFSET_1020_SDMMC_ACCESSOR_SD 0x14C200
+#define FS_OFFSET_1020_SDMMC_ACCESSOR_NAND 0x147080
+
+// Hooks
+#define FS_OFFSET_1020_SDMMC_WRAPPER_READ 0x1427E0
+#define FS_OFFSET_1020_SDMMC_WRAPPER_WRITE 0x1428C0
+#define FS_OFFSET_1020_RTLD 0x634
+#define FS_OFFSET_1020_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_1020_CLKRST_SET_MIN_V_CLK_RATE 0x141A00
+
+// Misc funcs
+#define FS_OFFSET_1020_LOCK_MUTEX 0x28910
+#define FS_OFFSET_1020_UNLOCK_MUTEX 0x28960
+
+#define FS_OFFSET_1020_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_1020_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x142740
+
+// Misc Data
+#define FS_OFFSET_1020_SD_MUTEX 0xE273E8
+#define FS_OFFSET_1020_NAND_MUTEX 0xE22DA0
+#define FS_OFFSET_1020_ACTIVE_PARTITION 0xE22DE0
+#define FS_OFFSET_1020_SDMMC_DAS_HANDLE 0xE0AB90
+
+// NOPs
+#define FS_OFFSET_1020_SD_DAS_INIT 0x15214C
+
+// Nintendo Paths
+#define FS_OFFSET_1020_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006BBA4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00078520, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007ED0C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009115C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1020_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1020_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1020_exfat.h
new file mode 100644
index 00000000..34c03d9a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1020_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1020_EXFAT_H__
+#define __FS_1020_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1020_EXFAT_SDMMC_ACCESSOR_GC 0x14E0F0
+#define FS_OFFSET_1020_EXFAT_SDMMC_ACCESSOR_SD 0x14C200
+#define FS_OFFSET_1020_EXFAT_SDMMC_ACCESSOR_NAND 0x147080
+
+// Hooks
+#define FS_OFFSET_1020_EXFAT_SDMMC_WRAPPER_READ 0x1427E0
+#define FS_OFFSET_1020_EXFAT_SDMMC_WRAPPER_WRITE 0x1428C0
+#define FS_OFFSET_1020_EXFAT_RTLD 0x634
+#define FS_OFFSET_1020_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_1020_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x141A00
+
+// Misc funcs
+#define FS_OFFSET_1020_EXFAT_LOCK_MUTEX 0x28910
+#define FS_OFFSET_1020_EXFAT_UNLOCK_MUTEX 0x28960
+
+#define FS_OFFSET_1020_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_1020_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x142740
+
+// Misc Data
+#define FS_OFFSET_1020_EXFAT_SD_MUTEX 0xE353E8
+#define FS_OFFSET_1020_EXFAT_NAND_MUTEX 0xE30DA0
+#define FS_OFFSET_1020_EXFAT_ACTIVE_PARTITION 0xE30DE0
+#define FS_OFFSET_1020_EXFAT_SDMMC_DAS_HANDLE 0xE18B90
+
+// NOPs
+#define FS_OFFSET_1020_EXFAT_SD_DAS_INIT 0x15214C
+
+// Nintendo Paths
+#define FS_OFFSET_1020_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006BBA4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00078520, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007ED0C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009115C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1020_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1100.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1100.h
new file mode 100644
index 00000000..164ec37d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1100.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1100_H__
+#define __FS_1100_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1100_SDMMC_ACCESSOR_GC 0x156D90
+#define FS_OFFSET_1100_SDMMC_ACCESSOR_SD 0x154F40
+#define FS_OFFSET_1100_SDMMC_ACCESSOR_NAND 0x1500F0
+
+// Hooks
+#define FS_OFFSET_1100_SDMMC_WRAPPER_READ 0x14B990
+#define FS_OFFSET_1100_SDMMC_WRAPPER_WRITE 0x14BA70
+#define FS_OFFSET_1100_RTLD 0x688
+#define FS_OFFSET_1100_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1100_CLKRST_SET_MIN_V_CLK_RATE 0x14AC40
+
+// Misc funcs
+#define FS_OFFSET_1100_LOCK_MUTEX 0x28FF0
+#define FS_OFFSET_1100_UNLOCK_MUTEX 0x29040
+
+#define FS_OFFSET_1100_SDMMC_WRAPPER_CONTROLLER_OPEN 0x14B840
+#define FS_OFFSET_1100_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x14B8F0
+
+// Misc Data
+#define FS_OFFSET_1100_SD_MUTEX 0xE323E8
+#define FS_OFFSET_1100_NAND_MUTEX 0xE2D338
+#define FS_OFFSET_1100_ACTIVE_PARTITION 0xE2D378
+#define FS_OFFSET_1100_SDMMC_DAS_HANDLE 0xE15D40
+
+// NOPs
+#define FS_OFFSET_1100_SD_DAS_INIT 0x273B4
+
+// Nintendo Paths
+#define FS_OFFSET_1100_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006D944, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007A3C0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00080708, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092198, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1100_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1100_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1100_exfat.h
new file mode 100644
index 00000000..ecc70566
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1100_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1100_EXFAT_H__
+#define __FS_1100_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1100_EXFAT_SDMMC_ACCESSOR_GC 0x156D90
+#define FS_OFFSET_1100_EXFAT_SDMMC_ACCESSOR_SD 0x154F40
+#define FS_OFFSET_1100_EXFAT_SDMMC_ACCESSOR_NAND 0x1500F0
+
+// Hooks
+#define FS_OFFSET_1100_EXFAT_SDMMC_WRAPPER_READ 0x14B990
+#define FS_OFFSET_1100_EXFAT_SDMMC_WRAPPER_WRITE 0x14BA70
+#define FS_OFFSET_1100_EXFAT_RTLD 0x688
+#define FS_OFFSET_1100_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1100_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x14AC40
+
+// Misc funcs
+#define FS_OFFSET_1100_EXFAT_LOCK_MUTEX 0x28FF0
+#define FS_OFFSET_1100_EXFAT_UNLOCK_MUTEX 0x29040
+
+#define FS_OFFSET_1100_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x14B840
+#define FS_OFFSET_1100_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x14B8F0
+
+// Misc Data
+#define FS_OFFSET_1100_EXFAT_SD_MUTEX 0xE403E8
+#define FS_OFFSET_1100_EXFAT_NAND_MUTEX 0xE3B338
+#define FS_OFFSET_1100_EXFAT_ACTIVE_PARTITION 0xE3B378
+#define FS_OFFSET_1100_EXFAT_SDMMC_DAS_HANDLE 0xE23D40
+
+// NOPs
+#define FS_OFFSET_1100_EXFAT_SD_DAS_INIT 0x273B4
+
+// Nintendo Paths
+#define FS_OFFSET_1100_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006D944, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007A3C0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00080708, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092198, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1100_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1200.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1200.h
new file mode 100644
index 00000000..c18d4340
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1200.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1200_H__
+#define __FS_1200_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1200_SDMMC_ACCESSOR_GC 0x154FD0
+#define FS_OFFSET_1200_SDMMC_ACCESSOR_SD 0x156DE0
+#define FS_OFFSET_1200_SDMMC_ACCESSOR_NAND 0x155500
+
+// Hooks
+#define FS_OFFSET_1200_SDMMC_WRAPPER_READ 0x150970
+#define FS_OFFSET_1200_SDMMC_WRAPPER_WRITE 0x150A30
+#define FS_OFFSET_1200_RTLD 0x688
+#define FS_OFFSET_1200_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1200_CLKRST_SET_MIN_V_CLK_RATE 0x14FCC0
+
+// Misc funcs
+#define FS_OFFSET_1200_LOCK_MUTEX 0x29350
+#define FS_OFFSET_1200_UNLOCK_MUTEX 0x293A0
+
+#define FS_OFFSET_1200_SDMMC_WRAPPER_CONTROLLER_OPEN 0x150850
+#define FS_OFFSET_1200_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1508E0
+
+// Misc Data
+#define FS_OFFSET_1200_SD_MUTEX 0xE3D3E8
+#define FS_OFFSET_1200_NAND_MUTEX 0xE38768
+#define FS_OFFSET_1200_ACTIVE_PARTITION 0xE387A8
+#define FS_OFFSET_1200_SDMMC_DAS_HANDLE 0xE20DB0
+
+// NOPs
+#define FS_OFFSET_1200_SD_DAS_INIT 0x27244
+
+// Nintendo Paths
+#define FS_OFFSET_1200_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006E810, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AEC0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00081254, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092850, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1200_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1200_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1200_exfat.h
new file mode 100644
index 00000000..b6fb7ef9
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1200_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1200_EXFAT_H__
+#define __FS_1200_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1200_EXFAT_SDMMC_ACCESSOR_GC 0x154FD0
+#define FS_OFFSET_1200_EXFAT_SDMMC_ACCESSOR_SD 0x156DE0
+#define FS_OFFSET_1200_EXFAT_SDMMC_ACCESSOR_NAND 0x155500
+
+// Hooks
+#define FS_OFFSET_1200_EXFAT_SDMMC_WRAPPER_READ 0x150970
+#define FS_OFFSET_1200_EXFAT_SDMMC_WRAPPER_WRITE 0x150A30
+#define FS_OFFSET_1200_EXFAT_RTLD 0x688
+#define FS_OFFSET_1200_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1200_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x14FCC0
+
+// Misc funcs
+#define FS_OFFSET_1200_EXFAT_LOCK_MUTEX 0x29350
+#define FS_OFFSET_1200_EXFAT_UNLOCK_MUTEX 0x293A0
+
+#define FS_OFFSET_1200_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x150850
+#define FS_OFFSET_1200_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1508E0
+
+// Misc Data
+#define FS_OFFSET_1200_EXFAT_SD_MUTEX 0xE4B3E8
+#define FS_OFFSET_1200_EXFAT_NAND_MUTEX 0xE46768
+#define FS_OFFSET_1200_EXFAT_ACTIVE_PARTITION 0xE467A8
+#define FS_OFFSET_1200_EXFAT_SDMMC_DAS_HANDLE 0xE2EDB0
+
+// NOPs
+#define FS_OFFSET_1200_EXFAT_SD_DAS_INIT 0x27244
+
+// Nintendo Paths
+#define FS_OFFSET_1200_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006E810, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AEC0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00081254, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092850, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1200_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1203.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1203.h
new file mode 100644
index 00000000..13aafa64
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1203.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ * Copyright (c) 2021 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1203_H__
+#define __FS_1203_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1203_SDMMC_ACCESSOR_GC 0x1550E0
+#define FS_OFFSET_1203_SDMMC_ACCESSOR_SD 0x156EF0
+#define FS_OFFSET_1203_SDMMC_ACCESSOR_NAND 0x155610
+
+// Hooks
+#define FS_OFFSET_1203_SDMMC_WRAPPER_READ 0x150A80
+#define FS_OFFSET_1203_SDMMC_WRAPPER_WRITE 0x150B40
+#define FS_OFFSET_1203_RTLD 0x688
+#define FS_OFFSET_1203_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1203_CLKRST_SET_MIN_V_CLK_RATE 0x14FDD0
+
+// Misc funcs
+#define FS_OFFSET_1203_LOCK_MUTEX 0x29350
+#define FS_OFFSET_1203_UNLOCK_MUTEX 0x293A0
+
+#define FS_OFFSET_1203_SDMMC_WRAPPER_CONTROLLER_OPEN 0x150960
+#define FS_OFFSET_1203_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1509F0
+
+// Misc Data
+#define FS_OFFSET_1203_SD_MUTEX 0xE3D3E8
+#define FS_OFFSET_1203_NAND_MUTEX 0xE38768
+#define FS_OFFSET_1203_ACTIVE_PARTITION 0xE387A8
+#define FS_OFFSET_1203_SDMMC_DAS_HANDLE 0xE20DB0
+
+// NOPs
+#define FS_OFFSET_1203_SD_DAS_INIT 0x27244
+
+// Nintendo Paths
+#define FS_OFFSET_1203_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006E920, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AFD0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00081364, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092960, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1203_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1203_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1203_exfat.h
new file mode 100644
index 00000000..016a1c4e
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1203_exfat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ * Copyright (c) 2021 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1203_EXFAT_H__
+#define __FS_1203_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1203_EXFAT_SDMMC_ACCESSOR_GC 0x1550E0
+#define FS_OFFSET_1203_EXFAT_SDMMC_ACCESSOR_SD 0x156EF0
+#define FS_OFFSET_1203_EXFAT_SDMMC_ACCESSOR_NAND 0x155610
+
+// Hooks
+#define FS_OFFSET_1203_EXFAT_SDMMC_WRAPPER_READ 0x150A80
+#define FS_OFFSET_1203_EXFAT_SDMMC_WRAPPER_WRITE 0x150B40
+#define FS_OFFSET_1203_EXFAT_RTLD 0x688
+#define FS_OFFSET_1203_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1203_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x14FDD0
+
+// Misc funcs
+#define FS_OFFSET_1203_EXFAT_LOCK_MUTEX 0x29350
+#define FS_OFFSET_1203_EXFAT_UNLOCK_MUTEX 0x293A0
+
+#define FS_OFFSET_1203_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x150960
+#define FS_OFFSET_1203_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1509F0
+
+// Misc Data
+#define FS_OFFSET_1203_EXFAT_SD_MUTEX 0xE4B3E8
+#define FS_OFFSET_1203_EXFAT_NAND_MUTEX 0xE46768
+#define FS_OFFSET_1203_EXFAT_ACTIVE_PARTITION 0xE467A8
+#define FS_OFFSET_1203_EXFAT_SDMMC_DAS_HANDLE 0xE2EDB0
+
+// NOPs
+#define FS_OFFSET_1203_EXFAT_SD_DAS_INIT 0x27244
+
+// Nintendo Paths
+#define FS_OFFSET_1203_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006E920, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AFD0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00081364, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092960, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1203_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1300.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1300.h
new file mode 100644
index 00000000..2503527a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1300.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1300_H__
+#define __FS_1300_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1300_SDMMC_ACCESSOR_GC 0x158C80
+#define FS_OFFSET_1300_SDMMC_ACCESSOR_SD 0x15AA90
+#define FS_OFFSET_1300_SDMMC_ACCESSOR_NAND 0x1591B0
+
+// Hooks
+#define FS_OFFSET_1300_SDMMC_WRAPPER_READ 0x154620
+#define FS_OFFSET_1300_SDMMC_WRAPPER_WRITE 0x1546E0
+#define FS_OFFSET_1300_RTLD 0x688
+#define FS_OFFSET_1300_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1300_CLKRST_SET_MIN_V_CLK_RATE 0x153820
+
+// Misc funcs
+#define FS_OFFSET_1300_LOCK_MUTEX 0x29690
+#define FS_OFFSET_1300_UNLOCK_MUTEX 0x296E0
+
+#define FS_OFFSET_1300_SDMMC_WRAPPER_CONTROLLER_OPEN 0x154500
+#define FS_OFFSET_1300_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x154590
+
+// Misc Data
+#define FS_OFFSET_1300_SD_MUTEX 0xE133E8
+#define FS_OFFSET_1300_NAND_MUTEX 0xE0E768
+#define FS_OFFSET_1300_ACTIVE_PARTITION 0xE0E7A8
+#define FS_OFFSET_1300_SDMMC_DAS_HANDLE 0xDF6E18
+
+// NOPs
+#define FS_OFFSET_1300_SD_DAS_INIT 0x27744
+
+// Nintendo Paths
+#define FS_OFFSET_1300_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006EBE0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007BEEC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00082294, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009422C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1300_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1300_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1300_exfat.h
new file mode 100644
index 00000000..08dfc396
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1300_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1300_EXFAT_H__
+#define __FS_1300_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1300_EXFAT_SDMMC_ACCESSOR_GC 0x158C80
+#define FS_OFFSET_1300_EXFAT_SDMMC_ACCESSOR_SD 0x15AA90
+#define FS_OFFSET_1300_EXFAT_SDMMC_ACCESSOR_NAND 0x1591B0
+
+// Hooks
+#define FS_OFFSET_1300_EXFAT_SDMMC_WRAPPER_READ 0x154620
+#define FS_OFFSET_1300_EXFAT_SDMMC_WRAPPER_WRITE 0x1546E0
+#define FS_OFFSET_1300_EXFAT_RTLD 0x688
+#define FS_OFFSET_1300_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1300_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x153820
+
+// Misc funcs
+#define FS_OFFSET_1300_EXFAT_LOCK_MUTEX 0x29690
+#define FS_OFFSET_1300_EXFAT_UNLOCK_MUTEX 0x296E0
+
+#define FS_OFFSET_1300_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x154500
+#define FS_OFFSET_1300_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x154590
+
+// Misc Data
+#define FS_OFFSET_1300_EXFAT_SD_MUTEX 0xE203E8
+#define FS_OFFSET_1300_EXFAT_NAND_MUTEX 0xE1B768
+#define FS_OFFSET_1300_EXFAT_ACTIVE_PARTITION 0xE1B7A8
+#define FS_OFFSET_1300_EXFAT_SDMMC_DAS_HANDLE 0xE03E18
+
+// NOPs
+#define FS_OFFSET_1300_EXFAT_SD_DAS_INIT 0x27744
+
+// Nintendo Paths
+#define FS_OFFSET_1300_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006EBE0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007BEEC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00082294, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009422C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1300_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1310.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1310.h
new file mode 100644
index 00000000..5d4e0522
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1310.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1310_H__
+#define __FS_1310_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1310_SDMMC_ACCESSOR_GC 0x158C20
+#define FS_OFFSET_1310_SDMMC_ACCESSOR_SD 0x15AA30
+#define FS_OFFSET_1310_SDMMC_ACCESSOR_NAND 0x159150
+
+// Hooks
+#define FS_OFFSET_1310_SDMMC_WRAPPER_READ 0x1545C0
+#define FS_OFFSET_1310_SDMMC_WRAPPER_WRITE 0x154680
+#define FS_OFFSET_1310_RTLD 0x688
+#define FS_OFFSET_1310_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1310_CLKRST_SET_MIN_V_CLK_RATE 0x1537C0
+
+// Misc funcs
+#define FS_OFFSET_1310_LOCK_MUTEX 0x29690
+#define FS_OFFSET_1310_UNLOCK_MUTEX 0x296E0
+
+#define FS_OFFSET_1310_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1544A0
+#define FS_OFFSET_1310_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x154530
+
+// Misc Data
+#define FS_OFFSET_1310_SD_MUTEX 0xE133E8
+#define FS_OFFSET_1310_NAND_MUTEX 0xE0E768
+#define FS_OFFSET_1310_ACTIVE_PARTITION 0xE0E7A8
+#define FS_OFFSET_1310_SDMMC_DAS_HANDLE 0xDF6E18
+
+// NOPs
+#define FS_OFFSET_1310_SD_DAS_INIT 0x27744
+
+// Nintendo Paths
+#define FS_OFFSET_1310_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006EBE0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007BEEC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00082294, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009422C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1310_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1310_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1310_exfat.h
new file mode 100644
index 00000000..33bdc24b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1310_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1310_EXFAT_H__
+#define __FS_1310_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1310_EXFAT_SDMMC_ACCESSOR_GC 0x158C20
+#define FS_OFFSET_1310_EXFAT_SDMMC_ACCESSOR_SD 0x15AA30
+#define FS_OFFSET_1310_EXFAT_SDMMC_ACCESSOR_NAND 0x159150
+
+// Hooks
+#define FS_OFFSET_1310_EXFAT_SDMMC_WRAPPER_READ 0x1545C0
+#define FS_OFFSET_1310_EXFAT_SDMMC_WRAPPER_WRITE 0x154680
+#define FS_OFFSET_1310_EXFAT_RTLD 0x688
+#define FS_OFFSET_1310_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1310_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1537C0
+
+// Misc funcs
+#define FS_OFFSET_1310_EXFAT_LOCK_MUTEX 0x29690
+#define FS_OFFSET_1310_EXFAT_UNLOCK_MUTEX 0x296E0
+
+#define FS_OFFSET_1310_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1544A0
+#define FS_OFFSET_1310_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x154530
+
+// Misc Data
+#define FS_OFFSET_1310_EXFAT_SD_MUTEX 0xE203E8
+#define FS_OFFSET_1310_EXFAT_NAND_MUTEX 0xE1B768
+#define FS_OFFSET_1310_EXFAT_ACTIVE_PARTITION 0xE1B7A8
+#define FS_OFFSET_1310_EXFAT_SDMMC_DAS_HANDLE 0xE03E18
+
+// NOPs
+#define FS_OFFSET_1310_EXFAT_SD_DAS_INIT 0x27744
+
+// Nintendo Paths
+#define FS_OFFSET_1310_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006EBE0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007BEEC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00082294, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009422C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1310_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1400.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1400.h
new file mode 100644
index 00000000..c6e1acae
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1400.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1400_H__
+#define __FS_1400_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1400_SDMMC_ACCESSOR_GC 0x189F50
+#define FS_OFFSET_1400_SDMMC_ACCESSOR_SD 0x18BD60
+#define FS_OFFSET_1400_SDMMC_ACCESSOR_NAND 0x18A480
+
+// Hooks
+#define FS_OFFSET_1400_SDMMC_WRAPPER_READ 0x185AF0
+#define FS_OFFSET_1400_SDMMC_WRAPPER_WRITE 0x185B50
+#define FS_OFFSET_1400_RTLD 0x282B8
+#define FS_OFFSET_1400_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1400_CLKRST_SET_MIN_V_CLK_RATE 0x1A5D90
+
+// Misc funcs
+#define FS_OFFSET_1400_LOCK_MUTEX 0x17E9F0
+#define FS_OFFSET_1400_UNLOCK_MUTEX 0x17EA40
+
+#define FS_OFFSET_1400_SDMMC_WRAPPER_CONTROLLER_OPEN 0x185AA0
+#define FS_OFFSET_1400_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x185AD0
+
+// Misc Data
+#define FS_OFFSET_1400_SD_MUTEX 0xF2E3F0
+#define FS_OFFSET_1400_NAND_MUTEX 0xF292F8
+#define FS_OFFSET_1400_ACTIVE_PARTITION 0xF29338
+#define FS_OFFSET_1400_SDMMC_DAS_HANDLE 0xDFE9C8
+
+// NOPs
+#define FS_OFFSET_1400_SD_DAS_INIT 0x27004
+
+// Nintendo Paths
+#define FS_OFFSET_1400_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006D9C0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AC24, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000813E8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009387C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1400_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1400_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1400_exfat.h
new file mode 100644
index 00000000..3ce63e5b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1400_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1400_EXFAT_H__
+#define __FS_1400_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1400_EXFAT_SDMMC_ACCESSOR_GC 0x1952D0
+#define FS_OFFSET_1400_EXFAT_SDMMC_ACCESSOR_SD 0x1970E0
+#define FS_OFFSET_1400_EXFAT_SDMMC_ACCESSOR_NAND 0x195800
+
+// Hooks
+#define FS_OFFSET_1400_EXFAT_SDMMC_WRAPPER_READ 0x190E70
+#define FS_OFFSET_1400_EXFAT_SDMMC_WRAPPER_WRITE 0x190ED0
+#define FS_OFFSET_1400_EXFAT_RTLD 0x282B8
+#define FS_OFFSET_1400_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1400_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1B1110
+
+// Misc funcs
+#define FS_OFFSET_1400_EXFAT_LOCK_MUTEX 0x189D70
+#define FS_OFFSET_1400_EXFAT_UNLOCK_MUTEX 0x189DC0
+
+#define FS_OFFSET_1400_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x190E20
+#define FS_OFFSET_1400_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x190E50
+
+// Misc Data
+#define FS_OFFSET_1400_EXFAT_SD_MUTEX 0x10123F0
+#define FS_OFFSET_1400_EXFAT_NAND_MUTEX 0x100D2F8
+#define FS_OFFSET_1400_EXFAT_ACTIVE_PARTITION 0x100D338
+#define FS_OFFSET_1400_EXFAT_SDMMC_DAS_HANDLE 0xE0B9C8
+
+// NOPs
+#define FS_OFFSET_1400_EXFAT_SD_DAS_INIT 0x27004
+
+// Nintendo Paths
+#define FS_OFFSET_1400_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006D9C0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AC24, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000813E8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009387C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1400_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1500.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1500.h
new file mode 100644
index 00000000..5802f247
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1500.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1500_H__
+#define __FS_1500_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1500_SDMMC_ACCESSOR_GC 0x183E20
+#define FS_OFFSET_1500_SDMMC_ACCESSOR_SD 0x185AA0
+#define FS_OFFSET_1500_SDMMC_ACCESSOR_NAND 0x1842E0
+
+// Hooks
+#define FS_OFFSET_1500_SDMMC_WRAPPER_READ 0x17FCF0
+#define FS_OFFSET_1500_SDMMC_WRAPPER_WRITE 0x17FD50
+#define FS_OFFSET_1500_RTLD 0x26518
+#define FS_OFFSET_1500_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1500_CLKRST_SET_MIN_V_CLK_RATE 0x1A0870
+
+// Misc funcs
+#define FS_OFFSET_1500_LOCK_MUTEX 0x1791A0
+#define FS_OFFSET_1500_UNLOCK_MUTEX 0x1791F0
+
+#define FS_OFFSET_1500_SDMMC_WRAPPER_CONTROLLER_OPEN 0x17FCB0
+#define FS_OFFSET_1500_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x17FCD0
+
+// Misc Data
+#define FS_OFFSET_1500_SD_MUTEX 0xFF33F0
+#define FS_OFFSET_1500_NAND_MUTEX 0xFEE2E8
+#define FS_OFFSET_1500_ACTIVE_PARTITION 0xFEE328
+#define FS_OFFSET_1500_SDMMC_DAS_HANDLE 0xFD38D8
+
+// NOPs
+#define FS_OFFSET_1500_SD_DAS_INIT 0x25454
+
+// Nintendo Paths
+#define FS_OFFSET_1500_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00063050, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0006FDE8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000768D4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00089364, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1500_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1500_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1500_exfat.h
new file mode 100644
index 00000000..d757f63d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1500_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1500_EXFAT_H__
+#define __FS_1500_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1500_EXFAT_SDMMC_ACCESSOR_GC 0x18EDB0
+#define FS_OFFSET_1500_EXFAT_SDMMC_ACCESSOR_SD 0x190A30
+#define FS_OFFSET_1500_EXFAT_SDMMC_ACCESSOR_NAND 0x18F270
+
+// Hooks
+#define FS_OFFSET_1500_EXFAT_SDMMC_WRAPPER_READ 0x18AC80
+#define FS_OFFSET_1500_EXFAT_SDMMC_WRAPPER_WRITE 0x18ACE0
+#define FS_OFFSET_1500_EXFAT_RTLD 0x26518
+#define FS_OFFSET_1500_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1500_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1AB800
+
+// Misc funcs
+#define FS_OFFSET_1500_EXFAT_LOCK_MUTEX 0x184130
+#define FS_OFFSET_1500_EXFAT_UNLOCK_MUTEX 0x184180
+
+#define FS_OFFSET_1500_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x18AC40
+#define FS_OFFSET_1500_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x18AC60
+
+// Misc Data
+#define FS_OFFSET_1500_EXFAT_SD_MUTEX 0x10053F0
+#define FS_OFFSET_1500_EXFAT_NAND_MUTEX 0x10002E8
+#define FS_OFFSET_1500_EXFAT_ACTIVE_PARTITION 0x1000328
+#define FS_OFFSET_1500_EXFAT_SDMMC_DAS_HANDLE 0xFE08D8
+
+// NOPs
+#define FS_OFFSET_1500_EXFAT_SD_DAS_INIT 0x25454
+
+// Nintendo Paths
+#define FS_OFFSET_1500_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00063050, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0006FDE8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000768D4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00089364, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1500_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1600.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1600.h
new file mode 100644
index 00000000..9491e8f0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1600.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1600_H__
+#define __FS_1600_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1600_SDMMC_ACCESSOR_GC 0x1862A0
+#define FS_OFFSET_1600_SDMMC_ACCESSOR_SD 0x187F20
+#define FS_OFFSET_1600_SDMMC_ACCESSOR_NAND 0x186760
+
+// Hooks
+#define FS_OFFSET_1600_SDMMC_WRAPPER_READ 0x1821F0
+#define FS_OFFSET_1600_SDMMC_WRAPPER_WRITE 0x182250
+#define FS_OFFSET_1600_RTLD 0x269B0
+#define FS_OFFSET_1600_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1600_CLKRST_SET_MIN_V_CLK_RATE 0x1A2D30
+
+// Misc funcs
+#define FS_OFFSET_1600_LOCK_MUTEX 0x17B730
+#define FS_OFFSET_1600_UNLOCK_MUTEX 0x17B780
+
+#define FS_OFFSET_1600_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1821B0
+#define FS_OFFSET_1600_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1821D0
+
+// Misc Data
+#define FS_OFFSET_1600_SD_MUTEX 0xFFB3F0
+#define FS_OFFSET_1600_NAND_MUTEX 0xFF6B58
+#define FS_OFFSET_1600_ACTIVE_PARTITION 0xFF6B98
+#define FS_OFFSET_1600_SDMMC_DAS_HANDLE 0xFDC8B0
+
+// NOPs
+#define FS_OFFSET_1600_SD_DAS_INIT 0x258D4
+
+// Nintendo Paths
+#define FS_OFFSET_1600_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00063B48, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070D6C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007790C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008A754, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1600_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1600_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1600_exfat.h
new file mode 100644
index 00000000..6d5c59c6
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1600_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1600_EXFAT_H__
+#define __FS_1600_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1600_EXFAT_SDMMC_ACCESSOR_GC 0x190F80
+#define FS_OFFSET_1600_EXFAT_SDMMC_ACCESSOR_SD 0x192C00
+#define FS_OFFSET_1600_EXFAT_SDMMC_ACCESSOR_NAND 0x191440
+
+// Hooks
+#define FS_OFFSET_1600_EXFAT_SDMMC_WRAPPER_READ 0x18CED0
+#define FS_OFFSET_1600_EXFAT_SDMMC_WRAPPER_WRITE 0x18CF30
+#define FS_OFFSET_1600_EXFAT_RTLD 0x269B0
+#define FS_OFFSET_1600_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1600_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1ADA10
+
+// Misc funcs
+#define FS_OFFSET_1600_EXFAT_LOCK_MUTEX 0x186410
+#define FS_OFFSET_1600_EXFAT_UNLOCK_MUTEX 0x186460
+
+#define FS_OFFSET_1600_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x18CE90
+#define FS_OFFSET_1600_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x18CEB0
+
+// Misc Data
+#define FS_OFFSET_1600_EXFAT_SD_MUTEX 0x100D3F0
+#define FS_OFFSET_1600_EXFAT_NAND_MUTEX 0x1008B58
+#define FS_OFFSET_1600_EXFAT_ACTIVE_PARTITION 0x1008B98
+#define FS_OFFSET_1600_EXFAT_SDMMC_DAS_HANDLE 0xFE98B0
+
+// NOPs
+#define FS_OFFSET_1600_EXFAT_SD_DAS_INIT 0x258D4
+
+// Nintendo Paths
+#define FS_OFFSET_1600_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00063B48, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070D6C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007790C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008A754, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1600_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1603.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1603.h
new file mode 100644
index 00000000..0bb2768f
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1603.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1603_H__
+#define __FS_1603_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1603_SDMMC_ACCESSOR_GC 0x1862F0
+#define FS_OFFSET_1603_SDMMC_ACCESSOR_SD 0x187F70
+#define FS_OFFSET_1603_SDMMC_ACCESSOR_NAND 0x1867B0
+
+// Hooks
+#define FS_OFFSET_1603_SDMMC_WRAPPER_READ 0x182240
+#define FS_OFFSET_1603_SDMMC_WRAPPER_WRITE 0x1822A0
+#define FS_OFFSET_1603_RTLD 0x269B0
+#define FS_OFFSET_1603_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1603_CLKRST_SET_MIN_V_CLK_RATE 0x1A2D80
+
+// Misc funcs
+#define FS_OFFSET_1603_LOCK_MUTEX 0x17B780
+#define FS_OFFSET_1603_UNLOCK_MUTEX 0x17B7D0
+
+#define FS_OFFSET_1603_SDMMC_WRAPPER_CONTROLLER_OPEN 0x182200
+#define FS_OFFSET_1603_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x182220
+
+// Misc Data
+#define FS_OFFSET_1603_SD_MUTEX 0xFFB3F0
+#define FS_OFFSET_1603_NAND_MUTEX 0xFF6B58
+#define FS_OFFSET_1603_ACTIVE_PARTITION 0xFF6B98
+#define FS_OFFSET_1603_SDMMC_DAS_HANDLE 0xFDC8B0
+
+// NOPs
+#define FS_OFFSET_1603_SD_DAS_INIT 0x258D4
+
+// Nintendo Paths
+#define FS_OFFSET_1603_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00063B98, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070DBC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007795C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008A7A4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1603_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1603_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1603_exfat.h
new file mode 100644
index 00000000..5e48cb48
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1603_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1603_EXFAT_H__
+#define __FS_1603_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1603_EXFAT_SDMMC_ACCESSOR_GC 0x190FD0
+#define FS_OFFSET_1603_EXFAT_SDMMC_ACCESSOR_SD 0x192C50
+#define FS_OFFSET_1603_EXFAT_SDMMC_ACCESSOR_NAND 0x191490
+
+// Hooks
+#define FS_OFFSET_1603_EXFAT_SDMMC_WRAPPER_READ 0x18CF20
+#define FS_OFFSET_1603_EXFAT_SDMMC_WRAPPER_WRITE 0x18CF80
+#define FS_OFFSET_1603_EXFAT_RTLD 0x269B0
+#define FS_OFFSET_1603_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1603_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1ADA60
+
+// Misc funcs
+#define FS_OFFSET_1603_EXFAT_LOCK_MUTEX 0x186460
+#define FS_OFFSET_1603_EXFAT_UNLOCK_MUTEX 0x1864B0
+
+#define FS_OFFSET_1603_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x18CEE0
+#define FS_OFFSET_1603_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x18CF00
+
+// Misc Data
+#define FS_OFFSET_1603_EXFAT_SD_MUTEX 0x100D3F0
+#define FS_OFFSET_1603_EXFAT_NAND_MUTEX 0x1008B58
+#define FS_OFFSET_1603_EXFAT_ACTIVE_PARTITION 0x1008B98
+#define FS_OFFSET_1603_EXFAT_SDMMC_DAS_HANDLE 0xFE98B0
+
+// NOPs
+#define FS_OFFSET_1603_EXFAT_SD_DAS_INIT 0x258D4
+
+// Nintendo Paths
+#define FS_OFFSET_1603_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00063B98, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070DBC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007795C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008A7A4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1603_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1700.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1700.h
new file mode 100644
index 00000000..7a9cf825
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1700.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1700_H__
+#define __FS_1700_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1700_SDMMC_ACCESSOR_GC 0x18AD00
+#define FS_OFFSET_1700_SDMMC_ACCESSOR_SD 0x18C9D0
+#define FS_OFFSET_1700_SDMMC_ACCESSOR_NAND 0x18B1D0
+
+// Hooks
+#define FS_OFFSET_1700_SDMMC_WRAPPER_READ 0x186BC0
+#define FS_OFFSET_1700_SDMMC_WRAPPER_WRITE 0x186C20
+#define FS_OFFSET_1700_RTLD 0x29D10
+#define FS_OFFSET_1700_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1700_CLKRST_SET_MIN_V_CLK_RATE 0x1A7B60
+
+// Misc funcs
+#define FS_OFFSET_1700_LOCK_MUTEX 0x17FEA0
+#define FS_OFFSET_1700_UNLOCK_MUTEX 0x17FEF0
+
+#define FS_OFFSET_1700_SDMMC_WRAPPER_CONTROLLER_OPEN 0x186B80
+#define FS_OFFSET_1700_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x186BA0
+
+// Misc Data
+#define FS_OFFSET_1700_SD_MUTEX 0xFCE3F0
+#define FS_OFFSET_1700_NAND_MUTEX 0xFC9B78
+#define FS_OFFSET_1700_ACTIVE_PARTITION 0xFC9BB8
+#define FS_OFFSET_1700_SDMMC_DAS_HANDLE 0xFAF840
+
+// NOPs
+#define FS_OFFSET_1700_SD_DAS_INIT 0x28C64
+
+// Nintendo Paths
+#define FS_OFFSET_1700_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068068, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007510C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007BEAC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008F674, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1700_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1700_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1700_exfat.h
new file mode 100644
index 00000000..e479724e
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1700_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1700_EXFAT_H__
+#define __FS_1700_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1700_EXFAT_SDMMC_ACCESSOR_GC 0x195B60
+#define FS_OFFSET_1700_EXFAT_SDMMC_ACCESSOR_SD 0x197830
+#define FS_OFFSET_1700_EXFAT_SDMMC_ACCESSOR_NAND 0x196030
+
+// Hooks
+#define FS_OFFSET_1700_EXFAT_SDMMC_WRAPPER_READ 0x191A20
+#define FS_OFFSET_1700_EXFAT_SDMMC_WRAPPER_WRITE 0x191A80
+#define FS_OFFSET_1700_EXFAT_RTLD 0x29D10
+#define FS_OFFSET_1700_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x3C)))
+
+#define FS_OFFSET_1700_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1B29C0
+
+// Misc funcs
+#define FS_OFFSET_1700_EXFAT_LOCK_MUTEX 0x18AD00
+#define FS_OFFSET_1700_EXFAT_UNLOCK_MUTEX 0x18AD50
+
+#define FS_OFFSET_1700_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1919E0
+#define FS_OFFSET_1700_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x191A00
+
+// Misc Data
+#define FS_OFFSET_1700_EXFAT_SD_MUTEX 0xFE03F0
+#define FS_OFFSET_1700_EXFAT_NAND_MUTEX 0xFDBB78
+#define FS_OFFSET_1700_EXFAT_ACTIVE_PARTITION 0xFDBBB8
+#define FS_OFFSET_1700_EXFAT_SDMMC_DAS_HANDLE 0xFBC840
+
+// NOPs
+#define FS_OFFSET_1700_EXFAT_SD_DAS_INIT 0x28C64
+
+// Nintendo Paths
+#define FS_OFFSET_1700_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068068, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007510C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007BEAC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008F674, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1700_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1800.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1800.h
new file mode 100644
index 00000000..1b051315
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1800.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1800_H__
+#define __FS_1800_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1800_SDMMC_ACCESSOR_GC 0x18AB00
+#define FS_OFFSET_1800_SDMMC_ACCESSOR_SD 0x18C800
+#define FS_OFFSET_1800_SDMMC_ACCESSOR_NAND 0x18AFE0
+
+// Hooks
+#define FS_OFFSET_1800_SDMMC_WRAPPER_READ 0x186A50
+#define FS_OFFSET_1800_SDMMC_WRAPPER_WRITE 0x186AB0
+#define FS_OFFSET_1800_RTLD 0x2A3A4
+#define FS_OFFSET_1800_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x44)))
+
+#define FS_OFFSET_1800_CLKRST_SET_MIN_V_CLK_RATE 0x1A77D0
+
+// Misc funcs
+#define FS_OFFSET_1800_LOCK_MUTEX 0x17FCC0
+#define FS_OFFSET_1800_UNLOCK_MUTEX 0x17FD10
+
+#define FS_OFFSET_1800_SDMMC_WRAPPER_CONTROLLER_OPEN 0x186A10
+#define FS_OFFSET_1800_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x186A30
+
+// Misc Data
+#define FS_OFFSET_1800_SD_MUTEX 0xFD13F0
+#define FS_OFFSET_1800_NAND_MUTEX 0xFCCB28
+#define FS_OFFSET_1800_ACTIVE_PARTITION 0xFCCB68
+#define FS_OFFSET_1800_SDMMC_DAS_HANDLE 0xFB1950
+
+// NOPs
+#define FS_OFFSET_1800_SD_DAS_INIT 0x28F24
+
+// Nintendo Paths
+#define FS_OFFSET_1800_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068B08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x000758DC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007C77C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000905C4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1800_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1800_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1800_exfat.h
new file mode 100644
index 00000000..358c65f8
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1800_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1800_EXFAT_H__
+#define __FS_1800_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1800_EXFAT_SDMMC_ACCESSOR_GC 0x195B90
+#define FS_OFFSET_1800_EXFAT_SDMMC_ACCESSOR_SD 0x197890
+#define FS_OFFSET_1800_EXFAT_SDMMC_ACCESSOR_NAND 0x196070
+
+// Hooks
+#define FS_OFFSET_1800_EXFAT_SDMMC_WRAPPER_READ 0x191AE0
+#define FS_OFFSET_1800_EXFAT_SDMMC_WRAPPER_WRITE 0x191B40
+#define FS_OFFSET_1800_EXFAT_RTLD 0x2A3A4
+#define FS_OFFSET_1800_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x44)))
+
+#define FS_OFFSET_1800_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1B2860
+
+// Misc funcs
+#define FS_OFFSET_1800_EXFAT_LOCK_MUTEX 0x18AD50
+#define FS_OFFSET_1800_EXFAT_UNLOCK_MUTEX 0x18ADA0
+
+#define FS_OFFSET_1800_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x191AA0
+#define FS_OFFSET_1800_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x191AC0
+
+// Misc Data
+#define FS_OFFSET_1800_EXFAT_SD_MUTEX 0xFE33F0
+#define FS_OFFSET_1800_EXFAT_NAND_MUTEX 0xFDEB28
+#define FS_OFFSET_1800_EXFAT_ACTIVE_PARTITION 0xFDEB68
+#define FS_OFFSET_1800_EXFAT_SDMMC_DAS_HANDLE 0xFBE950
+
+// NOPs
+#define FS_OFFSET_1800_EXFAT_SD_DAS_INIT 0x28F24
+
+// Nintendo Paths
+#define FS_OFFSET_1800_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068B08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x000758DC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007C77C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000905C4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1800_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1810.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1810.h
new file mode 100644
index 00000000..2b71db7a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1810.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1810_H__
+#define __FS_1810_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1810_SDMMC_ACCESSOR_GC 0x18AB00
+#define FS_OFFSET_1810_SDMMC_ACCESSOR_SD 0x18C800
+#define FS_OFFSET_1810_SDMMC_ACCESSOR_NAND 0x18AFE0
+
+// Hooks
+#define FS_OFFSET_1810_SDMMC_WRAPPER_READ 0x186A50
+#define FS_OFFSET_1810_SDMMC_WRAPPER_WRITE 0x186AB0
+#define FS_OFFSET_1810_RTLD 0x2A3A4
+#define FS_OFFSET_1810_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x44)))
+
+#define FS_OFFSET_1810_CLKRST_SET_MIN_V_CLK_RATE 0x1A77D0
+
+// Misc funcs
+#define FS_OFFSET_1810_LOCK_MUTEX 0x17FCC0
+#define FS_OFFSET_1810_UNLOCK_MUTEX 0x17FD10
+
+#define FS_OFFSET_1810_SDMMC_WRAPPER_CONTROLLER_OPEN 0x186A10
+#define FS_OFFSET_1810_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x186A30
+
+// Misc Data
+#define FS_OFFSET_1810_SD_MUTEX 0xFD13F0
+#define FS_OFFSET_1810_NAND_MUTEX 0xFCCB28
+#define FS_OFFSET_1810_ACTIVE_PARTITION 0xFCCB68
+#define FS_OFFSET_1810_SDMMC_DAS_HANDLE 0xFB1950
+
+// NOPs
+#define FS_OFFSET_1810_SD_DAS_INIT 0x28F24
+
+// Nintendo Paths
+#define FS_OFFSET_1810_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068B08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x000758DC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007C77C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000905C4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1810_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1810_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1810_exfat.h
new file mode 100644
index 00000000..4ac55481
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1810_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1810_EXFAT_H__
+#define __FS_1810_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1810_EXFAT_SDMMC_ACCESSOR_GC 0x195B90
+#define FS_OFFSET_1810_EXFAT_SDMMC_ACCESSOR_SD 0x197890
+#define FS_OFFSET_1810_EXFAT_SDMMC_ACCESSOR_NAND 0x196070
+
+// Hooks
+#define FS_OFFSET_1810_EXFAT_SDMMC_WRAPPER_READ 0x191AE0
+#define FS_OFFSET_1810_EXFAT_SDMMC_WRAPPER_WRITE 0x191B40
+#define FS_OFFSET_1810_EXFAT_RTLD 0x2A3A4
+#define FS_OFFSET_1810_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x44)))
+
+#define FS_OFFSET_1810_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1B2860
+
+// Misc funcs
+#define FS_OFFSET_1810_EXFAT_LOCK_MUTEX 0x18AD50
+#define FS_OFFSET_1810_EXFAT_UNLOCK_MUTEX 0x18ADA0
+
+#define FS_OFFSET_1810_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x191AA0
+#define FS_OFFSET_1810_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x191AC0
+
+// Misc Data
+#define FS_OFFSET_1810_EXFAT_SD_MUTEX 0xFE33F0
+#define FS_OFFSET_1810_EXFAT_NAND_MUTEX 0xFDEB28
+#define FS_OFFSET_1810_EXFAT_ACTIVE_PARTITION 0xFDEB68
+#define FS_OFFSET_1810_EXFAT_SDMMC_DAS_HANDLE 0xFBE950
+
+// NOPs
+#define FS_OFFSET_1810_EXFAT_SD_DAS_INIT 0x28F24
+
+// Nintendo Paths
+#define FS_OFFSET_1810_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068B08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x000758DC, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007C77C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x000905C4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1810_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1900.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1900.h
new file mode 100644
index 00000000..82ffd0f9
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1900.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1900_H__
+#define __FS_1900_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1900_SDMMC_ACCESSOR_GC 0x195C00
+#define FS_OFFSET_1900_SDMMC_ACCESSOR_SD 0x197F80
+#define FS_OFFSET_1900_SDMMC_ACCESSOR_NAND 0x1963B0
+
+// Hooks
+#define FS_OFFSET_1900_SDMMC_WRAPPER_READ 0x191A70
+#define FS_OFFSET_1900_SDMMC_WRAPPER_WRITE 0x191AD0
+#define FS_OFFSET_1900_RTLD 0x275F0
+#define FS_OFFSET_1900_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x50)))
+
+#define FS_OFFSET_1900_CLKRST_SET_MIN_V_CLK_RATE 0x1B3880
+
+// Misc funcs
+#define FS_OFFSET_1900_LOCK_MUTEX 0x18AC20
+#define FS_OFFSET_1900_UNLOCK_MUTEX 0x18AC70
+
+#define FS_OFFSET_1900_SDMMC_WRAPPER_CONTROLLER_OPEN 0x191A30
+#define FS_OFFSET_1900_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x191A50
+
+// Misc Data
+#define FS_OFFSET_1900_SD_MUTEX 0xFE1408
+#define FS_OFFSET_1900_NAND_MUTEX 0xFDCB60
+#define FS_OFFSET_1900_ACTIVE_PARTITION 0xFDCBA0
+#define FS_OFFSET_1900_SDMMC_DAS_HANDLE 0xFC1908
+
+// NOPs
+#define FS_OFFSET_1900_SD_DAS_INIT 0x260C4
+
+// Nintendo Paths
+#define FS_OFFSET_1900_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00067FC8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00075D6C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007D1E8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092818, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1900_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1900_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1900_exfat.h
new file mode 100644
index 00000000..2bbf0c89
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/1900_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_1900_EXFAT_H__
+#define __FS_1900_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_1900_EXFAT_SDMMC_ACCESSOR_GC 0x1A1430
+#define FS_OFFSET_1900_EXFAT_SDMMC_ACCESSOR_SD 0x1A37B0
+#define FS_OFFSET_1900_EXFAT_SDMMC_ACCESSOR_NAND 0x1A1BE0
+
+// Hooks
+#define FS_OFFSET_1900_EXFAT_SDMMC_WRAPPER_READ 0x19D2A0
+#define FS_OFFSET_1900_EXFAT_SDMMC_WRAPPER_WRITE 0x19D300
+#define FS_OFFSET_1900_EXFAT_RTLD 0x275F0
+#define FS_OFFSET_1900_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x50)))
+
+#define FS_OFFSET_1900_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1BF0B0
+
+// Misc funcs
+#define FS_OFFSET_1900_EXFAT_LOCK_MUTEX 0x196450
+#define FS_OFFSET_1900_EXFAT_UNLOCK_MUTEX 0x1964A0
+
+#define FS_OFFSET_1900_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x19D260
+#define FS_OFFSET_1900_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x19D280
+
+// Misc Data
+#define FS_OFFSET_1900_EXFAT_SD_MUTEX 0xFF4408
+#define FS_OFFSET_1900_EXFAT_NAND_MUTEX 0xFEFB60
+#define FS_OFFSET_1900_EXFAT_ACTIVE_PARTITION 0xFEFBA0
+#define FS_OFFSET_1900_EXFAT_SDMMC_DAS_HANDLE 0xFCF908
+
+// NOPs
+#define FS_OFFSET_1900_EXFAT_SD_DAS_INIT 0x260C4
+
+// Nintendo Paths
+#define FS_OFFSET_1900_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00067FC8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00075D6C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007D1E8, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00092818, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_1900_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/200.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/200.h
new file mode 100644
index 00000000..589e25c3
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/200.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_200_H__
+#define __FS_200_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_200_SDMMC_ACCESSOR_GC 0x78BAC
+#define FS_OFFSET_200_SDMMC_ACCESSOR_SD 0x7894C
+#define FS_OFFSET_200_SDMMC_ACCESSOR_NAND 0x784BC
+
+// Hooks
+#define FS_OFFSET_200_SDMMC_WRAPPER_READ 0x73478
+#define FS_OFFSET_200_SDMMC_WRAPPER_WRITE 0x73538
+#define FS_OFFSET_200_RTLD 0x500
+#define FS_OFFSET_200_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_200_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_200_LOCK_MUTEX 0x3264
+#define FS_OFFSET_200_UNLOCK_MUTEX 0x32D0
+
+#define FS_OFFSET_200_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_200_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x733F4
+
+// Misc Data
+#define FS_OFFSET_200_SD_MUTEX 0xE42268
+#define FS_OFFSET_200_NAND_MUTEX 0xE3CED0
+#define FS_OFFSET_200_ACTIVE_PARTITION 0xE3CF10
+#define FS_OFFSET_200_SDMMC_DAS_HANDLE 0xE3BDD0
+
+// NOPs
+#define FS_OFFSET_200_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_200_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00033F08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00035084, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003537C, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_200_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2000.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2000.h
new file mode 100644
index 00000000..b4fe302d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2000.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_2000_H__
+#define __FS_2000_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_2000_SDMMC_ACCESSOR_GC 0x1A7DB0
+#define FS_OFFSET_2000_SDMMC_ACCESSOR_SD 0x1AA130
+#define FS_OFFSET_2000_SDMMC_ACCESSOR_NAND 0x1A8560
+
+// Hooks
+#define FS_OFFSET_2000_SDMMC_WRAPPER_READ 0x1A3C20
+#define FS_OFFSET_2000_SDMMC_WRAPPER_WRITE 0x1A3C80
+#define FS_OFFSET_2000_RTLD 0x2B594
+#define FS_OFFSET_2000_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x4C)))
+
+#define FS_OFFSET_2000_CLKRST_SET_MIN_V_CLK_RATE 0x1C6150
+
+// Misc funcs
+#define FS_OFFSET_2000_LOCK_MUTEX 0x19CD80
+#define FS_OFFSET_2000_UNLOCK_MUTEX 0x19CDD0
+
+#define FS_OFFSET_2000_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1A3BE0
+#define FS_OFFSET_2000_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1A3C00
+
+// Misc Data
+#define FS_OFFSET_2000_SD_MUTEX 0xFF5408
+#define FS_OFFSET_2000_NAND_MUTEX 0xFF0CF0
+#define FS_OFFSET_2000_ACTIVE_PARTITION 0xFF0D30
+#define FS_OFFSET_2000_SDMMC_DAS_HANDLE 0xFD2B08
+
+// NOPs
+#define FS_OFFSET_2000_SD_DAS_INIT 0x289F4
+
+// Nintendo Paths
+#define FS_OFFSET_2000_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006DB14, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007CE1C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00084A08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009AE48, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_2000_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2000_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2000_exfat.h
new file mode 100644
index 00000000..a8245a2a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2000_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_2000_EXFAT_H__
+#define __FS_2000_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_2000_EXFAT_SDMMC_ACCESSOR_GC 0x1B36D0
+#define FS_OFFSET_2000_EXFAT_SDMMC_ACCESSOR_SD 0x1B5A50
+#define FS_OFFSET_2000_EXFAT_SDMMC_ACCESSOR_NAND 0x1B3E80
+
+// Hooks
+#define FS_OFFSET_2000_EXFAT_SDMMC_WRAPPER_READ 0x1AF540
+#define FS_OFFSET_2000_EXFAT_SDMMC_WRAPPER_WRITE 0x1AF5A0
+#define FS_OFFSET_2000_EXFAT_RTLD 0x2B594
+#define FS_OFFSET_2000_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x4C)))
+
+#define FS_OFFSET_2000_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1D1A70
+
+// Misc funcs
+#define FS_OFFSET_2000_EXFAT_LOCK_MUTEX 0x1A86A0
+#define FS_OFFSET_2000_EXFAT_UNLOCK_MUTEX 0x1A86F0
+
+#define FS_OFFSET_2000_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1AF500
+#define FS_OFFSET_2000_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1AF520
+
+// Misc Data
+#define FS_OFFSET_2000_EXFAT_SD_MUTEX 0x1006408
+#define FS_OFFSET_2000_EXFAT_NAND_MUTEX 0x1001CF0
+#define FS_OFFSET_2000_EXFAT_ACTIVE_PARTITION 0x1001D30
+#define FS_OFFSET_2000_EXFAT_SDMMC_DAS_HANDLE 0xFDFB08
+
+// NOPs
+#define FS_OFFSET_2000_EXFAT_SD_DAS_INIT 0x289F4
+
+// Nintendo Paths
+#define FS_OFFSET_2000_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006DB14, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007CE1C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00084A08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009AE48, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_2000_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/200_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/200_exfat.h
new file mode 100644
index 00000000..946d4fbd
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/200_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_200_EXFAT_H__
+#define __FS_200_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_200_EXFAT_SDMMC_ACCESSOR_GC 0x78BAC
+#define FS_OFFSET_200_EXFAT_SDMMC_ACCESSOR_SD 0x7894C
+#define FS_OFFSET_200_EXFAT_SDMMC_ACCESSOR_NAND 0x784BC
+
+// Hooks
+#define FS_OFFSET_200_EXFAT_SDMMC_WRAPPER_READ 0x73478
+#define FS_OFFSET_200_EXFAT_SDMMC_WRAPPER_WRITE 0x73538
+#define FS_OFFSET_200_EXFAT_RTLD 0x500
+#define FS_OFFSET_200_EXFAT_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_200_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_200_EXFAT_LOCK_MUTEX 0x3264
+#define FS_OFFSET_200_EXFAT_UNLOCK_MUTEX 0x32D0
+
+#define FS_OFFSET_200_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_200_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x733F4
+
+// Misc Data
+#define FS_OFFSET_200_EXFAT_SD_MUTEX 0xF22268
+#define FS_OFFSET_200_EXFAT_NAND_MUTEX 0xF1CED0
+#define FS_OFFSET_200_EXFAT_ACTIVE_PARTITION 0xF1CF10
+#define FS_OFFSET_200_EXFAT_SDMMC_DAS_HANDLE 0xF1BDD0
+
+// NOPs
+#define FS_OFFSET_200_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_200_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00033F08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00035084, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003537C, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_200_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2010.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2010.h
new file mode 100644
index 00000000..70691ece
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2010.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_2010_H__
+#define __FS_2010_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_2010_SDMMC_ACCESSOR_GC 0x1A7DB0
+#define FS_OFFSET_2010_SDMMC_ACCESSOR_SD 0x1AA130
+#define FS_OFFSET_2010_SDMMC_ACCESSOR_NAND 0x1A8560
+
+// Hooks
+#define FS_OFFSET_2010_SDMMC_WRAPPER_READ 0x1A3C20
+#define FS_OFFSET_2010_SDMMC_WRAPPER_WRITE 0x1A3C80
+#define FS_OFFSET_2010_RTLD 0x2B594
+#define FS_OFFSET_2010_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x4C)))
+
+#define FS_OFFSET_2010_CLKRST_SET_MIN_V_CLK_RATE 0x1C6150
+
+// Misc funcs
+#define FS_OFFSET_2010_LOCK_MUTEX 0x19CD80
+#define FS_OFFSET_2010_UNLOCK_MUTEX 0x19CDD0
+
+#define FS_OFFSET_2010_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1A3BE0
+#define FS_OFFSET_2010_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1A3C00
+
+// Misc Data
+#define FS_OFFSET_2010_SD_MUTEX 0xFF5408
+#define FS_OFFSET_2010_NAND_MUTEX 0xFF0CF0
+#define FS_OFFSET_2010_ACTIVE_PARTITION 0xFF0D30
+#define FS_OFFSET_2010_SDMMC_DAS_HANDLE 0xFD2B08
+
+// NOPs
+#define FS_OFFSET_2010_SD_DAS_INIT 0x289F4
+
+// Nintendo Paths
+#define FS_OFFSET_2010_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006DB14, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007CE1C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00084A08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009AE48, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_2010_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2010_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2010_exfat.h
new file mode 100644
index 00000000..d07c9e6e
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/2010_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_2010_EXFAT_H__
+#define __FS_2010_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_2010_EXFAT_SDMMC_ACCESSOR_GC 0x1B36D0
+#define FS_OFFSET_2010_EXFAT_SDMMC_ACCESSOR_SD 0x1B5A50
+#define FS_OFFSET_2010_EXFAT_SDMMC_ACCESSOR_NAND 0x1B3E80
+
+// Hooks
+#define FS_OFFSET_2010_EXFAT_SDMMC_WRAPPER_READ 0x1AF540
+#define FS_OFFSET_2010_EXFAT_SDMMC_WRAPPER_WRITE 0x1AF5A0
+#define FS_OFFSET_2010_EXFAT_RTLD 0x2B594
+#define FS_OFFSET_2010_EXFAT_RTLD_DESTINATION ((uintptr_t)(INT64_C(-0x4C)))
+
+#define FS_OFFSET_2010_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x1D1A70
+
+// Misc funcs
+#define FS_OFFSET_2010_EXFAT_LOCK_MUTEX 0x1A86A0
+#define FS_OFFSET_2010_EXFAT_UNLOCK_MUTEX 0x1A86F0
+
+#define FS_OFFSET_2010_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0x1AF500
+#define FS_OFFSET_2010_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1AF520
+
+// Misc Data
+#define FS_OFFSET_2010_EXFAT_SD_MUTEX 0x1006408
+#define FS_OFFSET_2010_EXFAT_NAND_MUTEX 0x1001CF0
+#define FS_OFFSET_2010_EXFAT_ACTIVE_PARTITION 0x1001D30
+#define FS_OFFSET_2010_EXFAT_SDMMC_DAS_HANDLE 0xFDFB08
+
+// NOPs
+#define FS_OFFSET_2010_EXFAT_SD_DAS_INIT 0x289F4
+
+// Nintendo Paths
+#define FS_OFFSET_2010_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0006DB14, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007CE1C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00084A08, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0009AE48, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_2010_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/210.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/210.h
new file mode 100644
index 00000000..20369f11
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/210.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_210_H__
+#define __FS_210_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_210_SDMMC_ACCESSOR_GC 0x78F8C
+#define FS_OFFSET_210_SDMMC_ACCESSOR_SD 0x78D2C
+#define FS_OFFSET_210_SDMMC_ACCESSOR_NAND 0x7889C
+
+// Hooks
+#define FS_OFFSET_210_SDMMC_WRAPPER_READ 0x73858
+#define FS_OFFSET_210_SDMMC_WRAPPER_WRITE 0x73918
+#define FS_OFFSET_210_RTLD 0x500
+#define FS_OFFSET_210_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_210_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_210_LOCK_MUTEX 0x3264
+#define FS_OFFSET_210_UNLOCK_MUTEX 0x32D0
+
+#define FS_OFFSET_210_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_210_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x737D4
+
+// Misc Data
+#define FS_OFFSET_210_SD_MUTEX 0xE43268
+#define FS_OFFSET_210_NAND_MUTEX 0xE3DED0
+#define FS_OFFSET_210_ACTIVE_PARTITION 0xE3DF10
+#define FS_OFFSET_210_SDMMC_DAS_HANDLE 0xE3CDD0
+
+// NOPs
+#define FS_OFFSET_210_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_210_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000342E0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003545C, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00035754, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_210_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/210_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/210_exfat.h
new file mode 100644
index 00000000..844338bc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/210_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_210_EXFAT_H__
+#define __FS_210_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_210_EXFAT_SDMMC_ACCESSOR_GC 0x78F8C
+#define FS_OFFSET_210_EXFAT_SDMMC_ACCESSOR_SD 0x78D2C
+#define FS_OFFSET_210_EXFAT_SDMMC_ACCESSOR_NAND 0x7889C
+
+// Hooks
+#define FS_OFFSET_210_EXFAT_SDMMC_WRAPPER_READ 0x73858
+#define FS_OFFSET_210_EXFAT_SDMMC_WRAPPER_WRITE 0x73918
+#define FS_OFFSET_210_EXFAT_RTLD 0x500
+#define FS_OFFSET_210_EXFAT_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_210_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_210_EXFAT_LOCK_MUTEX 0x3264
+#define FS_OFFSET_210_EXFAT_UNLOCK_MUTEX 0x32D0
+
+#define FS_OFFSET_210_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_210_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x737D4
+
+// Misc Data
+#define FS_OFFSET_210_EXFAT_SD_MUTEX 0xF22268
+#define FS_OFFSET_210_EXFAT_NAND_MUTEX 0xF1CED0
+#define FS_OFFSET_210_EXFAT_ACTIVE_PARTITION 0xF1CF10
+#define FS_OFFSET_210_EXFAT_SDMMC_DAS_HANDLE 0xF1BDD0
+
+// NOPs
+#define FS_OFFSET_210_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_210_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000342E0, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003545C, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00035754, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_210_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/300.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/300.h
new file mode 100644
index 00000000..9908a215
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/300.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_300_H__
+#define __FS_300_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_300_SDMMC_ACCESSOR_GC 0x8FAAC
+#define FS_OFFSET_300_SDMMC_ACCESSOR_SD 0x8F84C
+#define FS_OFFSET_300_SDMMC_ACCESSOR_NAND 0x8F3B8
+
+// Hooks
+#define FS_OFFSET_300_SDMMC_WRAPPER_READ 0x8A2F4
+#define FS_OFFSET_300_SDMMC_WRAPPER_WRITE 0x8A3B4
+#define FS_OFFSET_300_RTLD 0x4E8
+#define FS_OFFSET_300_RTLD_DESTINATION 0x8C
+
+#define FS_OFFSET_300_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_300_LOCK_MUTEX 0x35CC
+#define FS_OFFSET_300_UNLOCK_MUTEX 0x3638
+
+#define FS_OFFSET_300_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_300_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x8A270
+
+// Misc Data
+#define FS_OFFSET_300_SD_MUTEX 0xE69268
+#define FS_OFFSET_300_NAND_MUTEX 0xE646F0
+#define FS_OFFSET_300_ACTIVE_PARTITION 0xE64730
+#define FS_OFFSET_300_SDMMC_DAS_HANDLE 0xE635A0
+
+// NOPs
+#define FS_OFFSET_300_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_300_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000391F4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A480, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A778, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_300_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/300_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/300_exfat.h
new file mode 100644
index 00000000..d230f52b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/300_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_300_EXFAT_H__
+#define __FS_300_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_300_EXFAT_SDMMC_ACCESSOR_GC 0x8FAAC
+#define FS_OFFSET_300_EXFAT_SDMMC_ACCESSOR_SD 0x8F84C
+#define FS_OFFSET_300_EXFAT_SDMMC_ACCESSOR_NAND 0x8F3B8
+
+// Hooks
+#define FS_OFFSET_300_EXFAT_SDMMC_WRAPPER_READ 0x8A2F4
+#define FS_OFFSET_300_EXFAT_SDMMC_WRAPPER_WRITE 0x8A3B4
+#define FS_OFFSET_300_EXFAT_RTLD 0x4E8
+#define FS_OFFSET_300_EXFAT_RTLD_DESTINATION 0x8C
+
+#define FS_OFFSET_300_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_300_EXFAT_LOCK_MUTEX 0x35CC
+#define FS_OFFSET_300_EXFAT_UNLOCK_MUTEX 0x3638
+
+#define FS_OFFSET_300_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_300_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x8A270
+
+// Misc Data
+#define FS_OFFSET_300_EXFAT_SD_MUTEX 0xF4C268
+#define FS_OFFSET_300_EXFAT_NAND_MUTEX 0xF476F0
+#define FS_OFFSET_300_EXFAT_ACTIVE_PARTITION 0xF47730
+#define FS_OFFSET_300_EXFAT_SDMMC_DAS_HANDLE 0xF465A0
+
+// NOPs
+#define FS_OFFSET_300_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_300_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000391F4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A480, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A778, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_300_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/301.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/301.h
new file mode 100644
index 00000000..8dca1731
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/301.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_301_H__
+#define __FS_301_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_301_SDMMC_ACCESSOR_GC 0x8FB68
+#define FS_OFFSET_301_SDMMC_ACCESSOR_SD 0x8F908
+#define FS_OFFSET_301_SDMMC_ACCESSOR_NAND 0x8F474
+
+// Hooks
+#define FS_OFFSET_301_SDMMC_WRAPPER_READ 0x8A3B0
+#define FS_OFFSET_301_SDMMC_WRAPPER_WRITE 0x8A470
+#define FS_OFFSET_301_RTLD 0x4E8
+#define FS_OFFSET_301_RTLD_DESTINATION 0x8C
+
+#define FS_OFFSET_301_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_301_LOCK_MUTEX 0x3638
+#define FS_OFFSET_301_UNLOCK_MUTEX 0x36A4
+
+#define FS_OFFSET_301_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_301_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x8A32C
+
+// Misc Data
+#define FS_OFFSET_301_SD_MUTEX 0xE69268
+#define FS_OFFSET_301_NAND_MUTEX 0xE646F0
+#define FS_OFFSET_301_ACTIVE_PARTITION 0xE64730
+#define FS_OFFSET_301_SDMMC_DAS_HANDLE 0xE635A0
+
+// NOPs
+#define FS_OFFSET_301_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_301_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00039260, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A4EC, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A7E4, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_301_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/301_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/301_exfat.h
new file mode 100644
index 00000000..72f0f449
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/301_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_301_EXFAT_H__
+#define __FS_301_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_301_EXFAT_SDMMC_ACCESSOR_GC 0x8FB68
+#define FS_OFFSET_301_EXFAT_SDMMC_ACCESSOR_SD 0x8F908
+#define FS_OFFSET_301_EXFAT_SDMMC_ACCESSOR_NAND 0x8F474
+
+// Hooks
+#define FS_OFFSET_301_EXFAT_SDMMC_WRAPPER_READ 0x8A3B0
+#define FS_OFFSET_301_EXFAT_SDMMC_WRAPPER_WRITE 0x8A470
+#define FS_OFFSET_301_EXFAT_RTLD 0x4E8
+#define FS_OFFSET_301_EXFAT_RTLD_DESTINATION 0x8C
+
+#define FS_OFFSET_301_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_301_EXFAT_LOCK_MUTEX 0x3638
+#define FS_OFFSET_301_EXFAT_UNLOCK_MUTEX 0x36A4
+
+#define FS_OFFSET_301_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_301_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x8A32C
+
+// Misc Data
+#define FS_OFFSET_301_EXFAT_SD_MUTEX 0xF4C268
+#define FS_OFFSET_301_EXFAT_NAND_MUTEX 0xF476F0
+#define FS_OFFSET_301_EXFAT_ACTIVE_PARTITION 0xF47730
+#define FS_OFFSET_301_EXFAT_SDMMC_DAS_HANDLE 0xF465A0
+
+// NOPs
+#define FS_OFFSET_301_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_301_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00039260, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A4EC, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0003A7E4, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_301_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/400.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/400.h
new file mode 100644
index 00000000..fcd5ca92
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/400.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_400_H__
+#define __FS_400_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_400_SDMMC_ACCESSOR_GC 0xA3374
+#define FS_OFFSET_400_SDMMC_ACCESSOR_SD 0xA3114
+#define FS_OFFSET_400_SDMMC_ACCESSOR_NAND 0xA2C80
+
+// Hooks
+#define FS_OFFSET_400_SDMMC_WRAPPER_READ 0x9DBCC
+#define FS_OFFSET_400_SDMMC_WRAPPER_WRITE 0x9DC8C
+#define FS_OFFSET_400_RTLD 0x4DC
+#define FS_OFFSET_400_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_400_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_400_LOCK_MUTEX 0x39A0
+#define FS_OFFSET_400_UNLOCK_MUTEX 0x3A0C
+
+#define FS_OFFSET_400_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_400_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x9DB48
+
+// Misc Data
+#define FS_OFFSET_400_SD_MUTEX 0xE80268
+#define FS_OFFSET_400_NAND_MUTEX 0xE7BC60
+#define FS_OFFSET_400_ACTIVE_PARTITION 0xE7BCA0
+#define FS_OFFSET_400_SDMMC_DAS_HANDLE 0xE7ABF0
+
+// NOPs
+#define FS_OFFSET_400_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_400_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0002023C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021BE8, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021EC4, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_400_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/400_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/400_exfat.h
new file mode 100644
index 00000000..da1ac97d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/400_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_400_EXFAT_H__
+#define __FS_400_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_400_EXFAT_SDMMC_ACCESSOR_GC 0xA3374
+#define FS_OFFSET_400_EXFAT_SDMMC_ACCESSOR_SD 0xA3114
+#define FS_OFFSET_400_EXFAT_SDMMC_ACCESSOR_NAND 0xA2C80
+
+// Hooks
+#define FS_OFFSET_400_EXFAT_SDMMC_WRAPPER_READ 0x9DBCC
+#define FS_OFFSET_400_EXFAT_SDMMC_WRAPPER_WRITE 0x9DC8C
+#define FS_OFFSET_400_EXFAT_RTLD 0x4DC
+#define FS_OFFSET_400_EXFAT_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_400_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_400_EXFAT_LOCK_MUTEX 0x39A0
+#define FS_OFFSET_400_EXFAT_UNLOCK_MUTEX 0x3A0C
+
+#define FS_OFFSET_400_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_400_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x9DB48
+
+// Misc Data
+#define FS_OFFSET_400_EXFAT_SD_MUTEX 0xF63268
+#define FS_OFFSET_400_EXFAT_NAND_MUTEX 0xF5EC60
+#define FS_OFFSET_400_EXFAT_ACTIVE_PARTITION 0xF5ECA0
+#define FS_OFFSET_400_EXFAT_SDMMC_DAS_HANDLE 0xF5DBF0
+
+// NOPs
+#define FS_OFFSET_400_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_400_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0002023C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021BE8, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021EC4, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_400_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/410.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/410.h
new file mode 100644
index 00000000..4d068e71
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/410.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_410_H__
+#define __FS_410_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_410_SDMMC_ACCESSOR_GC 0xA33D8
+#define FS_OFFSET_410_SDMMC_ACCESSOR_SD 0xA3178
+#define FS_OFFSET_410_SDMMC_ACCESSOR_NAND 0xA2CE4
+
+// Hooks
+#define FS_OFFSET_410_SDMMC_WRAPPER_READ 0x9DC30
+#define FS_OFFSET_410_SDMMC_WRAPPER_WRITE 0x9DCF0
+#define FS_OFFSET_410_RTLD 0x4DC
+#define FS_OFFSET_410_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_410_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_410_LOCK_MUTEX 0x39A0
+#define FS_OFFSET_410_UNLOCK_MUTEX 0x3A0C
+
+#define FS_OFFSET_410_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_410_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x9DBAC
+
+// Misc Data
+#define FS_OFFSET_410_SD_MUTEX 0xE80268
+#define FS_OFFSET_410_NAND_MUTEX 0xE7BC60
+#define FS_OFFSET_410_ACTIVE_PARTITION 0xE7BCA0
+#define FS_OFFSET_410_SDMMC_DAS_HANDLE 0xE7ABF0
+
+// NOPs
+#define FS_OFFSET_410_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_410_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0002023C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021BE8, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021EC4, .add_rel_offset = 0x0000000C}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_410_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/410_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/410_exfat.h
new file mode 100644
index 00000000..cf66c23d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/410_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_410_EXFAT_H__
+#define __FS_410_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_410_EXFAT_SDMMC_ACCESSOR_GC 0xA33D8
+#define FS_OFFSET_410_EXFAT_SDMMC_ACCESSOR_SD 0xA3178
+#define FS_OFFSET_410_EXFAT_SDMMC_ACCESSOR_NAND 0xA2CE4
+
+// Hooks
+#define FS_OFFSET_410_EXFAT_SDMMC_WRAPPER_READ 0x9DC30
+#define FS_OFFSET_410_EXFAT_SDMMC_WRAPPER_WRITE 0x9DCF0
+#define FS_OFFSET_410_EXFAT_RTLD 0x4DC
+#define FS_OFFSET_410_EXFAT_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_410_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_410_EXFAT_LOCK_MUTEX 0x39A0
+#define FS_OFFSET_410_EXFAT_UNLOCK_MUTEX 0x3A0C
+
+#define FS_OFFSET_410_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_410_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x9DBAC
+
+// Misc Data
+#define FS_OFFSET_410_EXFAT_SD_MUTEX 0xF63268
+#define FS_OFFSET_410_EXFAT_NAND_MUTEX 0xF5EC60
+#define FS_OFFSET_410_EXFAT_ACTIVE_PARTITION 0xF5ECA0
+#define FS_OFFSET_410_EXFAT_SDMMC_DAS_HANDLE 0xF5DBF0
+
+// NOPs
+#define FS_OFFSET_410_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_410_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0002023C, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021BE8, .add_rel_offset = 12}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00021EC4, .add_rel_offset = 12}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_410_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/500.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/500.h
new file mode 100644
index 00000000..630a3317
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/500.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_500_H__
+#define __FS_500_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_500_SDMMC_ACCESSOR_GC 0xCF250
+#define FS_OFFSET_500_SDMMC_ACCESSOR_SD 0xCEFD0
+#define FS_OFFSET_500_SDMMC_ACCESSOR_NAND 0xCE990
+
+// Hooks
+#define FS_OFFSET_500_SDMMC_WRAPPER_READ 0xC9420
+#define FS_OFFSET_500_SDMMC_WRAPPER_WRITE 0xC9500
+#define FS_OFFSET_500_RTLD 0x584
+#define FS_OFFSET_500_RTLD_DESTINATION 0x94
+
+#define FS_OFFSET_500_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_500_LOCK_MUTEX 0x4080
+#define FS_OFFSET_500_UNLOCK_MUTEX 0x40D0
+
+#define FS_OFFSET_500_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_500_SDMMC_WRAPPER_CONTROLLER_CLOSE 0xC9380
+
+// Misc Data
+#define FS_OFFSET_500_SD_MUTEX 0xEC3268
+#define FS_OFFSET_500_NAND_MUTEX 0xEBDE58
+#define FS_OFFSET_500_ACTIVE_PARTITION 0xEBDE98
+#define FS_OFFSET_500_SDMMC_DAS_HANDLE 0xEBCE30
+
+// NOPs
+#define FS_OFFSET_500_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_500_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00028980, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002ACE4, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002B220, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_500_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/500_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/500_exfat.h
new file mode 100644
index 00000000..df239f04
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/500_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_500_EXFAT_H__
+#define __FS_500_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_500_EXFAT_SDMMC_ACCESSOR_GC 0xCF250
+#define FS_OFFSET_500_EXFAT_SDMMC_ACCESSOR_SD 0xCEFD0
+#define FS_OFFSET_500_EXFAT_SDMMC_ACCESSOR_NAND 0xCE990
+
+// Hooks
+#define FS_OFFSET_500_EXFAT_SDMMC_WRAPPER_READ 0xC9420
+#define FS_OFFSET_500_EXFAT_SDMMC_WRAPPER_WRITE 0xC9500
+#define FS_OFFSET_500_EXFAT_RTLD 0x584
+#define FS_OFFSET_500_EXFAT_RTLD_DESTINATION 0x94
+
+#define FS_OFFSET_500_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_500_EXFAT_LOCK_MUTEX 0x4080
+#define FS_OFFSET_500_EXFAT_UNLOCK_MUTEX 0x40D0
+
+#define FS_OFFSET_500_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_500_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0xC9380
+
+// Misc Data
+#define FS_OFFSET_500_EXFAT_SD_MUTEX 0xFA8268
+#define FS_OFFSET_500_EXFAT_NAND_MUTEX 0xFA2E58
+#define FS_OFFSET_500_EXFAT_ACTIVE_PARTITION 0xFA2E98
+#define FS_OFFSET_500_EXFAT_SDMMC_DAS_HANDLE 0xFA1E30
+
+// NOPs
+#define FS_OFFSET_500_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_500_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00028980, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002ACE4, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002B220, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_500_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/510.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/510.h
new file mode 100644
index 00000000..e867f190
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/510.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_510_H__
+#define __FS_510_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_510_SDMMC_ACCESSOR_GC 0xCF620
+#define FS_OFFSET_510_SDMMC_ACCESSOR_SD 0xCF3A0
+#define FS_OFFSET_510_SDMMC_ACCESSOR_NAND 0xCED60
+
+// Hooks
+#define FS_OFFSET_510_SDMMC_WRAPPER_READ 0xC97F0
+#define FS_OFFSET_510_SDMMC_WRAPPER_WRITE 0xC98D0
+#define FS_OFFSET_510_RTLD 0x584
+#define FS_OFFSET_510_RTLD_DESTINATION 0x94
+
+#define FS_OFFSET_510_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_510_LOCK_MUTEX 0x4080
+#define FS_OFFSET_510_UNLOCK_MUTEX 0x40D0
+
+#define FS_OFFSET_510_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_510_SDMMC_WRAPPER_CONTROLLER_CLOSE 0xC9750
+
+// Misc Data
+#define FS_OFFSET_510_SD_MUTEX 0xEC4268
+#define FS_OFFSET_510_NAND_MUTEX 0xEBEE58
+#define FS_OFFSET_510_ACTIVE_PARTITION 0xEBEE98
+#define FS_OFFSET_510_SDMMC_DAS_HANDLE 0xEBDE30
+
+// NOPs
+#define FS_OFFSET_510_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_510_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000289B0, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002AD14, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002B250, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_510_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/510_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/510_exfat.h
new file mode 100644
index 00000000..8ce184f2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/510_exfat.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_510_EXFAT_H__
+#define __FS_510_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_510_EXFAT_SDMMC_ACCESSOR_GC 0xCF620
+#define FS_OFFSET_510_EXFAT_SDMMC_ACCESSOR_SD 0xCF3A0
+#define FS_OFFSET_510_EXFAT_SDMMC_ACCESSOR_NAND 0xCED60
+
+// Hooks
+#define FS_OFFSET_510_EXFAT_SDMMC_WRAPPER_READ 0xC97F0
+#define FS_OFFSET_510_EXFAT_SDMMC_WRAPPER_WRITE 0xC98D0
+#define FS_OFFSET_510_EXFAT_RTLD 0x584
+#define FS_OFFSET_510_EXFAT_RTLD_DESTINATION 0x94
+
+#define FS_OFFSET_510_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_510_EXFAT_LOCK_MUTEX 0x4080
+#define FS_OFFSET_510_EXFAT_UNLOCK_MUTEX 0x40D0
+
+#define FS_OFFSET_510_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_510_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0xC9750
+
+// Misc Data
+#define FS_OFFSET_510_EXFAT_SD_MUTEX 0xFA9268
+#define FS_OFFSET_510_EXFAT_NAND_MUTEX 0xFA3E58
+#define FS_OFFSET_510_EXFAT_ACTIVE_PARTITION 0xFA3E98
+#define FS_OFFSET_510_EXFAT_SDMMC_DAS_HANDLE 0xFA2E30
+
+// NOPs
+#define FS_OFFSET_510_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_510_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000289B0, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002AD14, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0002B250, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_510_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/600.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/600.h
new file mode 100644
index 00000000..a0835a0d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/600.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_600_H__
+#define __FS_600_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_600_SDMMC_ACCESSOR_GC 0x153780
+#define FS_OFFSET_600_SDMMC_ACCESSOR_SD 0x1534F0
+#define FS_OFFSET_600_SDMMC_ACCESSOR_NAND 0x14F990
+
+// Hooks
+#define FS_OFFSET_600_SDMMC_WRAPPER_READ 0x1485A0
+#define FS_OFFSET_600_SDMMC_WRAPPER_WRITE 0x148680
+#define FS_OFFSET_600_RTLD 0x5B0
+#define FS_OFFSET_600_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_600_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_600_LOCK_MUTEX 0x1412C0
+#define FS_OFFSET_600_UNLOCK_MUTEX 0x141310
+
+#define FS_OFFSET_600_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_600_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x148500
+
+// Misc Data
+#define FS_OFFSET_600_SD_MUTEX 0xF06268
+#define FS_OFFSET_600_NAND_MUTEX 0xF01BA0
+#define FS_OFFSET_600_ACTIVE_PARTITION 0xF01BE0
+#define FS_OFFSET_600_SDMMC_DAS_HANDLE 0xE01670
+
+// NOPs
+#define FS_OFFSET_600_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_600_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000790DC, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007A924, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AB18, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007AEF4, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0} \
+}
+
+#endif // __FS_600_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/600_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/600_exfat.h
new file mode 100644
index 00000000..0600b016
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/600_exfat.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_600_EXFAT_H__
+#define __FS_600_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_600_EXFAT_SDMMC_ACCESSOR_GC 0x15EE80
+#define FS_OFFSET_600_EXFAT_SDMMC_ACCESSOR_SD 0x15EBF0
+#define FS_OFFSET_600_EXFAT_SDMMC_ACCESSOR_NAND 0x15B090
+
+// Hooks
+#define FS_OFFSET_600_EXFAT_SDMMC_WRAPPER_READ 0x153CA0
+#define FS_OFFSET_600_EXFAT_SDMMC_WRAPPER_WRITE 0x153D80
+#define FS_OFFSET_600_EXFAT_RTLD 0x5B0
+#define FS_OFFSET_600_EXFAT_RTLD_DESTINATION 0x98
+
+#define FS_OFFSET_600_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_600_EXFAT_LOCK_MUTEX 0x14C9C0
+#define FS_OFFSET_600_EXFAT_UNLOCK_MUTEX 0x14CA10
+
+#define FS_OFFSET_600_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_600_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x153C00
+
+// Misc Data
+#define FS_OFFSET_600_EXFAT_SD_MUTEX 0xFEB268
+#define FS_OFFSET_600_EXFAT_NAND_MUTEX 0xFE6BA0
+#define FS_OFFSET_600_EXFAT_ACTIVE_PARTITION 0xFE6BE0
+#define FS_OFFSET_600_EXFAT_SDMMC_DAS_HANDLE 0xEE6670
+
+// NOPs
+#define FS_OFFSET_600_EXFAT_SD_DAS_INIT 0x0
+
+// Nintendo Paths
+#define FS_OFFSET_600_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x000847DC, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00086024, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00086218, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x000865F4, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0} \
+}
+
+#endif // __FS_600_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/700.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/700.h
new file mode 100644
index 00000000..9281cb90
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/700.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_700_H__
+#define __FS_700_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_700_SDMMC_ACCESSOR_GC 0x15BD90
+#define FS_OFFSET_700_SDMMC_ACCESSOR_SD 0x15BB00
+#define FS_OFFSET_700_SDMMC_ACCESSOR_NAND 0x157FF0
+
+// Hooks
+#define FS_OFFSET_700_SDMMC_WRAPPER_READ 0x14FDF0
+#define FS_OFFSET_700_SDMMC_WRAPPER_WRITE 0x14FED0
+#define FS_OFFSET_700_RTLD 0x5B4
+#define FS_OFFSET_700_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_700_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_700_LOCK_MUTEX 0x148A90
+#define FS_OFFSET_700_UNLOCK_MUTEX 0x148AE0
+
+#define FS_OFFSET_700_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_700_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x14FD50
+
+// Misc Data
+#define FS_OFFSET_700_SD_MUTEX 0xF123E8
+#define FS_OFFSET_700_NAND_MUTEX 0xF0DBE8
+#define FS_OFFSET_700_ACTIVE_PARTITION 0xF0DC28
+#define FS_OFFSET_700_SDMMC_DAS_HANDLE 0xE0E7A0
+
+// NOPs
+#define FS_OFFSET_700_SD_DAS_INIT 0x85FE8
+
+// Nintendo Paths
+#define FS_OFFSET_700_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0007DA90, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007F344, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007F538, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0007F914, .add_rel_offset = 4}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0007FAD8, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_700_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/700_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/700_exfat.h
new file mode 100644
index 00000000..4f1ef130
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/700_exfat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_700_EXFAT_H__
+#define __FS_700_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_700_EXFAT_SDMMC_ACCESSOR_GC 0x167340
+#define FS_OFFSET_700_EXFAT_SDMMC_ACCESSOR_SD 0x1670B0
+#define FS_OFFSET_700_EXFAT_SDMMC_ACCESSOR_NAND 0x1635A0
+
+// Hooks
+#define FS_OFFSET_700_EXFAT_SDMMC_WRAPPER_READ 0x15B3A0
+#define FS_OFFSET_700_EXFAT_SDMMC_WRAPPER_WRITE 0x15B480
+#define FS_OFFSET_700_EXFAT_RTLD 0x5B4
+#define FS_OFFSET_700_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_700_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x0
+
+// Misc funcs
+#define FS_OFFSET_700_EXFAT_LOCK_MUTEX 0x154040
+#define FS_OFFSET_700_EXFAT_UNLOCK_MUTEX 0x154090
+
+#define FS_OFFSET_700_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_700_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x15B300
+
+// Misc Data
+#define FS_OFFSET_700_EXFAT_SD_MUTEX 0xFF73E8
+#define FS_OFFSET_700_EXFAT_NAND_MUTEX 0xFF2BE8
+#define FS_OFFSET_700_EXFAT_ACTIVE_PARTITION 0xFF2C28
+#define FS_OFFSET_700_EXFAT_SDMMC_DAS_HANDLE 0xEF3A00
+
+// NOPs
+#define FS_OFFSET_700_EXFAT_SD_DAS_INIT 0x91598
+
+// Nintendo Paths
+#define FS_OFFSET_700_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00089040, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008A8F4, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008AAE8, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008AEC4, .add_rel_offset = 4}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008B088, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_700_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/800.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/800.h
new file mode 100644
index 00000000..2eef520c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/800.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_800_H__
+#define __FS_800_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_800_SDMMC_ACCESSOR_GC 0x15EA20
+#define FS_OFFSET_800_SDMMC_ACCESSOR_SD 0x15E790
+#define FS_OFFSET_800_SDMMC_ACCESSOR_NAND 0x15AC80
+
+// Hooks
+#define FS_OFFSET_800_SDMMC_WRAPPER_READ 0x152A80
+#define FS_OFFSET_800_SDMMC_WRAPPER_WRITE 0x152B60
+#define FS_OFFSET_800_RTLD 0x5B4
+#define FS_OFFSET_800_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_800_CLKRST_SET_MIN_V_CLK_RATE 0x16F370
+
+// Misc funcs
+#define FS_OFFSET_800_LOCK_MUTEX 0x14B6D0
+#define FS_OFFSET_800_UNLOCK_MUTEX 0x14B720
+
+#define FS_OFFSET_800_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_800_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1529E0
+
+// Misc Data
+#define FS_OFFSET_800_SD_MUTEX 0xF1A3E8
+#define FS_OFFSET_800_NAND_MUTEX 0xF15BE8
+#define FS_OFFSET_800_ACTIVE_PARTITION 0xF15C28
+#define FS_OFFSET_800_SDMMC_DAS_HANDLE 0xE167C0
+
+// NOPs
+#define FS_OFFSET_800_SD_DAS_INIT 0x87D58
+
+// Nintendo Paths
+#define FS_OFFSET_800_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0007F5F0, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081084, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081278, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081654, .add_rel_offset = 4}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00081818, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0} \
+}
+
+#endif // __FS_800_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/800_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/800_exfat.h
new file mode 100644
index 00000000..786c40e5
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/800_exfat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_800_EXFAT_H__
+#define __FS_800_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_800_EXFAT_SDMMC_ACCESSOR_GC 0x169FD0
+#define FS_OFFSET_800_EXFAT_SDMMC_ACCESSOR_SD 0x169D40
+#define FS_OFFSET_800_EXFAT_SDMMC_ACCESSOR_NAND 0x166230
+
+// Hooks
+#define FS_OFFSET_800_EXFAT_SDMMC_WRAPPER_READ 0x15E030
+#define FS_OFFSET_800_EXFAT_SDMMC_WRAPPER_WRITE 0x15E110
+#define FS_OFFSET_800_EXFAT_RTLD 0x5B4
+#define FS_OFFSET_800_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_800_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x17A920
+
+// Misc funcs
+#define FS_OFFSET_800_EXFAT_LOCK_MUTEX 0x156C80
+#define FS_OFFSET_800_EXFAT_UNLOCK_MUTEX 0x156CD0
+
+#define FS_OFFSET_800_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_800_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x15DF90
+
+// Misc Data
+#define FS_OFFSET_800_EXFAT_SD_MUTEX 0xFFE3E8
+#define FS_OFFSET_800_EXFAT_NAND_MUTEX 0xFF9BE8
+#define FS_OFFSET_800_EXFAT_ACTIVE_PARTITION 0xFF9C28
+#define FS_OFFSET_800_EXFAT_SDMMC_DAS_HANDLE 0xEFAA20
+
+// NOPs
+#define FS_OFFSET_800_EXFAT_SD_DAS_INIT 0x93308
+
+// Nintendo Paths
+#define FS_OFFSET_800_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0008ABA0, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008C634, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008C828, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008CC04, .add_rel_offset = 4}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008CDC8, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0} \
+}
+
+#endif // __FS_800_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/810.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/810.h
new file mode 100644
index 00000000..7a6dd285
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/810.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_810_H__
+#define __FS_810_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_810_SDMMC_ACCESSOR_GC 0x15EA20
+#define FS_OFFSET_810_SDMMC_ACCESSOR_SD 0x15E790
+#define FS_OFFSET_810_SDMMC_ACCESSOR_NAND 0x15AC80
+
+// Hooks
+#define FS_OFFSET_810_SDMMC_WRAPPER_READ 0x152A80
+#define FS_OFFSET_810_SDMMC_WRAPPER_WRITE 0x152B60
+#define FS_OFFSET_810_RTLD 0x5B4
+#define FS_OFFSET_810_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_810_CLKRST_SET_MIN_V_CLK_RATE 0x16F370
+
+// Misc funcs
+#define FS_OFFSET_810_LOCK_MUTEX 0x14B6D0
+#define FS_OFFSET_810_UNLOCK_MUTEX 0x14B720
+
+#define FS_OFFSET_810_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_810_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x1529E0
+
+// Misc Data
+#define FS_OFFSET_810_SD_MUTEX 0xF1A3E8
+#define FS_OFFSET_810_NAND_MUTEX 0xF15BE8
+#define FS_OFFSET_810_ACTIVE_PARTITION 0xF15C28
+#define FS_OFFSET_810_SDMMC_DAS_HANDLE 0xE167C0
+
+// NOPs
+#define FS_OFFSET_810_SD_DAS_INIT 0x87D58
+
+// Nintendo Paths
+#define FS_OFFSET_810_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0007F5F0, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081084, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081278, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081654, .add_rel_offset = 4}, \
+ {.opcode_reg = 4, .adrp_offset = 0x00081818, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0} \
+}
+
+#endif // __FS_810_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/810_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/810_exfat.h
new file mode 100644
index 00000000..47c425ff
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/810_exfat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_810_EXFAT_H__
+#define __FS_810_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_810_EXFAT_SDMMC_ACCESSOR_GC 0x169FD0
+#define FS_OFFSET_810_EXFAT_SDMMC_ACCESSOR_SD 0x169D40
+#define FS_OFFSET_810_EXFAT_SDMMC_ACCESSOR_NAND 0x166230
+
+// Hooks
+#define FS_OFFSET_810_EXFAT_SDMMC_WRAPPER_READ 0x15E030
+#define FS_OFFSET_810_EXFAT_SDMMC_WRAPPER_WRITE 0x15E110
+#define FS_OFFSET_810_EXFAT_RTLD 0x5B4
+#define FS_OFFSET_810_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_810_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x17A920
+
+// Misc funcs
+#define FS_OFFSET_810_EXFAT_LOCK_MUTEX 0x156C80
+#define FS_OFFSET_810_EXFAT_UNLOCK_MUTEX 0x156CD0
+
+#define FS_OFFSET_810_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_810_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x15DF90
+
+// Misc Data
+#define FS_OFFSET_810_EXFAT_SD_MUTEX 0xFFE3E8
+#define FS_OFFSET_810_EXFAT_NAND_MUTEX 0xFF9BE8
+#define FS_OFFSET_810_EXFAT_ACTIVE_PARTITION 0xFF9C28
+#define FS_OFFSET_810_EXFAT_SDMMC_DAS_HANDLE 0xEFAA20
+
+// NOPs
+#define FS_OFFSET_810_EXFAT_SD_DAS_INIT 0x93308
+
+// Nintendo Paths
+#define FS_OFFSET_810_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x0008ABA0, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008C634, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008C828, .add_rel_offset = 4}, \
+ {.opcode_reg = 3, .adrp_offset = 0x0008CC04, .add_rel_offset = 4}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008CDC8, .add_rel_offset = 4}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0} \
+}
+
+#endif // __FS_810_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/900.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/900.h
new file mode 100644
index 00000000..c390ec82
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/900.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_900_H__
+#define __FS_900_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_900_SDMMC_ACCESSOR_GC 0x1430F0
+#define FS_OFFSET_900_SDMMC_ACCESSOR_SD 0x141200
+#define FS_OFFSET_900_SDMMC_ACCESSOR_NAND 0x13C080
+
+// Hooks
+#define FS_OFFSET_900_SDMMC_WRAPPER_READ 0x1377E0
+#define FS_OFFSET_900_SDMMC_WRAPPER_WRITE 0x1378C0
+#define FS_OFFSET_900_RTLD 0x454
+#define FS_OFFSET_900_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_900_CLKRST_SET_MIN_V_CLK_RATE 0x136A00
+
+// Misc funcs
+#define FS_OFFSET_900_LOCK_MUTEX 0x25280
+#define FS_OFFSET_900_UNLOCK_MUTEX 0x252D0
+
+#define FS_OFFSET_900_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_900_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x137740
+
+// Misc Data
+#define FS_OFFSET_900_SD_MUTEX 0xE1D3E8
+#define FS_OFFSET_900_NAND_MUTEX 0xE18258
+#define FS_OFFSET_900_ACTIVE_PARTITION 0xE18298
+#define FS_OFFSET_900_SDMMC_DAS_HANDLE 0xDFEFA0
+
+// NOPs
+#define FS_OFFSET_900_SD_DAS_INIT 0x1472BC
+
+// Nintendo Paths
+#define FS_OFFSET_900_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068A60, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070A40, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081CB4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081EF4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008211C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_900_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/900_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/900_exfat.h
new file mode 100644
index 00000000..184d7d09
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/900_exfat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_900_EXFAT_H__
+#define __FS_900_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_900_EXFAT_SDMMC_ACCESSOR_GC 0x1430F0
+#define FS_OFFSET_900_EXFAT_SDMMC_ACCESSOR_SD 0x141200
+#define FS_OFFSET_900_EXFAT_SDMMC_ACCESSOR_NAND 0x13C080
+
+// Hooks
+#define FS_OFFSET_900_EXFAT_SDMMC_WRAPPER_READ 0x1377E0
+#define FS_OFFSET_900_EXFAT_SDMMC_WRAPPER_WRITE 0x1378C0
+#define FS_OFFSET_900_EXFAT_RTLD 0x454
+#define FS_OFFSET_900_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_900_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x136A00
+
+// Misc funcs
+#define FS_OFFSET_900_EXFAT_LOCK_MUTEX 0x25280
+#define FS_OFFSET_900_EXFAT_UNLOCK_MUTEX 0x252D0
+
+#define FS_OFFSET_900_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_900_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x137740
+
+// Misc Data
+#define FS_OFFSET_900_EXFAT_SD_MUTEX 0xE2B3E8
+#define FS_OFFSET_900_EXFAT_NAND_MUTEX 0xE26258
+#define FS_OFFSET_900_EXFAT_ACTIVE_PARTITION 0xE26298
+#define FS_OFFSET_900_EXFAT_SDMMC_DAS_HANDLE 0xE0CFA0
+
+// NOPs
+#define FS_OFFSET_900_EXFAT_SD_DAS_INIT 0x1472BC
+
+// Nintendo Paths
+#define FS_OFFSET_900_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068A60, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070A40, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081CB4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081EF4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008211C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_900_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/910.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/910.h
new file mode 100644
index 00000000..65aded2d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/910.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_910_H__
+#define __FS_910_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_910_SDMMC_ACCESSOR_GC 0x143100
+#define FS_OFFSET_910_SDMMC_ACCESSOR_SD 0x141210
+#define FS_OFFSET_910_SDMMC_ACCESSOR_NAND 0x13C090
+
+// Hooks
+#define FS_OFFSET_910_SDMMC_WRAPPER_READ 0x1377F0
+#define FS_OFFSET_910_SDMMC_WRAPPER_WRITE 0x1378D0
+#define FS_OFFSET_910_RTLD 0x454
+#define FS_OFFSET_910_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_910_CLKRST_SET_MIN_V_CLK_RATE 0x136A10
+
+// Misc funcs
+#define FS_OFFSET_910_LOCK_MUTEX 0x25280
+#define FS_OFFSET_910_UNLOCK_MUTEX 0x252D0
+
+#define FS_OFFSET_910_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_910_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x137750
+
+// Misc Data
+#define FS_OFFSET_910_SD_MUTEX 0xE1D3E8
+#define FS_OFFSET_910_NAND_MUTEX 0xE18258
+#define FS_OFFSET_910_ACTIVE_PARTITION 0xE18298
+#define FS_OFFSET_910_SDMMC_DAS_HANDLE 0xDFEFA0
+
+// NOPs
+#define FS_OFFSET_910_SD_DAS_INIT 0x1472CC
+
+// Nintendo Paths
+#define FS_OFFSET_910_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068A70, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070A50, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081CC4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081F04, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008212C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_910_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/910_exfat.h b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/910_exfat.h
new file mode 100644
index 00000000..fd59d1ba
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/FS/offsets/910_exfat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#ifndef __FS_910_EXFAT_H__
+#define __FS_910_EXFAT_H__
+
+// Accessor vtable getters
+#define FS_OFFSET_910_EXFAT_SDMMC_ACCESSOR_GC 0x143100
+#define FS_OFFSET_910_EXFAT_SDMMC_ACCESSOR_SD 0x141210
+#define FS_OFFSET_910_EXFAT_SDMMC_ACCESSOR_NAND 0x13C090
+
+// Hooks
+#define FS_OFFSET_910_EXFAT_SDMMC_WRAPPER_READ 0x1377F0
+#define FS_OFFSET_910_EXFAT_SDMMC_WRAPPER_WRITE 0x1378D0
+#define FS_OFFSET_910_EXFAT_RTLD 0x454
+#define FS_OFFSET_910_EXFAT_RTLD_DESTINATION 0x9C
+
+#define FS_OFFSET_910_EXFAT_CLKRST_SET_MIN_V_CLK_RATE 0x136A10
+
+// Misc funcs
+#define FS_OFFSET_910_EXFAT_LOCK_MUTEX 0x25280
+#define FS_OFFSET_910_EXFAT_UNLOCK_MUTEX 0x252D0
+
+#define FS_OFFSET_910_EXFAT_SDMMC_WRAPPER_CONTROLLER_OPEN 0
+#define FS_OFFSET_910_EXFAT_SDMMC_WRAPPER_CONTROLLER_CLOSE 0x137750
+
+// Misc Data
+#define FS_OFFSET_910_EXFAT_SD_MUTEX 0xE2B3E8
+#define FS_OFFSET_910_EXFAT_NAND_MUTEX 0xE26258
+#define FS_OFFSET_910_EXFAT_ACTIVE_PARTITION 0xE26298
+#define FS_OFFSET_910_EXFAT_SDMMC_DAS_HANDLE 0xE0CFA0
+
+// NOPs
+#define FS_OFFSET_910_EXFAT_SD_DAS_INIT 0x1472CC
+
+// Nintendo Paths
+#define FS_OFFSET_910_EXFAT_NINTENDO_PATHS \
+{ \
+ {.opcode_reg = 3, .adrp_offset = 0x00068A70, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00070A50, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081CC4, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 3, .adrp_offset = 0x00081F04, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 4, .adrp_offset = 0x0008212C, .add_rel_offset = 0x00000004}, \
+ {.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \
+}
+
+#endif // __FS_910_EXFAT_H__
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/mmc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/mmc.h
new file mode 100644
index 00000000..efa9e107
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/mmc.h
@@ -0,0 +1,432 @@
+/*
+ * Header for MultiMediaCard (MMC)
+ *
+ * Copyright 2002 Hewlett-Packard Company
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ *
+ * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
+ * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
+ * FITNESS FOR ANY PARTICULAR PURPOSE.
+ *
+ * Many thanks to Alessandro Rubini and Jonathan Corbet!
+ *
+ * Based strongly on code by:
+ *
+ * Author: Yong-iL Joh
+ *
+ * Author: Andrew Christian
+ * 15 May 2002
+ */
+
+#ifndef LINUX_MMC_MMC_H
+#define LINUX_MMC_MMC_H
+
+/* Standard MMC commands (4.1) type argument response */
+/* class 1 */
+#define MMC_GO_IDLE_STATE 0 /* bc */
+#define MMC_SEND_OP_COND 1 /* bcr [31:0] OCR R3 */
+#define MMC_ALL_SEND_CID 2 /* bcr R2 */
+#define MMC_SET_RELATIVE_ADDR 3 /* ac [31:16] RCA R1 */
+#define MMC_SET_DSR 4 /* bc [31:16] RCA */
+#define MMC_SLEEP_AWAKE 5 /* ac [31:16] RCA 15:flg R1b */
+#define MMC_SWITCH 6 /* ac [31:0] See below R1b */
+#define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */
+#define MMC_SEND_EXT_CSD 8 /* adtc R1 */
+#define MMC_SEND_CSD 9 /* ac [31:16] RCA R2 */
+#define MMC_SEND_CID 10 /* ac [31:16] RCA R2 */
+#define MMC_READ_DAT_UNTIL_STOP 11 /* adtc [31:0] dadr R1 */
+#define MMC_STOP_TRANSMISSION 12 /* ac R1b */
+#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */
+#define MMC_BUS_TEST_R 14 /* adtc R1 */
+#define MMC_GO_INACTIVE_STATE 15 /* ac [31:16] RCA */
+#define MMC_BUS_TEST_W 19 /* adtc R1 */
+#define MMC_SPI_READ_OCR 58 /* spi spi_R3 */
+#define MMC_SPI_CRC_ON_OFF 59 /* spi [0:0] flag spi_R1 */
+
+/* class 2 */
+#define MMC_SET_BLOCKLEN 16 /* ac [31:0] block len R1 */
+#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */
+#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */
+#define MMC_SEND_TUNING_BLOCK 19 /* adtc R1 */
+#define MMC_SEND_TUNING_BLOCK_HS200 21 /* adtc R1 */
+
+/* class 3 */
+#define MMC_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */
+
+/* class 4 */
+#define MMC_SET_BLOCK_COUNT 23 /* adtc [31:0] data addr R1 */
+#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */
+#define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc R1 */
+#define MMC_PROGRAM_CID 26 /* adtc R1 */
+#define MMC_PROGRAM_CSD 27 /* adtc R1 */
+
+/* class 6 */
+#define MMC_SET_WRITE_PROT 28 /* ac [31:0] data addr R1b */
+#define MMC_CLR_WRITE_PROT 29 /* ac [31:0] data addr R1b */
+#define MMC_SEND_WRITE_PROT 30 /* adtc [31:0] wpdata addr R1 */
+
+/* class 5 */
+#define MMC_ERASE_GROUP_START 35 /* ac [31:0] data addr R1 */
+#define MMC_ERASE_GROUP_END 36 /* ac [31:0] data addr R1 */
+#define MMC_ERASE 38 /* ac R1b */
+
+/* class 9 */
+#define MMC_FAST_IO 39 /* ac R4 */
+#define MMC_GO_IRQ_STATE 40 /* bcr R5 */
+
+/* class 7 */
+#define MMC_LOCK_UNLOCK 42 /* adtc R1b */
+
+/* class 8 */
+#define MMC_APP_CMD 55 /* ac [31:16] RCA R1 */
+#define MMC_GEN_CMD 56 /* adtc [0] RD/WR R1 */
+
+/* class 11 */
+#define MMC_QUE_TASK_PARAMS 44 /* ac [20:16] task id R1 */
+#define MMC_QUE_TASK_ADDR 45 /* ac [31:0] data addr R1 */
+#define MMC_EXECUTE_READ_TASK 46 /* adtc [20:16] task id R1 */
+#define MMC_EXECUTE_WRITE_TASK 47 /* adtc [20:16] task id R1 */
+#define MMC_CMDQ_TASK_MGMT 48 /* ac [20:16] task id R1b */
+
+/*
+* MMC_SWITCH argument format:
+*
+* [31:26] Always 0
+* [25:24] Access Mode
+* [23:16] Location of target Byte in EXT_CSD
+* [15:08] Value Byte
+* [07:03] Always 0
+* [02:00] Command Set
+*/
+
+/*
+MMC status in R1, for native mode (SPI bits are different)
+Type
+e : error bit
+s : status bit
+r : detected and set for the actual command response
+x : detected and set during command execution. the host must poll
+the card by sending status command in order to read these bits.
+Clear condition
+a : according to the card state
+b : always related to the previous command. Reception of
+a valid command will clear it (with a delay of one command)
+c : clear by read
+*/
+
+#define R1_OUT_OF_RANGE (1 << 31) /* er, c */
+#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */
+#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */
+#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */
+#define R1_ERASE_PARAM (1 << 27) /* ex, c */
+#define R1_WP_VIOLATION (1 << 26) /* erx, c */
+#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */
+#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */
+#define R1_COM_CRC_ERROR (1 << 23) /* er, b */
+#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */
+#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */
+#define R1_CC_ERROR (1 << 20) /* erx, c */
+#define R1_ERROR (1 << 19) /* erx, c */
+#define R1_UNDERRUN (1 << 18) /* ex, c */
+#define R1_OVERRUN (1 << 17) /* ex, c */
+#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */
+#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */
+#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */
+#define R1_ERASE_RESET (1 << 13) /* sr, c */
+#define R1_STATUS(x) ((x) & 0xFFFFE000)
+#define R1_CURRENT_STATE(x) (((x) & 0x00001E00) >> 9) /* sx, b (4 bits) */
+#define R1_READY_FOR_DATA (1 << 8) /* sx, a */
+#define R1_SWITCH_ERROR (1 << 7) /* sx, c */
+#define R1_EXCEPTION_EVENT (1 << 6) /* sr, a */
+#define R1_APP_CMD (1 << 5) /* sr, c */
+
+#define R1_STATE_IDLE 0
+#define R1_STATE_READY 1
+#define R1_STATE_IDENT 2
+#define R1_STATE_STBY 3
+#define R1_STATE_TRAN 4
+#define R1_STATE_DATA 5
+#define R1_STATE_RCV 6
+#define R1_STATE_PRG 7
+#define R1_STATE_DIS 8
+
+/*
+* MMC/SD in SPI mode reports R1 status always, and R2 for SEND_STATUS
+* R1 is the low order byte; R2 is the next highest byte, when present.
+*/
+#define R1_SPI_IDLE (1 << 0)
+#define R1_SPI_ERASE_RESET (1 << 1)
+#define R1_SPI_ILLEGAL_COMMAND (1 << 2)
+#define R1_SPI_COM_CRC (1 << 3)
+#define R1_SPI_ERASE_SEQ (1 << 4)
+#define R1_SPI_ADDRESS (1 << 5)
+#define R1_SPI_PARAMETER (1 << 6)
+/* R1 bit 7 is always zero */
+#define R2_SPI_CARD_LOCKED (1 << 8)
+#define R2_SPI_WP_ERASE_SKIP (1 << 9) /* or lock/unlock fail */
+#define R2_SPI_LOCK_UNLOCK_FAIL R2_SPI_WP_ERASE_SKIP
+#define R2_SPI_ERROR (1 << 10)
+#define R2_SPI_CC_ERROR (1 << 11)
+#define R2_SPI_CARD_ECC_ERROR (1 << 12)
+#define R2_SPI_WP_VIOLATION (1 << 13)
+#define R2_SPI_ERASE_PARAM (1 << 14)
+#define R2_SPI_OUT_OF_RANGE (1 << 15) /* or CSD overwrite */
+#define R2_SPI_CSD_OVERWRITE R2_SPI_OUT_OF_RANGE
+
+/*
+* OCR bits are mostly in host.h
+*/
+#define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */
+
+/*
+* Card Command Classes (CCC)
+*/
+#define CCC_BASIC (1<<0) /* (0) Basic protocol functions */
+/* (CMD0,1,2,3,4,7,9,10,12,13,15) */
+/* (and for SPI, CMD58,59) */
+#define CCC_STREAM_READ (1<<1) /* (1) Stream read commands */
+/* (CMD11) */
+#define CCC_BLOCK_READ (1<<2) /* (2) Block read commands */
+/* (CMD16,17,18) */
+#define CCC_STREAM_WRITE (1<<3) /* (3) Stream write commands */
+/* (CMD20) */
+#define CCC_BLOCK_WRITE (1<<4) /* (4) Block write commands */
+/* (CMD16,24,25,26,27) */
+#define CCC_ERASE (1<<5) /* (5) Ability to erase blocks */
+/* (CMD32,33,34,35,36,37,38,39) */
+#define CCC_WRITE_PROT (1<<6) /* (6) Able to write protect blocks */
+/* (CMD28,29,30) */
+#define CCC_LOCK_CARD (1<<7) /* (7) Able to lock down card */
+/* (CMD16,CMD42) */
+#define CCC_APP_SPEC (1<<8) /* (8) Application specific */
+/* (CMD55,56,57,ACMD*) */
+#define CCC_IO_MODE (1<<9) /* (9) I/O mode */
+/* (CMD5,39,40,52,53) */
+#define CCC_SWITCH (1<<10) /* (10) High speed switch */
+/* (CMD6,34,35,36,37,50) */
+/* (11) Reserved */
+/* (CMD?) */
+
+/*
+* CSD field definitions
+*/
+
+#define CSD_STRUCT_VER_1_0 0 /* Valid for system specification 1.0 - 1.2 */
+#define CSD_STRUCT_VER_1_1 1 /* Valid for system specification 1.4 - 2.2 */
+#define CSD_STRUCT_VER_1_2 2 /* Valid for system specification 3.1 - 3.2 - 3.31 - 4.0 - 4.1 */
+#define CSD_STRUCT_EXT_CSD 3 /* Version is coded in CSD_STRUCTURE in EXT_CSD */
+
+#define CSD_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.2 */
+#define CSD_SPEC_VER_1 1 /* Implements system specification 1.4 */
+#define CSD_SPEC_VER_2 2 /* Implements system specification 2.0 - 2.2 */
+#define CSD_SPEC_VER_3 3 /* Implements system specification 3.1 - 3.2 - 3.31 */
+#define CSD_SPEC_VER_4 4 /* Implements system specification 4.0 - 4.1 */
+
+/*
+* EXT_CSD fields
+*/
+
+#define EXT_CSD_CMDQ_MODE_EN 15 /* R/W */
+#define EXT_CSD_FLUSH_CACHE 32 /* W */
+#define EXT_CSD_CACHE_CTRL 33 /* R/W */
+#define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */
+#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */
+#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */
+#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */
+#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */
+#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */
+#define EXT_CSD_GP_SIZE_MULT 143 /* R/W */
+#define EXT_CSD_PARTITION_SETTING_COMPLETED 155 /* R/W */
+#define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */
+#define EXT_CSD_PARTITION_SUPPORT 160 /* RO */
+#define EXT_CSD_HPI_MGMT 161 /* R/W */
+#define EXT_CSD_RST_N_FUNCTION 162 /* R/W */
+#define EXT_CSD_BKOPS_EN 163 /* R/W */
+#define EXT_CSD_BKOPS_START 164 /* W */
+#define EXT_CSD_SANITIZE_START 165 /* W */
+#define EXT_CSD_WR_REL_PARAM 166 /* RO */
+#define EXT_CSD_RPMB_MULT 168 /* RO */
+#define EXT_CSD_FW_CONFIG 169 /* R/W */
+#define EXT_CSD_BOOT_WP 173 /* R/W */
+#define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */
+#define EXT_CSD_PART_CONFIG 179 /* R/W */
+#define EXT_CSD_ERASED_MEM_CONT 181 /* RO */
+#define EXT_CSD_BUS_WIDTH 183 /* R/W */
+#define EXT_CSD_STROBE_SUPPORT 184 /* RO */
+#define EXT_CSD_HS_TIMING 185 /* R/W */
+#define EXT_CSD_POWER_CLASS 187 /* R/W */
+#define EXT_CSD_REV 192 /* RO */
+#define EXT_CSD_STRUCTURE 194 /* RO */
+#define EXT_CSD_CARD_TYPE 196 /* RO */
+#define EXT_CSD_DRIVER_STRENGTH 197 /* RO */
+#define EXT_CSD_OUT_OF_INTERRUPT_TIME 198 /* RO */
+#define EXT_CSD_PART_SWITCH_TIME 199 /* RO */
+#define EXT_CSD_PWR_CL_52_195 200 /* RO */
+#define EXT_CSD_PWR_CL_26_195 201 /* RO */
+#define EXT_CSD_PWR_CL_52_360 202 /* RO */
+#define EXT_CSD_PWR_CL_26_360 203 /* RO */
+#define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */
+#define EXT_CSD_S_A_TIMEOUT 217 /* RO */
+#define EXT_CSD_REL_WR_SEC_C 222 /* RO */
+#define EXT_CSD_HC_WP_GRP_SIZE 221 /* RO */
+#define EXT_CSD_ERASE_TIMEOUT_MULT 223 /* RO */
+#define EXT_CSD_HC_ERASE_GRP_SIZE 224 /* RO */
+#define EXT_CSD_BOOT_MULT 226 /* RO */
+#define EXT_CSD_SEC_TRIM_MULT 229 /* RO */
+#define EXT_CSD_SEC_ERASE_MULT 230 /* RO */
+#define EXT_CSD_SEC_FEATURE_SUPPORT 231 /* RO */
+#define EXT_CSD_TRIM_MULT 232 /* RO */
+#define EXT_CSD_PWR_CL_200_195 236 /* RO */
+#define EXT_CSD_PWR_CL_200_360 237 /* RO */
+#define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */
+#define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */
+#define EXT_CSD_BKOPS_STATUS 246 /* RO */
+#define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */
+#define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */
+#define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */
+#define EXT_CSD_PWR_CL_DDR_200_360 253 /* RO */
+#define EXT_CSD_FIRMWARE_VERSION 254 /* RO, 8 bytes */
+#define EXT_CSD_DEVICE_VERSION 262 /* RO, 2 bytes */
+#define EXT_CSD_PRE_EOL_INFO 267 /* RO */
+#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A 268 /* RO */
+#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B 269 /* RO */
+#define EXT_CSD_CMDQ_DEPTH 307 /* RO */
+#define EXT_CSD_CMDQ_SUPPORT 308 /* RO */
+#define EXT_CSD_SUPPORTED_MODE 493 /* RO */
+#define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */
+#define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */
+#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */
+#define EXT_CSD_MAX_PACKED_READS 501 /* RO */
+#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */
+#define EXT_CSD_HPI_FEATURES 503 /* RO */
+
+/*
+* EXT_CSD field definitions
+*/
+
+#define EXT_CSD_WR_REL_PARAM_EN (1<<2)
+
+#define EXT_CSD_BOOT_WP_B_PWR_WP_DIS (0x40)
+#define EXT_CSD_BOOT_WP_B_PERM_WP_DIS (0x10)
+#define EXT_CSD_BOOT_WP_B_PERM_WP_EN (0x04)
+#define EXT_CSD_BOOT_WP_B_PWR_WP_EN (0x01)
+
+#define EXT_CSD_PART_CONFIG_ACC_MASK (0x7)
+#define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1)
+#define EXT_CSD_PART_CONFIG_ACC_RPMB (0x3)
+#define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4)
+
+#define EXT_CSD_PART_SETTING_COMPLETED (0x1)
+#define EXT_CSD_PART_SUPPORT_PART_EN (0x1)
+
+#define EXT_CSD_CMD_SET_NORMAL (1<<0)
+#define EXT_CSD_CMD_SET_SECURE (1<<1)
+#define EXT_CSD_CMD_SET_CPSECURE (1<<2)
+
+#define EXT_CSD_CARD_TYPE_HS_26 (1<<0) /* Card can run at 26MHz */
+#define EXT_CSD_CARD_TYPE_HS_52 (1<<1) /* Card can run at 52MHz */
+#define EXT_CSD_CARD_TYPE_HS (EXT_CSD_CARD_TYPE_HS_26 | \
+ EXT_CSD_CARD_TYPE_HS_52)
+#define EXT_CSD_CARD_TYPE_DDR_1_8V (1<<2) /* Card can run at 52MHz */
+/* DDR mode @1.8V or 3V I/O */
+#define EXT_CSD_CARD_TYPE_DDR_1_2V (1<<3) /* Card can run at 52MHz */
+/* DDR mode @1.2V I/O */
+#define EXT_CSD_CARD_TYPE_DDR_52 (EXT_CSD_CARD_TYPE_DDR_1_8V \
+ | EXT_CSD_CARD_TYPE_DDR_1_2V)
+#define EXT_CSD_CARD_TYPE_HS200_1_8V (1<<4) /* Card can run at 200MHz */
+#define EXT_CSD_CARD_TYPE_HS200_1_2V (1<<5) /* Card can run at 200MHz */
+/* SDR mode @1.2V I/O */
+#define EXT_CSD_CARD_TYPE_HS200 (EXT_CSD_CARD_TYPE_HS200_1_8V | \
+ EXT_CSD_CARD_TYPE_HS200_1_2V)
+#define EXT_CSD_CARD_TYPE_HS400_1_8V (1<<6) /* Card can run at 200MHz DDR, 1.8V */
+#define EXT_CSD_CARD_TYPE_HS400_1_2V (1<<7) /* Card can run at 200MHz DDR, 1.2V */
+#define EXT_CSD_CARD_TYPE_HS400 (EXT_CSD_CARD_TYPE_HS400_1_8V | \
+ EXT_CSD_CARD_TYPE_HS400_1_2V)
+#define EXT_CSD_CARD_TYPE_HS400ES (1<<8) /* Card can run at HS400ES */
+
+#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */
+#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */
+#define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */
+#define EXT_CSD_DDR_BUS_WIDTH_4 5 /* Card is in 4 bit DDR mode */
+#define EXT_CSD_DDR_BUS_WIDTH_8 6 /* Card is in 8 bit DDR mode */
+#define EXT_CSD_BUS_WIDTH_STROBE (1<<7) /* Enhanced strobe mode */
+
+#define EXT_CSD_TIMING_BC 0 /* Backwards compatility */
+#define EXT_CSD_TIMING_HS 1 /* High speed */
+#define EXT_CSD_TIMING_HS200 2 /* HS200 */
+#define EXT_CSD_TIMING_HS400 3 /* HS400 */
+#define EXT_CSD_DRV_STR_SHIFT 4 /* Driver Strength shift */
+
+#define EXT_CSD_SEC_ER_EN (1<<0)
+#define EXT_CSD_SEC_BD_BLK_EN (1<<2)
+#define EXT_CSD_SEC_GB_CL_EN (1<<4)
+#define EXT_CSD_SEC_SANITIZE (1<<6) /* v4.5 only */
+
+#define EXT_CSD_RST_N_EN_MASK 0x3
+#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */
+
+#define EXT_CSD_NO_POWER_NOTIFICATION 0
+#define EXT_CSD_POWER_ON 1
+#define EXT_CSD_POWER_OFF_SHORT 2
+#define EXT_CSD_POWER_OFF_LONG 3
+
+#define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */
+#define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */
+#define EXT_CSD_PWR_CL_8BIT_SHIFT 4
+#define EXT_CSD_PWR_CL_4BIT_SHIFT 0
+
+#define EXT_CSD_PACKED_EVENT_EN (1<<3)
+
+/*
+* EXCEPTION_EVENT_STATUS field
+*/
+#define EXT_CSD_URGENT_BKOPS (1<<0)
+#define EXT_CSD_DYNCAP_NEEDED (1<<1)
+#define EXT_CSD_SYSPOOL_EXHAUSTED (1<<2)
+#define EXT_CSD_PACKED_FAILURE (1<<3)
+
+#define EXT_CSD_PACKED_GENERIC_ERROR (1<<0)
+#define EXT_CSD_PACKED_INDEXED_ERROR (1<<1)
+
+/*
+* BKOPS status level
+*/
+#define EXT_CSD_BKOPS_LEVEL_2 0x2
+
+/*
+* BKOPS modes
+*/
+#define EXT_CSD_MANUAL_BKOPS_MASK 0x01
+#define EXT_CSD_AUTO_BKOPS_MASK 0x02
+
+/*
+* Command Queue
+*/
+#define EXT_CSD_CMDQ_MODE_ENABLED (1<<0)
+#define EXT_CSD_CMDQ_DEPTH_MASK 0x1F
+#define EXT_CSD_CMDQ_SUPPORTED (1<<0)
+
+/*
+* MMC_SWITCH access modes
+*/
+#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */
+#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits which are 1 in value */
+#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */
+#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
+
+/*
+* Erase/trim/discard
+*/
+#define MMC_ERASE_ARG 0x00000000
+#define MMC_SECURE_ERASE_ARG 0x80000000
+#define MMC_TRIM_ARG 0x00000001
+#define MMC_DISCARD_ARG 0x00000003
+#define MMC_SECURE_TRIM1_ARG 0x80000001
+#define MMC_SECURE_TRIM2_ARG 0x80008000
+#define MMC_SECURE_ARGS 0x80000000
+#define MMC_TRIM_ARGS 0x00008001
+
+#endif /* LINUX_MMC_MMC_H */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_emmc.c b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_emmc.c
new file mode 100644
index 00000000..2dca775a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_emmc.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+#include "../utils/types.h"
+#include "nx_emmc.h"
+
+int nx_emmc_part_read(sdmmc_storage_t *storage, emmc_part_t *part, u32 sector_off, u32 num_sectors, void *buf)
+{
+ // The last LBA is inclusive.
+ if (part->lba_start + sector_off > part->lba_end)
+ return 0;
+ return sdmmc_storage_read(storage, part->lba_start + sector_off, num_sectors, buf);
+}
+
+int nx_emmc_part_write(sdmmc_storage_t *storage, emmc_part_t *part, u32 sector_off, u32 num_sectors, void *buf)
+{
+ // The last LBA is inclusive.
+ if (part->lba_start + sector_off > part->lba_end)
+ return 0;
+ return sdmmc_storage_write(storage, part->lba_start + sector_off, num_sectors, buf);
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_emmc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_emmc.h
new file mode 100644
index 00000000..e90d49f6
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_emmc.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _NX_EMMC_H_
+#define _NX_EMMC_H_
+
+#include "../utils/types.h"
+#include "sdmmc.h"
+
+typedef struct _gpt_entry_t
+{
+ u8 type_guid[0x10];
+ u8 part_guid[0x10];
+ u64 lba_start;
+ u64 lba_end;
+ u64 attrs;
+ u16 name[36];
+} gpt_entry_t;
+
+typedef struct _gpt_header_t
+{
+ u64 signature;
+ u32 revision;
+ u32 size;
+ u32 crc32;
+ u32 res1;
+ u64 my_lba;
+ u64 alt_lba;
+ u64 first_use_lba;
+ u64 last_use_lba;
+ u8 disk_guid[0x10];
+ u64 part_ent_lba;
+ u32 num_part_ents;
+ u32 part_ent_size;
+ u32 part_ents_crc32;
+ u8 res2[420];
+} gpt_header_t;
+
+#define NX_GPT_FIRST_LBA 1
+#define NX_GPT_NUM_BLOCKS 33
+#define NX_EMMC_BLOCKSIZE 512
+
+typedef struct _emmc_part_t
+{
+ u32 lba_start;
+ u32 lba_end;
+ u64 attrs;
+ s8 name[37];
+} emmc_part_t;
+
+int nx_emmc_part_read(sdmmc_storage_t *storage, emmc_part_t *part, u32 sector_off, u32 num_sectors, void *buf);
+int nx_emmc_part_write(sdmmc_storage_t *storage, emmc_part_t *part, u32 sector_off, u32 num_sectors, void *buf);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_sd.c b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_sd.c
new file mode 100644
index 00000000..1f5fd50d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_sd.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include "nx_sd.h"
+#include "sdmmc.h"
+#include "sdmmc_driver.h"
+#include "../soc/gpio.h"
+#include "../libs/fatfs/ff.h"
+
+extern sdmmc_t sd_sdmmc;
+extern sdmmc_storage_t sd_storage;
+
+static u32 sd_mode = SD_UHS_SDR104;
+
+u32 nx_sd_mode_get()
+{
+ return sd_mode;
+}
+
+int nx_sd_init_retry(bool power_cycle)
+{
+ u32 bus_width = SDMMC_BUS_WIDTH_4;
+ u32 type = SDHCI_TIMING_UHS_SDR104;
+
+ // Power cycle SD card.
+ if (power_cycle)
+ {
+ sd_mode--;
+ sdmmc_storage_end(&sd_storage);
+ }
+
+ // Get init parameters.
+ switch (sd_mode)
+ {
+ case SD_INIT_FAIL: // Reset to max.
+ return 0;
+ case SD_1BIT_HS25:
+ bus_width = SDMMC_BUS_WIDTH_1;
+ type = SDHCI_TIMING_SD_HS25;
+ break;
+ case SD_4BIT_HS25:
+ type = SDHCI_TIMING_SD_HS25;
+ break;
+ case SD_UHS_SDR82:
+ type = SDHCI_TIMING_UHS_SDR82;
+ break;
+ case SD_UHS_SDR104:
+ type = SDHCI_TIMING_UHS_SDR104;
+ break;
+ default:
+ sd_mode = SD_UHS_SDR104;
+ }
+
+ return sdmmc_storage_init_sd(&sd_storage, &sd_sdmmc, bus_width, type);
+}
+
+bool nx_sd_initialize(bool power_cycle)
+{
+ if (power_cycle)
+ sdmmc_storage_end(&sd_storage);
+
+ int res = !nx_sd_init_retry(false);
+
+ while (true)
+ {
+ if (!res)
+ return true;
+ else
+ {
+ if (sd_mode == SD_INIT_FAIL)
+ break;
+
+ res = !nx_sd_init_retry(true);
+ }
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_sd.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_sd.h
new file mode 100644
index 00000000..38c74edb
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/nx_sd.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef NX_SD_H
+#define NX_SD_H
+
+#include "../utils/types.h"
+
+enum
+{
+ SD_INIT_FAIL = 0,
+ SD_1BIT_HS25 = 1,
+ SD_4BIT_HS25 = 2,
+ SD_UHS_SDR82 = 3,
+ SD_UHS_SDR104 = 4
+};
+
+u32 nx_sd_get_mode();
+int nx_sd_init_retry(bool power_cycle);
+bool nx_sd_initialize(bool power_cycle);
+
+#endif
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sd.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sd.h
new file mode 100644
index 00000000..a780ec85
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sd.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2005-2007 Pierre Ossman, All Rights Reserved.
+ * Copyright (c) 2018 CTCaer
+ *
+ * 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.
+ */
+
+#ifndef MMC_SD_H
+#define MMC_SD_H
+
+/* SD commands type argument response */
+/* class 0 */
+/* This is basically the same command as for MMC with some quirks. */
+#define SD_SEND_RELATIVE_ADDR 3 /* bcr R6 */
+#define SD_SEND_IF_COND 8 /* bcr [11:0] See below R7 */
+#define SD_SWITCH_VOLTAGE 11 /* ac R1 */
+
+/* class 10 */
+#define SD_SWITCH 6 /* adtc [31:0] See below R1 */
+
+/* class 5 */
+#define SD_ERASE_WR_BLK_START 32 /* ac [31:0] data addr R1 */
+#define SD_ERASE_WR_BLK_END 33 /* ac [31:0] data addr R1 */
+
+/* Application commands */
+#define SD_APP_SET_BUS_WIDTH 6 /* ac [1:0] bus width R1 */
+#define SD_APP_SD_STATUS 13 /* adtc R1 */
+#define SD_APP_SEND_NUM_WR_BLKS 22 /* adtc R1 */
+#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */
+#define SD_APP_SET_CLR_CARD_DETECT 42
+#define SD_APP_SEND_SCR 51 /* adtc R1 */
+
+/* OCR bit definitions */
+#define SD_OCR_S18R (1 << 24) /* 1.8V switching request */
+#define SD_ROCR_S18A SD_OCR_S18R /* 1.8V switching accepted by card */
+#define SD_OCR_XPC (1 << 28) /* SDXC power control */
+#define SD_OCR_CCS (1 << 30) /* Card Capacity Status */
+#define SD_OCR_VDD_27_34 (0x7F << 15) /* VDD voltage 2.7 ~ 3.4 */
+#define SD_OCR_VDD_32_33 (1 << 20) /* VDD voltage 3.2 ~ 3.3 */
+#define SD_OCR_VDD_18 (1 << 7) /* VDD voltage 1.8 */
+
+/*
+* SD_SWITCH argument format:
+*
+* [31] Check (0) or switch (1)
+* [30:24] Reserved (0)
+* [23:20] Function group 6
+* [19:16] Function group 5
+* [15:12] Function group 4
+* [11:8] Function group 3
+* [7:4] Function group 2
+* [3:0] Function group 1
+*/
+
+/*
+* SD_SEND_IF_COND argument format:
+*
+* [31:12] Reserved (0)
+* [11:8] Host Voltage Supply Flags
+* [7:0] Check Pattern (0xAA)
+*/
+
+/*
+* SCR field definitions
+*/
+#define SCR_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.01 */
+#define SCR_SPEC_VER_1 1 /* Implements system specification 1.10 */
+#define SCR_SPEC_VER_2 2 /* Implements system specification 2.00-3.0X */
+#define SD_SCR_BUS_WIDTH_1 (1<<0)
+#define SD_SCR_BUS_WIDTH_4 (1<<2)
+
+/*
+* SD bus widths
+*/
+#define SD_BUS_WIDTH_1 0
+#define SD_BUS_WIDTH_4 2
+
+/*
+* SD bus speeds
+*/
+#define UHS_SDR12_BUS_SPEED 0
+#define HIGH_SPEED_BUS_SPEED 1
+#define UHS_SDR25_BUS_SPEED 1
+#define UHS_SDR50_BUS_SPEED 2
+#define UHS_SDR104_BUS_SPEED 3
+#define UHS_DDR50_BUS_SPEED 4
+#define HS400_BUS_SPEED 5
+
+#define SD_MODE_HIGH_SPEED (1 << HIGH_SPEED_BUS_SPEED)
+#define SD_MODE_UHS_SDR12 (1 << UHS_SDR12_BUS_SPEED)
+#define SD_MODE_UHS_SDR25 (1 << UHS_SDR25_BUS_SPEED)
+#define SD_MODE_UHS_SDR50 (1 << UHS_SDR50_BUS_SPEED)
+#define SD_MODE_UHS_SDR104 (1 << UHS_SDR104_BUS_SPEED)
+#define SD_MODE_UHS_DDR50 (1 << UHS_DDR50_BUS_SPEED)
+
+#define SD_DRIVER_TYPE_B 0x01
+#define SD_DRIVER_TYPE_A 0x02
+
+#define SD_SET_CURRENT_LIMIT_200 0
+#define SD_SET_CURRENT_LIMIT_400 1
+#define SD_SET_CURRENT_LIMIT_600 2
+#define SD_SET_CURRENT_LIMIT_800 3
+
+#define SD_MAX_CURRENT_200 (1 << SD_SET_CURRENT_LIMIT_200)
+#define SD_MAX_CURRENT_400 (1 << SD_SET_CURRENT_LIMIT_400)
+#define SD_MAX_CURRENT_600 (1 << SD_SET_CURRENT_LIMIT_600)
+#define SD_MAX_CURRENT_800 (1 << SD_SET_CURRENT_LIMIT_800)
+
+/*
+* SD_SWITCH mode
+*/
+#define SD_SWITCH_CHECK 0
+#define SD_SWITCH_SET 1
+
+/*
+* SD_SWITCH function groups
+*/
+#define SD_SWITCH_GRP_ACCESS 0
+
+/*
+* SD_SWITCH access modes
+*/
+#define SD_SWITCH_ACCESS_DEF 0
+#define SD_SWITCH_ACCESS_HS 1
+
+#endif /* LINUX_MMC_SD_H */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc.c b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc.c
new file mode 100644
index 00000000..56024bd5
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc.c
@@ -0,0 +1,1480 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+#include
+#include
+#include "sdmmc.h"
+#include "mmc.h"
+#include "nx_sd.h"
+#include "sd.h"
+#include "../utils/types.h"
+#include "../utils/util.h"
+#include "../utils/fatal.h"
+#include "../emuMMC/emummc.h"
+
+#define DPRINTF(...) //fprintf(stdout, __VA_ARGS__)
+
+sdmmc_accessor_t *_current_accessor = NULL;
+bool sdmmc_memcpy_buf = false;
+extern bool custom_driver;
+
+static inline u32 unstuff_bits(u32 *resp, u32 start, u32 size)
+{
+ const u32 mask = (size < 32 ? 1 << size : 0) - 1;
+ const u32 off = 3 - ((start) / 32);
+ const u32 shft = (start) & 31;
+ u32 res = resp[off] >> shft;
+ if (size + shft > 32)
+ res |= resp[off - 1] << ((32 - shft) % 32);
+ return res & mask;
+}
+
+/*
+* Common functions for SD and MMC.
+*/
+
+// FS DMA calculations.
+intptr_t sdmmc_calculate_dma_addr(sdmmc_accessor_t *_this, void *buf, unsigned int num_sectors)
+{
+ int dma_buf_idx = 0;
+ char *_buf = (char *)buf;
+ char *actual_buf_start = _buf;
+ char *actual_buf_end = &_buf[512 * num_sectors];
+ char *dma_buffer_start = _this->parent->dmaBuffers[0].device_addr_buffer;
+
+ if (dma_buffer_start <= _buf && actual_buf_end <= &dma_buffer_start[_this->parent->dmaBuffers[0].device_addr_buffer_size])
+ {
+ dma_buf_idx = 0;
+ }
+ else
+ {
+ dma_buffer_start = _this->parent->dmaBuffers[1].device_addr_buffer;
+ if (dma_buffer_start <= actual_buf_start && actual_buf_end <= &dma_buffer_start[_this->parent->dmaBuffers[1].device_addr_buffer_size])
+ {
+ dma_buf_idx = 1;
+ }
+ else
+ {
+ dma_buffer_start = _this->parent->dmaBuffers[2].device_addr_buffer;
+ if (dma_buffer_start <= actual_buf_start && actual_buf_end <= &dma_buffer_start[_this->parent->dmaBuffers[2].device_addr_buffer_size])
+ {
+ dma_buf_idx = 2;
+ }
+ else
+ {
+ // If buffer is on a random heap
+ return 0;
+ }
+ }
+ }
+
+ sdmmc_memcpy_buf = false;
+
+ intptr_t admaaddr = (intptr_t)&_this->parent->dmaBuffers[dma_buf_idx].device_addr_buffer_masked[actual_buf_start - dma_buffer_start];
+ return admaaddr;
+}
+
+int sdmmc_calculate_dma_index(sdmmc_accessor_t *_this, void *buf, unsigned int num_sectors)
+{
+ int dma_buf_idx = 0;
+ char *_buf = (char *)buf;
+ char *actual_buf_start = _buf;
+ char *actual_buf_end = &_buf[512 * num_sectors];
+ char *dma_buffer_start = _this->parent->dmaBuffers[0].device_addr_buffer;
+
+ if (dma_buffer_start <= _buf && actual_buf_end <= &dma_buffer_start[_this->parent->dmaBuffers[0].device_addr_buffer_size])
+ {
+ dma_buf_idx = 0;
+ }
+ else
+ {
+ dma_buffer_start = _this->parent->dmaBuffers[1].device_addr_buffer;
+ if (dma_buffer_start <= actual_buf_start && actual_buf_end <= &dma_buffer_start[_this->parent->dmaBuffers[1].device_addr_buffer_size])
+ {
+ dma_buf_idx = 1;
+ }
+ else
+ {
+ dma_buffer_start = _this->parent->dmaBuffers[2].device_addr_buffer;
+ if (dma_buffer_start <= actual_buf_start && actual_buf_end <= &dma_buffer_start[_this->parent->dmaBuffers[2].device_addr_buffer_size])
+ {
+ dma_buf_idx = 2;
+ }
+ else
+ {
+ // If buffer is on a random heap
+ return -1;
+ }
+ }
+ }
+
+ sdmmc_memcpy_buf = false;
+
+ return dma_buf_idx;
+}
+
+int sdmmc_calculate_fitting_dma_index(sdmmc_accessor_t *_this, unsigned int num_sectors)
+{
+ int dma_buf_idx = 0;
+ int blkSize = num_sectors * 512;
+
+ if (_this->parent->dmaBuffers[0].device_addr_buffer_size >= blkSize)
+ {
+ dma_buf_idx = 0;
+ }
+ else
+ {
+ if (_this->parent->dmaBuffers[1].device_addr_buffer_size >= blkSize)
+ {
+ dma_buf_idx = 1;
+ }
+ else
+ {
+ if (_this->parent->dmaBuffers[2].device_addr_buffer_size >= blkSize)
+ {
+ dma_buf_idx = 2;
+ }
+ else
+ {
+ // Can't find a fitting buffer
+ return 0;
+ }
+ }
+ }
+
+ sdmmc_memcpy_buf = true;
+ return dma_buf_idx;
+}
+
+static int _sdmmc_storage_check_result(u32 res)
+{
+ //Error mask:
+ //TODO: R1_SWITCH_ERROR can be skipped for certain card types.
+ if (res &
+ (R1_OUT_OF_RANGE | R1_ADDRESS_ERROR | R1_BLOCK_LEN_ERROR |
+ R1_ERASE_SEQ_ERROR | R1_ERASE_PARAM | R1_WP_VIOLATION |
+ R1_LOCK_UNLOCK_FAILED | R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND |
+ R1_CARD_ECC_FAILED | R1_CC_ERROR | R1_ERROR |
+ R1_CID_CSD_OVERWRITE | R1_WP_ERASE_SKIP | R1_ERASE_RESET |
+ R1_SWITCH_ERROR))
+ return 0;
+
+ // No errors.
+ return 1;
+}
+
+static int _sdmmc_storage_execute_cmd_type1_ex(sdmmc_storage_t *storage, u32 *resp, u32 cmd, u32 arg, u32 check_busy, u32 expected_state, u32 mask)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, cmd, arg, SDMMC_RSP_TYPE_1, check_busy);
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
+ return 0;
+
+ sdmmc_get_rsp(storage->sdmmc, resp, 4, SDMMC_RSP_TYPE_1);
+ if (mask)
+ *resp &= ~mask;
+
+ if (_sdmmc_storage_check_result(*resp))
+ if (expected_state == 0x10 || R1_CURRENT_STATE(*resp) == expected_state)
+ return 1;
+
+ return 0;
+}
+
+static int _sdmmc_storage_execute_cmd_type1(sdmmc_storage_t *storage, u32 cmd, u32 arg, u32 check_busy, u32 expected_state)
+{
+ u32 tmp;
+ return _sdmmc_storage_execute_cmd_type1_ex(storage, &tmp, cmd, arg, check_busy, expected_state, 0);
+}
+
+static int _sdmmc_storage_go_idle_state(sdmmc_storage_t *storage)
+{
+ sdmmc_cmd_t cmd;
+ sdmmc_init_cmd(&cmd, MMC_GO_IDLE_STATE, 0, SDMMC_RSP_TYPE_0, 0);
+
+ return sdmmc_execute_cmd(storage->sdmmc, &cmd, 0, 0);
+}
+
+static int _sdmmc_storage_get_cid(sdmmc_storage_t *storage, void *buf)
+{
+ sdmmc_cmd_t cmd;
+ sdmmc_init_cmd(&cmd, MMC_ALL_SEND_CID, 0, SDMMC_RSP_TYPE_2, 0);
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmd, 0, 0))
+ return 0;
+
+ sdmmc_get_rsp(storage->sdmmc, buf, 0x10, SDMMC_RSP_TYPE_2);
+
+ return 1;
+}
+
+static int _sdmmc_storage_select_card(sdmmc_storage_t *storage)
+{
+ return _sdmmc_storage_execute_cmd_type1(storage, MMC_SELECT_CARD, storage->rca << 16, 1, 0x10);
+}
+
+static int _sdmmc_storage_get_csd(sdmmc_storage_t *storage, void *buf)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, MMC_SEND_CSD, storage->rca << 16, SDMMC_RSP_TYPE_2, 0);
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
+ return 0;
+
+ sdmmc_get_rsp(storage->sdmmc, buf, 0x10, SDMMC_RSP_TYPE_2);
+
+ return 1;
+}
+
+static int _sdmmc_storage_set_blocklen(sdmmc_storage_t *storage, u32 blocklen)
+{
+ return _sdmmc_storage_execute_cmd_type1(storage, MMC_SET_BLOCKLEN, blocklen, 0, R1_STATE_TRAN);
+}
+
+static int _sdmmc_storage_get_status(sdmmc_storage_t *storage, u32 *resp, u32 mask)
+{
+ return _sdmmc_storage_execute_cmd_type1_ex(storage, resp, MMC_SEND_STATUS, storage->rca << 16, 0, R1_STATE_TRAN, mask);
+}
+
+static int _sdmmc_storage_check_status(sdmmc_storage_t *storage)
+{
+ u32 tmp;
+ return _sdmmc_storage_get_status(storage, &tmp, 0);
+}
+
+static int _sdmmc_storage_readwrite_ex(sdmmc_storage_t *storage, u32 *blkcnt_out, u32 sector, u32 num_sectors, void *buf, u32 is_write)
+{
+ u32 tmp = 0;
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_req_t reqbuf;
+
+ sdmmc_init_cmd(&cmdbuf, is_write ? MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK, sector, SDMMC_RSP_TYPE_1, 0);
+
+ reqbuf.buf = buf;
+ reqbuf.num_sectors = num_sectors;
+ reqbuf.blksize = 512;
+ reqbuf.is_write = is_write;
+ reqbuf.is_multi_block = 1;
+ reqbuf.is_auto_cmd12 = 1;
+
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, blkcnt_out))
+ {
+ sdmmc_stop_transmission(storage->sdmmc, &tmp);
+ _sdmmc_storage_get_status(storage, &tmp, 0);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+int sdmmc_storage_end(sdmmc_storage_t *storage)
+{
+ if (!_sdmmc_storage_go_idle_state(storage))
+ return 0;
+
+ sdmmc_end(storage->sdmmc);
+
+ storage->initialized = 0;
+
+ return 1;
+}
+
+static int _sdmmc_storage_readwrite(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf, u32 is_write)
+{
+ u8 *bbuf = (u8 *)buf;
+ u32 sct_off = sector;
+ u32 sct_total = num_sectors;
+ bool first_reinit = true;
+
+ // Exit if not initialized.
+ if (!storage->initialized)
+ return 0;
+
+ while (sct_total)
+ {
+ u32 blkcnt = 0;
+ // Retry 5 times if failed.
+ u32 retries = 5;
+ do
+ {
+reinit_try:
+ if (_sdmmc_storage_readwrite_ex(storage, &blkcnt, sct_off, MIN(sct_total, 0xFFFF), bbuf, is_write))
+ goto out;
+ else
+ retries--;
+
+ msleep(50);
+ } while (retries);
+
+ // Disk IO failure! Reinit SD Card to a lower speed.
+ if (storage->sdmmc->id == SDMMC_1)
+ {
+ int res;
+
+ if (first_reinit)
+ res = nx_sd_initialize(true);
+ else
+ res = nx_sd_init_retry(true);
+
+ // Reset values for a retry.
+ blkcnt = 0;
+ retries = 3;
+ first_reinit = false;
+
+ // If succesful reinit, restart xfer.
+ if (res)
+ {
+ bbuf = (u8 *)buf;
+ sct_off = sector;
+ sct_total = num_sectors;
+
+ goto reinit_try;
+ }
+ }
+
+ // Failed.
+ return 0;
+
+out:
+ sct_off += blkcnt;
+ sct_total -= blkcnt;
+ bbuf += 512 * blkcnt;
+ }
+
+ return 1;
+}
+
+extern _sdmmc_accessor_sd sdmmc_accessor_sd;
+extern _sdmmc_accessor_nand sdmmc_accessor_nand;
+int sdmmc_storage_read(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf)
+{
+ if (!custom_driver)
+ {
+ sdmmc_accessor_t *accessor_sd = sdmmc_accessor_sd();
+ sdmmc_accessor_t *accessor_nand = sdmmc_accessor_nand();
+
+ if (sdmmc_calculate_dma_addr(accessor_sd, buf, num_sectors))
+ {
+ return !accessor_sd->vtab->read_write(accessor_sd, sector, num_sectors, buf, num_sectors * 512, 1);
+ }
+ else
+ {
+ if (sdmmc_calculate_dma_addr(accessor_nand, buf, num_sectors))
+ {
+ // buf is on the nand dma buffer
+ int original_dma_idx = sdmmc_calculate_dma_index(accessor_nand, buf, num_sectors);
+ sdmmc_dma_buffer_t *original_dma_buffer = &accessor_nand->parent->dmaBuffers[original_dma_idx];
+
+ // Next entry
+ int dma_idx = sdmmc_calculate_fitting_dma_index(accessor_sd, num_sectors) + 1;
+
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer = original_dma_buffer->device_addr_buffer;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_masked = original_dma_buffer->device_addr_buffer_masked;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_size = original_dma_buffer->device_addr_buffer_size;
+
+ u64 res = accessor_sd->vtab->read_write(accessor_sd, sector, num_sectors, buf, num_sectors * 512, 1);
+
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer = 0;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_masked = 0;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_size = 0;
+
+ return !res;
+ }
+ else
+ {
+ // buf is on a heap
+ int dma_idx = sdmmc_calculate_fitting_dma_index(accessor_sd, num_sectors);
+ void *dma_buf = &accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer[0];
+
+ u64 res = accessor_sd->vtab->read_write(accessor_sd, sector, num_sectors, dma_buf, num_sectors * 512, 1);
+ memcpy(buf, dma_buf, num_sectors * 512);
+
+ return !res;
+ }
+ }
+ }
+ else
+ {
+ return _sdmmc_storage_readwrite(storage, sector, num_sectors, buf, 0);
+ }
+}
+
+int sdmmc_storage_write(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf)
+{
+ if (!custom_driver)
+ {
+ sdmmc_accessor_t *accessor_sd = sdmmc_accessor_sd();
+ sdmmc_accessor_t *accessor_nand = sdmmc_accessor_nand();
+
+ if (sdmmc_calculate_dma_addr(accessor_sd, buf, num_sectors))
+ {
+ return !accessor_sd->vtab->read_write(accessor_sd, sector, num_sectors, buf, num_sectors * 512, 0);
+ }
+ else
+ {
+ if (sdmmc_calculate_dma_addr(accessor_nand, buf, num_sectors))
+ {
+ // buf is on the nand dma buffer
+ int original_dma_idx = sdmmc_calculate_dma_index(accessor_nand, buf, num_sectors);
+ sdmmc_dma_buffer_t *original_dma_buffer = &accessor_nand->parent->dmaBuffers[original_dma_idx];
+
+ // Next entry
+ int dma_idx = sdmmc_calculate_fitting_dma_index(accessor_sd, num_sectors) + 1;
+
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer = original_dma_buffer->device_addr_buffer;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_masked = original_dma_buffer->device_addr_buffer_masked;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_size = original_dma_buffer->device_addr_buffer_size;
+
+ u64 res = accessor_sd->vtab->read_write(accessor_sd, sector, num_sectors, buf, num_sectors * 512, 0);
+
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer = 0;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_masked = 0;
+ accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer_size = 0;
+
+ return !res;
+ }
+ else
+ {
+ // buf is on a heap
+ int dma_idx = sdmmc_calculate_fitting_dma_index(accessor_sd, num_sectors);
+ void *dma_buf = &accessor_sd->parent->dmaBuffers[dma_idx].device_addr_buffer[0];
+
+ memcpy(dma_buf, buf, num_sectors * 512);
+ u64 res = accessor_sd->vtab->read_write(accessor_sd, sector, num_sectors, dma_buf, num_sectors * 512, 0);
+
+ return !res;
+ }
+ }
+ }
+ else
+ {
+ return _sdmmc_storage_readwrite(storage, sector, num_sectors, buf, 1);
+ }
+}
+
+/*
+* MMC specific functions.
+*/
+
+static int _mmc_storage_get_op_cond_inner(sdmmc_storage_t *storage, u32 *pout, u32 power)
+{
+ sdmmc_cmd_t cmd;
+
+ u32 arg = 0;
+ switch (power)
+ {
+ case SDMMC_POWER_1_8:
+ arg = SD_OCR_CCS | SD_OCR_VDD_18;
+ break;
+
+ case SDMMC_POWER_3_3:
+ arg = SD_OCR_CCS | SD_OCR_VDD_27_34;
+ break;
+
+ default:
+ return 0;
+ }
+
+ sdmmc_init_cmd(&cmd, MMC_SEND_OP_COND, arg, SDMMC_RSP_TYPE_3, 0);
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmd, 0, 0))
+ return 0;
+
+ return sdmmc_get_rsp(storage->sdmmc, pout, 4, SDMMC_RSP_TYPE_3);
+}
+
+static int _mmc_storage_get_op_cond(sdmmc_storage_t *storage, u32 power)
+{
+ u64 timeout = get_tmr_ms() + 1500;
+
+ while (1)
+ {
+ u32 cond = 0;
+ if (!_mmc_storage_get_op_cond_inner(storage, &cond, power))
+ break;
+
+ if (cond & MMC_CARD_BUSY)
+ {
+ if (cond & SD_OCR_CCS)
+ storage->has_sector_access = 1;
+
+ return 1;
+ }
+ if (get_tmr_ms() > timeout)
+ break;
+
+ usleep(1000);
+ }
+
+ return 0;
+}
+
+static int _mmc_storage_set_relative_addr(sdmmc_storage_t *storage)
+{
+ return _sdmmc_storage_execute_cmd_type1(storage, MMC_SET_RELATIVE_ADDR, storage->rca << 16, 0, 0x10);
+}
+
+static void _mmc_storage_parse_cid(sdmmc_storage_t *storage)
+{
+ u32 *raw_cid = (u32 *)&(storage->raw_cid);
+
+ switch (storage->csd.mmca_vsn)
+ {
+ case 0: /* MMC v1.0 - v1.2 */
+ case 1: /* MMC v1.4 */
+ storage->cid.prod_name[6] = unstuff_bits(raw_cid, 48, 8);
+ storage->cid.manfid = unstuff_bits(raw_cid, 104, 24);
+ storage->cid.hwrev = unstuff_bits(raw_cid, 44, 4);
+ storage->cid.fwrev = unstuff_bits(raw_cid, 40, 4);
+ storage->cid.serial = unstuff_bits(raw_cid, 16, 24);
+ break;
+
+ case 2: /* MMC v2.0 - v2.2 */
+ case 3: /* MMC v3.1 - v3.3 */
+ case 4: /* MMC v4 */
+ storage->cid.manfid = unstuff_bits(raw_cid, 120, 8);
+ storage->cid.card_bga = unstuff_bits(raw_cid, 112, 2);
+ storage->cid.oemid = unstuff_bits(raw_cid, 104, 8);
+ storage->cid.prv = unstuff_bits(raw_cid, 48, 8);
+ storage->cid.serial = unstuff_bits(raw_cid, 16, 32);
+ break;
+
+ default:
+ break;
+ }
+
+ storage->cid.prod_name[0] = unstuff_bits(raw_cid, 96, 8);
+ storage->cid.prod_name[1] = unstuff_bits(raw_cid, 88, 8);
+ storage->cid.prod_name[2] = unstuff_bits(raw_cid, 80, 8);
+ storage->cid.prod_name[3] = unstuff_bits(raw_cid, 72, 8);
+ storage->cid.prod_name[4] = unstuff_bits(raw_cid, 64, 8);
+ storage->cid.prod_name[5] = unstuff_bits(raw_cid, 56, 8);
+
+ storage->cid.month = unstuff_bits(raw_cid, 12, 4);
+ storage->cid.year = unstuff_bits(raw_cid, 8, 4) + 1997;
+ if (storage->ext_csd.rev >= 5)
+ {
+ if (storage->cid.year < 2010)
+ storage->cid.year += 16;
+ }
+}
+
+static void _mmc_storage_parse_csd(sdmmc_storage_t *storage)
+{
+ u32 *raw_csd = (u32 *)&(storage->raw_csd);
+
+ storage->csd.mmca_vsn = unstuff_bits(raw_csd, 122, 4);
+ storage->csd.structure = unstuff_bits(raw_csd, 126, 2);
+ storage->csd.cmdclass = unstuff_bits(raw_csd, 84, 12);
+ storage->csd.read_blkbits = unstuff_bits(raw_csd, 80, 4);
+ storage->csd.capacity = (1 + unstuff_bits(raw_csd, 62, 12)) << (unstuff_bits(raw_csd, 47, 3) + 2);
+}
+
+static void _mmc_storage_parse_ext_csd(sdmmc_storage_t *storage, u8 *buf)
+{
+ storage->ext_csd.rev = buf[EXT_CSD_REV];
+ storage->ext_csd.ext_struct = buf[EXT_CSD_STRUCTURE];
+ storage->ext_csd.card_type = buf[EXT_CSD_CARD_TYPE];
+ storage->ext_csd.dev_version = *(u16 *)&buf[EXT_CSD_DEVICE_VERSION];
+ storage->ext_csd.boot_mult = buf[EXT_CSD_BOOT_MULT];
+ storage->ext_csd.rpmb_mult = buf[EXT_CSD_RPMB_MULT];
+ storage->ext_csd.sectors = *(u32 *)&buf[EXT_CSD_SEC_CNT];
+ storage->ext_csd.bkops = buf[EXT_CSD_BKOPS_SUPPORT];
+ storage->ext_csd.bkops_en = buf[EXT_CSD_BKOPS_EN];
+ storage->ext_csd.bkops_status = buf[EXT_CSD_BKOPS_STATUS];
+
+ storage->ext_csd.pre_eol_info = buf[EXT_CSD_PRE_EOL_INFO];
+ storage->ext_csd.dev_life_est_a = buf[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A];
+ storage->ext_csd.dev_life_est_b = buf[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B];
+
+ storage->sec_cnt = *(u32 *)&buf[EXT_CSD_SEC_CNT];
+}
+
+static int _mmc_storage_get_ext_csd(sdmmc_storage_t *storage, void *buf)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, MMC_SEND_EXT_CSD, 0, SDMMC_RSP_TYPE_1, 0);
+
+ sdmmc_req_t reqbuf;
+ reqbuf.buf = buf;
+ reqbuf.blksize = 512;
+ reqbuf.num_sectors = 1;
+ reqbuf.is_write = 0;
+ reqbuf.is_multi_block = 0;
+ reqbuf.is_auto_cmd12 = 0;
+
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
+ return 0;
+
+ u32 tmp = 0;
+ sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
+ _mmc_storage_parse_ext_csd(storage, buf);
+
+ return _sdmmc_storage_check_result(tmp);
+}
+
+static int _mmc_storage_switch(sdmmc_storage_t *storage, u32 arg)
+{
+ return _sdmmc_storage_execute_cmd_type1(storage, MMC_SWITCH, arg, 1, 0x10);
+}
+
+static int _mmc_storage_switch_buswidth(sdmmc_storage_t *storage, u32 bus_width)
+{
+ if (bus_width == SDMMC_BUS_WIDTH_1)
+ return 1;
+
+ u32 arg = 0;
+ switch (bus_width)
+ {
+ case SDMMC_BUS_WIDTH_4:
+ arg = SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4);
+ break;
+
+ case SDMMC_BUS_WIDTH_8:
+ arg = SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_8);
+ break;
+ }
+
+ if (_mmc_storage_switch(storage, arg))
+ if (_sdmmc_storage_check_status(storage))
+ {
+ sdmmc_set_bus_width(storage->sdmmc, bus_width);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static int _mmc_storage_enable_HS(sdmmc_storage_t *storage, int check)
+{
+ if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS)))
+ return 0;
+
+ if (check && !_sdmmc_storage_check_status(storage))
+ return 0;
+
+ if (!sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_MMC_HS52))
+ return 0;
+
+DPRINTF("[MMC] switched to HS\n");
+ storage->csd.busspeed = 52;
+
+ if (check || _sdmmc_storage_check_status(storage))
+ return 1;
+
+ return 0;
+}
+
+static int _mmc_storage_enable_HS200(sdmmc_storage_t *storage)
+{
+ if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS200)))
+ return 0;
+
+ if (!sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_MMC_HS200))
+ return 0;
+
+ if (!sdmmc_tuning_execute(storage->sdmmc, SDHCI_TIMING_MMC_HS200, MMC_SEND_TUNING_BLOCK_HS200))
+ return 0;
+
+DPRINTF("[MMC] switched to HS200\n");
+ storage->csd.busspeed = 200;
+
+ return _sdmmc_storage_check_status(storage);
+}
+
+static int _mmc_storage_enable_HS400(sdmmc_storage_t *storage)
+{
+ if (!_mmc_storage_enable_HS200(storage))
+ return 0;
+
+ sdmmc_save_tap_value(storage->sdmmc);
+
+ if (!_mmc_storage_enable_HS(storage, 0))
+ return 0;
+
+ if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_BUS_WIDTH, EXT_CSD_DDR_BUS_WIDTH_8)))
+ return 0;
+
+ if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS400)))
+ return 0;
+
+ if (!sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_MMC_HS400))
+ return 0;
+
+DPRINTF("[MMC] switched to HS400\n");
+ storage->csd.busspeed = 400;
+
+ return _sdmmc_storage_check_status(storage);
+}
+
+static int _mmc_storage_enable_highspeed(sdmmc_storage_t *storage, u32 card_type, u32 type)
+{
+ if (sdmmc_get_io_power(storage->sdmmc) != SDMMC_POWER_1_8)
+ goto out;
+
+ if (sdmmc_get_bus_width(storage->sdmmc) == SDMMC_BUS_WIDTH_8 &&
+ card_type & EXT_CSD_CARD_TYPE_HS400_1_8V && type == SDHCI_TIMING_MMC_HS400)
+ return _mmc_storage_enable_HS400(storage);
+
+ if (sdmmc_get_bus_width(storage->sdmmc) == SDMMC_BUS_WIDTH_8 ||
+ (sdmmc_get_bus_width(storage->sdmmc) == SDMMC_BUS_WIDTH_4
+ && card_type & EXT_CSD_CARD_TYPE_HS200_1_8V
+ && (type == SDHCI_TIMING_MMC_HS400 || type == SDHCI_TIMING_MMC_HS200)))
+ return _mmc_storage_enable_HS200(storage);
+
+out:
+ if (card_type & EXT_CSD_CARD_TYPE_HS_52)
+ return _mmc_storage_enable_HS(storage, 1);
+
+ return 1;
+}
+
+static int _mmc_storage_enable_bkops(sdmmc_storage_t *storage)
+{
+ if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_SET_BITS, EXT_CSD_BKOPS_EN, EXT_CSD_BKOPS_LEVEL_2)))
+ return 0;
+
+ return _sdmmc_storage_check_status(storage);
+}
+
+int sdmmc_storage_init_mmc(sdmmc_storage_t *storage, sdmmc_t *sdmmc, u32 bus_width, u32 type)
+{
+ memset(storage, 0, sizeof(sdmmc_storage_t));
+ storage->sdmmc = sdmmc;
+ storage->rca = 2; //TODO: this could be a config item.
+
+ if (!sdmmc_init(sdmmc, SDMMC_4, SDMMC_POWER_1_8, SDMMC_BUS_WIDTH_1, SDHCI_TIMING_MMC_ID, SDMMC_POWER_SAVE_DISABLE))
+ return 0;
+DPRINTF("[MMC] after init\n");
+
+ usleep(1000 + (74000 + sdmmc->divisor - 1) / sdmmc->divisor);
+
+ if (!_sdmmc_storage_go_idle_state(storage))
+ return 0;
+DPRINTF("[MMC] went to idle state\n");
+
+ if (!_mmc_storage_get_op_cond(storage, SDMMC_POWER_1_8))
+ return 0;
+DPRINTF("[MMC] got op cond\n");
+
+ if (!_sdmmc_storage_get_cid(storage, storage->raw_cid))
+ return 0;
+DPRINTF("[MMC] got cid\n");
+
+ if (!_mmc_storage_set_relative_addr(storage))
+ return 0;
+DPRINTF("[MMC] set relative addr\n");
+
+ if (!_sdmmc_storage_get_csd(storage, storage->raw_csd))
+ return 0;
+DPRINTF("[MMC] got csd\n");
+ _mmc_storage_parse_csd(storage);
+
+ if (!sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_MMC_LS26))
+ return 0;
+DPRINTF("[MMC] after setup clock\n");
+
+ if (!_sdmmc_storage_select_card(storage))
+ return 0;
+DPRINTF("[MMC] card selected\n");
+
+ if (!_sdmmc_storage_set_blocklen(storage, 512))
+ return 0;
+DPRINTF("[MMC] set blocklen to 512\n");
+
+ u32 *csd = (u32 *)storage->raw_csd;
+ //Check system specification version, only version 4.0 and later support below features.
+ if (unstuff_bits(csd, 122, 4) < CSD_SPEC_VER_4)
+ {
+ storage->sec_cnt = (1 + unstuff_bits(csd, 62, 12)) << (unstuff_bits(csd, 47, 3) + 2);
+ return 1;
+ }
+
+ if (!_mmc_storage_switch_buswidth(storage, bus_width))
+ return 0;
+DPRINTF("[MMC] switched buswidth\n");
+ u8 buf[512];
+ memset(buf, 0, sizeof(buf));
+ if (!_mmc_storage_get_ext_csd(storage, buf))
+ return 0;
+DPRINTF("[MMC] got ext_csd\n");
+
+ _mmc_storage_parse_cid(storage); //This needs to be after csd and ext_csd
+
+ /* When auto BKOPS is enabled the mmc device should be powered all the time until we disable this and check status.
+ Disable it for now until BKOPS disable added to power down sequence at sdmmc_storage_end().
+ Additionally this works only when we put the device in idle mode which we don't after enabling it. */
+ if (0 && storage->ext_csd.bkops & 0x1 && !(storage->ext_csd.bkops_en & EXT_CSD_BKOPS_LEVEL_2))
+ {
+ _mmc_storage_enable_bkops(storage);
+DPRINTF("[MMC] BKOPS enabled\n");
+ }
+
+ if (!_mmc_storage_enable_highspeed(storage, storage->ext_csd.card_type, type))
+ return 0;
+DPRINTF("[MMC] succesfully switched to HS mode\n");
+
+ sdmmc_card_clock_powersave(storage->sdmmc, SDMMC_POWER_SAVE_ENABLE);
+
+ storage->initialized = 1;
+
+ return 1;
+}
+
+int sdmmc_storage_set_mmc_partition(sdmmc_storage_t *storage, u32 partition)
+{
+ if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_PART_CONFIG, partition)))
+ return 0;
+
+ if (!_sdmmc_storage_check_status(storage))
+ return 0;
+
+ storage->partition = partition;
+
+ return 1;
+}
+
+/*
+* SD specific functions.
+*/
+
+static int _sd_storage_execute_app_cmd(sdmmc_storage_t *storage, u32 expected_state, u32 mask, sdmmc_cmd_t *cmd, sdmmc_req_t *req, u32 *blkcnt_out)
+{
+ u32 tmp;
+ if (!_sdmmc_storage_execute_cmd_type1_ex(storage, &tmp, MMC_APP_CMD, storage->rca << 16, 0, expected_state, mask))
+ return 0;
+
+ return sdmmc_execute_cmd(storage->sdmmc, cmd, req, blkcnt_out);
+}
+
+static int _sd_storage_execute_app_cmd_type1(sdmmc_storage_t *storage, u32 *resp, u32 cmd, u32 arg, u32 check_busy, u32 expected_state)
+{
+ if (!_sdmmc_storage_execute_cmd_type1(storage, MMC_APP_CMD, storage->rca << 16, 0, R1_STATE_TRAN))
+ return 0;
+
+ return _sdmmc_storage_execute_cmd_type1_ex(storage, resp, cmd, arg, check_busy, expected_state, 0);
+}
+
+static int _sd_storage_send_if_cond(sdmmc_storage_t *storage)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, SD_SEND_IF_COND, 0x1AA, SDMMC_RSP_TYPE_5, 0);
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
+ return 1; // The SD Card is version 1.X
+
+ u32 resp = 0;
+ if (!sdmmc_get_rsp(storage->sdmmc, &resp, 4, SDMMC_RSP_TYPE_5))
+ return 2;
+
+ return (resp & 0xFF) == 0xAA ? 0 : 2;
+}
+
+static int _sd_storage_get_op_cond_once(sdmmc_storage_t *storage, u32 *cond, int is_version_1, int bus_low_voltage_support)
+{
+ sdmmc_cmd_t cmdbuf;
+ // Support for Current > 150mA
+ u32 arg = (~is_version_1 & 1) ? SD_OCR_XPC : 0;
+ // Support for handling block-addressed SDHC cards
+ arg |= (~is_version_1 & 1) ? SD_OCR_CCS : 0;
+ // Support for 1.8V
+ arg |= (bus_low_voltage_support & ~is_version_1 & 1) ? SD_OCR_S18R : 0;
+ // This is needed for most cards. Do not set bit7 even if 1.8V is supported.
+ arg |= SD_OCR_VDD_32_33;
+ sdmmc_init_cmd(&cmdbuf, SD_APP_OP_COND, arg, SDMMC_RSP_TYPE_3, 0);
+ if (!_sd_storage_execute_app_cmd(storage, 0x10, is_version_1 ? 0x400000 : 0, &cmdbuf, 0, 0))
+ return 0;
+
+ return sdmmc_get_rsp(storage->sdmmc, cond, 4, SDMMC_RSP_TYPE_3);
+}
+
+static int _sd_storage_get_op_cond(sdmmc_storage_t *storage, int is_version_1, int bus_low_voltage_support)
+{
+ u64 timeout = get_tmr_ms() + 1500;
+
+ while (1)
+ {
+ u32 cond = 0;
+ if (!_sd_storage_get_op_cond_once(storage, &cond, is_version_1, bus_low_voltage_support))
+ break;
+ if (cond & MMC_CARD_BUSY)
+ {
+DPRINTF("[SD] cond: %08X, lv: %d\n", cond, bus_low_voltage_support);
+
+ if (cond & SD_OCR_CCS)
+ storage->has_sector_access = 1;
+
+ // Check if card supports 1.8V signaling.
+ if (cond & SD_ROCR_S18A && bus_low_voltage_support)
+ {
+ //The low voltage regulator configuration is valid for SDMMC1 only.
+ if (storage->sdmmc->id == SDMMC_1 &&
+ _sdmmc_storage_execute_cmd_type1(storage, SD_SWITCH_VOLTAGE, 0, 0, R1_STATE_READY))
+ {
+ if (!sdmmc_enable_low_voltage(storage->sdmmc))
+ return 0;
+ storage->is_low_voltage = 1;
+
+DPRINTF("-> switched to low voltage\n");
+ }
+ }
+ else
+ {
+DPRINTF("[SD] no low voltage support\n");
+ }
+
+ return 1;
+ }
+ if (get_tmr_ms() > timeout)
+ break;
+ msleep(10); // Needs to be at least 10ms for some SD Cards
+ }
+
+ return 0;
+}
+
+static int _sd_storage_get_rca(sdmmc_storage_t *storage)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, SD_SEND_RELATIVE_ADDR, 0, SDMMC_RSP_TYPE_4, 0);
+
+ u64 timeout = get_tmr_ms() + 1500;
+
+ while (1)
+ {
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
+ break;
+
+ u32 resp = 0;
+ if (!sdmmc_get_rsp(storage->sdmmc, &resp, 4, SDMMC_RSP_TYPE_4))
+ break;
+
+ if (resp >> 16)
+ {
+ storage->rca = resp >> 16;
+ return 1;
+ }
+
+ if (get_tmr_ms() > timeout)
+ break;
+ usleep(1000);
+ }
+
+ return 0;
+}
+
+static void _sd_storage_parse_scr(sdmmc_storage_t *storage)
+{
+ // unstuff_bits can parse only 4 u32
+ u32 resp[4];
+
+ resp[3] = *(u32 *)&storage->raw_scr[4];
+ resp[2] = *(u32 *)&storage->raw_scr[0];
+
+ storage->scr.sda_vsn = unstuff_bits(resp, 56, 4);
+ storage->scr.bus_widths = unstuff_bits(resp, 48, 4);
+ if (storage->scr.sda_vsn == SCR_SPEC_VER_2)
+ /* Check if Physical Layer Spec v3.0 is supported */
+ storage->scr.sda_spec3 = unstuff_bits(resp, 47, 1);
+ if (storage->scr.sda_spec3)
+ storage->scr.cmds = unstuff_bits(resp, 32, 2);
+}
+
+int _sd_storage_get_scr(sdmmc_storage_t *storage, u8 *buf)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, SD_APP_SEND_SCR, 0, SDMMC_RSP_TYPE_1, 0);
+
+ sdmmc_req_t reqbuf;
+ reqbuf.buf = buf;
+ reqbuf.blksize = 8;
+ reqbuf.num_sectors = 1;
+ reqbuf.is_write = 0;
+ reqbuf.is_multi_block = 0;
+ reqbuf.is_auto_cmd12 = 0;
+
+ if (!_sd_storage_execute_app_cmd(storage, R1_STATE_TRAN, 0, &cmdbuf, &reqbuf, 0))
+ return 0;
+
+ u32 tmp = 0;
+ sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
+ //Prepare buffer for unstuff_bits
+ for (int i = 0; i < 8; i+=4)
+ {
+ storage->raw_scr[i + 3] = buf[i];
+ storage->raw_scr[i + 2] = buf[i + 1];
+ storage->raw_scr[i + 1] = buf[i + 2];
+ storage->raw_scr[i] = buf[i + 3];
+ }
+ _sd_storage_parse_scr(storage);
+
+ return _sdmmc_storage_check_result(tmp);
+}
+
+int _sd_storage_switch_get(sdmmc_storage_t *storage, void *buf)
+{
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, SD_SWITCH, 0xFFFFFF, SDMMC_RSP_TYPE_1, 0);
+
+ sdmmc_req_t reqbuf;
+ reqbuf.buf = buf;
+ reqbuf.blksize = 64;
+ reqbuf.num_sectors = 1;
+ reqbuf.is_write = 0;
+ reqbuf.is_multi_block = 0;
+ reqbuf.is_auto_cmd12 = 0;
+
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
+ return 0;
+
+ u32 tmp = 0;
+ sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
+ return _sdmmc_storage_check_result(tmp);
+}
+
+int _sd_storage_switch(sdmmc_storage_t *storage, void *buf, int mode, int group, u32 arg)
+{
+ sdmmc_cmd_t cmdbuf;
+ u32 switchcmd = mode << 31 | 0x00FFFFFF;
+ switchcmd &= ~(0xF << (group * 4));
+ switchcmd |= arg << (group * 4);
+ sdmmc_init_cmd(&cmdbuf, SD_SWITCH, switchcmd, SDMMC_RSP_TYPE_1, 0);
+
+ sdmmc_req_t reqbuf;
+ reqbuf.buf = buf;
+ reqbuf.blksize = 64;
+ reqbuf.num_sectors = 1;
+ reqbuf.is_write = 0;
+ reqbuf.is_multi_block = 0;
+ reqbuf.is_auto_cmd12 = 0;
+
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
+ return 0;
+
+ u32 tmp = 0;
+ sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
+ return _sdmmc_storage_check_result(tmp);
+}
+
+void _sd_storage_set_current_limit(sdmmc_storage_t *storage, u16 current_limit, u8 *buf)
+{
+ u32 pwr = SD_SET_CURRENT_LIMIT_200;
+
+ if (current_limit & SD_MAX_CURRENT_800)
+ pwr = SD_SET_CURRENT_LIMIT_800;
+ else if (current_limit & SD_MAX_CURRENT_600)
+ pwr = SD_SET_CURRENT_LIMIT_600;
+ else if (current_limit & SD_MAX_CURRENT_400)
+ pwr = SD_SET_CURRENT_LIMIT_400;
+
+ _sd_storage_switch(storage, buf, SD_SWITCH_SET, 3, pwr);
+
+ if (((buf[15] >> 4) & 0x0F) == pwr)
+ {
+ switch (pwr)
+ {
+ case SD_SET_CURRENT_LIMIT_800:
+DPRINTF("[SD] power limit raised to 800mA\n");
+ break;
+
+ case SD_SET_CURRENT_LIMIT_600:
+DPRINTF("[SD] power limit raised to 600mA\n");
+ break;
+
+ case SD_SET_CURRENT_LIMIT_400:
+DPRINTF("[SD] power limit raised to 400mA\n");
+ break;
+
+ default:
+ case SD_SET_CURRENT_LIMIT_200:
+DPRINTF("[SD] power limit defaulted to 200mA\n");
+ break;
+ }
+ }
+}
+
+int _sd_storage_enable_highspeed(sdmmc_storage_t *storage, u32 hs_type, u8 *buf)
+{
+ if (!_sd_storage_switch(storage, buf, SD_SWITCH_CHECK, 0, hs_type))
+ return 0;
+DPRINTF("[SD] supports (U)HS mode: %d\n", buf[16] & 0xF);
+
+ u32 type_out = buf[16] & 0xF;
+ if (type_out != hs_type)
+ return 0;
+DPRINTF("[SD] supports selected (U)HS mode\n");
+
+ u16 total_pwr_consumption = ((u16)buf[0] << 8) | buf[1];
+DPRINTF("[SD] total max current: %d\n", total_pwr_consumption);
+
+ if (total_pwr_consumption <= 800)
+ {
+ if (!_sd_storage_switch(storage, buf, SD_SWITCH_SET, 0, hs_type))
+ return 0;
+
+ if (type_out != (buf[16] & 0xF))
+ return 0;
+
+ return 1;
+ }
+DPRINTF("[SD] card max current over limit\n");
+
+ return 0;
+}
+
+int _sd_storage_enable_uhs_low_volt(sdmmc_storage_t *storage, u32 type, u8 *buf)
+{
+ if (sdmmc_get_bus_width(storage->sdmmc) != SDMMC_BUS_WIDTH_4)
+ return 0;
+
+ if (!_sd_storage_switch_get(storage, buf))
+ return 0;
+ //gfx_hexdump(0, (u8 *)buf, 64);
+
+ u8 access_mode = buf[13];
+ u16 current_limit = buf[7] | buf[6] << 8;
+DPRINTF("[SD] access: %02X, current: %02X\n", access_mode, current_limit);
+
+ // Try to raise the current limit to let the card perform better.
+ _sd_storage_set_current_limit(storage, current_limit, buf);
+
+ u32 hs_type = 0;
+ switch (type)
+ {
+ case SDHCI_TIMING_UHS_SDR104:
+ case SDHCI_TIMING_UHS_SDR82:
+ // Fall through if not supported.
+ if (access_mode & SD_MODE_UHS_SDR104)
+ {
+ hs_type = UHS_SDR104_BUS_SPEED;
+DPRINTF("[SD] bus speed set to SDR104\n");
+ switch (type)
+ {
+ case SDHCI_TIMING_UHS_SDR104:
+ storage->csd.busspeed = 104;
+ break;
+ case SDHCI_TIMING_UHS_SDR82:
+ storage->csd.busspeed = 82;
+ break;
+ }
+ break;
+ }
+ case SDHCI_TIMING_UHS_SDR50:
+ if (access_mode & SD_MODE_UHS_SDR50)
+ {
+ type = SDHCI_TIMING_UHS_SDR50;
+ hs_type = UHS_SDR50_BUS_SPEED;
+DPRINTF("[SD] bus speed set to SDR50\n");
+ storage->csd.busspeed = 50;
+ break;
+ }
+ case SDHCI_TIMING_UHS_SDR25:
+ if (access_mode & SD_MODE_UHS_SDR25)
+ {
+ type = SDHCI_TIMING_UHS_SDR25;
+ hs_type = UHS_SDR25_BUS_SPEED;
+DPRINTF("[SD] bus speed set to SDR25\n");
+ storage->csd.busspeed = 25;
+ break;
+ }
+ case SDHCI_TIMING_UHS_SDR12:
+ if (!(access_mode & SD_MODE_UHS_SDR12))
+ return 0;
+ type = SDHCI_TIMING_UHS_SDR12;
+ hs_type = UHS_SDR12_BUS_SPEED;
+DPRINTF("[SD] bus speed set to SDR12\n");
+ storage->csd.busspeed = 12;
+ break;
+
+ default:
+ return 0;
+ break;
+ }
+
+ if (!_sd_storage_enable_highspeed(storage, hs_type, buf))
+ return 0;
+DPRINTF("[SD] card accepted UHS\n");
+ if (!sdmmc_setup_clock(storage->sdmmc, type))
+ return 0;
+DPRINTF("[SD] after setup clock\n");
+ if (!sdmmc_tuning_execute(storage->sdmmc, type, MMC_SEND_TUNING_BLOCK))
+ return 0;
+DPRINTF("[SD] after tuning\n");
+ return _sdmmc_storage_check_status(storage);
+}
+
+int _sd_storage_enable_hs_high_volt(sdmmc_storage_t *storage, u8 *buf)
+{
+ if (!_sd_storage_switch_get(storage, buf))
+ return 0;
+ //gfx_hexdump(0, (u8 *)buf, 64);
+
+ u8 access_mode = buf[13];
+ u16 current_limit = buf[7] | buf[6] << 8;
+
+ // Try to raise the current limit to let the card perform better.
+ _sd_storage_set_current_limit(storage, current_limit, buf);
+
+ if (!(access_mode & SD_MODE_HIGH_SPEED))
+ return 1;
+
+ if (!_sd_storage_enable_highspeed(storage, HIGH_SPEED_BUS_SPEED, buf))
+ return 0;
+
+ if (!_sdmmc_storage_check_status(storage))
+ return 0;
+
+ return sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_SD_HS25);
+}
+
+static void _sd_storage_parse_cid(sdmmc_storage_t *storage)
+{
+ u32 *raw_cid = (u32 *)&(storage->raw_cid);
+
+ storage->cid.manfid = unstuff_bits(raw_cid, 120, 8);
+ storage->cid.oemid = unstuff_bits(raw_cid, 104, 16);
+ storage->cid.prod_name[0] = unstuff_bits(raw_cid, 96, 8);
+ storage->cid.prod_name[1] = unstuff_bits(raw_cid, 88, 8);
+ storage->cid.prod_name[2] = unstuff_bits(raw_cid, 80, 8);
+ storage->cid.prod_name[3] = unstuff_bits(raw_cid, 72, 8);
+ storage->cid.prod_name[4] = unstuff_bits(raw_cid, 64, 8);
+ storage->cid.hwrev = unstuff_bits(raw_cid, 60, 4);
+ storage->cid.fwrev = unstuff_bits(raw_cid, 56, 4);
+ storage->cid.serial = unstuff_bits(raw_cid, 24, 32);
+ storage->cid.month = unstuff_bits(raw_cid, 8, 4);
+ storage->cid.year = unstuff_bits(raw_cid, 12, 8) + 2000;
+}
+
+static void _sd_storage_parse_csd(sdmmc_storage_t *storage)
+{
+ u32 *raw_csd = (u32 *)&(storage->raw_csd);
+
+ storage->csd.structure = unstuff_bits(raw_csd, 126, 2);
+ storage->csd.cmdclass = unstuff_bits(raw_csd, 84, 12);
+ storage->csd.read_blkbits = unstuff_bits(raw_csd, 80, 4);
+ storage->csd.write_protect = unstuff_bits(raw_csd, 12, 2);
+ switch(storage->csd.structure)
+ {
+ case 0:
+ storage->csd.capacity = (1 + unstuff_bits(raw_csd, 62, 12)) << (unstuff_bits(raw_csd, 47, 3) + 2);
+ break;
+
+ case 1:
+ storage->csd.c_size = (1 + unstuff_bits(raw_csd, 48, 22));
+ storage->csd.capacity = storage->csd.c_size << 10;
+ storage->csd.read_blkbits = 9;
+ break;
+ }
+}
+
+static bool _sdmmc_storage_get_low_voltage_support(u32 bus_width, u32 type)
+{
+ switch (type)
+ {
+ case SDHCI_TIMING_UHS_SDR12:
+ case SDHCI_TIMING_UHS_SDR25:
+ case SDHCI_TIMING_UHS_SDR50:
+ case SDHCI_TIMING_UHS_SDR104:
+ case SDHCI_TIMING_UHS_SDR82:
+ case SDHCI_TIMING_UHS_DDR50:
+ if (bus_width == SDMMC_BUS_WIDTH_4)
+ return true;
+ default:
+ return false;
+ }
+}
+
+int sdmmc_storage_init_sd(sdmmc_storage_t *storage, sdmmc_t *sdmmc, u32 bus_width, u32 type)
+{
+ u8 buf[512];
+ int is_version_1 = 0;
+
+ memset(buf, 0, sizeof(buf));
+ memset(storage, 0, sizeof(sdmmc_storage_t));
+ storage->sdmmc = sdmmc;
+
+ if (!sdmmc_init(sdmmc, SDMMC_1, SDMMC_POWER_3_3, SDMMC_BUS_WIDTH_1, SDHCI_TIMING_SD_ID, SDMMC_POWER_SAVE_DISABLE))
+ return 0;
+DPRINTF("[SD] after init\n");
+
+ usleep(1000 + (74000 + sdmmc->divisor - 1) / sdmmc->divisor);
+
+ if (!_sdmmc_storage_go_idle_state(storage))
+ return 0;
+DPRINTF("[SD] went to idle state\n");
+
+ is_version_1 = _sd_storage_send_if_cond(storage);
+ if (is_version_1 == 2)
+ return 0;
+DPRINTF("[SD] after send if cond\n");
+
+ bool bus_low_voltage_support = _sdmmc_storage_get_low_voltage_support(bus_width, type);
+
+ if (!_sd_storage_get_op_cond(storage, is_version_1, bus_low_voltage_support))
+ return 0;
+DPRINTF("[SD] got op cond\n");
+
+ if (!_sdmmc_storage_get_cid(storage, storage->raw_cid))
+ return 0;
+DPRINTF("[SD] got cid\n");
+ _sd_storage_parse_cid(storage);
+
+ if (!_sd_storage_get_rca(storage))
+ return 0;
+DPRINTF("[SD] got rca (= %04X)\n", storage->rca);
+
+ if (!_sdmmc_storage_get_csd(storage, storage->raw_csd))
+ return 0;
+DPRINTF("[SD] got csd\n");
+
+ //Parse CSD.
+ _sd_storage_parse_csd(storage);
+ switch (storage->csd.structure)
+ {
+ case 0:
+ storage->sec_cnt = storage->csd.capacity;
+ break;
+ case 1:
+ storage->sec_cnt = storage->csd.c_size << 10;
+ break;
+ default:
+DPRINTF("[SD] unknown CSD structure %d\n", storage->csd.structure);
+ break;
+ }
+
+ if (!storage->is_low_voltage)
+ {
+ if (!sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_SD_DS12))
+ return 0;
+DPRINTF("[SD] after setup clock\n");
+ }
+
+ if (!_sdmmc_storage_select_card(storage))
+ return 0;
+DPRINTF("[SD] card selected\n");
+
+ if (!_sdmmc_storage_set_blocklen(storage, 512))
+ return 0;
+DPRINTF("[SD] set blocklen to 512\n");
+
+ u32 tmp = 0;
+ if (!_sd_storage_execute_app_cmd_type1(storage, &tmp, SD_APP_SET_CLR_CARD_DETECT, 0, 0, R1_STATE_TRAN))
+ return 0;
+DPRINTF("[SD] cleared card detect\n");
+
+ if (!_sd_storage_get_scr(storage, buf))
+ return 0;
+
+ //gfx_hexdump(0, storage->raw_scr, 8);
+DPRINTF("[SD] got scr\n");
+
+ // Check if card supports a wider bus and if it's not SD Version 1.X
+ if (bus_width == SDMMC_BUS_WIDTH_4 && (storage->scr.bus_widths & 4) && (storage->scr.sda_vsn & 0xF))
+ {
+ if (!_sd_storage_execute_app_cmd_type1(storage, &tmp, SD_APP_SET_BUS_WIDTH, SD_BUS_WIDTH_4, 0, R1_STATE_TRAN))
+ return 0;
+
+ sdmmc_set_bus_width(storage->sdmmc, SDMMC_BUS_WIDTH_4);
+DPRINTF("[SD] switched to wide bus width\n");
+ }
+ else
+ {
+DPRINTF("[SD] SD does not support wide bus width\n");
+ }
+
+ if (storage->is_low_voltage)
+ {
+ if (!_sd_storage_enable_uhs_low_volt(storage, type, buf))
+ return 0;
+DPRINTF("[SD] enabled UHS\n");
+
+ sdmmc_card_clock_powersave(sdmmc, SDMMC_POWER_SAVE_ENABLE);
+ }
+ else if (type != SDHCI_TIMING_SD_DS12 && (storage->scr.sda_vsn & 0xF) != 0)
+ {
+ if (!_sd_storage_enable_hs_high_volt(storage, buf))
+ return 0;
+
+DPRINTF("[SD] enabled HS\n");
+ switch (bus_width)
+ {
+ case SDMMC_BUS_WIDTH_4:
+ storage->csd.busspeed = 25;
+ break;
+
+ case SDMMC_BUS_WIDTH_1:
+ storage->csd.busspeed = 6;
+ break;
+ }
+ }
+
+ storage->initialized = 1;
+
+ return 1;
+}
+
+/*
+* Gamecard specific functions.
+*/
+
+int _gc_storage_custom_cmd(sdmmc_storage_t *storage, void *buf)
+{
+ u32 resp;
+ sdmmc_cmd_t cmdbuf;
+ sdmmc_init_cmd(&cmdbuf, 60, 0, SDMMC_RSP_TYPE_1, 1);
+
+ sdmmc_req_t reqbuf;
+ reqbuf.buf = buf;
+ reqbuf.blksize = 64;
+ reqbuf.num_sectors = 1;
+ reqbuf.is_write = 1;
+ reqbuf.is_multi_block = 0;
+ reqbuf.is_auto_cmd12 = 0;
+
+ if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
+ {
+ sdmmc_stop_transmission(storage->sdmmc, &resp);
+ return 0;
+ }
+
+ if (!sdmmc_get_rsp(storage->sdmmc, &resp, 4, SDMMC_RSP_TYPE_1))
+ return 0;
+ if (!_sdmmc_storage_check_result(resp))
+ return 0;
+ return _sdmmc_storage_check_status(storage);
+}
+
+int sdmmc_storage_init_gc(sdmmc_storage_t *storage, sdmmc_t *sdmmc)
+{
+ memset(storage, 0, sizeof(sdmmc_storage_t));
+ storage->sdmmc = sdmmc;
+
+ if (!sdmmc_init(sdmmc, SDMMC_2, SDMMC_POWER_1_8, SDMMC_BUS_WIDTH_8, SDHCI_TIMING_MMC_HS102, SDMMC_POWER_SAVE_DISABLE))
+ return 0;
+DPRINTF("[gc] after init\n");
+
+ usleep(1000 + (10000 + sdmmc->divisor - 1) / sdmmc->divisor);
+
+ if (!sdmmc_tuning_execute(storage->sdmmc, SDHCI_TIMING_MMC_HS102, MMC_SEND_TUNING_BLOCK_HS200))
+ return 0;
+DPRINTF("[gc] after tuning\n");
+
+ sdmmc_card_clock_powersave(sdmmc, SDMMC_POWER_SAVE_ENABLE);
+
+ storage->initialized = 1;
+
+ return 1;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc.h
new file mode 100644
index 00000000..536bd8d2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _SDMMC_H_
+#define _SDMMC_H_
+
+#include "../utils/types.h"
+#include "../FS/FS.h"
+#include "sdmmc_driver.h"
+
+typedef struct _mmc_cid
+{
+ u32 manfid;
+ u8 prod_name[8];
+ u8 card_bga;
+ u8 prv;
+ u32 serial;
+ u16 oemid;
+ u16 year;
+ u8 hwrev;
+ u8 fwrev;
+ u8 month;
+} mmc_cid_t;
+
+typedef struct _mmc_csd
+{
+ u8 structure;
+ u8 mmca_vsn;
+ u16 cmdclass;
+ u32 c_size;
+ u32 r2w_factor;
+ u32 max_dtr;
+ u32 erase_size; /* In sectors */
+ u32 read_blkbits;
+ u32 write_blkbits;
+ u32 capacity;
+ u8 write_protect;
+ u16 busspeed;
+} mmc_csd_t;
+
+typedef struct _mmc_ext_csd
+{
+ u32 sectors;
+ int bkops; /* background support bit */
+ int bkops_en; /* manual bkops enable bit */
+ u8 rev;
+ u8 ext_struct; /* 194 */
+ u8 card_type; /* 196 */
+ u8 bkops_status; /* 246 */
+ u8 pre_eol_info;
+ u8 dev_life_est_a;
+ u8 dev_life_est_b;
+ u8 boot_mult;
+ u8 rpmb_mult;
+ u16 dev_version;
+} mmc_ext_csd_t;
+
+typedef struct _sd_scr
+{
+ u8 sda_vsn;
+ u8 sda_spec3;
+ u8 bus_widths;
+ u8 cmds;
+} sd_scr_t;
+
+typedef struct _sd_ssr
+{
+ u8 bus_width;
+ u8 speed_class;
+ u8 uhs_grade;
+ u8 video_class;
+ u8 app_class;
+ u32 protected_size;
+} sd_ssr_t;
+
+/*! SDMMC storage context. */
+typedef struct _sdmmc_storage_t
+{
+ sdmmc_t *sdmmc;
+ u32 rca;
+ int has_sector_access;
+ u32 sec_cnt;
+ int is_low_voltage;
+ u32 partition;
+ u8 raw_cid[0x10];
+ u8 raw_csd[0x10];
+ u8 raw_scr[8];
+ mmc_cid_t cid;
+ mmc_csd_t csd;
+ mmc_ext_csd_t ext_csd;
+ sd_scr_t scr;
+ int initialized;
+} sdmmc_storage_t;
+
+extern sdmmc_accessor_t *_current_accessor;
+extern bool sdmmc_memcpy_buf;
+
+int sdmmc_storage_end(sdmmc_storage_t *storage);
+int sdmmc_storage_read(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf);
+int sdmmc_storage_write(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf);
+int sdmmc_storage_init_mmc(sdmmc_storage_t *storage, sdmmc_t *sdmmc, u32 bus_width, u32 type);
+int sdmmc_storage_set_mmc_partition(sdmmc_storage_t *storage, u32 partition);
+int sdmmc_storage_init_sd(sdmmc_storage_t *storage, sdmmc_t *sdmmc, u32 bus_width, u32 type);
+int sdmmc_storage_init_gc(sdmmc_storage_t *storage, sdmmc_t *sdmmc);
+intptr_t sdmmc_calculate_dma_addr(sdmmc_accessor_t *_this, void *buf, unsigned int num_sectors);
+int sdmmc_calculate_dma_index(sdmmc_accessor_t *_this, void *buf, unsigned int num_sectors);
+int sdmmc_calculate_fitting_dma_index(sdmmc_accessor_t *_this, unsigned int num_sectors);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_driver.c b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_driver.c
new file mode 100644
index 00000000..64b2ba08
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_driver.c
@@ -0,0 +1,1429 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+#include "mmc.h"
+#include "sdmmc.h"
+#include "../nx/cache.h"
+#include "../power/max7762x.h"
+#include "../soc/clock.h"
+#include "../soc/gpio.h"
+#include "../soc/pinmux.h"
+#include "../soc/pmc.h"
+#include "../soc/t210.h"
+#include "../utils/fatal.h"
+#include "../utils/types.h"
+#include "../utils/util.h"
+
+#define DPRINTF(...)
+
+/*! SCMMC controller base addresses. */
+static const u64 _sdmmc_bases[4] = {
+ 0x700B0000,
+ 0x700B0200,
+ 0x700B0400,
+ 0x700B0600,
+};
+
+int sdmmc_get_io_power(sdmmc_t *sdmmc)
+{
+ u32 p = sdmmc->regs->pwrcon;
+ if (!(p & SDHCI_POWER_ON))
+ return SDMMC_POWER_OFF;
+ if (p & SDHCI_POWER_180)
+ return SDMMC_POWER_1_8;
+ if (p & SDHCI_POWER_330)
+ return SDMMC_POWER_3_3;
+ return -1;
+}
+
+static int _sdmmc_set_io_power(sdmmc_t *sdmmc, u32 power)
+{
+ switch (power)
+ {
+ case SDMMC_POWER_OFF:
+ sdmmc->regs->pwrcon &= ~SDHCI_POWER_ON;
+ break;
+
+ case SDMMC_POWER_1_8:
+ sdmmc->regs->pwrcon = SDHCI_POWER_180;
+ break;
+
+ case SDMMC_POWER_3_3:
+ sdmmc->regs->pwrcon = SDHCI_POWER_330;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (power != SDMMC_POWER_OFF)
+ sdmmc->regs->pwrcon |= SDHCI_POWER_ON;
+
+ return 1;
+}
+
+u32 sdmmc_get_bus_width(sdmmc_t *sdmmc)
+{
+ u32 h = sdmmc->regs->hostctl;
+ if (h & SDHCI_CTRL_8BITBUS)
+ return SDMMC_BUS_WIDTH_8;
+ if (h & SDHCI_CTRL_4BITBUS)
+ return SDMMC_BUS_WIDTH_4;
+ return SDMMC_BUS_WIDTH_1;
+}
+
+void sdmmc_set_bus_width(sdmmc_t *sdmmc, u32 bus_width)
+{
+ u32 host_control = sdmmc->regs->hostctl & ~(SDHCI_CTRL_4BITBUS | SDHCI_CTRL_8BITBUS);
+
+ if (bus_width == SDMMC_BUS_WIDTH_1)
+ sdmmc->regs->hostctl = host_control;
+ else if (bus_width == SDMMC_BUS_WIDTH_4)
+ sdmmc->regs->hostctl = host_control | SDHCI_CTRL_4BITBUS;
+ else if (bus_width == SDMMC_BUS_WIDTH_8)
+ sdmmc->regs->hostctl = host_control | SDHCI_CTRL_8BITBUS;
+}
+
+void sdmmc_save_tap_value(sdmmc_t *sdmmc)
+{
+ sdmmc->venclkctl_tap = sdmmc->regs->venclkctl >> 16;
+ sdmmc->venclkctl_set = 1;
+}
+
+static int _sdmmc_config_tap_val(sdmmc_t *sdmmc, u32 type)
+{
+ const u32 dqs_trim_val = 0x28;
+ const u32 tap_values_t210[] = { 4, 0, 3, 0 };
+
+ u32 tap_val = 0;
+
+ if (type == SDHCI_TIMING_MMC_HS400)
+ sdmmc->regs->vencapover = (sdmmc->regs->vencapover & 0xFFFFC0FF) | (dqs_trim_val << 8);
+
+ sdmmc->regs->ventunctl0 &= ~TEGRA_MMC_VNDR_TUN_CTRL0_TAP_VAL_UPDATED_BY_HW;
+
+ if (type == SDHCI_TIMING_MMC_HS400)
+ {
+ if (!sdmmc->venclkctl_set)
+ return 0;
+
+ tap_val = sdmmc->venclkctl_tap;
+ }
+ else
+ tap_val = sdmmc->t210b01 ? 11 : tap_values_t210[sdmmc->id];
+
+ sdmmc->regs->venclkctl = (sdmmc->regs->venclkctl & 0xFF00FFFF) | (tap_val << 16);
+
+ return 1;
+}
+
+static int _sdmmc_commit_changes(sdmmc_t *sdmmc)
+{
+ return sdmmc->regs->clkcon;
+}
+
+static void _sdmmc_pad_config_fallback(sdmmc_t *sdmmc, u32 power)
+{
+ _sdmmc_commit_changes(sdmmc);
+ switch (sdmmc->id)
+ {
+ case SDMMC_1: // 33 Ohm 2X Driver.
+ if (power == SDMMC_POWER_OFF)
+ break;
+ u32 sdmmc1_pad_cfg = APB_MISC(APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL) & 0xF8080FFF;
+ if (sdmmc->t210b01)
+ sdmmc1_pad_cfg |= (0x808 << 12); // Up: 8, Dn: 8. For 33 ohm.
+ else if (power == SDMMC_POWER_1_8)
+ sdmmc1_pad_cfg |= (0xB0F << 12); // Up: 11, Dn: 15. For 33 ohm.
+ else if (power == SDMMC_POWER_3_3)
+ sdmmc1_pad_cfg |= (0xC0C << 12); // Up: 12, Dn: 12. For 33 ohm.
+ APB_MISC(APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL) = sdmmc1_pad_cfg;
+ (void)APB_MISC(APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); // Commit write.
+ break;
+
+ case SDMMC_2:
+ if (sdmmc->t210b01)
+ APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL) = (APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL) & 0xF8080FFF) | 0xA0A000;
+ else
+ APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL) = (APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL) & 0xFFFFC003) | 0x1040; // PU:16, PD:16.
+ (void)APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL);
+ break;
+
+ case SDMMC_4: // 50 Ohm 2X Driver. PU:16, PD:16, B01: PU:10, PD:10.
+ APB_MISC(APB_MISC_GP_EMMC4_PAD_CFGPADCTRL) =
+ (APB_MISC(APB_MISC_GP_EMMC4_PAD_CFGPADCTRL) & 0xFFFFC003) | (sdmmc->t210b01 ? 0xA28 : 0x1040);
+ (void)APB_MISC(APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); // Commit write.
+ break;
+ }
+}
+
+static void _sdmmc_autocal_execute(sdmmc_t *sdmmc, u32 power)
+{
+ bool should_enable_sd_clock = false;
+ if (sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN)
+ {
+ should_enable_sd_clock = true;
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+ }
+
+ // Enable E_INPUT power.
+ if (!(sdmmc->regs->sdmemcmppadctl & TEGRA_MMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_PWRD))
+ {
+ sdmmc->regs->sdmemcmppadctl |= TEGRA_MMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_PWRD;
+ _sdmmc_commit_changes(sdmmc);
+ usleep(1);
+ }
+
+ // Enable auto calibration and start auto configuration.
+ sdmmc->regs->autocalcfg |= TEGRA_MMC_AUTOCALCFG_AUTO_CAL_ENABLE | TEGRA_MMC_AUTOCALCFG_AUTO_CAL_START;
+ _sdmmc_commit_changes(sdmmc);
+ usleep(2);
+
+ u64 timeout = get_tmr_ms() + 10;
+ while (sdmmc->regs->autocalsts & TEGRA_MMC_AUTOCALSTS_AUTO_CAL_ACTIVE)
+ {
+ if (get_tmr_ms() > timeout)
+ {
+ timeout = 0; // Set timeout to 0 if we timed out.
+ break;
+ }
+ }
+
+#if 0
+ // Check if Comp pad is open or short to ground.
+ // SDMMC1: CZ pads - T210/T210B01: 7-bit/5-bit. SDMMC2/4: LV_CZ pads - 5-bit.
+ u8 code_mask = (sdmmc->t210b01 || sdmmc->id != SDMMC_1) ? 0x1F : 0x7F;
+ u8 autocal_pu_status = sdmmc->regs->autocalsts & code_mask;
+ if (!autocal_pu_status)
+ EPRINTF("SDMMC: Comp Pad short to gnd!");
+ else if (autocal_pu_status == code_mask)
+ EPRINTF("SDMMC: Comp Pad open!");
+#endif
+
+ // In case auto calibration fails, we load suggested standard values.
+ if (!timeout)
+ {
+ _sdmmc_pad_config_fallback(sdmmc, power);
+ sdmmc->regs->autocalcfg &= ~TEGRA_MMC_AUTOCALCFG_AUTO_CAL_ENABLE;
+ }
+
+ // Disable E_INPUT to conserve power.
+ sdmmc->regs->sdmemcmppadctl &= ~TEGRA_MMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_PWRD;
+
+ if(should_enable_sd_clock)
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+}
+
+static int _sdmmc_dll_cal_execute(sdmmc_t *sdmmc)
+{
+ int result = 1, should_disable_sd_clock = 0;
+
+ if (!(sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN))
+ {
+ should_disable_sd_clock = 1;
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+ }
+
+ sdmmc->regs->vendllcalcfg |= TEGRA_MMC_DLLCAL_CFG_EN_CALIBRATE;
+ _sdmmc_commit_changes(sdmmc);
+
+ u64 timeout = get_tmr_ms() + 5;
+ while (sdmmc->regs->vendllcalcfg & TEGRA_MMC_DLLCAL_CFG_EN_CALIBRATE)
+ {
+ if (get_tmr_ms() > timeout)
+ {
+ result = 0;
+ goto out;
+ }
+ }
+
+ timeout = get_tmr_ms() + 10;
+ while (sdmmc->regs->vendllcalcfgsts & TEGRA_MMC_DLLCAL_CFG_STATUS_DLL_ACTIVE)
+ {
+ if (get_tmr_ms() > timeout)
+ {
+ result = 0;
+ goto out;
+ }
+ }
+
+out:;
+ if (should_disable_sd_clock)
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+ return result;
+}
+
+static void _sdmmc_reset(sdmmc_t *sdmmc)
+{
+ sdmmc->regs->swrst |= SDHCI_RESET_CMD | SDHCI_RESET_DATA;
+ _sdmmc_commit_changes(sdmmc);
+ u64 timeout = get_tmr_ms() + 2000;
+ while ((sdmmc->regs->swrst & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)) && get_tmr_ms() < timeout)
+ ;
+}
+
+static void _sdmmc_reset_all(sdmmc_t *sdmmc)
+{
+ sdmmc->regs->swrst |= SDHCI_RESET_ALL;
+ _sdmmc_commit_changes(sdmmc);
+ u32 timeout = get_tmr_ms() + 2000;//100ms
+ while ((sdmmc->regs->swrst & SDHCI_RESET_ALL) && get_tmr_ms() < timeout)
+ ;
+}
+
+int sdmmc_setup_clock(sdmmc_t *sdmmc, u32 type)
+{
+ // Disable the SD clock if it was enabled, and reenable it later.
+ bool should_enable_sd_clock = false;
+ if (sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN)
+ {
+ should_enable_sd_clock = true;
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+ }
+
+ _sdmmc_config_tap_val(sdmmc, type);
+
+ _sdmmc_reset(sdmmc);
+
+ switch (type)
+ {
+ case SDHCI_TIMING_MMC_ID:
+ case SDHCI_TIMING_MMC_LS26:
+ case SDHCI_TIMING_SD_ID:
+ case SDHCI_TIMING_SD_DS12:
+ sdmmc->regs->hostctl &= ~SDHCI_CTRL_HISPD;
+ sdmmc->regs->hostctl2 &= ~SDHCI_CTRL_VDD_180;
+ break;
+
+ case SDHCI_TIMING_MMC_HS52:
+ case SDHCI_TIMING_SD_HS25:
+ sdmmc->regs->hostctl |= SDHCI_CTRL_HISPD;
+ sdmmc->regs->hostctl2 &= ~SDHCI_CTRL_VDD_180;
+ break;
+
+ case SDHCI_TIMING_MMC_HS200:
+ case SDHCI_TIMING_UHS_SDR50: // T210 Errata for SDR50, the host must be set to SDR104.
+ case SDHCI_TIMING_UHS_SDR104:
+ case SDHCI_TIMING_UHS_SDR82:
+ case SDHCI_TIMING_UHS_DDR50:
+ case SDHCI_TIMING_MMC_HS102:
+ sdmmc->regs->hostctl2 = (sdmmc->regs->hostctl2 & SDHCI_CTRL_UHS_MASK) | UHS_SDR104_BUS_SPEED;
+ sdmmc->regs->hostctl2 |= SDHCI_CTRL_VDD_180;
+ break;
+
+ case SDHCI_TIMING_MMC_HS400:
+ // Non standard.
+ sdmmc->regs->hostctl2 = (sdmmc->regs->hostctl2 & SDHCI_CTRL_UHS_MASK) | HS400_BUS_SPEED;
+ sdmmc->regs->hostctl2 |= SDHCI_CTRL_VDD_180;
+ break;
+
+ case SDHCI_TIMING_UHS_SDR25:
+ sdmmc->regs->hostctl2 = (sdmmc->regs->hostctl2 & SDHCI_CTRL_UHS_MASK) | UHS_SDR25_BUS_SPEED;
+ sdmmc->regs->hostctl2 |= SDHCI_CTRL_VDD_180;
+ break;
+
+ case SDHCI_TIMING_UHS_SDR12:
+ sdmmc->regs->hostctl2 = (sdmmc->regs->hostctl2 & SDHCI_CTRL_UHS_MASK) | UHS_SDR12_BUS_SPEED;
+ sdmmc->regs->hostctl2 |= SDHCI_CTRL_VDD_180;
+ break;
+ }
+
+ _sdmmc_commit_changes(sdmmc);
+
+ u32 clock;
+ u16 divisor;
+ clock_sdmmc_get_card_clock_div(&clock, &divisor, type);
+ clock_sdmmc_config_clock_source(&clock, sdmmc->id, clock);
+ sdmmc->divisor = (clock + divisor - 1) / divisor;
+
+ //if divisor != 1 && divisor << 31 -> error
+
+ u16 div = divisor >> 1;
+ divisor = 0;
+ if (div > 0xFF)
+ divisor = div >> SDHCI_DIVIDER_SHIFT;
+
+ sdmmc->regs->clkcon = (sdmmc->regs->clkcon & ~(SDHCI_DIV_MASK | SDHCI_DIV_HI_MASK))
+ | (div << SDHCI_DIVIDER_SHIFT) | (divisor << SDHCI_DIVIDER_HI_SHIFT);
+
+ // Enable the SD clock again.
+ if (should_enable_sd_clock)
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+
+ if (type == SDHCI_TIMING_MMC_HS400)
+ return _sdmmc_dll_cal_execute(sdmmc);
+ return 1;
+}
+
+static void _sdmmc_card_clock_enable(sdmmc_t *sdmmc)
+{
+ // Recalibrate conditionally.
+ if (sdmmc->manual_cal && !sdmmc->powersave_enabled)
+ _sdmmc_autocal_execute(sdmmc, sdmmc_get_io_power(sdmmc));
+
+ if (!sdmmc->powersave_enabled)
+ {
+ if (!(sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN))
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+ }
+ sdmmc->card_clock_enabled = 1;
+}
+
+static void _sdmmc_sd_clock_disable(sdmmc_t *sdmmc)
+{
+ sdmmc->card_clock_enabled = 0;
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+}
+
+void sdmmc_card_clock_powersave(sdmmc_t *sdmmc, int powersave_enable)
+{
+ // Recalibrate periodically for SDMMC1.
+ if (sdmmc->manual_cal && !powersave_enable && sdmmc->card_clock_enabled)
+ _sdmmc_autocal_execute(sdmmc, sdmmc_get_io_power(sdmmc));
+
+ sdmmc->powersave_enabled = powersave_enable;
+ if (powersave_enable)
+ {
+ if (sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN)
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+ return;
+ }
+
+ if (sdmmc->card_clock_enabled)
+ if (!(sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN))
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+}
+
+static int _sdmmc_cache_rsp(sdmmc_t *sdmmc, u32 *rsp, u32 size, u32 type)
+{
+ switch (type)
+ {
+ case SDMMC_RSP_TYPE_1:
+ case SDMMC_RSP_TYPE_3:
+ case SDMMC_RSP_TYPE_4:
+ case SDMMC_RSP_TYPE_5:
+ if (size < 4)
+ return 0;
+ rsp[0] = sdmmc->regs->rspreg0;
+ break;
+
+ case SDMMC_RSP_TYPE_2:
+ if (size < 0x10)
+ return 0;
+ // CRC is stripped, so shifting is needed.
+ u32 tempreg;
+ for (int i = 0; i < 4; i++)
+ {
+ switch(i)
+ {
+ case 0:
+ tempreg = sdmmc->regs->rspreg3;
+ break;
+ case 1:
+ tempreg = sdmmc->regs->rspreg2;
+ break;
+ case 2:
+ tempreg = sdmmc->regs->rspreg1;
+ break;
+ case 3:
+ tempreg = sdmmc->regs->rspreg0;
+ break;
+ }
+ rsp[i] = tempreg << 8;
+
+ if (i != 0)
+ rsp[i - 1] |= (tempreg >> 24) & 0xFF;
+ }
+ break;
+
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+int sdmmc_get_rsp(sdmmc_t *sdmmc, u32 *rsp, u32 size, u32 type)
+{
+ if (!rsp || sdmmc->expected_rsp_type != type)
+ return 0;
+
+ switch (type)
+ {
+ case SDMMC_RSP_TYPE_1:
+ case SDMMC_RSP_TYPE_3:
+ case SDMMC_RSP_TYPE_4:
+ case SDMMC_RSP_TYPE_5:
+ if (size < 4)
+ return 0;
+ rsp[0] = sdmmc->rsp[0];
+ break;
+
+ case SDMMC_RSP_TYPE_2:
+ if (size < 0x10)
+ return 0;
+ rsp[0] = sdmmc->rsp[0];
+ rsp[1] = sdmmc->rsp[1];
+ rsp[2] = sdmmc->rsp[2];
+ rsp[3] = sdmmc->rsp[3];
+ break;
+
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+static int _sdmmc_wait_cmd_data_inhibit(sdmmc_t *sdmmc, bool wait_dat)
+{
+ _sdmmc_commit_changes(sdmmc);
+
+ u64 timeout = get_tmr_ms() + 2000;
+ while(sdmmc->regs->prnsts & SDHCI_CMD_INHIBIT)
+ if (get_tmr_ms() > timeout)
+ {
+ _sdmmc_reset(sdmmc);
+ return 0;
+ }
+
+ if (wait_dat)
+ {
+ timeout = get_tmr_ms() + 2000;
+ while (sdmmc->regs->prnsts & SDHCI_DATA_INHIBIT)
+ if (get_tmr_ms() > timeout)
+ {
+ _sdmmc_reset(sdmmc);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int _sdmmc_wait_card_busy(sdmmc_t *sdmmc)
+{
+ _sdmmc_commit_changes(sdmmc);
+
+ u64 timeout = get_tmr_ms() + 2000;
+ while (!(sdmmc->regs->prnsts & SDHCI_DATA_0_LVL_MASK))
+ if (get_tmr_ms() > timeout)
+ {
+ _sdmmc_reset(sdmmc);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int _sdmmc_setup_read_small_block(sdmmc_t *sdmmc)
+{
+ switch (sdmmc_get_bus_width(sdmmc))
+ {
+ case SDMMC_BUS_WIDTH_1:
+ return 0;
+
+ case SDMMC_BUS_WIDTH_4:
+ sdmmc->regs->blksize = 64;
+ break;
+
+ case SDMMC_BUS_WIDTH_8:
+ sdmmc->regs->blksize = 128;
+ break;
+ }
+
+ sdmmc->regs->blkcnt = 1;
+ sdmmc->regs->trnmod = SDHCI_TRNS_READ;
+
+ return 1;
+}
+
+static int _sdmmc_send_cmd(sdmmc_t *sdmmc, sdmmc_cmd_t *cmd, bool is_data_present)
+{
+ u16 cmdflags = 0;
+
+ switch (cmd->rsp_type)
+ {
+ case SDMMC_RSP_TYPE_0:
+ break;
+
+ case SDMMC_RSP_TYPE_1:
+ case SDMMC_RSP_TYPE_4:
+ case SDMMC_RSP_TYPE_5:
+ if (cmd->check_busy)
+ cmdflags = SDHCI_CMD_RESP_LEN48_BUSY | SDHCI_CMD_INDEX | SDHCI_CMD_CRC;
+ else
+ cmdflags = SDHCI_CMD_RESP_LEN48 | SDHCI_CMD_INDEX | SDHCI_CMD_CRC;
+ break;
+
+ case SDMMC_RSP_TYPE_2:
+ cmdflags = SDHCI_CMD_RESP_LEN136 | SDHCI_CMD_CRC;
+ break;
+
+ case SDMMC_RSP_TYPE_3:
+ cmdflags = SDHCI_CMD_RESP_LEN48;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (is_data_present)
+ cmdflags |= SDHCI_CMD_DATA;
+ sdmmc->regs->argument = cmd->arg;
+ sdmmc->regs->cmdreg = (cmd->cmd << 8) | cmdflags;
+
+ return 1;
+}
+
+static void _sdmmc_send_tuning_cmd(sdmmc_t *sdmmc, u32 cmd)
+{
+ sdmmc_cmd_t cmdbuf;
+ cmdbuf.cmd = cmd;
+ cmdbuf.arg = 0;
+ cmdbuf.rsp_type = SDMMC_RSP_TYPE_1;
+ cmdbuf.check_busy = 0;
+ _sdmmc_send_cmd(sdmmc, &cmdbuf, true);
+}
+
+static int _sdmmc_tuning_execute_once(sdmmc_t *sdmmc, u32 cmd)
+{
+ if (sdmmc->powersave_enabled)
+ return 0;
+ if (!_sdmmc_wait_cmd_data_inhibit(sdmmc, true))
+ return 0;
+
+ _sdmmc_setup_read_small_block(sdmmc);
+
+ sdmmc->regs->norintstsen |= SDHCI_INT_DATA_AVAIL;
+ sdmmc->regs->norintsts = sdmmc->regs->norintsts;
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+
+ _sdmmc_send_tuning_cmd(sdmmc, cmd);
+ _sdmmc_commit_changes(sdmmc);
+ usleep(1);
+
+ _sdmmc_reset(sdmmc);
+
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+ _sdmmc_commit_changes(sdmmc);
+
+ u64 timeout = get_tmr_us() + 5000;
+ while (get_tmr_us() < timeout)
+ {
+ if (sdmmc->regs->norintsts & SDHCI_INT_DATA_AVAIL)
+ {
+ sdmmc->regs->norintsts = SDHCI_INT_DATA_AVAIL;
+ sdmmc->regs->norintstsen &= ~SDHCI_INT_DATA_AVAIL;
+ _sdmmc_commit_changes(sdmmc);
+ usleep((1000 * 8 + sdmmc->divisor - 1) / sdmmc->divisor);
+ return 1;
+ }
+ }
+
+ _sdmmc_reset(sdmmc);
+
+ sdmmc->regs->norintstsen &= ~SDHCI_INT_DATA_AVAIL;
+ _sdmmc_commit_changes(sdmmc);
+ usleep((1000 * 8 + sdmmc->divisor - 1) / sdmmc->divisor);
+
+ return 0;
+}
+
+int sdmmc_tuning_execute(sdmmc_t *sdmmc, u32 type, u32 cmd)
+{
+ u32 max = 0, flag = 0;
+
+ switch (type)
+ {
+ case SDHCI_TIMING_MMC_HS200:
+ case SDHCI_TIMING_MMC_HS400:
+ case SDHCI_TIMING_UHS_SDR104:
+ case SDHCI_TIMING_UHS_SDR82:
+ max = 128;
+ flag = (2 << 13); // 128 iterations.
+ break;
+
+ case SDHCI_TIMING_UHS_SDR50:
+ case SDHCI_TIMING_UHS_DDR50:
+ case SDHCI_TIMING_MMC_HS102:
+ max = 256;
+ flag = (4 << 13); // 256 iterations.
+ break;
+
+ case SDHCI_TIMING_UHS_SDR12:
+ case SDHCI_TIMING_UHS_SDR25:
+ return 1;
+
+ default:
+ return 0;
+ }
+
+ sdmmc->regs->ventunctl1 = 0; // step_size 1.
+
+ sdmmc->regs->ventunctl0 = (sdmmc->regs->ventunctl0 & 0xFFFF1FFF) | flag; // Tries.
+ sdmmc->regs->ventunctl0 = (sdmmc->regs->ventunctl0 & 0xFFFFE03F) | (1 << 6); // 1x Multiplier.
+ sdmmc->regs->ventunctl0 |= TEGRA_MMC_VNDR_TUN_CTRL0_TAP_VAL_UPDATED_BY_HW;
+ sdmmc->regs->hostctl2 |= SDHCI_CTRL_EXEC_TUNING;
+
+ for (u32 i = 0; i < max; i++)
+ {
+ _sdmmc_tuning_execute_once(sdmmc, cmd);
+ if (!(sdmmc->regs->hostctl2 & SDHCI_CTRL_EXEC_TUNING))
+ break;
+ }
+
+ if (sdmmc->regs->hostctl2 & SDHCI_CTRL_TUNED_CLK)
+ return 1;
+
+ return 0;
+}
+
+static int _sdmmc_enable_internal_clock(sdmmc_t *sdmmc)
+{
+ //Enable internal clock and wait till it is stable.
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_INT_EN;
+ _sdmmc_commit_changes(sdmmc);
+ u64 timeout = get_tmr_ms() + 2000;
+ while (!(sdmmc->regs->clkcon & SDHCI_CLOCK_INT_STABLE))
+ {
+ if (get_tmr_ms() > timeout)
+ return 0;
+ }
+
+ sdmmc->regs->hostctl2 &= ~SDHCI_CTRL_PRESET_VAL_EN;
+ sdmmc->regs->clkcon &= ~SDHCI_PROG_CLOCK_MODE;
+ sdmmc->regs->hostctl2 |= SDHCI_HOST_VERSION_4_EN;
+
+ if (!(sdmmc->regs->capareg & SDHCI_CAN_64BIT))
+ return 0;
+
+ sdmmc->regs->hostctl2 |= SDHCI_ADDRESSING_64BIT_EN;
+ sdmmc->regs->hostctl &= ~SDHCI_CTRL_DMA_MASK;
+ sdmmc->regs->timeoutcon = (sdmmc->regs->timeoutcon & 0xF0) | 0xE;
+
+ return 1;
+}
+
+static int _sdmmc_autocal_config_offset(sdmmc_t *sdmmc, u32 power)
+{
+ u32 off_pd = 0;
+ u32 off_pu = 0;
+
+ switch (sdmmc->id)
+ {
+ case SDMMC_2:
+ case SDMMC_4:
+ if (power != SDMMC_POWER_1_8)
+ return 0;
+ off_pd = 5;
+ off_pu = 5;
+ break;
+
+ case SDMMC_1:
+ if (power == SDMMC_POWER_1_8)
+ {
+ if (!sdmmc->t210b01)
+ {
+ off_pd = 123;
+ off_pu = 123;
+ }
+ else
+ {
+ off_pd = 6;
+ off_pu = 6;
+ }
+ }
+ else if (power == SDMMC_POWER_3_3)
+ {
+ if (!sdmmc->t210b01)
+ {
+ off_pd = 125;
+ off_pu = 0;
+ }
+ }
+ else
+ return 0;
+ break;
+ }
+
+ sdmmc->regs->autocalcfg = (sdmmc->regs->autocalcfg & 0xFFFF8080) | (off_pd << 8) | off_pu;
+ return 1;
+}
+
+static void _sdmmc_enable_interrupts(sdmmc_t *sdmmc)
+{
+ sdmmc->regs->norintstsen |= SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE;
+ sdmmc->regs->errintstsen |= SDHCI_ERR_INT_ALL_EXCEPT_ADMA_BUSPWR;
+ sdmmc->regs->norintsts = sdmmc->regs->norintsts;
+ sdmmc->regs->errintsts = sdmmc->regs->errintsts;
+}
+
+static void _sdmmc_mask_interrupts(sdmmc_t *sdmmc)
+{
+ sdmmc->regs->errintstsen &= ~SDHCI_ERR_INT_ALL_EXCEPT_ADMA_BUSPWR;
+ sdmmc->regs->norintstsen &= ~(SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE);
+}
+
+static int _sdmmc_check_mask_interrupt(sdmmc_t *sdmmc, u16 *pout, u16 mask)
+{
+ u16 norintsts = sdmmc->regs->norintsts;
+ u16 errintsts = sdmmc->regs->errintsts;
+
+DPRINTF("norintsts %08X, errintsts %08X\n", norintsts, errintsts);
+
+ if (pout)
+ *pout = norintsts;
+
+ // Check for error interrupt.
+ if (norintsts & SDHCI_INT_ERROR)
+ {
+ sdmmc->regs->errintsts = errintsts;
+ return SDMMC_MASKINT_ERROR;
+ }
+ else if (norintsts & mask)
+ {
+ sdmmc->regs->norintsts = norintsts & mask;
+ return SDMMC_MASKINT_MASKED;
+ }
+
+ return SDMMC_MASKINT_NOERROR;
+}
+
+static int _sdmmc_wait_response(sdmmc_t *sdmmc)
+{
+ _sdmmc_commit_changes(sdmmc);
+
+ u64 timeout = get_tmr_ms() + 2000;
+ while (true)
+ {
+ int result = _sdmmc_check_mask_interrupt(sdmmc, NULL, SDHCI_INT_RESPONSE);
+ if (result == SDMMC_MASKINT_MASKED)
+ break;
+ if (result != SDMMC_MASKINT_NOERROR || get_tmr_ms() > timeout)
+ {
+ _sdmmc_reset(sdmmc);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int _sdmmc_stop_transmission_inner(sdmmc_t *sdmmc, u32 *rsp)
+{
+ sdmmc_cmd_t cmd;
+
+ if (!_sdmmc_wait_cmd_data_inhibit(sdmmc, false))
+ return 0;
+
+ _sdmmc_enable_interrupts(sdmmc);
+
+ cmd.cmd = MMC_STOP_TRANSMISSION;
+ cmd.arg = 0;
+ cmd.rsp_type = SDMMC_RSP_TYPE_1;
+ cmd.check_busy = 1;
+
+ _sdmmc_send_cmd(sdmmc, &cmd, false);
+
+ int result = _sdmmc_wait_response(sdmmc);
+ _sdmmc_mask_interrupts(sdmmc);
+
+ if (!result)
+ return 0;
+
+ _sdmmc_cache_rsp(sdmmc, rsp, 4, SDMMC_RSP_TYPE_1);
+
+ return _sdmmc_wait_card_busy(sdmmc);
+}
+
+int sdmmc_stop_transmission(sdmmc_t *sdmmc, u32 *rsp)
+{
+ if (!sdmmc->card_clock_enabled)
+ return 0;
+
+ // Recalibrate periodically for SDMMC1.
+ if (sdmmc->manual_cal && sdmmc->powersave_enabled)
+ _sdmmc_autocal_execute(sdmmc, sdmmc_get_io_power(sdmmc));
+
+ bool should_disable_sd_clock = false;
+ if (!(sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN))
+ {
+ should_disable_sd_clock = true;
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+ _sdmmc_commit_changes(sdmmc);
+ usleep((8000 + sdmmc->divisor - 1) / sdmmc->divisor);
+ }
+
+ int result = _sdmmc_stop_transmission_inner(sdmmc, rsp);
+ usleep((8000 + sdmmc->divisor - 1) / sdmmc->divisor);
+
+ if (should_disable_sd_clock)
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+
+ return result;
+}
+
+static int _sdmmc_config_dma(sdmmc_t *sdmmc, u32 *blkcnt_out, sdmmc_req_t *req)
+{
+ if (!req->blksize || !req->num_sectors)
+ return 0;
+
+ u32 blkcnt = req->num_sectors;
+ if (blkcnt >= 0xFFFF)
+ blkcnt = 0xFFFF;
+
+ u64 admaaddr = (u64)sdmmc_calculate_dma_addr(_current_accessor, req->buf, blkcnt);
+ if (!admaaddr)
+ {
+ // buf is on a heap
+ int dma_idx = sdmmc_calculate_fitting_dma_index(_current_accessor, blkcnt);
+ admaaddr = (u64)&_current_accessor->parent->dmaBuffers[dma_idx].device_addr_buffer_masked[0];
+ sdmmc->last_dma_idx = dma_idx;
+ }
+
+ // Check alignment.
+ if (admaaddr & 7)
+ return 0;
+
+ sdmmc->regs->admaaddr = admaaddr & 0xFFFFFFFFF;
+ sdmmc->regs->admaaddr_hi = (admaaddr >> 32) & 0xFFFFFFFFF;
+
+ sdmmc->dma_addr_next = (admaaddr + 0x80000) & 0xFFFFFFFFFFF80000;
+
+ sdmmc->regs->blksize = req->blksize | 0x7000; // DMA 512KB (Detects A18 carry out).
+ sdmmc->regs->blkcnt = blkcnt;
+
+ if (blkcnt_out)
+ *blkcnt_out = blkcnt;
+
+ u32 trnmode = SDHCI_TRNS_DMA;
+ if (req->is_multi_block)
+ trnmode = SDHCI_TRNS_MULTI | SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_DMA;
+ if (!req->is_write)
+ trnmode |= SDHCI_TRNS_READ;
+ if (req->is_auto_cmd12)
+ trnmode = (trnmode & ~(SDHCI_TRNS_AUTO_CMD12 | SDHCI_TRNS_AUTO_CMD23)) | SDHCI_TRNS_AUTO_CMD12;
+
+ sdmmc->regs->trnmod = trnmode;
+
+ return 1;
+}
+
+static int _sdmmc_update_dma(sdmmc_t *sdmmc)
+{
+ u16 blkcnt = 0;
+ do
+ {
+ blkcnt = sdmmc->regs->blkcnt;
+ u64 timeout = get_tmr_ms() + 1500;
+ do
+ {
+ int result = 0;
+ while (true)
+ {
+ u16 intr = 0;
+ result = _sdmmc_check_mask_interrupt(sdmmc, &intr,
+ SDHCI_INT_DATA_END | SDHCI_INT_DMA_END);
+ if (result < 0)
+ break;
+
+ if (intr & SDHCI_INT_DATA_END)
+ return 1; // Transfer complete.
+
+ if (intr & SDHCI_INT_DMA_END)
+ {
+ // Update DMA.
+ sdmmc->regs->admaaddr = sdmmc->dma_addr_next & 0xFFFFFFFFF;
+ sdmmc->regs->admaaddr_hi = (sdmmc->dma_addr_next >> 32) & 0xFFFFFFFFF;
+ sdmmc->dma_addr_next += 0x80000;
+ }
+ }
+ if (result != SDMMC_MASKINT_NOERROR)
+ {
+ _sdmmc_reset(sdmmc);
+ return 0;
+ }
+ } while (get_tmr_ms() < timeout);
+ } while (sdmmc->regs->blkcnt != blkcnt);
+
+ _sdmmc_reset(sdmmc);
+ return 0;
+}
+
+static int _sdmmc_execute_cmd_inner(sdmmc_t *sdmmc, sdmmc_cmd_t *cmd, sdmmc_req_t *req, u32 *blkcnt_out)
+{
+ int has_req_or_check_busy = req || cmd->check_busy;
+ if (!_sdmmc_wait_cmd_data_inhibit(sdmmc, has_req_or_check_busy))
+ return 0;
+
+ u32 blkcnt = 0;
+ bool is_data_present = false;
+ if (req)
+ {
+ if (!_sdmmc_config_dma(sdmmc, &blkcnt, req))
+ return 0;
+
+ if(!sdmmc_memcpy_buf)
+ {
+ // Flush from/to phys
+ armDCacheFlush(req->buf, req->blksize * blkcnt);
+ }
+ else
+ {
+ if(req->is_write)
+ {
+ void* dma_addr = &_current_accessor->parent->dmaBuffers[sdmmc->last_dma_idx].device_addr_buffer[0];
+ memcpy(dma_addr, req->buf, req->blksize * blkcnt);
+
+ // Flush to phys
+ armDCacheFlush(dma_addr, req->blksize * blkcnt);
+ }
+ }
+
+ is_data_present = true;
+ }
+
+ _sdmmc_enable_interrupts(sdmmc);
+
+ if (!_sdmmc_send_cmd(sdmmc, cmd, is_data_present))
+ return 0;
+
+ int result = _sdmmc_wait_response(sdmmc);
+DPRINTF("rsp(%d): %08X, %08X, %08X, %08X\n", result,
+ sdmmc->regs->rspreg0, sdmmc->regs->rspreg1, sdmmc->regs->rspreg2, sdmmc->regs->rspreg3);
+ if (result)
+ {
+ if (cmd->rsp_type)
+ {
+ sdmmc->expected_rsp_type = cmd->rsp_type;
+ result = _sdmmc_cache_rsp(sdmmc, sdmmc->rsp, 0x10, cmd->rsp_type);
+ }
+
+ if (req && result)
+ result = _sdmmc_update_dma(sdmmc);
+ }
+
+ _sdmmc_mask_interrupts(sdmmc);
+
+ if (result)
+ {
+ if (req)
+ {
+ if(!req->is_write)
+ {
+ if(!sdmmc_memcpy_buf)
+ {
+ // Flush from phys
+ armDCacheFlush(req->buf, req->blksize * blkcnt);
+ }
+ else
+ {
+ void* dma_addr = &_current_accessor->parent->dmaBuffers[sdmmc->last_dma_idx].device_addr_buffer[0];
+ // Flush from phys
+ armDCacheFlush(dma_addr, req->blksize * blkcnt);
+ // Copy to buffer
+ memcpy(req->buf, dma_addr, req->blksize * blkcnt);
+ }
+ }
+
+ if (blkcnt_out)
+ *blkcnt_out = blkcnt;
+
+ if (req->is_auto_cmd12)
+ sdmmc->rsp3 = sdmmc->regs->rspreg3;
+ }
+
+ if (cmd->check_busy || req)
+ return _sdmmc_wait_card_busy(sdmmc);
+ }
+
+ return result;
+}
+
+int sdmmc_get_sd_power_enabled()
+{
+ return gpio_read(GPIO_PORT_E, GPIO_PIN_4);
+}
+
+bool sdmmc_get_sd_inserted()
+{
+ return (!gpio_read(GPIO_PORT_Z, GPIO_PIN_1));
+}
+
+static void _sdmmc_config_sdmmc1_schmitt()
+{
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_CLK) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_CMD) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT3) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT2) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT1) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT0) |= PINMUX_SCHMT;
+}
+
+static void _sdmmc_config_sdmmc2_schmitt()
+{
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_CLK) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_CMD) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT7) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT6) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT5) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT4) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT3) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT2) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT1) |= PINMUX_SCHMT;
+ PINMUX_AUX(PINMUX_AUX_SDMMC2_DAT0) |= PINMUX_SCHMT;
+}
+
+static void _sdmmc_config_sdmmc1_pads(bool discharge)
+{
+ u32 sdmmc1_pin_mask = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
+
+ // Set values for Reset state.
+ u32 function = GPIO_MODE_SPIO;
+ u32 level = GPIO_LOW;
+ u32 output = GPIO_OUTPUT_DISABLE;
+
+ // Set values for dicharging.
+ if (discharge)
+ {
+ function = GPIO_MODE_GPIO;
+ level = GPIO_HIGH;
+ output = GPIO_OUTPUT_ENABLE;
+ }
+
+ // Set all pads function.
+ gpio_config(GPIO_PORT_M, sdmmc1_pin_mask, function);
+ // Set all pads output level.
+ gpio_write(GPIO_PORT_M, sdmmc1_pin_mask, level);
+ // Set all pads output.
+ gpio_output_enable(GPIO_PORT_M, sdmmc1_pin_mask, output);
+}
+
+static int _sdmmc_config_sdmmc1(bool t210b01)
+{
+ // Configure SD card detect.
+ PINMUX_AUX(PINMUX_AUX_GPIO_PZ1) = PINMUX_INPUT_ENABLE | PINMUX_PULL_UP | 2; // GPIO control, pull up.
+ APB_MISC(APB_MISC_GP_VGPIO_GPIO_MUX_SEL) = 0;
+ gpio_config(GPIO_PORT_Z, GPIO_PIN_1, GPIO_MODE_GPIO);
+ gpio_output_enable(GPIO_PORT_Z, GPIO_PIN_1, GPIO_OUTPUT_DISABLE);
+ usleep(100);
+
+ // Check if SD card is inserted.
+ if(!sdmmc_get_sd_inserted())
+ return 0;
+
+ /*
+ * Pinmux config:
+ * DRV_TYPE = DRIVE_2X (for 33 Ohm driver)
+ * E_SCHMT = ENABLE (for 1.8V), DISABLE (for 3.3V)
+ * E_INPUT = ENABLE
+ * TRISTATE = PASSTHROUGH
+ * APB_MISC_GP_SDMMCx_CLK_LPBK_CONTROL = SDMMCx_CLK_PAD_E_LPBK for CLK
+ */
+
+ // Enable deep loopback for SDMMC1 CLK pad.
+ APB_MISC(APB_MISC_GP_SDMMC1_CLK_LPBK_CONTROL) = 1;
+
+ // Configure SDMMC1 CLK pinmux, based on state and SoC type.
+ if (PINMUX_AUX(PINMUX_AUX_SDMMC1_CLK) != (PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | PINMUX_PULL_DOWN)) // Check if CLK pad is already configured.
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_CLK) = PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | (t210b01 ? PINMUX_PULL_NONE : PINMUX_PULL_DOWN);
+
+ // Configure the reset of SDMMC1 pins pinmux.
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_CMD) = PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | PINMUX_PULL_UP;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT3) = PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | PINMUX_PULL_UP;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT2) = PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | PINMUX_PULL_UP;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT1) = PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | PINMUX_PULL_UP;
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_DAT0) = PINMUX_DRIVE_2X | PINMUX_INPUT_ENABLE | PINMUX_PULL_UP;
+
+ // Force schmitt trigger for T210B01.
+ if (t210b01)
+ _sdmmc_config_sdmmc1_schmitt();
+
+ // Make sure the SDMMC1 controller is powered.
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_NO_IOPOWER, ~PMC_NO_IOPOWER_SDMMC1_IO_EN, PMC_NO_IOPOWER_SDMMC1_IO_EN);
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_NO_IOPOWER, 0, 0); // Commit write.
+
+ // Inform IO pads that voltage is gonna be 3.3V.
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_PWR_DET_VAL, PMC_PWR_DET_SDMMC1_IO_EN, PMC_PWR_DET_SDMMC1_IO_EN);
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_PWR_DET_VAL, 0, 0); // Commit write.
+
+ // Set enable SD card power.
+ //PINMUX_AUX(PINMUX_AUX_DMIC3_CLK) = PINMUX_PULL_DOWN | 2; // Proper pinmuxing. Breaks on HOS, takes over 1 minute to recover.
+ PINMUX_AUX(PINMUX_AUX_DMIC3_CLK) = PINMUX_INPUT_ENABLE | PINMUX_PULL_DOWN | 1; // Wrong but working pinmuxing. Instant take over by FS.
+ gpio_config(GPIO_PORT_E, GPIO_PIN_4, GPIO_MODE_GPIO);
+ gpio_write(GPIO_PORT_E, GPIO_PIN_4, GPIO_HIGH);
+ gpio_output_enable(GPIO_PORT_E, GPIO_PIN_4, GPIO_OUTPUT_ENABLE);
+ usleep(10000);
+
+ // Enable SD card IO power.
+ max77620_regulator_set_voltage(REGULATOR_LDO2, 3300000);
+ max77620_regulator_enable(REGULATOR_LDO2, 1);
+ usleep(1000);
+
+ // Set pad slew codes to get good quality clock.
+ if (!t210b01)
+ {
+ APB_MISC(APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL) = (APB_MISC(APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL) & 0xFFFFFFF) | 0x50000000;
+ (void)APB_MISC(APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); // Commit write.
+ usleep(1000);
+ }
+
+ return 1;
+}
+
+static void _sdmmc_config_emmc(u32 id, bool t210b01)
+{
+ switch (id)
+ {
+ case SDMMC_2:
+ if (!t210b01)
+ {
+ // Unset park for pads.
+ APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL) &= 0xF8003FFF;
+ (void)APB_MISC(APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); // Commit write.
+ }
+ else // Enable schmitt trigger for T210B01.
+ _sdmmc_config_sdmmc2_schmitt();
+ break;
+
+ case SDMMC_4:
+ // Unset park for pads.
+ APB_MISC(APB_MISC_GP_EMMC4_PAD_CFGPADCTRL) &= 0xF8003FFF;
+ // Set default pad cfg.
+ if (t210b01)
+ APB_MISC(APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL) &= 0xFFBFFFF9; // Unset CMD/CLK/DQS powedown.
+ // Enable schmitt trigger.
+ APB_MISC(APB_MISC_GP_EMMC4_PAD_CFGPADCTRL) |= 1;
+ (void)APB_MISC(APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); // Commit write.
+ break;
+ }
+}
+
+int sdmmc_init(sdmmc_t *sdmmc, u32 id, u32 power, u32 bus_width, u32 type, int powersave_enable)
+{
+ u32 clock;
+ u16 divisor;
+ u8 vref_sel = 7;
+
+ const u32 trim_values_t210[] = { 2, 8, 3, 8 };
+ const u32 trim_values_t210b01[] = { 14, 13, 15, 13 };
+ const u32 *trim_values = sdmmc->t210b01 ? trim_values_t210b01 : trim_values_t210;
+
+ if (id > SDMMC_4 || id == SDMMC_3)
+ return 0;
+
+ memset(sdmmc, 0, sizeof(sdmmc_t));
+
+ sdmmc->regs = (t210_sdmmc_t *)QueryIoMapping(_sdmmc_bases[id], 0x200);
+ sdmmc->id = id;
+ sdmmc->clock_stopped = 1;
+ sdmmc->t210b01 = splGetSocType() == SplSocType_Mariko;
+
+ // Do specific SDMMC HW configuration.
+ switch (id)
+ {
+ case SDMMC_1:
+ if (!_sdmmc_config_sdmmc1(sdmmc->t210b01))
+ return 0;
+ if (sdmmc->t210b01)
+ vref_sel = 0;
+ else
+ sdmmc->manual_cal = 1;
+ break;
+
+ case SDMMC_2:
+ case SDMMC_4:
+ _sdmmc_config_emmc(id, sdmmc->t210b01);
+ break;
+ }
+
+ // Disable clock if enabled.
+ if (clock_sdmmc_is_not_reset_and_enabled(id))
+ {
+ _sdmmc_sd_clock_disable(sdmmc);
+ _sdmmc_commit_changes(sdmmc);
+ }
+
+ // Configure and enable selected clock.
+ clock_sdmmc_get_card_clock_div(&clock, &divisor, type);
+ clock_sdmmc_enable(id, clock);
+
+ // Make sure all sdmmc registers are reset.
+ _sdmmc_reset_all(sdmmc);
+
+ sdmmc->clock_stopped = 0;
+
+ // Set default pad IO trimming configuration.
+ sdmmc->regs->iospare |= 0x80000; // Enable muxing.
+ sdmmc->regs->veniotrimctl &= 0xFFFFFFFB; // Set Band Gap VREG to supply DLL.
+ sdmmc->regs->venclkctl = (sdmmc->regs->venclkctl & 0xE0FFFFFB) | (trim_values[sdmmc->id] << 24);
+ sdmmc->regs->sdmemcmppadctl =
+ (sdmmc->regs->sdmemcmppadctl & TEGRA_MMC_SDMEMCOMPPADCTRL_COMP_VREF_SEL_MASK) | vref_sel;
+
+ // Configure auto calibration values.
+ if (!_sdmmc_autocal_config_offset(sdmmc, power))
+ return 0;
+
+ // Calibrate pads.
+ _sdmmc_autocal_execute(sdmmc, power);
+
+ // Enable internal clock and power.
+ if (_sdmmc_enable_internal_clock(sdmmc))
+ {
+ sdmmc_set_bus_width(sdmmc, bus_width);
+ _sdmmc_set_io_power(sdmmc, power);
+
+ if (sdmmc_setup_clock(sdmmc, type))
+ {
+ sdmmc_card_clock_powersave(sdmmc, powersave_enable);
+ _sdmmc_card_clock_enable(sdmmc);
+ _sdmmc_commit_changes(sdmmc);
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void sdmmc1_disable_power()
+{
+ // Clear pull down from CLK pad.
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_CLK) &= ~PINMUX_PULL_MASK;
+
+ // Set pads to discharge state.
+ _sdmmc_config_sdmmc1_pads(true);
+
+ // Disable SD card IO power regulator.
+ max77620_regulator_enable(REGULATOR_LDO2, 0);
+ usleep(4000);
+
+ // Disable SD card IO power pin.
+ gpio_write(GPIO_PORT_E, GPIO_PIN_4, GPIO_LOW);
+
+ // T210/T210B01 WAR: Set start timer for IO and Controller power discharge.
+ msleep(239);
+
+ // Disable SDMMC1 controller power.
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_NO_IOPOWER, PMC_NO_IOPOWER_SDMMC1_IO_EN, PMC_NO_IOPOWER_SDMMC1_IO_EN);
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_NO_IOPOWER, 0, 0); // Commit write.
+
+ // Inform IO pads that next voltage might be 3.3V.
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_PWR_DET_VAL, PMC_PWR_DET_SDMMC1_IO_EN, PMC_PWR_DET_SDMMC1_IO_EN);
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_PWR_DET_VAL, 0, 0); // Commit write.
+
+ // Set pads to reset state.
+ _sdmmc_config_sdmmc1_pads(false);
+
+ // Set pull down to CLK pad.
+ PINMUX_AUX(PINMUX_AUX_SDMMC1_CLK) |= PINMUX_PULL_DOWN;
+}
+
+void sdmmc_end(sdmmc_t *sdmmc)
+{
+ if (!sdmmc->clock_stopped)
+ {
+ _sdmmc_sd_clock_disable(sdmmc);
+ // Disable SDMMC power.
+ _sdmmc_set_io_power(sdmmc, SDMMC_POWER_OFF);
+
+ // Disable SD card power.
+ if (sdmmc->id == SDMMC_1)
+ sdmmc1_disable_power();
+
+ _sdmmc_commit_changes(sdmmc);
+ clock_sdmmc_disable(sdmmc->id);
+ sdmmc->clock_stopped = 1;
+ }
+}
+
+void sdmmc_init_cmd(sdmmc_cmd_t *cmdbuf, u16 cmd, u32 arg, u32 rsp_type, u32 check_busy)
+{
+ cmdbuf->cmd = cmd;
+ cmdbuf->arg = arg;
+ cmdbuf->rsp_type = rsp_type;
+ cmdbuf->check_busy = check_busy;
+}
+
+int sdmmc_execute_cmd(sdmmc_t *sdmmc, sdmmc_cmd_t *cmd, sdmmc_req_t *req, u32 *blkcnt_out)
+{
+ if (!sdmmc->card_clock_enabled)
+ return 0;
+
+ // Recalibrate periodically for SDMMC1.
+ if (sdmmc->manual_cal && sdmmc->powersave_enabled)
+ _sdmmc_autocal_execute(sdmmc, sdmmc_get_io_power(sdmmc));
+
+ int should_disable_sd_clock = 0;
+ if (!(sdmmc->regs->clkcon & SDHCI_CLOCK_CARD_EN))
+ {
+ should_disable_sd_clock = 1;
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+ _sdmmc_commit_changes(sdmmc);
+ usleep((8000 + sdmmc->divisor - 1) / sdmmc->divisor);
+ }
+
+ int result = _sdmmc_execute_cmd_inner(sdmmc, cmd, req, blkcnt_out);
+ usleep((8000 + sdmmc->divisor - 1) / sdmmc->divisor);
+
+ if (should_disable_sd_clock)
+ sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
+
+ return result;
+}
+
+int sdmmc_enable_low_voltage(sdmmc_t *sdmmc)
+{
+ if(sdmmc->id != SDMMC_1)
+ return 0;
+
+ if (!sdmmc_setup_clock(sdmmc, SDHCI_TIMING_UHS_SDR12))
+ return 0;
+
+ _sdmmc_commit_changes(sdmmc);
+
+ // Switch to 1.8V and wait for regulator to stabilize. Assume max possible wait needed.
+ max77620_regulator_set_voltage(REGULATOR_LDO2, 1800000);
+ usleep(150);
+
+ // Inform IO pads that we switched to 1.8V.
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_PWR_DET_VAL, ~PMC_PWR_DET_SDMMC1_IO_EN, PMC_PWR_DET_SDMMC1_IO_EN);
+ smcReadWriteRegister(PMC_BASE + APBDEV_PMC_PWR_DET_VAL, 0, 0); // Commit write.
+
+ // Enable schmitt trigger for better duty cycle and low jitter clock.
+ _sdmmc_config_sdmmc1_schmitt();
+
+ _sdmmc_autocal_config_offset(sdmmc, SDMMC_POWER_1_8);
+ _sdmmc_autocal_execute(sdmmc, SDMMC_POWER_1_8);
+ _sdmmc_set_io_power(sdmmc, SDMMC_POWER_1_8);
+ _sdmmc_commit_changes(sdmmc);
+ msleep(5); // Wait minimum 5ms before turning on the card clock.
+
+ // Turn on SDCLK.
+ if (sdmmc->regs->hostctl2 & SDHCI_CTRL_VDD_180)
+ {
+ sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
+ _sdmmc_commit_changes(sdmmc);
+ usleep(1000);
+ if ((sdmmc->regs->prnsts & SDHCI_DATA_LVL_MASK) == SDHCI_DATA_LVL_MASK)
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_driver.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_driver.h
new file mode 100644
index 00000000..c4c8067c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_driver.h
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _SDMMC_DRIVER_H_
+#define _SDMMC_DRIVER_H_
+
+#include "../utils/types.h"
+#include "sdmmc_t210.h"
+
+/*! SDMMC controller IDs. */
+#define SDMMC_1 0
+#define SDMMC_2 1
+#define SDMMC_3 2
+#define SDMMC_4 3
+
+/*! SDMMC power types. */
+#define SDMMC_POWER_OFF 0
+#define SDMMC_POWER_1_8 1
+#define SDMMC_POWER_3_3 2
+
+/*! SDMMC bus widths. */
+#define SDMMC_BUS_WIDTH_1 0
+#define SDMMC_BUS_WIDTH_4 1
+#define SDMMC_BUS_WIDTH_8 2
+
+/*! SDMMC response types. */
+#define SDMMC_RSP_TYPE_0 0
+#define SDMMC_RSP_TYPE_1 1
+#define SDMMC_RSP_TYPE_2 2
+#define SDMMC_RSP_TYPE_3 3
+#define SDMMC_RSP_TYPE_4 4
+#define SDMMC_RSP_TYPE_5 5
+
+/*! SDMMC mask interrupt status. */
+#define SDMMC_MASKINT_MASKED 0
+#define SDMMC_MASKINT_NOERROR -1
+#define SDMMC_MASKINT_ERROR -2
+
+/*! SDMMC present state. */
+#define SDHCI_CMD_INHIBIT 0x1
+#define SDHCI_DATA_INHIBIT 0x2
+#define SDHCI_DOING_WRITE 0x100
+#define SDHCI_DOING_READ 0x200
+#define SDHCI_SPACE_AVAILABLE 0x400
+#define SDHCI_DATA_AVAILABLE 0x800
+#define SDHCI_CARD_PRESENT 0x10000
+#define SDHCI_CD_STABLE 0x20000
+#define SDHCI_CD_LVL 0x40000
+#define SDHCI_WRITE_PROTECT 0x80000
+#define SDHCI_DATA_LVL_MASK 0xF00000
+#define SDHCI_DATA_0_LVL_MASK 0x100000
+#define SDHCI_CMD_LVL 0x1000000
+
+/*! SDMMC transfer mode. */
+#define SDHCI_TRNS_DMA 0x01
+#define SDHCI_TRNS_BLK_CNT_EN 0x02
+#define SDHCI_TRNS_AUTO_CMD12 0x04
+#define SDHCI_TRNS_AUTO_CMD23 0x08
+#define SDHCI_TRNS_AUTO_SEL 0x0C
+#define SDHCI_TRNS_WRITE 0x00
+#define SDHCI_TRNS_READ 0x10
+#define SDHCI_TRNS_MULTI 0x20
+
+/*! SDMMC command. */
+#define SDHCI_CMD_RESP_MASK 0x3
+#define SDHCI_CMD_RESP_NO_RESP 0x0
+#define SDHCI_CMD_RESP_LEN136 0x1
+#define SDHCI_CMD_RESP_LEN48 0x2
+#define SDHCI_CMD_RESP_LEN48_BUSY 0x3
+#define SDHCI_CMD_CRC 0x08
+#define SDHCI_CMD_INDEX 0x10
+#define SDHCI_CMD_DATA 0x20
+#define SDHCI_CMD_ABORTCMD 0xC0
+
+/*! SDMMC host control. */
+#define SDHCI_CTRL_LED 0x01
+#define SDHCI_CTRL_4BITBUS 0x02
+#define SDHCI_CTRL_HISPD 0x04
+#define SDHCI_CTRL_DMA_MASK 0x18
+#define SDHCI_CTRL_SDMA 0x00
+#define SDHCI_CTRL_ADMA1 0x08
+#define SDHCI_CTRL_ADMA32 0x10
+#define SDHCI_CTRL_ADMA64 0x18
+#define SDHCI_CTRL_8BITBUS 0x20
+#define SDHCI_CTRL_CDTEST_INS 0x40
+#define SDHCI_CTRL_CDTEST_EN 0x80
+
+/*! SDMMC host control 2. */
+#define SDHCI_CTRL_UHS_MASK 0xFFF8
+#define SDHCI_CTRL_VDD_180 8
+#define SDHCI_CTRL_DRV_TYPE_B 0x00
+#define SDHCI_CTRL_DRV_TYPE_A 0x10
+#define SDHCI_CTRL_DRV_TYPE_C 0x20
+#define SDHCI_CTRL_DRV_TYPE_D 0x30
+#define SDHCI_CTRL_EXEC_TUNING 0x40
+#define SDHCI_CTRL_TUNED_CLK 0x80
+#define SDHCI_HOST_VERSION_4_EN 0x1000
+#define SDHCI_ADDRESSING_64BIT_EN 0x2000
+#define SDHCI_CTRL_PRESET_VAL_EN 0x8000
+
+/*! SDMMC power control. */
+#define SDHCI_POWER_ON 0x01
+#define SDHCI_POWER_180 0x0A
+#define SDHCI_POWER_300 0x0C
+#define SDHCI_POWER_330 0x0E
+#define SDHCI_POWER_MASK 0xF1
+
+// /*! SDMMC max current. */
+// #define SDHCI_MAX_CURRENT_330_MASK 0xFF
+// #define SDHCI_MAX_CURRENT_180_MASK 0xFF0000
+// #define SDHCI_MAX_CURRENT_MULTIPLIER 4
+
+/*! SDMMC clock control. */
+#define SDHCI_DIVIDER_SHIFT 8
+#define SDHCI_DIVIDER_HI_SHIFT 6
+#define SDHCI_DIV_MASK 0xFF00
+#define SDHCI_DIV_HI_MASK 0xC0
+#define SDHCI_PROG_CLOCK_MODE 0x20
+#define SDHCI_CLOCK_CARD_EN 0x4
+#define SDHCI_CLOCK_INT_STABLE 0x2
+#define SDHCI_CLOCK_INT_EN 0x1
+
+/*! SDMMC software reset. */
+#define SDHCI_RESET_ALL 0x01
+#define SDHCI_RESET_CMD 0x02
+#define SDHCI_RESET_DATA 0x04
+
+/*! SDMMC interrupt status and control. */
+#define SDHCI_INT_RESPONSE 0x1
+#define SDHCI_INT_DATA_END 0x2
+#define SDHCI_INT_BLK_GAP 0x4
+#define SDHCI_INT_DMA_END 0x8
+#define SDHCI_INT_SPACE_AVAIL 0x10
+#define SDHCI_INT_DATA_AVAIL 0x20
+#define SDHCI_INT_CARD_INSERT 0x40
+#define SDHCI_INT_CARD_REMOVE 0x80
+#define SDHCI_INT_CARD_INT 0x100
+#define SDHCI_INT_RETUNE 0x1000
+#define SDHCI_INT_CQE 0x4000
+#define SDHCI_INT_ERROR 0x8000
+
+/*! SDMMC error interrupt status and control. */
+#define SDHCI_ERR_INT_TIMEOUT 0x1
+#define SDHCI_ERR_INT_CRC 0x2
+#define SDHCI_ERR_INT_END_BIT 0x4
+#define SDHCI_ERR_INT_INDEX 0x8
+#define SDHCI_ERR_INT_DATA_TIMEOUT 0x10
+#define SDHCI_ERR_INT_DATA_CRC 0x20
+#define SDHCI_ERR_INT_DATA_END_BIT 0x40
+#define SDHCI_ERR_INT_BUS_POWER 0x80
+#define SDHCI_ERR_INT_AUTO_CMD_ERR 0x100
+#define SDHCI_ERR_INT_ADMA_ERROR 0x200
+
+#define SDHCI_ERR_INT_ALL_EXCEPT_ADMA_BUSPWR \
+ (SDHCI_ERR_INT_AUTO_CMD_ERR | SDHCI_ERR_INT_DATA_END_BIT | \
+ SDHCI_ERR_INT_DATA_CRC | SDHCI_ERR_INT_DATA_TIMEOUT | \
+ SDHCI_ERR_INT_INDEX | SDHCI_ERR_INT_END_BIT | \
+ SDHCI_ERR_INT_CRC | SDHCI_ERR_INT_TIMEOUT)
+
+/*! SD bus speeds. */
+#define UHS_SDR12_BUS_SPEED 0
+#define HIGH_SPEED_BUS_SPEED 1
+#define UHS_SDR25_BUS_SPEED 1
+#define UHS_SDR50_BUS_SPEED 2
+#define UHS_SDR104_BUS_SPEED 3
+#define UHS_DDR50_BUS_SPEED 4
+#define HS400_BUS_SPEED 5
+
+/*! SDMMC timmings. */
+#define SDHCI_TIMING_MMC_ID 0
+#define SDHCI_TIMING_MMC_LS26 1
+#define SDHCI_TIMING_MMC_HS52 2
+#define SDHCI_TIMING_MMC_HS200 3
+#define SDHCI_TIMING_MMC_HS400 4
+#define SDHCI_TIMING_SD_ID 5
+#define SDHCI_TIMING_SD_DS12 6
+#define SDHCI_TIMING_SD_HS25 7
+#define SDHCI_TIMING_UHS_SDR12 8
+#define SDHCI_TIMING_UHS_SDR25 9
+#define SDHCI_TIMING_UHS_SDR50 10
+#define SDHCI_TIMING_UHS_SDR104 11
+#define SDHCI_TIMING_UHS_SDR82 12 // SDR104 with a 163.2MHz -> 81.6MHz clock.
+#define SDHCI_TIMING_UHS_DDR50 13
+#define SDHCI_TIMING_MMC_HS102 14
+
+#define SDHCI_CAN_64BIT 0x10000000
+
+/*! SDMMC Low power features. */
+#define SDMMC_POWER_SAVE_DISABLE 0
+#define SDMMC_POWER_SAVE_ENABLE 1
+
+/*! Helper for SWITCH command argument. */
+#define SDMMC_SWITCH(mode, index, value) (((mode) << 24) | ((index) << 16) | ((value) << 8))
+
+/*! SDMMC controller context. */
+typedef struct _sdmmc_t
+{
+ t210_sdmmc_t *regs;
+ u32 id;
+ u32 divisor;
+ u32 clock_stopped;
+ int powersave_enabled;
+ int manual_cal;
+ int card_clock_enabled;
+ int venclkctl_set;
+ u32 venclkctl_tap;
+ u32 expected_rsp_type;
+ u64 last_dma_idx;
+ u64 dma_addr_next;
+ u32 rsp[4];
+ u32 rsp3;
+ int t210b01;
+} sdmmc_t;
+
+/*! SDMMC command. */
+typedef struct _sdmmc_cmd_t
+{
+ u16 cmd;
+ u32 arg;
+ u32 rsp_type;
+ u32 check_busy;
+} sdmmc_cmd_t;
+
+/*! SDMMC request. */
+typedef struct _sdmmc_req_t
+{
+ void *buf;
+ u32 blksize;
+ u32 num_sectors;
+ int is_write;
+ int is_multi_block;
+ int is_auto_cmd12;
+} sdmmc_req_t;
+
+int sdmmc_get_io_power(sdmmc_t *sdmmc);
+u32 sdmmc_get_bus_width(sdmmc_t *sdmmc);
+void sdmmc_set_bus_width(sdmmc_t *sdmmc, u32 bus_width);
+void sdmmc_save_tap_value(sdmmc_t *sdmmc);
+int sdmmc_setup_clock(sdmmc_t *sdmmc, u32 type);
+void sdmmc_card_clock_powersave(sdmmc_t *sdmmc, int powersave_enable);
+int sdmmc_get_rsp(sdmmc_t *sdmmc, u32 *rsp, u32 size, u32 type);
+int sdmmc_tuning_execute(sdmmc_t *sdmmc, u32 type, u32 cmd);
+int sdmmc_stop_transmission(sdmmc_t *sdmmc, u32 *rsp);
+int sdmmc_get_sd_power_enabled();
+bool sdmmc_get_sd_inserted();
+int sdmmc_init(sdmmc_t *sdmmc, u32 id, u32 power, u32 bus_width, u32 type, int powersave_enable);
+void sdmmc_end(sdmmc_t *sdmmc);
+void sdmmc_init_cmd(sdmmc_cmd_t *cmdbuf, u16 cmd, u32 arg, u32 rsp_type, u32 check_busy);
+int sdmmc_execute_cmd(sdmmc_t *sdmmc, sdmmc_cmd_t *cmd, sdmmc_req_t *req, u32 *blkcnt_out);
+int sdmmc_enable_low_voltage(sdmmc_t *sdmmc);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_t210.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_t210.h
new file mode 100644
index 00000000..2fcf3d04
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emmc/sdmmc_t210.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _SDMMC_T210_H_
+#define _SDMMC_T210_H_
+
+#include "../utils/types.h"
+
+#define TEGRA_MMC_VNDR_TUN_CTRL0_TAP_VAL_UPDATED_BY_HW 0x20000
+#define TEGRA_MMC_DLLCAL_CFG_EN_CALIBRATE 0x80000000
+#define TEGRA_MMC_DLLCAL_CFG_STATUS_DLL_ACTIVE 0x80000000
+#define TEGRA_MMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_PWRD 0x80000000
+#define TEGRA_MMC_SDMEMCOMPPADCTRL_COMP_VREF_SEL_MASK 0xFFFFFFF0
+#define TEGRA_MMC_AUTOCALCFG_AUTO_CAL_ENABLE 0x20000000
+#define TEGRA_MMC_AUTOCALCFG_AUTO_CAL_START 0x80000000
+#define TEGRA_MMC_AUTOCALSTS_AUTO_CAL_ACTIVE 0x80000000
+
+typedef struct _t210_sdmmc_t
+{
+ vu32 sysad;
+ vu16 blksize;
+ vu16 blkcnt;
+ vu32 argument;
+ vu16 trnmod;
+ vu16 cmdreg;
+ vu32 rspreg0;
+ vu32 rspreg1;
+ vu32 rspreg2;
+ vu32 rspreg3;
+ vu32 bdata;
+ vu32 prnsts;
+ vu8 hostctl;
+ vu8 pwrcon;
+ vu8 blkgap;
+ vu8 wakcon;
+ vu16 clkcon;
+ vu8 timeoutcon;
+ vu8 swrst;
+ vu16 norintsts;
+ vu16 errintsts;
+ vu16 norintstsen; // Enable irq status.
+ vu16 errintstsen; // Enable irq status.
+ vu16 norintsigen; // Enable irq signal to LIC/GIC.
+ vu16 errintsigen; // Enable irq signal to LIC/GIC.
+ vu16 acmd12errsts;
+ vu16 hostctl2;
+ vu32 capareg;
+ vu32 capareg_1;
+ vu32 maxcurr;
+ vu8 rsvd0[4]; // 4C-4F reserved for more max current.
+ vu16 setacmd12err;
+ vu16 setinterr;
+ vu8 admaerr;
+ vu8 rsvd1[3]; // 55-57 reserved.
+ vu32 admaaddr;
+ vu32 admaaddr_hi;
+ vu8 rsvd2[156]; // 60-FB reserved.
+ vu16 slotintsts;
+ vu16 hcver;
+ vu32 venclkctl;
+ vu32 vensysswctl;
+ vu32 venerrintsts;
+ vu32 vencapover;
+ vu32 venbootctl;
+ vu32 venbootacktout;
+ vu32 venbootdattout;
+ vu32 vendebouncecnt;
+ vu32 venmiscctl;
+ vu32 maxcurrover;
+ vu32 maxcurrover_hi;
+ vu32 unk0[32]; // 0x12C
+ vu32 veniotrimctl;
+ vu32 vendllcalcfg;
+ vu32 vendllctl0;
+ vu32 vendllctl1;
+ vu32 vendllcalcfgsts;
+ vu32 ventunctl0;
+ vu32 ventunctl1;
+ vu32 ventunsts0;
+ vu32 ventunsts1;
+ vu32 venclkgatehystcnt;
+ vu32 venpresetval0;
+ vu32 venpresetval1;
+ vu32 venpresetval2;
+ vu32 sdmemcmppadctl;
+ vu32 autocalcfg;
+ vu32 autocalintval;
+ vu32 autocalsts;
+ vu32 iospare;
+ vu32 mcciffifoctl;
+ vu32 timeoutwcoal;
+ vu32 unk1;
+} t210_sdmmc_t;
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc.c b/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc.c
new file mode 100644
index 00000000..04c8e95b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc.c
@@ -0,0 +1,573 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+
+#include "emummc.h"
+#include "emummc_ctx.h"
+#include "../utils/fatal.h"
+#include "../libs/fatfs/diskio.h"
+
+static bool sdmmc_first_init = false;
+static bool storageSDinitialized = false;
+
+// hekate sdmmmc vars
+sdmmc_t sdmmc;
+sdmmc_storage_t storage;
+sdmmc_t sd_sdmmc;
+sdmmc_storage_t sd_storage;
+
+// init vars
+bool init_done = false;
+bool custom_driver = true;
+
+// FS funcs
+_sdmmc_accessor_gc sdmmc_accessor_gc;
+_sdmmc_accessor_sd sdmmc_accessor_sd;
+_sdmmc_accessor_nand sdmmc_accessor_nand;
+_lock_mutex lock_mutex;
+_unlock_mutex unlock_mutex;
+
+// FS misc
+void *sd_mutex;
+void *nand_mutex;
+volatile int *active_partition;
+volatile Handle *sdmmc_das_handle;
+
+// FatFS
+file_based_ctxt f_emu;
+static bool fat_mounted = false;
+
+static void _sdmmc_ensure_device_attached(void)
+{
+ // This ensures that the sd device address space handle is always attached,
+ // even if FS hasn't attached it
+ static bool did_attach = false;
+ if (!did_attach)
+ {
+ svcAttachDeviceAddressSpace(DeviceName_SDMMC1A, *sdmmc_das_handle);
+ did_attach = true;
+ }
+}
+
+static void _sdmmc_ensure_initialized(void)
+{
+ // First Initial init
+ if (!sdmmc_first_init)
+ {
+ sdmmc_initialize();
+ sdmmc_first_init = true;
+ }
+ else
+ {
+ // The boot sysmodule will eventually kill power to SD.
+ // Detect this, and reinitialize when it happens.
+ if (!init_done)
+ {
+ if (sdmmc_get_sd_power_enabled() == 0)
+ {
+ sdmmc_finalize();
+ sdmmc_initialize();
+ init_done = true;
+ }
+ }
+ }
+}
+
+static void _file_based_update_filename(char *outFilename, unsigned int sd_path_len, unsigned int part_idx)
+{
+ snprintf(outFilename + sd_path_len, 3, "%02d", part_idx);
+}
+
+static void _file_based_emmc_finalize(void)
+{
+ if ((emuMMC_ctx.EMMC_Type == emuMMC_SD_File) && fat_mounted)
+ {
+ // Close all open handles.
+ f_close(&f_emu.fp_boot0);
+ f_close(&f_emu.fp_boot1);
+
+ for (int i = 0; i < f_emu.parts; i++)
+ f_close(&f_emu.fp_gpp[i]);
+
+ // Force unmount FAT volume.
+ f_mount(NULL, "", 1);
+
+ fat_mounted = false;
+ }
+}
+
+static void _nand_patrol_ensure_integrity(void)
+{
+ fs_nand_patrol_t nand_patrol;
+ static bool nand_patrol_checked = false;
+
+ if (!nand_patrol_checked)
+ {
+ if (emuMMC_ctx.EMMC_Type == emuMMC_SD_Raw)
+ {
+ unsigned int nand_patrol_sector = emuMMC_ctx.EMMC_StoragePartitionOffset + NAND_PATROL_SECTOR;
+ if (!sdmmc_storage_read(&sd_storage, nand_patrol_sector, 1, &nand_patrol))
+ goto out;
+
+ // Clear nand patrol if last offset exceeds storage.
+ if (nand_patrol.offset > sd_storage.sec_cnt)
+ {
+ memset(&nand_patrol, 0, sizeof(fs_nand_patrol_t));
+ sdmmc_storage_write(&sd_storage, nand_patrol_sector, 1, &nand_patrol);
+ }
+ }
+ else if (emuMMC_ctx.EMMC_Type == emuMMC_SD_File && fat_mounted)
+ {
+ FIL *fp = &f_emu.fp_boot0;
+ if (f_lseek(fp, NAND_PATROL_OFFSET) != FR_OK)
+ goto out;
+
+ if (f_read_fast(fp, &nand_patrol, sizeof(fs_nand_patrol_t)) != FR_OK)
+ goto out;
+
+ // Clear nand patrol if last offset exceeds total file based size.
+ if (nand_patrol.offset > f_emu.total_sect)
+ {
+ memset(&nand_patrol, 0, sizeof(fs_nand_patrol_t));
+
+ if (f_lseek(fp, NAND_PATROL_OFFSET) != FR_OK)
+ goto out;
+
+ if (f_write_fast(fp, &nand_patrol, sizeof(fs_nand_patrol_t)) != FR_OK)
+ goto out;
+
+ f_sync(fp);
+ }
+ }
+
+out:
+ nand_patrol_checked = true;
+ }
+}
+
+void sdmmc_finalize(void)
+{
+ if (!sdmmc_storage_end(&sd_storage))
+ fatal_abort(Fatal_InitSD);
+
+ storageSDinitialized = false;
+}
+
+static void _file_based_emmc_initialize(void)
+{
+ char path[sizeof(emuMMC_ctx.storagePath) + 0x20];
+ memset(&path, 0, sizeof(path));
+
+ memcpy(path, (void *)emuMMC_ctx.storagePath, sizeof(emuMMC_ctx.storagePath));
+ strcat(path, "/eMMC/");
+ int path_len = strlen(path);
+
+ // Open BOOT0 physical partition.
+ memcpy(path + path_len, "BOOT0", 6);
+ if (f_open(&f_emu.fp_boot0, path, FA_READ | FA_WRITE) != FR_OK)
+ fatal_abort(Fatal_FatfsFileOpen);
+ if (!f_expand_cltbl(&f_emu.fp_boot0, EMUMMC_FP_CLMT_COUNT, f_emu.clmt_boot0, f_size(&f_emu.fp_boot0)))
+ fatal_abort(Fatal_FatfsMemExhaustion);
+
+ // Open BOOT1 physical partition.
+ memcpy(path + path_len, "BOOT1", 6);
+ if (f_open(&f_emu.fp_boot1, path, FA_READ | FA_WRITE) != FR_OK)
+ fatal_abort(Fatal_FatfsFileOpen);
+ if (!f_expand_cltbl(&f_emu.fp_boot1, EMUMMC_FP_CLMT_COUNT, f_emu.clmt_boot1, f_size(&f_emu.fp_boot1)))
+ fatal_abort(Fatal_FatfsMemExhaustion);
+
+ // Open handles for GPP physical partition files.
+ _file_based_update_filename(path, path_len, 00);
+
+ if (f_open(&f_emu.fp_gpp[0], path, FA_READ | FA_WRITE) != FR_OK)
+ fatal_abort(Fatal_FatfsFileOpen);
+ if (!f_expand_cltbl(&f_emu.fp_gpp[0], EMUMMC_FP_CLMT_COUNT, &f_emu.clmt_gpp[0], f_size(&f_emu.fp_gpp[0])))
+ fatal_abort(Fatal_FatfsMemExhaustion);
+
+ f_emu.part_size = (uint64_t)f_size(&f_emu.fp_gpp[0]) >> 9;
+ f_emu.total_sect = f_emu.part_size;
+
+ // Iterate folder for split parts and stop if next doesn't exist.
+ for (f_emu.parts = 1; f_emu.parts < EMUMMC_FILE_MAX_PARTS; f_emu.parts++)
+ {
+ _file_based_update_filename(path, path_len, f_emu.parts);
+
+ if (f_open(&f_emu.fp_gpp[f_emu.parts], path, FA_READ | FA_WRITE) != FR_OK)
+ {
+ // Check if single file.
+ if (f_emu.parts == 1)
+ f_emu.parts = 0;
+
+ return;
+ }
+
+ if (!f_expand_cltbl(&f_emu.fp_gpp[f_emu.parts], EMUMMC_FP_CLMT_COUNT,
+ &f_emu.clmt_gpp[f_emu.parts * EMUMMC_FP_CLMT_COUNT], f_size(&f_emu.fp_gpp[f_emu.parts])))
+ {
+ fatal_abort(Fatal_FatfsMemExhaustion);
+ }
+
+ f_emu.total_sect += (uint64_t)f_size(&f_emu.fp_gpp[f_emu.parts]) >> 9;
+ }
+}
+
+bool sdmmc_initialize(void)
+{
+ if (!storageSDinitialized)
+ {
+ int retries = 3;
+ while (retries)
+ {
+ if (nx_sd_initialize(false))
+ {
+ storageSDinitialized = true;
+
+ // Init file based emummc.
+ if ((emuMMC_ctx.EMMC_Type == emuMMC_SD_File) && !fat_mounted)
+ {
+ if (f_mount(&f_emu.sd_fs, "", 1) != FR_OK)
+ fatal_abort(Fatal_InitSD);
+ else
+ fat_mounted = true;
+
+ _file_based_emmc_initialize();
+ }
+
+ // Check if nand patrol offset is inside limits.
+ _nand_patrol_ensure_integrity();
+
+ break;
+ }
+
+ retries--;
+ }
+
+ if (!storageSDinitialized)
+ fatal_abort(Fatal_InitSD);
+ }
+
+ return storageSDinitialized;
+}
+
+sdmmc_accessor_t *sdmmc_accessor_get(int mmc_id)
+{
+ sdmmc_accessor_t *_this;
+ switch (mmc_id)
+ {
+ case FS_SDMMC_EMMC:
+ _this = sdmmc_accessor_nand();
+ break;
+ case FS_SDMMC_SD:
+ _this = sdmmc_accessor_sd();
+ break;
+ case FS_SDMMC_GC:
+ _this = sdmmc_accessor_gc();
+ break;
+ default:
+ fatal_abort(Fatal_InvalidAccessor);
+ }
+
+ return _this;
+}
+
+void mutex_lock_handler(int mmc_id)
+{
+ if (custom_driver)
+ lock_mutex(sd_mutex);
+
+ lock_mutex(nand_mutex);
+}
+
+void mutex_unlock_handler(int mmc_id)
+{
+ unlock_mutex(nand_mutex);
+
+ if (custom_driver)
+ unlock_mutex(sd_mutex);
+}
+
+int sdmmc_nand_get_active_partition_index()
+{
+ switch (*active_partition)
+ {
+ case FS_EMMC_PARTITION_GPP:
+ return 2;
+ case FS_EMMC_PARTITION_BOOT1:
+ return 1;
+ case FS_EMMC_PARTITION_BOOT0:
+ return 0;
+ }
+
+ fatal_abort(Fatal_InvalidAccessor);
+}
+
+static uint64_t emummc_read_write_inner(void *buf, unsigned int sector, unsigned int num_sectors, bool is_write)
+{
+ if (emuMMC_ctx.EMMC_Type == emuMMC_SD_Raw)
+ {
+ // raw partition sector offset: emuMMC_ctx.EMMC_StoragePartitionOffset.
+ sector += emuMMC_ctx.EMMC_StoragePartitionOffset;
+ // Set physical partition offset.
+ sector += (sdmmc_nand_get_active_partition_index() * BOOT_PARTITION_SIZE);
+
+ if (__builtin_expect(sector + num_sectors > sd_storage.sec_cnt, 0))
+ return 0; // Out of bounds. Can only happen with Nand Patrol if resized.
+
+ if (!is_write)
+ return sdmmc_storage_read(&sd_storage, sector, num_sectors, buf);
+ else
+ return sdmmc_storage_write(&sd_storage, sector, num_sectors, buf);
+ }
+
+ // File based emummc.
+ FIL *fp = NULL;
+ switch (*active_partition)
+ {
+ case FS_EMMC_PARTITION_GPP:
+ if (f_emu.parts)
+ {
+ if (__builtin_expect(sector + num_sectors > f_emu.total_sect, 0))
+ return 0; // Out of bounds. Can only happen with Nand Patrol if resized.
+
+ fp = &f_emu.fp_gpp[sector / f_emu.part_size];
+ sector = sector % f_emu.part_size;
+
+ // Special handling for reads/writes which cross file-boundaries.
+ if (__builtin_expect(sector + num_sectors > f_emu.part_size, 0))
+ {
+ unsigned int remaining = num_sectors;
+ while (remaining > 0) {
+ const unsigned int cur_sectors = MIN(remaining, f_emu.part_size - sector);
+
+ if (f_lseek(fp, (uint64_t)sector << 9) != FR_OK)
+ return 0; // Out of bounds.
+
+ if (!is_write)
+ {
+ if (f_read_fast(fp, buf, (uint64_t)cur_sectors << 9) != FR_OK)
+ return 0;
+ }
+ else
+ {
+ if (f_write_fast(fp, buf, (uint64_t)cur_sectors << 9) != FR_OK)
+ return 0;
+ }
+
+ buf = (char *)buf + ((uint64_t)cur_sectors << 9);
+ remaining -= cur_sectors;
+ sector = 0;
+ ++fp;
+ }
+
+ return 1;
+ }
+ }
+ else
+ fp = &f_emu.fp_gpp[0];
+ break;
+
+ case FS_EMMC_PARTITION_BOOT1:
+ fp = &f_emu.fp_boot1;
+ break;
+
+ case FS_EMMC_PARTITION_BOOT0:
+ fp = &f_emu.fp_boot0;
+ break;
+ }
+
+ if (f_lseek(fp, (uint64_t)sector << 9) != FR_OK)
+ return 0; // Out of bounds. Can only happen with Nand Patrol if resized.
+
+ if (!is_write)
+ return !f_read_fast(fp, buf, (uint64_t)num_sectors << 9);
+ else
+ return !f_write_fast(fp, buf, (uint64_t)num_sectors << 9);
+}
+
+// Controller open wrapper
+uint64_t sdmmc_wrapper_controller_open(int mmc_id)
+{
+ uint64_t result;
+ sdmmc_accessor_t *_this;
+ _this = sdmmc_accessor_get(mmc_id);
+
+ if (_this != NULL)
+ {
+ // Lock eMMC xfer while SD card is being initialized by FS.
+ if (mmc_id == FS_SDMMC_SD)
+ mutex_lock_handler(FS_SDMMC_EMMC); // Recursive Mutex, handler will lock SD as well if custom_driver
+
+ result = _this->vtab->sdmmc_accessor_controller_open(_this);
+
+ // Unlock eMMC.
+ if (mmc_id == FS_SDMMC_SD)
+ mutex_unlock_handler(FS_SDMMC_EMMC);
+
+ return result;
+ }
+
+ fatal_abort(Fatal_OpenAccessor);
+}
+
+// Controller close wrapper
+uint64_t sdmmc_wrapper_controller_close(int mmc_id)
+{
+ sdmmc_accessor_t *_this;
+ _this = sdmmc_accessor_get(mmc_id);
+
+ if (_this != NULL)
+ {
+ if (mmc_id == FS_SDMMC_SD)
+ return 0;
+
+ if (mmc_id == FS_SDMMC_EMMC)
+ {
+ // Close file handles and unmount
+ _file_based_emmc_finalize();
+
+ // Close SD
+ sdmmc_accessor_get(FS_SDMMC_SD)->vtab->sdmmc_accessor_controller_close(sdmmc_accessor_get(FS_SDMMC_SD));
+
+ // Close eMMC
+ return _this->vtab->sdmmc_accessor_controller_close(_this);
+ }
+
+ return _this->vtab->sdmmc_accessor_controller_close(_this);
+ }
+
+ fatal_abort(Fatal_CloseAccessor);
+}
+
+// FS read wrapper.
+uint64_t sdmmc_wrapper_read(void *buf, uint64_t bufSize, int mmc_id, unsigned int sector, unsigned int num_sectors)
+{
+ sdmmc_accessor_t *_this;
+ uint64_t read_res;
+
+ _this = sdmmc_accessor_get(mmc_id);
+
+ if (_this != NULL)
+ {
+ if (mmc_id == FS_SDMMC_EMMC || mmc_id == FS_SDMMC_SD)
+ {
+ mutex_lock_handler(mmc_id);
+ // Assign FS accessor to the SDMMC driver
+ _current_accessor = _this;
+ // Make sure we're attached to the device address space.
+ _sdmmc_ensure_device_attached();
+ // Make sure we're still initialized if boot killed sd card power.
+ _sdmmc_ensure_initialized();
+ }
+
+ if (mmc_id == FS_SDMMC_EMMC)
+ {
+ // Call hekates driver.
+ if (emummc_read_write_inner(buf, sector, num_sectors, false))
+ {
+ mutex_unlock_handler(mmc_id);
+ return 0;
+ }
+
+ mutex_unlock_handler(mmc_id);
+ return FS_READ_WRITE_ERROR;
+ }
+
+ if (mmc_id == FS_SDMMC_SD)
+ {
+ static bool first_sd_read = true;
+ if (first_sd_read)
+ {
+ first_sd_read = false;
+ if (emuMMC_ctx.EMMC_Type == emuMMC_SD_Raw)
+ {
+ // Because some SD cards have issues with emuMMC's driver
+ // we currently swap to FS's driver after first SD read
+ // for raw based emuMMC
+ custom_driver = false;
+ // FS will handle sd mutex w/o custom driver from here on
+ unlock_mutex(sd_mutex);
+ }
+ }
+
+ // Call hekate's driver.
+ if (sdmmc_storage_read(&sd_storage, sector, num_sectors, buf))
+ {
+ mutex_unlock_handler(mmc_id);
+ return 0;
+ }
+
+ mutex_unlock_handler(mmc_id);
+ return FS_READ_WRITE_ERROR;
+ }
+
+ read_res = _this->vtab->read_write(_this, sector, num_sectors, buf, bufSize, 1);
+ return read_res;
+ }
+
+ fatal_abort(Fatal_ReadNoAccessor);
+}
+
+// FS write wrapper.
+uint64_t sdmmc_wrapper_write(int mmc_id, unsigned int sector, unsigned int num_sectors, void *buf, uint64_t bufSize)
+{
+ sdmmc_accessor_t *_this;
+ uint64_t write_res;
+
+ _this = sdmmc_accessor_get(mmc_id);
+
+ if (_this != NULL)
+ {
+ if (mmc_id == FS_SDMMC_EMMC)
+ {
+ mutex_lock_handler(mmc_id);
+ _current_accessor = _this;
+
+ // Call hekates driver.
+ if (emummc_read_write_inner(buf, sector, num_sectors, true))
+ {
+ mutex_unlock_handler(mmc_id);
+ return 0;
+ }
+
+ mutex_unlock_handler(mmc_id);
+ return FS_READ_WRITE_ERROR;
+ }
+
+ if (mmc_id == FS_SDMMC_SD)
+ {
+ mutex_lock_handler(mmc_id);
+ _current_accessor = _this;
+
+ // Call hekates driver.
+ if (sdmmc_storage_write(&sd_storage, sector, num_sectors, buf))
+ {
+ mutex_unlock_handler(mmc_id);
+ return 0;
+ }
+
+ mutex_unlock_handler(mmc_id);
+ return FS_READ_WRITE_ERROR;
+ }
+
+ write_res = _this->vtab->read_write(_this, sector, num_sectors, buf, bufSize, 0);
+ return write_res;
+ }
+
+ fatal_abort(Fatal_WriteNoAccessor);
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc.h
new file mode 100644
index 00000000..24580c18
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef __EMUMMC_H__
+#define __EMUMMC_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include
+#include
+#include
+
+#include "../emmc/nx_sd.h"
+#include "../emmc/sdmmc.h"
+#include "../soc/i2c.h"
+#include "../soc/gpio.h"
+#include "../utils/util.h"
+#include "../FS/FS.h"
+#include "../libs/fatfs/ff.h"
+
+#define EMUMMC_FILE_MAX_PARTS 32
+#define EMUMMC_FP_CLMT_COUNT 1024
+
+// FS typedefs
+typedef sdmmc_accessor_t *(*_sdmmc_accessor_gc)();
+typedef sdmmc_accessor_t *(*_sdmmc_accessor_sd)();
+typedef sdmmc_accessor_t *(*_sdmmc_accessor_nand)();
+typedef void (*_lock_mutex)(void *mtx);
+typedef void (*_unlock_mutex)(void *mtx);
+
+bool sdmmc_initialize(void);
+void sdmmc_finalize(void);
+int sdmmc_nand_get_active_partition_index();
+sdmmc_accessor_t *sdmmc_accessor_get(int mmc_id);
+
+void mutex_lock_handler(int mmc_id);
+void mutex_unlock_handler(int mmc_id);
+
+// Hooks
+uint64_t sdmmc_wrapper_controller_open(int mmc_id);
+uint64_t sdmmc_wrapper_controller_close(int mmc_id);
+uint64_t sdmmc_wrapper_read(void *buf, uint64_t bufSize, int mmc_id, unsigned int sector, unsigned int num_sectors);
+uint64_t sdmmc_wrapper_write(int mmc_id, unsigned int sector, unsigned int num_sectors, void *buf, uint64_t bufSize);
+
+typedef struct _file_based_ctxt
+{
+ FATFS sd_fs;
+ uint64_t parts;
+ uint64_t part_size;
+ FIL fp_boot0;
+ DWORD clmt_boot0[EMUMMC_FP_CLMT_COUNT];
+ FIL fp_boot1;
+ DWORD clmt_boot1[EMUMMC_FP_CLMT_COUNT];
+ FIL fp_gpp[EMUMMC_FILE_MAX_PARTS];
+ DWORD clmt_gpp[EMUMMC_FILE_MAX_PARTS * EMUMMC_FP_CLMT_COUNT];
+ uint64_t total_sect;
+} file_based_ctxt;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __EMUMMC_H__ */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc_ctx.h b/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc_ctx.h
new file mode 100644
index 00000000..9bee3fd0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/emuMMC/emummc_ctx.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef __EMUMMC_CTX_H__
+#define __EMUMMC_CTX_H__
+
+#include "../utils/types.h"
+#include "../FS/FS_versions.h"
+
+#define EMUMMC_STORAGE_MAGIC 0x30534645 /* EFS0, EmuFS0 */
+#define EMUMMC_MAX_DIR_LENGTH 0x7F
+
+enum emuMMC_Type
+{
+ // EMMC Device raw
+ emuMMC_EMMC = 0,
+
+ // SD Device raw
+ emuMMC_SD_Raw,
+ // SD Device File
+ emuMMC_SD_File,
+
+ emuMMC_MAX
+};
+
+typedef struct _emuMMC_ctx_t
+{
+ u32 magic;
+ u32 id;
+ enum FS_VER fs_ver;
+ enum emuMMC_Type EMMC_Type;
+ enum emuMMC_Type SD_Type;
+
+ /* Partition based */
+ u64 EMMC_StoragePartitionOffset;
+ u64 SD_StoragePartitionOffset;
+
+ /* File-Based */
+ char storagePath[EMUMMC_MAX_DIR_LENGTH+1];
+} emuMMC_ctx_t, *PemuMMC_ctx_t;
+
+#endif /* __EMUMMC_CTX_H__ */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/diskio.c b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/diskio.c
new file mode 100644
index 00000000..d800d218
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/diskio.c
@@ -0,0 +1,72 @@
+/*-----------------------------------------------------------------------*/
+/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
+/*-----------------------------------------------------------------------*/
+/* 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
+#include "diskio.h" /* FatFs lower layer API */
+#include "../../emmc/sdmmc.h"
+
+extern sdmmc_storage_t sd_storage;
+
+/*-----------------------------------------------------------------------*/
+/* Get Drive Status */
+/*-----------------------------------------------------------------------*/
+DSTATUS disk_status (
+ BYTE pdrv /* Physical drive nmuber to identify the drive */
+)
+{
+ return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Inidialize a Drive */
+/*-----------------------------------------------------------------------*/
+DSTATUS disk_initialize (
+ BYTE pdrv /* Physical drive nmuber to identify the drive */
+)
+{
+ return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Read Sector(s) */
+/*-----------------------------------------------------------------------*/
+DRESULT disk_read (
+ BYTE pdrv, /* Physical drive nmuber to identify the drive */
+ BYTE *buff, /* Data buffer to store read data */
+ DWORD sector, /* Start sector in LBA */
+ UINT count /* Number of sectors to read */
+)
+{
+ return sdmmc_storage_read(&sd_storage, sector, count, buff) ? RES_OK : RES_ERROR;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Write Sector(s) */
+/*-----------------------------------------------------------------------*/
+DRESULT disk_write (
+ BYTE pdrv, /* Physical drive nmuber to identify the drive */
+ const BYTE *buff, /* Data to be written */
+ DWORD sector, /* Start sector in LBA */
+ UINT count /* Number of sectors to write */
+)
+{
+ return sdmmc_storage_write(&sd_storage, sector, count, (void *)buff) ? RES_OK : RES_ERROR;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Miscellaneous Functions */
+/*-----------------------------------------------------------------------*/
+DRESULT disk_ioctl (
+ BYTE pdrv, /* Physical drive nmuber (0..) */
+ BYTE cmd, /* Control code */
+ void *buff /* Buffer to send/receive control data */
+)
+{
+ return RES_OK;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/diskio.h b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/diskio.h
new file mode 100644
index 00000000..d6ae8f83
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/diskio.h
@@ -0,0 +1,79 @@
+/*-----------------------------------------------------------------------/
+/ Low level disk interface modlue include file (C)ChaN, 2014 /
+/-----------------------------------------------------------------------*/
+
+#ifndef _DISKIO_DEFINED
+#define _DISKIO_DEFINED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../../utils/types.h"
+
+/* 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, DWORD sector, UINT count);
+DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD 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
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ff.c b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ff.c
new file mode 100644
index 00000000..50105173
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ff.c
@@ -0,0 +1,6883 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+/*----------------------------------------------------------------------------/
+/ FatFs - Generic FAT Filesystem Module R0.13c (p4) /
+/-----------------------------------------------------------------------------/
+/
+/ Copyright (C) 2018, 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.
+/
+/----------------------------------------------------------------------------*/
+
+
+#include "ff.h" /* Declarations of FatFs API */
+#include "diskio.h" /* Declarations of device I/O functions */
+
+#define EFSPRINTF(text, ...)
+
+/*--------------------------------------------------------------------------
+
+ Module Private Definitions
+
+---------------------------------------------------------------------------*/
+
+#if FF_DEFINED != 86604 /* Revision ID */
+#error Wrong include file (ff.h).
+#endif
+
+
+/* Limits and boundaries */
+#define MAX_DIR 0x200000 /* Max size of FAT directory */
+#define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */
+#define MAX_FAT12 0xFF5 /* Max FAT12 clusters (differs from specs, but right for real DOS/Windows behavior) */
+#define MAX_FAT16 0xFFF5 /* Max FAT16 clusters (differs from specs, but right for real DOS/Windows behavior) */
+#define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */
+#define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */
+
+
+/* Character code support macros */
+#define IsUpper(c) ((c) >= 'A' && (c) <= 'Z')
+#define IsLower(c) ((c) >= 'a' && (c) <= 'z')
+#define IsDigit(c) ((c) >= '0' && (c) <= '9')
+#define IsSurrogate(c) ((c) >= 0xD800 && (c) <= 0xDFFF)
+#define IsSurrogateH(c) ((c) >= 0xD800 && (c) <= 0xDBFF)
+#define IsSurrogateL(c) ((c) >= 0xDC00 && (c) <= 0xDFFF)
+
+
+/* Additional file access control and file status flags for internal use */
+#define FA_SEEKEND 0x20 /* Seek to end of the file on file open */
+#define FA_MODIFIED 0x40 /* File has been modified */
+#define FA_DIRTY 0x80 /* FIL.buf[] needs to be written-back */
+
+
+/* Additional file attribute bits for internal use */
+#define AM_VOL 0x08 /* Volume label */
+#define AM_LFN 0x0F /* LFN entry */
+#define AM_MASK 0x3F /* Mask of defined bits */
+
+
+/* Name status flags in fn[11] */
+#define NSFLAG 11 /* Index of the name status byte */
+#define NS_LOSS 0x01 /* Out of 8.3 format */
+#define NS_LFN 0x02 /* Force to create LFN entry */
+#define NS_LAST 0x04 /* Last segment */
+#define NS_BODY 0x08 /* Lower case flag (body) */
+#define NS_EXT 0x10 /* Lower case flag (ext) */
+#define NS_DOT 0x20 /* Dot entry */
+#define NS_NOLFN 0x40 /* Do not find LFN */
+#define NS_NONAME 0x80 /* Not followed */
+
+
+/* exFAT directory entry types */
+#define ET_BITMAP 0x81 /* Allocation bitmap */
+#define ET_UPCASE 0x82 /* Up-case table */
+#define ET_VLABEL 0x83 /* Volume label */
+#define ET_FILEDIR 0x85 /* File and directory */
+#define ET_STREAM 0xC0 /* Stream extension */
+#define ET_FILENAME 0xC1 /* Name extension */
+
+
+/* FatFs refers the FAT structure as simple byte array instead of structure member
+/ because the C structure is not binary compatible between different platforms */
+
+#define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */
+#define BS_OEMName 3 /* OEM name (8-byte) */
+#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */
+#define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */
+#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */
+#define BPB_NumFATs 16 /* Number of FATs (BYTE) */
+#define BPB_RootEntCnt 17 /* Size of root directory area for FAT [entry] (WORD) */
+#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */
+#define BPB_Media 21 /* Media descriptor byte (BYTE) */
+#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */
+#define BPB_SecPerTrk 24 /* Number of sectors per track for int13h [sector] (WORD) */
+#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */
+#define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */
+#define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */
+#define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */
+#define BS_NTres 37 /* WindowsNT error flag (BYTE) */
+#define BS_BootSig 38 /* Extended boot signature (BYTE) */
+#define BS_VolID 39 /* Volume serial number (DWORD) */
+#define BS_VolLab 43 /* Volume label string (8-byte) */
+#define BS_FilSysType 54 /* Filesystem type string (8-byte) */
+#define BS_BootCode 62 /* Boot code (448-byte) */
+#define BS_55AA 510 /* Signature word (WORD) */
+
+#define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */
+#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */
+#define BPB_FSVer32 42 /* FAT32: Filesystem version (WORD) */
+#define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */
+#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */
+#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */
+#define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */
+#define BS_NTres32 65 /* FAT32: Error flag (BYTE) */
+#define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */
+#define BS_VolID32 67 /* FAT32: Volume serial number (DWORD) */
+#define BS_VolLab32 71 /* FAT32: Volume label string (8-byte) */
+#define BS_FilSysType32 82 /* FAT32: Filesystem type string (8-byte) */
+#define BS_BootCode32 90 /* FAT32: Boot code (420-byte) */
+
+#define BPB_ZeroedEx 11 /* exFAT: MBZ field (53-byte) */
+#define BPB_VolOfsEx 64 /* exFAT: Volume offset from top of the drive [sector] (QWORD) */
+#define BPB_TotSecEx 72 /* exFAT: Volume size [sector] (QWORD) */
+#define BPB_FatOfsEx 80 /* exFAT: FAT offset from top of the volume [sector] (DWORD) */
+#define BPB_FatSzEx 84 /* exFAT: FAT size [sector] (DWORD) */
+#define BPB_DataOfsEx 88 /* exFAT: Data offset from top of the volume [sector] (DWORD) */
+#define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */
+#define BPB_RootClusEx 96 /* exFAT: Root directory start cluster (DWORD) */
+#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */
+#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD) */
+#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD) */
+#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in unit of byte (BYTE) */
+#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in unit of sector (BYTE) */
+#define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */
+#define BPB_DrvNumEx 111 /* exFAT: Physical drive number for int13h (BYTE) */
+#define BPB_PercInUseEx 112 /* exFAT: Percent in use (BYTE) */
+#define BPB_RsvdEx 113 /* exFAT: Reserved (7-byte) */
+#define BS_BootCodeEx 120 /* exFAT: Boot code (390-byte) */
+
+#define DIR_Name 0 /* Short file name (11-byte) */
+#define DIR_Attr 11 /* Attribute (BYTE) */
+#define DIR_NTres 12 /* Lower case flag (BYTE) */
+#define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */
+#define DIR_CrtTime 14 /* Created time (DWORD) */
+#define DIR_LstAccDate 18 /* Last accessed date (WORD) */
+#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */
+#define DIR_ModTime 22 /* Modified time (DWORD) */
+#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */
+#define DIR_FileSize 28 /* File size (DWORD) */
+#define LDIR_Ord 0 /* LFN: LFN order and LLE flag (BYTE) */
+#define LDIR_Attr 11 /* LFN: LFN attribute (BYTE) */
+#define LDIR_Type 12 /* LFN: Entry type (BYTE) */
+#define LDIR_Chksum 13 /* LFN: Checksum of the SFN (BYTE) */
+#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD) */
+#define XDIR_Type 0 /* exFAT: Type of exFAT directory entry (BYTE) */
+#define XDIR_NumLabel 1 /* exFAT: Number of volume label characters (BYTE) */
+#define XDIR_Label 2 /* exFAT: Volume label (11-WORD) */
+#define XDIR_CaseSum 4 /* exFAT: Sum of case conversion table (DWORD) */
+#define XDIR_NumSec 1 /* exFAT: Number of secondary entries (BYTE) */
+#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD) */
+#define XDIR_Attr 4 /* exFAT: File attribute (WORD) */
+#define XDIR_CrtTime 8 /* exFAT: Created time (DWORD) */
+#define XDIR_ModTime 12 /* exFAT: Modified time (DWORD) */
+#define XDIR_AccTime 16 /* exFAT: Last accessed time (DWORD) */
+#define XDIR_CrtTime10 20 /* exFAT: Created time subsecond (BYTE) */
+#define XDIR_ModTime10 21 /* exFAT: Modified time subsecond (BYTE) */
+#define XDIR_CrtTZ 22 /* exFAT: Created timezone (BYTE) */
+#define XDIR_ModTZ 23 /* exFAT: Modified timezone (BYTE) */
+#define XDIR_AccTZ 24 /* exFAT: Last accessed timezone (BYTE) */
+#define XDIR_GenFlags 33 /* exFAT: General secondary flags (BYTE) */
+#define XDIR_NumName 35 /* exFAT: Number of file name characters (BYTE) */
+#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD) */
+#define XDIR_ValidFileSize 40 /* exFAT: Valid file size (QWORD) */
+#define XDIR_FstClus 52 /* exFAT: First cluster of the file data (DWORD) */
+#define XDIR_FileSize 56 /* exFAT: File/Directory size (QWORD) */
+
+#define SZDIRE 32 /* Size of a directory entry */
+#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */
+#define RDDEM 0x05 /* Replacement of the character collides with DDEM */
+#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */
+
+#define FSI_LeadSig 0 /* FAT32 FSI: Leading signature (DWORD) */
+#define FSI_StrucSig 484 /* FAT32 FSI: Structure signature (DWORD) */
+#define FSI_Free_Count 488 /* FAT32 FSI: Number of free clusters (DWORD) */
+#define FSI_Nxt_Free 492 /* FAT32 FSI: Last allocated cluster (DWORD) */
+
+#define MBR_Table 446 /* MBR: Offset of partition table in the MBR */
+#define SZ_PTE 16 /* MBR: Size of a partition table entry */
+#define PTE_Boot 0 /* MBR PTE: Boot indicator */
+#define PTE_StHead 1 /* MBR PTE: Start head */
+#define PTE_StSec 2 /* MBR PTE: Start sector */
+#define PTE_StCyl 3 /* MBR PTE: Start cylinder */
+#define PTE_System 4 /* MBR PTE: System ID */
+#define PTE_EdHead 5 /* MBR PTE: End head */
+#define PTE_EdSec 6 /* MBR PTE: End sector */
+#define PTE_EdCyl 7 /* MBR PTE: End cylinder */
+#define PTE_StLba 8 /* MBR PTE: Start in LBA */
+#define PTE_SizLba 12 /* MBR PTE: Size in LBA */
+
+
+/* Post process on fatal error in the file operations */
+#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); }
+
+
+/* Re-entrancy related */
+#if FF_FS_REENTRANT
+#if FF_USE_LFN == 1
+#error Static LFN work area cannot be used at thread-safe configuration
+#endif
+#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; }
+#else
+#define LEAVE_FF(fs, res) return res
+#endif
+
+
+/* Definitions of volume - physical location conversion */
+#if FF_MULTI_PARTITION
+#define LD2PD(vol) VolToPart[vol].pd /* Get physical drive number */
+#define LD2PT(vol) VolToPart[vol].pt /* Get partition index */
+#else
+#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is bound to the same physical drive number */
+#define LD2PT(vol) 0 /* Find first valid partition or in SFD */
+#endif
+
+
+/* Definitions of sector size */
+#if (FF_MAX_SS < FF_MIN_SS) || (FF_MAX_SS != 512 && FF_MAX_SS != 1024 && FF_MAX_SS != 2048 && FF_MAX_SS != 4096) || (FF_MIN_SS != 512 && FF_MIN_SS != 1024 && FF_MIN_SS != 2048 && FF_MIN_SS != 4096)
+#error Wrong sector size configuration
+#endif
+#if FF_MAX_SS == FF_MIN_SS
+#define SS(fs) ((UINT)FF_MAX_SS) /* Fixed sector size */
+#else
+#define SS(fs) ((fs)->ssize) /* Variable sector size */
+#endif
+
+
+/* Timestamp */
+#if FF_FS_NORTC == 1
+#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31
+#error Invalid FF_FS_NORTC settings
+#endif
+#define GET_FATTIME() ((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16)
+#else
+#define GET_FATTIME() get_fattime()
+#endif
+
+
+/* File lock controls */
+#if FF_FS_LOCK != 0
+#if FF_FS_READONLY
+#error FF_FS_LOCK must be 0 at read-only configuration
+#endif
+typedef struct {
+ FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */
+ DWORD clu; /* Object ID 2, containing directory (0:root) */
+ DWORD ofs; /* Object ID 3, offset in the directory */
+ WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */
+} FILESEM;
+#endif
+
+
+/* SBCS up-case tables (\x80-\xFF) */
+#define TBL_CT437 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT720 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT737 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \
+ 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT771 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDC,0xDE,0xDE, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF}
+#define TBL_CT775 {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
+ 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT850 {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \
+ 0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \
+ 0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT852 {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \
+ 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF}
+#define TBL_CT855 {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \
+ 0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \
+ 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \
+ 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \
+ 0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT857 {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \
+ 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT860 {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x86,0x8B,0x9F,0x96,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT861 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
+ 0xA4,0xA5,0xA6,0xA7,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT862 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT863 {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \
+ 0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \
+ 0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT864 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT865 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT866 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT869 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x86,0x9C,0x8D,0x8F,0x90, \
+ 0x91,0x90,0x92,0x95,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \
+ 0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \
+ 0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF}
+
+
+/* DBCS code range |----- 1st byte -----| |----------- 2nd byte -----------| */
+#define TBL_DC932 {0x81, 0x9F, 0xE0, 0xFC, 0x40, 0x7E, 0x80, 0xFC, 0x00, 0x00}
+#define TBL_DC936 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0x80, 0xFE, 0x00, 0x00}
+#define TBL_DC949 {0x81, 0xFE, 0x00, 0x00, 0x41, 0x5A, 0x61, 0x7A, 0x81, 0xFE}
+#define TBL_DC950 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0xA1, 0xFE, 0x00, 0x00}
+
+
+/* Macros for table definitions */
+#define MERGE_2STR(a, b) a ## b
+#define MKCVTBL(hd, cp) MERGE_2STR(hd, cp)
+
+
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Work Area
+
+---------------------------------------------------------------------------*/
+/* Remark: Variables defined here without initial value shall be guaranteed
+/ zero/null at start-up. If not, the linker option or start-up routine is
+/ not compliance with C standard. */
+
+/*--------------------------------*/
+/* File/Volume controls */
+/*--------------------------------*/
+
+#if FF_VOLUMES < 1 || FF_VOLUMES > 10
+#error Wrong FF_VOLUMES setting
+#endif
+static FATFS* FatFs[FF_VOLUMES]; /* Pointer to the filesystem objects (logical drives) */
+static WORD Fsid; /* Filesystem mount ID */
+
+#if FF_FS_RPATH != 0
+static BYTE CurrVol; /* Current drive */
+#endif
+
+#if FF_FS_LOCK != 0
+static FILESEM Files[FF_FS_LOCK]; /* Open object lock semaphores */
+#endif
+
+#if FF_STR_VOLUME_ID
+#ifdef FF_VOLUME_STRS
+static const char* const VolumeStr[FF_VOLUMES] = {FF_VOLUME_STRS}; /* Pre-defined volume ID */
+#endif
+#endif
+
+
+/*--------------------------------*/
+/* LFN/Directory working buffer */
+/*--------------------------------*/
+
+#if FF_USE_LFN == 0 /* Non-LFN configuration */
+#if FF_FS_EXFAT
+#error LFN must be enabled when enable exFAT
+#endif
+#define DEF_NAMBUF
+#define INIT_NAMBUF(fs)
+#define FREE_NAMBUF()
+#define LEAVE_MKFS(res) return res
+
+#else /* LFN configurations */
+#if FF_MAX_LFN < 12 || FF_MAX_LFN > 255
+#error Wrong setting of FF_MAX_LFN
+#endif
+#if FF_LFN_BUF < FF_SFN_BUF || FF_SFN_BUF < 12
+#error Wrong setting of FF_LFN_BUF or FF_SFN_BUF
+#endif
+#if FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3
+#error Wrong setting of FF_LFN_UNICODE
+#endif
+static const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* FAT: Offset of LFN characters in the directory entry */
+#define MAXDIRB(nc) ((nc + 44U) / 15 * SZDIRE) /* exFAT: Size of directory entry block scratchpad buffer needed for the name length */
+
+#if FF_USE_LFN == 1 /* LFN enabled with static working buffer */
+#if FF_FS_EXFAT
+static BYTE DirBuf[MAXDIRB(FF_MAX_LFN)]; /* Directory entry block scratchpad buffer */
+#endif
+static WCHAR LfnBuf[FF_MAX_LFN + 1]; /* LFN working buffer */
+#define DEF_NAMBUF
+#define INIT_NAMBUF(fs)
+#define FREE_NAMBUF()
+#define LEAVE_MKFS(res) return res
+
+#elif FF_USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */
+#if FF_FS_EXFAT
+#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; BYTE dbuf[MAXDIRB(FF_MAX_LFN)]; /* LFN working buffer and directory entry block scratchpad buffer */
+#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; }
+#define FREE_NAMBUF()
+#else
+#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; /* LFN working buffer */
+#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; }
+#define FREE_NAMBUF()
+#endif
+#define LEAVE_MKFS(res) return res
+
+#elif FF_USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */
+#if FF_FS_EXFAT
+#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer and directory entry block scratchpad buffer */
+#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2 + MAXDIRB(FF_MAX_LFN)); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+FF_MAX_LFN+1); }
+#define FREE_NAMBUF() ff_memfree(lfn)
+#else
+#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer */
+#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; }
+#define FREE_NAMBUF() ff_memfree(lfn)
+#endif
+#define LEAVE_MKFS(res) { if (!work) ff_memfree(buf); return res; }
+#define MAX_MALLOC 0x4000 /* Must be >=FF_MAX_SS */
+
+#else
+#error Wrong setting of FF_USE_LFN
+
+#endif /* FF_USE_LFN == 1 */
+#endif /* FF_USE_LFN == 0 */
+
+
+
+/*--------------------------------*/
+/* Code conversion tables */
+/*--------------------------------*/
+
+#if FF_CODE_PAGE == 0 /* Run-time code page configuration */
+#define CODEPAGE CodePage
+static WORD CodePage; /* Current code page */
+static const BYTE *ExCvt, *DbcTbl; /* Pointer to current SBCS up-case table and DBCS code range table below */
+
+static const BYTE Ct437[] = TBL_CT437;
+static const BYTE Ct720[] = TBL_CT720;
+static const BYTE Ct737[] = TBL_CT737;
+static const BYTE Ct771[] = TBL_CT771;
+static const BYTE Ct775[] = TBL_CT775;
+static const BYTE Ct850[] = TBL_CT850;
+static const BYTE Ct852[] = TBL_CT852;
+static const BYTE Ct855[] = TBL_CT855;
+static const BYTE Ct857[] = TBL_CT857;
+static const BYTE Ct860[] = TBL_CT860;
+static const BYTE Ct861[] = TBL_CT861;
+static const BYTE Ct862[] = TBL_CT862;
+static const BYTE Ct863[] = TBL_CT863;
+static const BYTE Ct864[] = TBL_CT864;
+static const BYTE Ct865[] = TBL_CT865;
+static const BYTE Ct866[] = TBL_CT866;
+static const BYTE Ct869[] = TBL_CT869;
+static const BYTE Dc932[] = TBL_DC932;
+static const BYTE Dc936[] = TBL_DC936;
+static const BYTE Dc949[] = TBL_DC949;
+static const BYTE Dc950[] = TBL_DC950;
+
+#elif FF_CODE_PAGE < 900 /* Static code page configuration (SBCS) */
+#define CODEPAGE FF_CODE_PAGE
+static const BYTE ExCvt[] = MKCVTBL(TBL_CT, FF_CODE_PAGE);
+
+#else /* Static code page configuration (DBCS) */
+#define CODEPAGE FF_CODE_PAGE
+static const BYTE DbcTbl[] = MKCVTBL(TBL_DC, FF_CODE_PAGE);
+
+#endif
+
+
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Functions
+
+---------------------------------------------------------------------------*/
+
+/*-----------------------------------------------------------------------*/
+/* Load/Store multi-byte word in the FAT structure */
+/*-----------------------------------------------------------------------*/
+
+static WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */
+{
+ WORD rv;
+
+ rv = ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+
+static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */
+{
+ DWORD rv;
+
+ rv = ptr[3];
+ rv = rv << 8 | ptr[2];
+ rv = rv << 8 | ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+
+#if FF_FS_EXFAT
+static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */
+{
+ QWORD rv;
+
+ rv = ptr[7];
+ rv = rv << 8 | ptr[6];
+ rv = rv << 8 | ptr[5];
+ rv = rv << 8 | ptr[4];
+ rv = rv << 8 | ptr[3];
+ rv = rv << 8 | ptr[2];
+ rv = rv << 8 | ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+#endif
+
+#if !FF_FS_READONLY
+static void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+
+static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+
+#if FF_FS_EXFAT
+static void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+#endif
+#endif /* !FF_FS_READONLY */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* String functions */
+/*-----------------------------------------------------------------------*/
+
+/* Copy memory to memory */
+static void mem_cpy (void* dst, const void* src, UINT cnt)
+{
+ BYTE *d = (BYTE*)dst;
+ const BYTE *s = (const BYTE*)src;
+
+ if (cnt != 0) {
+ do {
+ *d++ = *s++;
+ } while (--cnt);
+ }
+}
+
+
+/* Fill memory block */
+static void mem_set (void* dst, int val, UINT cnt)
+{
+ BYTE *d = (BYTE*)dst;
+
+ do {
+ *d++ = (BYTE)val;
+ } while (--cnt);
+}
+
+
+/* Compare memory block */
+static int mem_cmp (const void* dst, const void* src, UINT cnt) /* ZR:same, NZ:different */
+{
+ const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src;
+ int r = 0;
+
+ do {
+ r = *d++ - *s++;
+ } while (--cnt && r == 0);
+
+ return r;
+}
+
+
+/* Check if chr is contained in the string */
+static int chk_chr (const char* str, int chr) /* NZ:contained, ZR:not contained */
+{
+ while (*str && *str != chr) str++;
+ return *str;
+}
+
+
+/* Test if the character is DBC 1st byte */
+static int dbc_1st (BYTE c)
+{
+#if FF_CODE_PAGE == 0 /* Variable code page */
+ if (DbcTbl && c >= DbcTbl[0]) {
+ if (c <= DbcTbl[1]) return 1; /* 1st byte range 1 */
+ if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; /* 1st byte range 2 */
+ }
+#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */
+ if (c >= DbcTbl[0]) {
+ if (c <= DbcTbl[1]) return 1;
+ if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1;
+ }
+#else /* SBCS fixed code page */
+ if (c != 0) return 0; /* Always false */
+#endif
+ return 0;
+}
+
+
+/* Test if the character is DBC 2nd byte */
+static int dbc_2nd (BYTE c)
+{
+#if FF_CODE_PAGE == 0 /* Variable code page */
+ if (DbcTbl && c >= DbcTbl[4]) {
+ if (c <= DbcTbl[5]) return 1; /* 2nd byte range 1 */
+ if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; /* 2nd byte range 2 */
+ if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; /* 2nd byte range 3 */
+ }
+#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */
+ if (c >= DbcTbl[4]) {
+ if (c <= DbcTbl[5]) return 1;
+ if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1;
+ if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1;
+ }
+#else /* SBCS fixed code page */
+ if (c != 0) return 0; /* Always false */
+#endif
+ return 0;
+}
+
+
+#if FF_USE_LFN
+
+/* Get a character from TCHAR string in defined API encodeing */
+static DWORD tchar2uni ( /* Returns character in UTF-16 encoding (>=0x10000 on double encoding unit, 0xFFFFFFFF on decode error) */
+ const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */
+)
+{
+ DWORD uc;
+ const TCHAR *p = *str;
+
+#if FF_LFN_UNICODE == 1 /* UTF-16 input */
+ WCHAR wc;
+
+ uc = *p++; /* Get a unit */
+ if (IsSurrogate(uc)) { /* Surrogate? */
+ wc = *p++; /* Get low surrogate */
+ if (!IsSurrogateH(uc) || !IsSurrogateL(wc)) return 0xFFFFFFFF; /* Wrong surrogate? */
+ uc = uc << 16 | wc;
+ }
+
+#elif FF_LFN_UNICODE == 2 /* UTF-8 input */
+ BYTE b;
+ int nf;
+
+ uc = (BYTE)*p++; /* Get a unit */
+ if (uc & 0x80) { /* Multiple byte code? */
+ if ((uc & 0xE0) == 0xC0) { /* 2-byte sequence? */
+ uc &= 0x1F; nf = 1;
+ } else {
+ if ((uc & 0xF0) == 0xE0) { /* 3-byte sequence? */
+ uc &= 0x0F; nf = 2;
+ } else {
+ if ((uc & 0xF8) == 0xF0) { /* 4-byte sequence? */
+ uc &= 0x07; nf = 3;
+ } else { /* Wrong sequence */
+ return 0xFFFFFFFF;
+ }
+ }
+ }
+ do { /* Get trailing bytes */
+ b = (BYTE)*p++;
+ if ((b & 0xC0) != 0x80) return 0xFFFFFFFF; /* Wrong sequence? */
+ uc = uc << 6 | (b & 0x3F);
+ } while (--nf != 0);
+ if (uc < 0x80 || IsSurrogate(uc) || uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */
+ if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */
+ }
+
+#elif FF_LFN_UNICODE == 3 /* UTF-32 input */
+ uc = (TCHAR)*p++; /* Get a unit */
+ if (uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */
+ if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */
+
+#else /* ANSI/OEM input */
+ BYTE b;
+ WCHAR wc;
+
+ wc = (BYTE)*p++; /* Get a byte */
+ if (dbc_1st((BYTE)wc)) { /* Is it a DBC 1st byte? */
+ b = (BYTE)*p++; /* Get 2nd byte */
+ if (!dbc_2nd(b)) return 0xFFFFFFFF; /* Invalid code? */
+ wc = (wc << 8) + b; /* Make a DBC */
+ }
+ if (wc != 0) {
+ wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM ==> Unicode */
+ if (wc == 0) return 0xFFFFFFFF; /* Invalid code? */
+ }
+ uc = wc;
+
+#endif
+ *str = p; /* Next read pointer */
+ return uc;
+}
+
+
+/* Output a TCHAR string in defined API encoding */
+static BYTE put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */
+ DWORD chr, /* UTF-16 encoded character (Double encoding unit char if >=0x10000) */
+ TCHAR* buf, /* Output buffer */
+ UINT szb /* Size of the buffer */
+)
+{
+#if FF_LFN_UNICODE == 1 /* UTF-16 output */
+ WCHAR hs, wc;
+
+ hs = (WCHAR)(chr >> 16);
+ wc = (WCHAR)chr;
+ if (hs == 0) { /* Single encoding unit? */
+ if (szb < 1 || IsSurrogate(wc)) return 0; /* Buffer overflow or wrong code? */
+ *buf = wc;
+ return 1;
+ }
+ if (szb < 2 || !IsSurrogateH(hs) || !IsSurrogateL(wc)) return 0; /* Buffer overflow or wrong surrogate? */
+ *buf++ = hs;
+ *buf++ = wc;
+ return 2;
+
+#elif FF_LFN_UNICODE == 2 /* UTF-8 output */
+ DWORD hc;
+
+ if (chr < 0x80) { /* Single byte code? */
+ if (szb < 1) return 0; /* Buffer overflow? */
+ *buf = (TCHAR)chr;
+ return 1;
+ }
+ if (chr < 0x800) { /* 2-byte sequence? */
+ if (szb < 2) return 0; /* Buffer overflow? */
+ *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F));
+ return 2;
+ }
+ if (chr < 0x10000) { /* 3-byte sequence? */
+ if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */
+ *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F));
+ return 3;
+ }
+ /* 4-byte sequence */
+ if (szb < 4) return 0; /* Buffer overflow? */
+ hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */
+ chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */
+ if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */
+ chr = (hc | chr) + 0x10000;
+ *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07));
+ *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F));
+ return 4;
+
+#elif FF_LFN_UNICODE == 3 /* UTF-32 output */
+ DWORD hc;
+
+ if (szb < 1) return 0; /* Buffer overflow? */
+ if (chr >= 0x10000) { /* Out of BMP? */
+ hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */
+ chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */
+ if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */
+ chr = (hc | chr) + 0x10000;
+ }
+ *buf++ = (TCHAR)chr;
+ return 1;
+
+#else /* ANSI/OEM output */
+ WCHAR wc;
+
+ wc = ff_uni2oem(chr, CODEPAGE);
+ if (wc >= 0x100) { /* Is this a DBC? */
+ if (szb < 2) return 0;
+ *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */
+ *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */
+ return 2;
+ }
+ if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */
+ *buf++ = (TCHAR)wc; /* Store the character */
+ return 1;
+#endif
+}
+#endif /* FF_USE_LFN */
+
+
+#if FF_FS_REENTRANT
+/*-----------------------------------------------------------------------*/
+/* Request/Release grant to access the volume */
+/*-----------------------------------------------------------------------*/
+static int lock_fs ( /* 1:Ok, 0:timeout */
+ FATFS* fs /* Filesystem object */
+)
+{
+ return ff_req_grant(fs->sobj);
+}
+
+
+static void unlock_fs (
+ FATFS* fs, /* Filesystem object */
+ FRESULT res /* Result code to be returned */
+)
+{
+ if (fs && res != FR_NOT_ENABLED && res != FR_INVALID_DRIVE && res != FR_TIMEOUT) {
+ ff_rel_grant(fs->sobj);
+ }
+}
+
+#endif
+
+
+
+#if FF_FS_LOCK != 0
+/*-----------------------------------------------------------------------*/
+/* File lock control functions */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT chk_lock ( /* Check if the file can be accessed */
+ DIR* dp, /* Directory object pointing the file to be checked */
+ int acc /* Desired access type (0:Read mode open, 1:Write mode open, 2:Delete or rename) */
+)
+{
+ UINT i, be;
+
+ /* Search open object table for the object */
+ be = 0;
+ for (i = 0; i < FF_FS_LOCK; i++) {
+ if (Files[i].fs) { /* Existing entry */
+ if (Files[i].fs == dp->obj.fs && /* Check if the object matches with an open object */
+ Files[i].clu == dp->obj.sclust &&
+ Files[i].ofs == dp->dptr) break;
+ } else { /* Blank entry */
+ be = 1;
+ }
+ }
+ if (i == FF_FS_LOCK) { /* The object has not been opened */
+ return (!be && acc != 2) ? FR_TOO_MANY_OPEN_FILES : FR_OK; /* Is there a blank entry for new object? */
+ }
+
+ /* The object was opened. Reject any open against writing file and all write mode open */
+ return (acc != 0 || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK;
+}
+
+
+static int enq_lock (void) /* Check if an entry is available for a new object */
+{
+ UINT i;
+
+ for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ;
+ return (i == FF_FS_LOCK) ? 0 : 1;
+}
+
+
+static UINT inc_lock ( /* Increment object open counter and returns its index (0:Internal error) */
+ DIR* dp, /* Directory object pointing the file to register or increment */
+ int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */
+)
+{
+ UINT i;
+
+
+ for (i = 0; i < FF_FS_LOCK; i++) { /* Find the object */
+ if (Files[i].fs == dp->obj.fs &&
+ Files[i].clu == dp->obj.sclust &&
+ Files[i].ofs == dp->dptr) break;
+ }
+
+ if (i == FF_FS_LOCK) { /* Not opened. Register it as new. */
+ for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ;
+ if (i == FF_FS_LOCK) return 0; /* No free entry to register (int err) */
+ Files[i].fs = dp->obj.fs;
+ Files[i].clu = dp->obj.sclust;
+ Files[i].ofs = dp->dptr;
+ Files[i].ctr = 0;
+ }
+
+ if (acc >= 1 && Files[i].ctr) return 0; /* Access violation (int err) */
+
+ Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */
+
+ return i + 1; /* Index number origin from 1 */
+}
+
+
+static FRESULT dec_lock ( /* Decrement object open counter */
+ UINT i /* Semaphore index (1..) */
+)
+{
+ WORD n;
+ FRESULT res;
+
+
+ if (--i < FF_FS_LOCK) { /* Index number origin from 0 */
+ n = Files[i].ctr;
+ if (n == 0x100) n = 0; /* If write mode open, delete the entry */
+ if (n > 0) n--; /* Decrement read mode open count */
+ Files[i].ctr = n;
+ if (n == 0) Files[i].fs = 0; /* Delete the entry if open count gets zero */
+ res = FR_OK;
+ } else {
+ res = FR_INT_ERR; /* Invalid index nunber */
+ }
+ return res;
+}
+
+
+static void clear_lock ( /* Clear lock entries of the volume */
+ FATFS *fs
+)
+{
+ UINT i;
+
+ for (i = 0; i < FF_FS_LOCK; i++) {
+ if (Files[i].fs == fs) Files[i].fs = 0;
+ }
+}
+
+#endif /* FF_FS_LOCK != 0 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Move/Flush disk access window in the filesystem object */
+/*-----------------------------------------------------------------------*/
+#if !FF_FS_READONLY
+static FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS* fs /* Filesystem object */
+)
+{
+ FRESULT res = FR_OK;
+
+
+ if (fs->wflag) { /* Is the disk access window dirty */
+ if (disk_write(fs->pdrv, fs->win, fs->winsect, 1) == RES_OK) { /* Write back the window */
+ fs->wflag = 0; /* Clear window dirty flag */
+ if (fs->winsect - fs->fatbase < fs->fsize) { /* Is it in the 1st FAT? */
+ if (fs->n_fats == 2) disk_write(fs->pdrv, fs->win, fs->winsect + fs->fsize, 1); /* Reflect it to 2nd FAT if needed */
+ }
+ } else {
+ res = FR_DISK_ERR;
+ }
+ }
+ return res;
+}
+#endif
+
+
+static FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS* fs, /* Filesystem object */
+ DWORD sector /* Sector number to make appearance in the fs->win[] */
+)
+{
+ FRESULT res = FR_OK;
+
+
+ if (sector != fs->winsect) { /* Window offset changed? */
+#if !FF_FS_READONLY
+ res = sync_window(fs); /* Write-back changes */
+#endif
+ if (res == FR_OK) { /* Fill sector window with new data */
+ if (disk_read(fs->pdrv, fs->win, sector, 1) != RES_OK) {
+ sector = 0xFFFFFFFF; /* Invalidate window if read data is not valid */
+ res = FR_DISK_ERR;
+ }
+ fs->winsect = sector;
+ }
+ }
+ return res;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Synchronize filesystem and data on the storage */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS* fs /* Filesystem object */
+)
+{
+ FRESULT res;
+
+
+ res = sync_window(fs);
+ if (res == FR_OK) {
+ if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */
+ /* Create FSInfo structure */
+ mem_set(fs->win, 0, sizeof fs->win);
+ st_word(fs->win + BS_55AA, 0xAA55);
+ st_dword(fs->win + FSI_LeadSig, 0x41615252);
+ st_dword(fs->win + FSI_StrucSig, 0x61417272);
+ st_dword(fs->win + FSI_Free_Count, fs->free_clst);
+ st_dword(fs->win + FSI_Nxt_Free, fs->last_clst);
+ /* Write it into the FSInfo sector */
+ fs->winsect = fs->volbase + 1;
+ disk_write(fs->pdrv, fs->win, fs->winsect, 1);
+ fs->fsi_flag = 0;
+ }
+ /* Make sure that no pending write process in the lower layer */
+ if (disk_ioctl(fs->pdrv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR;
+ }
+
+ return res;
+}
+
+#endif
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Get physical sector number from cluster number */
+/*-----------------------------------------------------------------------*/
+
+static DWORD clst2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */
+ FATFS* fs, /* Filesystem object */
+ DWORD clst /* Cluster# to be converted */
+)
+{
+ clst -= 2; /* Cluster number is origin from 2 */
+ if (clst >= fs->n_fatent - 2) return 0; /* Is it invalid cluster number? */
+ return fs->database + fs->csize * clst; /* Start sector number of the cluster */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT access - Read value of a FAT entry */
+/*-----------------------------------------------------------------------*/
+
+static DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */
+ FFOBJID* obj, /* Corresponding object */
+ DWORD clst /* Cluster number to get the value */
+)
+{
+ UINT wc, bc;
+ DWORD val;
+ FATFS *fs = obj->fs;
+
+
+ if (clst < 2 || clst >= fs->n_fatent) { /* Check if in valid range */
+ val = 1; /* Internal error */
+
+ } else {
+ val = 0xFFFFFFFF; /* Default value falls on disk error */
+
+ switch (fs->fs_type) {
+ case FS_FAT12 :
+ bc = (UINT)clst; bc += bc / 2;
+ if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break;
+ wc = fs->win[bc++ % SS(fs)]; /* Get 1st byte of the entry */
+ if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break;
+ wc |= fs->win[bc % SS(fs)] << 8; /* Merge 2nd byte of the entry */
+ val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF); /* Adjust bit position */
+ break;
+
+ case FS_FAT16 :
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break;
+ val = ld_word(fs->win + clst * 2 % SS(fs)); /* Simple WORD array */
+ break;
+
+ case FS_FAT32 :
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break;
+ val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x0FFFFFFF; /* Simple DWORD array but mask out upper 4 bits */
+ break;
+#if FF_FS_EXFAT
+ case FS_EXFAT :
+ if ((obj->objsize != 0 && obj->sclust != 0) || obj->stat == 0) { /* Object except root dir must have valid data length */
+ DWORD cofs = clst - obj->sclust; /* Offset from start cluster */
+ DWORD clen = (DWORD)((obj->objsize - 1) / SS(fs)) / fs->csize; /* Number of clusters - 1 */
+
+ if (obj->stat == 2 && cofs <= clen) { /* Is it a contiguous chain? */
+ val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* No data on the FAT, generate the value */
+ break;
+ }
+ if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the 1st fragment? */
+ val = clst + 1; /* Generate the value */
+ break;
+ }
+ if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */
+ if (obj->n_frag != 0) { /* Is it on the growing edge? */
+ val = 0x7FFFFFFF; /* Generate EOC */
+ } else {
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break;
+ val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x7FFFFFFF;
+ }
+ break;
+ }
+ }
+ /* go to default */
+#endif
+ default:
+ val = 1; /* Internal error */
+ }
+ }
+
+ return val;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT access - Change value of a FAT entry */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */
+ FATFS* fs, /* Corresponding filesystem object */
+ DWORD clst, /* FAT index number (cluster number) to be changed */
+ DWORD val /* New value to be set to the entry */
+)
+{
+ UINT bc;
+ BYTE *p;
+ FRESULT res = FR_INT_ERR;
+
+
+ if (clst >= 2 && clst < fs->n_fatent) { /* Check if in valid range */
+ switch (fs->fs_type) {
+ case FS_FAT12 :
+ bc = (UINT)clst; bc += bc / 2; /* bc: byte offset of the entry */
+ res = move_window(fs, fs->fatbase + (bc / SS(fs)));
+ if (res != FR_OK) break;
+ p = fs->win + bc++ % SS(fs);
+ *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; /* Put 1st byte */
+ fs->wflag = 1;
+ res = move_window(fs, fs->fatbase + (bc / SS(fs)));
+ if (res != FR_OK) break;
+ p = fs->win + bc % SS(fs);
+ *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); /* Put 2nd byte */
+ fs->wflag = 1;
+ break;
+
+ case FS_FAT16 :
+ res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2)));
+ if (res != FR_OK) break;
+ st_word(fs->win + clst * 2 % SS(fs), (WORD)val); /* Simple WORD array */
+ fs->wflag = 1;
+ break;
+
+ case FS_FAT32 :
+#if FF_FS_EXFAT
+ case FS_EXFAT :
+#endif
+ res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4)));
+ if (res != FR_OK) break;
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ val = (val & 0x0FFFFFFF) | (ld_dword(fs->win + clst * 4 % SS(fs)) & 0xF0000000);
+ }
+ st_dword(fs->win + clst * 4 % SS(fs), val);
+ fs->wflag = 1;
+ break;
+ }
+ }
+ return res;
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+#if FF_FS_EXFAT && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* exFAT: Accessing FAT and Allocation Bitmap */
+/*-----------------------------------------------------------------------*/
+
+/*--------------------------------------*/
+/* Find a contiguous free cluster block */
+/*--------------------------------------*/
+
+static DWORD find_bitmap ( /* 0:Not found, 2..:Cluster block found, 0xFFFFFFFF:Disk error */
+ FATFS* fs, /* Filesystem object */
+ DWORD clst, /* Cluster number to scan from */
+ DWORD ncl /* Number of contiguous clusters to find (1..) */
+)
+{
+ BYTE bm, bv;
+ UINT i;
+ DWORD val, scl, ctr;
+
+
+ clst -= 2; /* The first bit in the bitmap corresponds to cluster #2 */
+ if (clst >= fs->n_fatent - 2) clst = 0;
+ scl = val = clst; ctr = 0;
+ for (;;) {
+ if (move_window(fs, fs->bitbase + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF;
+ i = val / 8 % SS(fs); bm = 1 << (val % 8);
+ do {
+ do {
+ bv = fs->win[i] & bm; bm <<= 1; /* Get bit value */
+ if (++val >= fs->n_fatent - 2) { /* Next cluster (with wrap-around) */
+ val = 0; bm = 0; i = SS(fs);
+ }
+ if (bv == 0) { /* Is it a free cluster? */
+ if (++ctr == ncl) return scl + 2; /* Check if run length is sufficient for required */
+ } else {
+ scl = val; ctr = 0; /* Encountered a cluster in-use, restart to scan */
+ }
+ if (val == clst) return 0; /* All cluster scanned? */
+ } while (bm != 0);
+ bm = 1;
+ } while (++i < SS(fs));
+ }
+}
+
+
+/*----------------------------------------*/
+/* Set/Clear a block of allocation bitmap */
+/*----------------------------------------*/
+
+static FRESULT change_bitmap (
+ FATFS* fs, /* Filesystem object */
+ DWORD clst, /* Cluster number to change from */
+ DWORD ncl, /* Number of clusters to be changed */
+ int bv /* bit value to be set (0 or 1) */
+)
+{
+ BYTE bm;
+ UINT i;
+ DWORD sect;
+
+
+ clst -= 2; /* The first bit corresponds to cluster #2 */
+ sect = fs->bitbase + clst / 8 / SS(fs); /* Sector address */
+ i = clst / 8 % SS(fs); /* Byte offset in the sector */
+ bm = 1 << (clst % 8); /* Bit mask in the byte */
+ for (;;) {
+ if (move_window(fs, sect++) != FR_OK) return FR_DISK_ERR;
+ do {
+ do {
+ if (bv == (int)((fs->win[i] & bm) != 0)) return FR_INT_ERR; /* Is the bit expected value? */
+ fs->win[i] ^= bm; /* Flip the bit */
+ fs->wflag = 1;
+ if (--ncl == 0) return FR_OK; /* All bits processed? */
+ } while (bm <<= 1); /* Next bit */
+ bm = 1;
+ } while (++i < SS(fs)); /* Next byte */
+ i = 0;
+ }
+}
+
+
+/*---------------------------------------------*/
+/* Fill the first fragment of the FAT chain */
+/*---------------------------------------------*/
+
+static FRESULT fill_first_frag (
+ FFOBJID* obj /* Pointer to the corresponding object */
+)
+{
+ FRESULT res;
+ DWORD cl, n;
+
+
+ if (obj->stat == 3) { /* Has the object been changed 'fragmented' in this session? */
+ for (cl = obj->sclust, n = obj->n_cont; n; cl++, n--) { /* Create cluster chain on the FAT */
+ res = put_fat(obj->fs, cl, cl + 1);
+ if (res != FR_OK) return res;
+ }
+ obj->stat = 0; /* Change status 'FAT chain is valid' */
+ }
+ return FR_OK;
+}
+
+
+/*---------------------------------------------*/
+/* Fill the last fragment of the FAT chain */
+/*---------------------------------------------*/
+
+static FRESULT fill_last_frag (
+ FFOBJID* obj, /* Pointer to the corresponding object */
+ DWORD lcl, /* Last cluster of the fragment */
+ DWORD term /* Value to set the last FAT entry */
+)
+{
+ FRESULT res;
+
+
+ while (obj->n_frag > 0) { /* Create the chain of last fragment */
+ res = put_fat(obj->fs, lcl - obj->n_frag + 1, (obj->n_frag > 1) ? lcl - obj->n_frag + 2 : term);
+ if (res != FR_OK) return res;
+ obj->n_frag--;
+ }
+ return FR_OK;
+}
+
+#endif /* FF_FS_EXFAT && !FF_FS_READONLY */
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Remove a cluster chain */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */
+ FFOBJID* obj, /* Corresponding object */
+ DWORD clst, /* Cluster to remove a chain from */
+ DWORD pclst /* Previous cluster of clst (0 if entire chain) */
+)
+{
+ FRESULT res = FR_OK;
+ DWORD nxt;
+ FATFS *fs = obj->fs;
+#if FF_FS_EXFAT || FF_USE_TRIM
+ DWORD scl = clst, ecl = clst;
+#endif
+#if FF_USE_TRIM
+ DWORD rt[2];
+#endif
+
+ if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Check if in valid range */
+
+ /* Mark the previous cluster 'EOC' on the FAT if it exists */
+ if (pclst != 0 && (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT || obj->stat != 2)) {
+ res = put_fat(fs, pclst, 0xFFFFFFFF);
+ if (res != FR_OK) return res;
+ }
+
+ /* Remove the chain */
+ do {
+ nxt = get_fat(obj, clst); /* Get cluster status */
+ if (nxt == 0) break; /* Empty cluster? */
+ if (nxt == 1) return FR_INT_ERR; /* Internal error? */
+ if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error? */
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ res = put_fat(fs, clst, 0); /* Mark the cluster 'free' on the FAT */
+ if (res != FR_OK) return res;
+ }
+ if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */
+ fs->free_clst++;
+ fs->fsi_flag |= 1;
+ }
+#if FF_FS_EXFAT || FF_USE_TRIM
+ if (ecl + 1 == nxt) { /* Is next cluster contiguous? */
+ ecl = nxt;
+ } else { /* End of contiguous cluster block */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */
+ if (res != FR_OK) return res;
+ }
+#endif
+#if FF_USE_TRIM
+ rt[0] = clst2sect(fs, scl); /* Start of data area freed */
+ rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area freed */
+ disk_ioctl(fs->pdrv, CTRL_TRIM, rt); /* Inform device the data in the block is no longer needed */
+#endif
+ scl = ecl = nxt;
+ }
+#endif
+ clst = nxt; /* Next cluster */
+ } while (clst < fs->n_fatent); /* Repeat while not the last link */
+
+#if FF_FS_EXFAT
+ /* Some post processes for chain status */
+ if (fs->fs_type == FS_EXFAT) {
+ if (pclst == 0) { /* Has the entire chain been removed? */
+ obj->stat = 0; /* Change the chain status 'initial' */
+ } else {
+ if (obj->stat == 0) { /* Is it a fragmented chain from the beginning of this session? */
+ clst = obj->sclust; /* Follow the chain to check if it gets contiguous */
+ while (clst != pclst) {
+ nxt = get_fat(obj, clst);
+ if (nxt < 2) return FR_INT_ERR;
+ if (nxt == 0xFFFFFFFF) return FR_DISK_ERR;
+ if (nxt != clst + 1) break; /* Not contiguous? */
+ clst++;
+ }
+ if (clst == pclst) { /* Has the chain got contiguous again? */
+ obj->stat = 2; /* Change the chain status 'contiguous' */
+ }
+ } else {
+ if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Was the chain fragmented in this session and got contiguous again? */
+ obj->stat = 2; /* Change the chain status 'contiguous' */
+ }
+ }
+ }
+ }
+#endif
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Stretch a chain or Create a new chain */
+/*-----------------------------------------------------------------------*/
+
+static DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */
+ FFOBJID* obj, /* Corresponding object */
+ DWORD clst /* Cluster# to stretch, 0:Create a new chain */
+)
+{
+ DWORD cs, ncl, scl;
+ FRESULT res;
+ FATFS *fs = obj->fs;
+
+
+ if (clst == 0) { /* Create a new chain */
+ scl = fs->last_clst; /* Suggested cluster to start to find */
+ if (scl == 0 || scl >= fs->n_fatent) scl = 1;
+ }
+ else { /* Stretch a chain */
+ cs = get_fat(obj, clst); /* Check the cluster status */
+ if (cs < 2) return 1; /* Test for insanity */
+ if (cs == 0xFFFFFFFF) return cs; /* Test for disk error */
+ if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */
+ scl = clst; /* Cluster to start to find */
+ }
+ if (fs->free_clst == 0) return 0; /* No free cluster */
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ ncl = find_bitmap(fs, scl, 1); /* Find a free cluster */
+ if (ncl == 0 || ncl == 0xFFFFFFFF) return ncl; /* No free cluster or hard error? */
+ res = change_bitmap(fs, ncl, 1, 1); /* Mark the cluster 'in use' */
+ if (res == FR_INT_ERR) return 1;
+ if (res == FR_DISK_ERR) return 0xFFFFFFFF;
+ if (clst == 0) { /* Is it a new chain? */
+ obj->stat = 2; /* Set status 'contiguous' */
+ } else { /* It is a stretched chain */
+ if (obj->stat == 2 && ncl != scl + 1) { /* Is the chain got fragmented? */
+ obj->n_cont = scl - obj->sclust; /* Set size of the contiguous part */
+ obj->stat = 3; /* Change status 'just fragmented' */
+ }
+ }
+ if (obj->stat != 2) { /* Is the file non-contiguous? */
+ if (ncl == clst + 1) { /* Is the cluster next to previous one? */
+ obj->n_frag = obj->n_frag ? obj->n_frag + 1 : 2; /* Increment size of last framgent */
+ } else { /* New fragment */
+ if (obj->n_frag == 0) obj->n_frag = 1;
+ res = fill_last_frag(obj, clst, ncl); /* Fill last fragment on the FAT and link it to new one */
+ if (res == FR_OK) obj->n_frag = 1;
+ }
+ }
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ ncl = 0;
+ if (scl == clst) { /* Stretching an existing chain? */
+ ncl = scl + 1; /* Test if next cluster is free */
+ if (ncl >= fs->n_fatent) ncl = 2;
+ cs = get_fat(obj, ncl); /* Get next cluster status */
+ if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */
+ if (cs != 0) { /* Not free? */
+ cs = fs->last_clst; /* Start at suggested cluster if it is valid */
+ if (cs >= 2 && cs < fs->n_fatent) scl = cs;
+ ncl = 0;
+ }
+ }
+ if (ncl == 0) { /* The new cluster cannot be contiguous and find another fragment */
+ ncl = scl; /* Start cluster */
+ for (;;) {
+ ncl++; /* Next cluster */
+ if (ncl >= fs->n_fatent) { /* Check wrap-around */
+ ncl = 2;
+ if (ncl > scl) return 0; /* No free cluster found? */
+ }
+ cs = get_fat(obj, ncl); /* Get the cluster status */
+ if (cs == 0) break; /* Found a free cluster? */
+ if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */
+ if (ncl == scl) return 0; /* No free cluster found? */
+ }
+ }
+ res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */
+ if (res == FR_OK && clst != 0) {
+ res = put_fat(fs, clst, ncl); /* Link it from the previous one if needed */
+ }
+ }
+
+ if (res == FR_OK) { /* Update FSINFO if function succeeded. */
+ fs->last_clst = ncl;
+ if (fs->free_clst <= fs->n_fatent - 2) fs->free_clst--;
+ fs->fsi_flag |= 1;
+ } else {
+ ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; /* Failed. Generate error status */
+ }
+
+ return ncl; /* Return new cluster number or error status */
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+#if FF_USE_FASTSEEK
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Convert offset into cluster with link map table */
+/*-----------------------------------------------------------------------*/
+
+static DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t ofs /* File offset to be converted to cluster# */
+)
+{
+ DWORD cl, ncl, *tbl;
+ FATFS *fs = fp->obj.fs;
+
+
+ tbl = fp->cltbl + 1; /* Top of CLMT */
+ cl = (DWORD)(ofs / SS(fs) / fs->csize); /* Cluster order from top of the file */
+ for (;;) {
+ ncl = *tbl++; /* Number of cluters in the fragment */
+ if (ncl == 0) return 0; /* End of table? (error) */
+ if (cl < ncl) break; /* In this fragment? */
+ cl -= ncl; tbl++; /* Next fragment */
+ }
+ return cl + *tbl; /* Return the cluster number */
+}
+
+#endif /* FF_USE_FASTSEEK */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Fill a cluster with zeros */
+/*-----------------------------------------------------------------------*/
+
+#if !FF_FS_READONLY
+static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS *fs, /* Filesystem object */
+ DWORD clst /* Directory table to clear */
+)
+{
+ DWORD sect;
+ UINT n, szb;
+ BYTE *ibuf;
+
+
+ if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */
+ sect = clst2sect(fs, clst); /* Top of the cluster */
+ fs->winsect = sect; /* Set window to top of the cluster */
+ mem_set(fs->win, 0, sizeof fs->win); /* Clear window buffer */
+#if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */
+ /* Allocate a temporary buffer */
+ for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ;
+ if (szb > SS(fs)) { /* Buffer allocated? */
+ mem_set(ibuf, 0, szb);
+ szb /= SS(fs); /* Bytes -> Sectors */
+ for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */
+ ff_memfree(ibuf);
+ } else
+#endif
+ {
+ ibuf = fs->win; szb = 1; /* Use window buffer (many single-sector writes may take a time) */
+ for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */
+ }
+ return (n == fs->csize) ? FR_OK : FR_DISK_ERR;
+}
+#endif /* !FF_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Set directory index */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp, /* Pointer to directory object */
+ DWORD ofs /* Offset of directory table */
+)
+{
+ DWORD csz, clst;
+ FATFS *fs = dp->obj.fs;
+
+
+ if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR) || ofs % SZDIRE) { /* Check range of offset and alignment */
+ return FR_INT_ERR;
+ }
+ dp->dptr = ofs; /* Set current offset */
+ clst = dp->obj.sclust; /* Table start cluster (0:root) */
+ if (clst == 0 && fs->fs_type >= FS_FAT32) { /* Replace cluster# 0 with root cluster# */
+ clst = fs->dirbase;
+ if (FF_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */
+ }
+
+ if (clst == 0) { /* Static table (root-directory on the FAT volume) */
+ if (ofs / SZDIRE >= fs->n_rootdir) return FR_INT_ERR; /* Is index out of range? */
+ dp->sect = fs->dirbase;
+
+ } else { /* Dynamic table (sub-directory or root-directory on the FAT32/exFAT volume) */
+ csz = (DWORD)fs->csize * SS(fs); /* Bytes per cluster */
+ while (ofs >= csz) { /* Follow cluster chain */
+ clst = get_fat(&dp->obj, clst); /* Get next cluster */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Reached to end of table or internal error */
+ ofs -= csz;
+ }
+ dp->sect = clst2sect(fs, clst);
+ }
+ dp->clust = clst; /* Current cluster# */
+ if (dp->sect == 0) return FR_INT_ERR;
+ dp->sect += ofs / SS(fs); /* Sector# of the directory entry */
+ dp->dir = fs->win + (ofs % SS(fs)); /* Pointer to the entry in the win[] */
+
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Move directory table index next */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */
+ DIR* dp, /* Pointer to the directory object */
+ int stretch /* 0: Do not stretch table, 1: Stretch table if needed */
+)
+{
+ DWORD ofs, clst;
+ FATFS *fs = dp->obj.fs;
+
+
+ ofs = dp->dptr + SZDIRE; /* Next entry */
+ if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) dp->sect = 0; /* Disable it if the offset reached the max value */
+ if (dp->sect == 0) return FR_NO_FILE; /* Report EOT if it has been disabled */
+
+ if (ofs % SS(fs) == 0) { /* Sector changed? */
+ dp->sect++; /* Next sector */
+
+ if (dp->clust == 0) { /* Static table */
+ if (ofs / SZDIRE >= fs->n_rootdir) { /* Report EOT if it reached end of static table */
+ dp->sect = 0; return FR_NO_FILE;
+ }
+ }
+ else { /* Dynamic table */
+ if ((ofs / SS(fs) & (fs->csize - 1)) == 0) { /* Cluster changed? */
+ clst = get_fat(&dp->obj, dp->clust); /* Get next cluster */
+ if (clst <= 1) return FR_INT_ERR; /* Internal error */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (clst >= fs->n_fatent) { /* It reached end of dynamic table */
+#if !FF_FS_READONLY
+ if (!stretch) { /* If no stretch, report EOT */
+ dp->sect = 0; return FR_NO_FILE;
+ }
+ clst = create_chain(&dp->obj, dp->clust); /* Allocate a cluster */
+ if (clst == 0) return FR_DENIED; /* No free cluster */
+ if (clst == 1) return FR_INT_ERR; /* Internal error */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (dir_clear(fs, clst) != FR_OK) return FR_DISK_ERR; /* Clean up the stretched table */
+ if (FF_FS_EXFAT) dp->obj.stat |= 4; /* exFAT: The directory has been stretched */
+#else
+ if (!stretch) dp->sect = 0; /* (this line is to suppress compiler warning) */
+ dp->sect = 0; return FR_NO_FILE; /* Report EOT */
+#endif
+ }
+ dp->clust = clst; /* Initialize data for new cluster */
+ dp->sect = clst2sect(fs, clst);
+ }
+ }
+ }
+ dp->dptr = ofs; /* Current entry */
+ dp->dir = fs->win + ofs % SS(fs); /* Pointer to the entry in the win[] */
+
+ return FR_OK;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Reserve a block of directory entries */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp, /* Pointer to the directory object */
+ UINT nent /* Number of contiguous entries to allocate */
+)
+{
+ FRESULT res;
+ UINT n;
+ FATFS *fs = dp->obj.fs;
+
+
+ res = dir_sdi(dp, 0);
+ if (res == FR_OK) {
+ n = 0;
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+#if FF_FS_EXFAT
+ if ((fs->fs_type == FS_EXFAT) ? (int)((dp->dir[XDIR_Type] & 0x80) == 0) : (int)(dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0)) {
+#else
+ if (dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0) {
+#endif
+ if (++n == nent) break; /* A block of contiguous free entries is found */
+ } else {
+ n = 0; /* Not a blank entry. Restart to search */
+ }
+ res = dir_next(dp, 1);
+ } while (res == FR_OK); /* Next entry with table stretch enabled */
+ }
+
+ if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */
+ return res;
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT: Directory handling - Load/Store start cluster number */
+/*-----------------------------------------------------------------------*/
+
+static DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */
+ FATFS* fs, /* Pointer to the fs object */
+ const BYTE* dir /* Pointer to the key entry */
+)
+{
+ DWORD cl;
+
+ cl = ld_word(dir + DIR_FstClusLO);
+ if (fs->fs_type == FS_FAT32) {
+ cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16;
+ }
+
+ return cl;
+}
+
+
+#if !FF_FS_READONLY
+static void st_clust (
+ FATFS* fs, /* Pointer to the fs object */
+ BYTE* dir, /* Pointer to the key entry */
+ DWORD cl /* Value to be set */
+)
+{
+ st_word(dir + DIR_FstClusLO, (WORD)cl);
+ if (fs->fs_type == FS_FAT32) {
+ st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16));
+ }
+}
+#endif
+
+
+
+#if FF_USE_LFN
+/*--------------------------------------------------------*/
+/* FAT-LFN: Compare a part of file name with an LFN entry */
+/*--------------------------------------------------------*/
+
+static int cmp_lfn ( /* 1:matched, 0:not matched */
+ const WCHAR* lfnbuf, /* Pointer to the LFN working buffer to be compared */
+ BYTE* dir /* Pointer to the directory entry containing the part of LFN */
+)
+{
+ UINT i, s;
+ WCHAR wc, uc;
+
+
+ if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */
+
+ i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */
+
+ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */
+ uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */
+ if (wc != 0) {
+ if (i >= FF_MAX_LFN + 1 || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */
+ return 0; /* Not matched */
+ }
+ wc = uc;
+ } else {
+ if (uc != 0xFFFF) return 0; /* Check filler */
+ }
+ }
+
+ if ((dir[LDIR_Ord] & LLEF) && wc && lfnbuf[i]) return 0; /* Last segment matched but different length */
+
+ return 1; /* The part of LFN matched */
+}
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT
+/*-----------------------------------------------------*/
+/* FAT-LFN: Pick a part of file name from an LFN entry */
+/*-----------------------------------------------------*/
+
+static int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */
+ WCHAR* lfnbuf, /* Pointer to the LFN working buffer */
+ BYTE* dir /* Pointer to the LFN entry */
+)
+{
+ UINT i, s;
+ WCHAR wc, uc;
+
+
+ if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO is 0 */
+
+ i = ((dir[LDIR_Ord] & ~LLEF) - 1) * 13; /* Offset in the LFN buffer */
+
+ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */
+ uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */
+ if (wc != 0) {
+ if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */
+ lfnbuf[i++] = wc = uc; /* Store it */
+ } else {
+ if (uc != 0xFFFF) return 0; /* Check filler */
+ }
+ }
+
+ if (dir[LDIR_Ord] & LLEF && wc != 0) { /* Put terminator if it is the last LFN part and not terminated */
+ if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */
+ lfnbuf[i] = 0;
+ }
+
+ return 1; /* The part of LFN is valid */
+}
+#endif
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------*/
+/* FAT-LFN: Create an entry of LFN entries */
+/*-----------------------------------------*/
+
+static void put_lfn (
+ const WCHAR* lfn, /* Pointer to the LFN */
+ BYTE* dir, /* Pointer to the LFN entry to be created */
+ BYTE ord, /* LFN order (1-20) */
+ BYTE sum /* Checksum of the corresponding SFN */
+)
+{
+ UINT i, s;
+ WCHAR wc;
+
+
+ dir[LDIR_Chksum] = sum; /* Set checksum */
+ dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */
+ dir[LDIR_Type] = 0;
+ st_word(dir + LDIR_FstClusLO, 0);
+
+ i = (ord - 1) * 13; /* Get offset in the LFN working buffer */
+ s = wc = 0;
+ do {
+ if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */
+ st_word(dir + LfnOfs[s], wc); /* Put it */
+ if (wc == 0) wc = 0xFFFF; /* Padding characters for left locations */
+ } while (++s < 13);
+ if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */
+ dir[LDIR_Ord] = ord; /* Set the LFN order */
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_USE_LFN */
+
+
+
+#if FF_USE_LFN && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT-LFN: Create a Numbered SFN */
+/*-----------------------------------------------------------------------*/
+
+static void gen_numname (
+ BYTE* dst, /* Pointer to the buffer to store numbered SFN */
+ const BYTE* src, /* Pointer to SFN */
+ const WCHAR* lfn, /* Pointer to LFN */
+ UINT seq /* Sequence number */
+)
+{
+ BYTE ns[8], c;
+ UINT i, j;
+ WCHAR wc;
+ DWORD sr;
+
+
+ mem_cpy(dst, src, 11);
+
+ if (seq > 5) { /* In case of many collisions, generate a hash number instead of sequential number */
+ sr = seq;
+ while (*lfn) { /* Create a CRC as hash value */
+ wc = *lfn++;
+ for (i = 0; i < 16; i++) {
+ sr = (sr << 1) + (wc & 1);
+ wc >>= 1;
+ if (sr & 0x10000) sr ^= 0x11021;
+ }
+ }
+ seq = (UINT)sr;
+ }
+
+ /* itoa (hexdecimal) */
+ i = 7;
+ do {
+ c = (BYTE)((seq % 16) + '0');
+ if (c > '9') c += 7;
+ ns[i--] = c;
+ seq /= 16;
+ } while (seq);
+ ns[i] = '~';
+
+ /* Append the number to the SFN body */
+ for (j = 0; j < i && dst[j] != ' '; j++) {
+ if (dbc_1st(dst[j])) {
+ if (j == i - 1) break;
+ j++;
+ }
+ }
+ do {
+ dst[j++] = (i < 8) ? ns[i++] : ' ';
+ } while (j < 8);
+}
+#endif /* FF_USE_LFN && !FF_FS_READONLY */
+
+
+
+#if FF_USE_LFN
+/*-----------------------------------------------------------------------*/
+/* FAT-LFN: Calculate checksum of an SFN entry */
+/*-----------------------------------------------------------------------*/
+
+static BYTE sum_sfn (
+ const BYTE* dir /* Pointer to the SFN entry */
+)
+{
+ BYTE sum = 0;
+ UINT n = 11;
+
+ do {
+ sum = (sum >> 1) + (sum << 7) + *dir++;
+ } while (--n);
+ return sum;
+}
+
+#endif /* FF_USE_LFN */
+
+
+
+#if FF_FS_EXFAT
+/*-----------------------------------------------------------------------*/
+/* exFAT: Checksum */
+/*-----------------------------------------------------------------------*/
+
+static WORD xdir_sum ( /* Get checksum of the directoly entry block */
+ const BYTE* dir /* Directory entry block to be calculated */
+)
+{
+ UINT i, szblk;
+ WORD sum;
+
+
+ szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; /* Number of bytes of the entry block */
+ for (i = sum = 0; i < szblk; i++) {
+ if (i == XDIR_SetSum) { /* Skip 2-byte sum field */
+ i++;
+ } else {
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i];
+ }
+ }
+ return sum;
+}
+
+
+
+static WORD xname_sum ( /* Get check sum (to be used as hash) of the file name */
+ const WCHAR* name /* File name to be calculated */
+)
+{
+ WCHAR chr;
+ WORD sum = 0;
+
+
+ while ((chr = *name++) != 0) {
+ chr = (WCHAR)ff_wtoupper(chr); /* File name needs to be up-case converted */
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF);
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8);
+ }
+ return sum;
+}
+
+
+#if !FF_FS_READONLY && FF_USE_MKFS
+static DWORD xsum32 ( /* Returns 32-bit checksum */
+ BYTE dat, /* Byte to be calculated (byte-by-byte processing) */
+ DWORD sum /* Previous sum value */
+)
+{
+ sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat;
+ return sum;
+}
+#endif
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2
+/*------------------------------------------------------*/
+/* exFAT: Get object information from a directory block */
+/*------------------------------------------------------*/
+
+static void get_xfileinfo (
+ BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */
+ FILINFO* fno /* Buffer to store the extracted file information */
+)
+{
+ WCHAR wc, hs;
+ UINT di, si, nc;
+
+ /* Get file name from the entry block */
+ si = SZDIRE * 2; /* 1st C1 entry */
+ nc = 0; hs = 0; di = 0;
+ while (nc < dirb[XDIR_NumName]) {
+ if (si >= MAXDIRB(FF_MAX_LFN)) { di = 0; break; } /* Truncated directory block? */
+ if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */
+ wc = ld_word(dirb + si); si += 2; nc++; /* Get a character */
+ if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */
+ hs = wc; continue; /* Get low surrogate */
+ }
+ wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in API encoding */
+ if (wc == 0) { di = 0; break; } /* Buffer overflow or wrong encoding? */
+ di += wc;
+ hs = 0;
+ }
+ if (hs != 0) di = 0; /* Broken surrogate pair? */
+ if (di == 0) fno->fname[di++] = '?'; /* Inaccessible object name? */
+ fno->fname[di] = 0; /* Terminate the name */
+ fno->altname[0] = 0; /* exFAT does not support SFN */
+
+ fno->fattrib = dirb[XDIR_Attr]; /* Attribute */
+ fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(dirb + XDIR_FileSize); /* Size */
+ fno->ftime = ld_word(dirb + XDIR_ModTime + 0); /* Time */
+ fno->fdate = ld_word(dirb + XDIR_ModTime + 2); /* Date */
+}
+
+#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */
+
+
+/*-----------------------------------*/
+/* exFAT: Get a directry entry block */
+/*-----------------------------------*/
+
+static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */
+ DIR* dp /* Reading direcotry object pointing top of the entry block to load */
+)
+{
+ FRESULT res;
+ UINT i, sz_ent;
+ BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */
+
+
+ /* Load file-directory entry */
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != ET_FILEDIR) return FR_INT_ERR; /* Invalid order */
+ mem_cpy(dirb + 0 * SZDIRE, dp->dir, SZDIRE);
+ sz_ent = (dirb[XDIR_NumSec] + 1) * SZDIRE;
+ if (sz_ent < 3 * SZDIRE || sz_ent > 19 * SZDIRE) return FR_INT_ERR;
+
+ /* Load stream-extension entry */
+ res = dir_next(dp, 0);
+ if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */
+ if (res != FR_OK) return res;
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != ET_STREAM) return FR_INT_ERR; /* Invalid order */
+ mem_cpy(dirb + 1 * SZDIRE, dp->dir, SZDIRE);
+ if (MAXDIRB(dirb[XDIR_NumName]) > sz_ent) return FR_INT_ERR;
+
+ /* Load file-name entries */
+ i = 2 * SZDIRE; /* Name offset to load */
+ do {
+ res = dir_next(dp, 0);
+ if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */
+ if (res != FR_OK) return res;
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != ET_FILENAME) return FR_INT_ERR; /* Invalid order */
+ if (i < MAXDIRB(FF_MAX_LFN)) mem_cpy(dirb + i, dp->dir, SZDIRE);
+ } while ((i += SZDIRE) < sz_ent);
+
+ /* Sanity check (do it for only accessible object) */
+ if (i <= MAXDIRB(FF_MAX_LFN)) {
+ if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR;
+ }
+ return FR_OK;
+}
+
+
+/*------------------------------------------------------------------*/
+/* exFAT: Initialize object allocation info with loaded entry block */
+/*------------------------------------------------------------------*/
+
+static void init_alloc_info (
+ FATFS* fs, /* Filesystem object */
+ FFOBJID* obj /* Object allocation information to be initialized */
+)
+{
+ obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Start cluster */
+ obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); /* Size */
+ obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; /* Allocation status */
+ obj->n_frag = 0; /* No last fragment info */
+}
+
+
+
+#if !FF_FS_READONLY || FF_FS_RPATH != 0
+/*------------------------------------------------*/
+/* exFAT: Load the object's directory entry block */
+/*------------------------------------------------*/
+
+static FRESULT load_obj_xdir (
+ DIR* dp, /* Blank directory object to be used to access containing direcotry */
+ const FFOBJID* obj /* Object with its containing directory information */
+)
+{
+ FRESULT res;
+
+ /* Open object containing directory */
+ dp->obj.fs = obj->fs;
+ dp->obj.sclust = obj->c_scl;
+ dp->obj.stat = (BYTE)obj->c_size;
+ dp->obj.objsize = obj->c_size & 0xFFFFFF00;
+ dp->obj.n_frag = 0;
+ dp->blk_ofs = obj->c_ofs;
+
+ res = dir_sdi(dp, dp->blk_ofs); /* Goto object's entry block */
+ if (res == FR_OK) {
+ res = load_xdir(dp); /* Load the object's entry block */
+ }
+ return res;
+}
+#endif
+
+
+#if !FF_FS_READONLY
+/*----------------------------------------*/
+/* exFAT: Store the directory entry block */
+/*----------------------------------------*/
+
+static FRESULT store_xdir (
+ DIR* dp /* Pointer to the direcotry object */
+)
+{
+ FRESULT res;
+ UINT nent;
+ BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */
+
+ /* Create set sum */
+ st_word(dirb + XDIR_SetSum, xdir_sum(dirb));
+ nent = dirb[XDIR_NumSec] + 1;
+
+ /* Store the direcotry entry block to the directory */
+ res = dir_sdi(dp, dp->blk_ofs);
+ while (res == FR_OK) {
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) break;
+ mem_cpy(dp->dir, dirb, SZDIRE);
+ dp->obj.fs->wflag = 1;
+ if (--nent == 0) break;
+ dirb += SZDIRE;
+ res = dir_next(dp, 0);
+ }
+ return (res == FR_OK || res == FR_DISK_ERR) ? res : FR_INT_ERR;
+}
+
+
+
+/*-------------------------------------------*/
+/* exFAT: Create a new directory enrty block */
+/*-------------------------------------------*/
+
+static void create_xdir (
+ BYTE* dirb, /* Pointer to the direcotry entry block buffer */
+ const WCHAR* lfn /* Pointer to the object name */
+)
+{
+ UINT i;
+ BYTE nc1, nlen;
+ WCHAR wc;
+
+
+ /* Create file-directory and stream-extension entry */
+ mem_set(dirb, 0, 2 * SZDIRE);
+ dirb[0 * SZDIRE + XDIR_Type] = ET_FILEDIR;
+ dirb[1 * SZDIRE + XDIR_Type] = ET_STREAM;
+
+ /* Create file-name entries */
+ i = SZDIRE * 2; /* Top of file_name entries */
+ nlen = nc1 = 0; wc = 1;
+ do {
+ dirb[i++] = ET_FILENAME; dirb[i++] = 0;
+ do { /* Fill name field */
+ if (wc != 0 && (wc = lfn[nlen]) != 0) nlen++; /* Get a character if exist */
+ st_word(dirb + i, wc); /* Store it */
+ i += 2;
+ } while (i % SZDIRE != 0);
+ nc1++;
+ } while (lfn[nlen]); /* Fill next entry if any char follows */
+
+ dirb[XDIR_NumName] = nlen; /* Set name length */
+ dirb[XDIR_NumSec] = 1 + nc1; /* Set secondary count (C0 + C1s) */
+ st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_FS_EXFAT */
+
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT
+/*-----------------------------------------------------------------------*/
+/* Read an object from the directory */
+/*-----------------------------------------------------------------------*/
+
+#define DIR_READ_FILE(dp) dir_read(dp, 0)
+#define DIR_READ_LABEL(dp) dir_read(dp, 1)
+
+static FRESULT dir_read (
+ DIR* dp, /* Pointer to the directory object */
+ int vol /* Filtered by 0:file/directory or 1:volume label */
+)
+{
+ FRESULT res = FR_NO_FILE;
+ FATFS *fs = dp->obj.fs;
+ BYTE attr, b;
+#if FF_USE_LFN
+ BYTE ord = 0xFF, sum = 0xFF;
+#endif
+
+ while (dp->sect) {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ b = dp->dir[DIR_Name]; /* Test for the entry type */
+ if (b == 0) {
+ res = FR_NO_FILE; break; /* Reached to end of the directory */
+ }
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ if (FF_USE_LABEL && vol) {
+ if (b == ET_VLABEL) break; /* Volume label entry? */
+ } else {
+ if (b == ET_FILEDIR) { /* Start of the file entry block? */
+ dp->blk_ofs = dp->dptr; /* Get location of the block */
+ res = load_xdir(dp); /* Load the entry block */
+ if (res == FR_OK) {
+ dp->obj.attr = fs->dirbuf[XDIR_Attr] & AM_MASK; /* Get attribute */
+ }
+ break;
+ }
+ }
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ dp->obj.attr = attr = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */
+#if FF_USE_LFN /* LFN configuration */
+ if (b == DDEM || b == '.' || (int)((attr & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */
+ ord = 0xFF;
+ } else {
+ if (attr == AM_LFN) { /* An LFN entry is found */
+ if (b & LLEF) { /* Is it start of an LFN sequence? */
+ sum = dp->dir[LDIR_Chksum];
+ b &= (BYTE)~LLEF; ord = b;
+ dp->blk_ofs = dp->dptr;
+ }
+ /* Check LFN validity and capture it */
+ ord = (b == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
+ } else { /* An SFN entry is found */
+ if (ord != 0 || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */
+ dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */
+ }
+ break;
+ }
+ }
+#else /* Non LFN configuration */
+ if (b != DDEM && b != '.' && attr != AM_LFN && (int)((attr & ~AM_ARC) == AM_VOL) == vol) { /* Is it a valid entry? */
+ break;
+ }
+#endif
+ }
+ res = dir_next(dp, 0); /* Next entry */
+ if (res != FR_OK) break;
+ }
+
+ if (res != FR_OK) dp->sect = 0; /* Terminate the read operation on error or EOT */
+ return res;
+}
+
+#endif /* FF_FS_MINIMIZE <= 1 || FF_USE_LABEL || FF_FS_RPATH >= 2 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Find an object in the directory */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp /* Pointer to the directory object with the file name */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+ BYTE c;
+#if FF_USE_LFN
+ BYTE a, ord, sum;
+#endif
+
+ res = dir_sdi(dp, 0); /* Rewind directory object */
+ if (res != FR_OK) return res;
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ BYTE nc;
+ UINT di, ni;
+ WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */
+
+ while ((res = DIR_READ_FILE(dp)) == FR_OK) { /* Read an item */
+#if FF_MAX_LFN < 255
+ if (fs->dirbuf[XDIR_NumName] > FF_MAX_LFN) continue; /* Skip comparison if inaccessible object name */
+#endif
+ if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */
+ for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */
+ if ((di % SZDIRE) == 0) di += 2;
+ if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break;
+ }
+ if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */
+ }
+ return res;
+ }
+#endif
+ /* On the FAT/FAT32 volume */
+#if FF_USE_LFN
+ ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+#endif
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ c = dp->dir[DIR_Name];
+ if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */
+#if FF_USE_LFN /* LFN configuration */
+ dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK;
+ if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */
+ ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+ } else {
+ if (a == AM_LFN) { /* An LFN entry is found */
+ if (!(dp->fn[NSFLAG] & NS_NOLFN)) {
+ if (c & LLEF) { /* Is it start of LFN sequence? */
+ sum = dp->dir[LDIR_Chksum];
+ c &= (BYTE)~LLEF; ord = c; /* LFN start order */
+ dp->blk_ofs = dp->dptr; /* Start offset of LFN */
+ }
+ /* Check validity of the LFN entry and compare it with given name */
+ ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
+ }
+ } else { /* An SFN entry is found */
+ if (ord == 0 && sum == sum_sfn(dp->dir)) break; /* LFN matched? */
+ if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */
+ ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+ }
+ }
+#else /* Non LFN configuration */
+ dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK;
+ if (!(dp->dir[DIR_Attr] & AM_VOL) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */
+#endif
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK);
+
+ return res;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Register an object to the directory */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */
+ DIR* dp /* Target directory with object name to be created */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+#if FF_USE_LFN /* LFN configuration */
+ UINT n, nlen, nent;
+ BYTE sn[12], sum;
+
+
+ if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME; /* Check name validity */
+ for (nlen = 0; fs->lfnbuf[nlen]; nlen++) ; /* Get lfn length */
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ nent = (nlen + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */
+ res = dir_alloc(dp, nent); /* Allocate directory entries */
+ if (res != FR_OK) return res;
+ dp->blk_ofs = dp->dptr - SZDIRE * (nent - 1); /* Set the allocated entry block offset */
+
+ if (dp->obj.stat & 4) { /* Has the directory been stretched by new allocation? */
+ dp->obj.stat &= ~4;
+ res = fill_first_frag(&dp->obj); /* Fill the first fragment on the FAT if needed */
+ if (res != FR_OK) return res;
+ res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */
+ if (res != FR_OK) return res;
+ if (dp->obj.sclust != 0) { /* Is it a sub-directory? */
+ DIR dj;
+
+ res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */
+ if (res != FR_OK) return res;
+ dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase the directory size by cluster size */
+ st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize); /* Update the allocation status */
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize);
+ fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1;
+ res = store_xdir(&dj); /* Store the object status */
+ if (res != FR_OK) return res;
+ }
+ }
+
+ create_xdir(fs->dirbuf, fs->lfnbuf); /* Create on-memory directory block to be written later */
+ return FR_OK;
+ }
+#endif
+ /* On the FAT/FAT32 volume */
+ mem_cpy(sn, dp->fn, 12);
+ if (sn[NSFLAG] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */
+ dp->fn[NSFLAG] = NS_NOLFN; /* Find only SFN */
+ for (n = 1; n < 100; n++) {
+ gen_numname(dp->fn, sn, fs->lfnbuf, n); /* Generate a numbered name */
+ res = dir_find(dp); /* Check if the name collides with existing SFN */
+ if (res != FR_OK) break;
+ }
+ if (n == 100) return FR_DENIED; /* Abort if too many collisions */
+ if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */
+ dp->fn[NSFLAG] = sn[NSFLAG];
+ }
+
+ /* Create an SFN with/without LFNs. */
+ nent = (sn[NSFLAG] & NS_LFN) ? (nlen + 12) / 13 + 1 : 1; /* Number of entries to allocate */
+ res = dir_alloc(dp, nent); /* Allocate entries */
+ if (res == FR_OK && --nent) { /* Set LFN entry if needed */
+ res = dir_sdi(dp, dp->dptr - nent * SZDIRE);
+ if (res == FR_OK) {
+ sum = sum_sfn(dp->fn); /* Checksum value of the SFN tied to the LFN */
+ do { /* Store LFN entries in bottom first */
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ put_lfn(fs->lfnbuf, dp->dir, (BYTE)nent, sum);
+ fs->wflag = 1;
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK && --nent);
+ }
+ }
+
+#else /* Non LFN configuration */
+ res = dir_alloc(dp, 1); /* Allocate an entry for SFN */
+
+#endif
+
+ /* Set SFN entry */
+ if (res == FR_OK) {
+ res = move_window(fs, dp->sect);
+ if (res == FR_OK) {
+ mem_set(dp->dir, 0, SZDIRE); /* Clean the entry */
+ mem_cpy(dp->dir + DIR_Name, dp->fn, 11); /* Put SFN */
+#if FF_USE_LFN
+ dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */
+#endif
+ fs->wflag = 1;
+ }
+ }
+
+ return res;
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+#if !FF_FS_READONLY && FF_FS_MINIMIZE == 0
+/*-----------------------------------------------------------------------*/
+/* Remove an object from the directory */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */
+ DIR* dp /* Directory object pointing the entry to be removed */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+#if FF_USE_LFN /* LFN configuration */
+ DWORD last = dp->dptr;
+
+ res = (dp->blk_ofs == 0xFFFFFFFF) ? FR_OK : dir_sdi(dp, dp->blk_ofs); /* Goto top of the entry block if LFN is exist */
+ if (res == FR_OK) {
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ dp->dir[XDIR_Type] &= 0x7F; /* Clear the entry InUse flag. */
+ } else { /* On the FAT/FAT32 volume */
+ dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'. */
+ }
+ fs->wflag = 1;
+ if (dp->dptr >= last) break; /* If reached last entry then all entries of the object has been deleted. */
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK);
+ if (res == FR_NO_FILE) res = FR_INT_ERR;
+ }
+#else /* Non LFN configuration */
+
+ res = move_window(fs, dp->sect);
+ if (res == FR_OK) {
+ dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'.*/
+ fs->wflag = 1;
+ }
+#endif
+
+ return res;
+}
+
+#endif /* !FF_FS_READONLY && FF_FS_MINIMIZE == 0 */
+
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2
+/*-----------------------------------------------------------------------*/
+/* Get file information from directory entry */
+/*-----------------------------------------------------------------------*/
+
+static void get_fileinfo (
+ DIR* dp, /* Pointer to the directory object */
+ FILINFO* fno /* Pointer to the file information to be filled */
+)
+{
+ UINT si, di;
+#if FF_USE_LFN
+ BYTE lcf;
+ WCHAR wc, hs;
+ FATFS *fs = dp->obj.fs;
+#else
+ TCHAR c;
+#endif
+
+
+ fno->fname[0] = 0; /* Invaidate file info */
+ if (dp->sect == 0) return; /* Exit if read pointer has reached end of directory */
+
+#if FF_USE_LFN /* LFN configuration */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ get_xfileinfo(fs->dirbuf, fno);
+ return;
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */
+ si = di = hs = 0;
+ while (fs->lfnbuf[si] != 0) {
+ wc = fs->lfnbuf[si++]; /* Get an LFN character (UTF-16) */
+ if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */
+ hs = wc; continue; /* Get low surrogate */
+ }
+ wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in UTF-16 or UTF-8 encoding */
+ if (wc == 0) { di = 0; break; } /* Invalid char or buffer overflow? */
+ di += wc;
+ hs = 0;
+ }
+ if (hs != 0) di = 0; /* Broken surrogate pair? */
+ fno->fname[di] = 0; /* Terminate the LFN (null string means LFN is invalid) */
+ }
+ }
+
+ si = di = 0;
+ while (si < 11) { /* Get SFN from SFN entry */
+ wc = dp->dir[si++]; /* Get a char */
+ if (wc == ' ') continue; /* Skip padding spaces */
+ if (wc == RDDEM) wc = DDEM; /* Restore replaced DDEM character */
+ if (si == 9 && di < FF_SFN_BUF) fno->altname[di++] = '.'; /* Insert a . if extension is exist */
+#if FF_LFN_UNICODE >= 1 /* Unicode output */
+ if (dbc_1st((BYTE)wc) && si != 8 && si != 11 && dbc_2nd(dp->dir[si])) { /* Make a DBC if needed */
+ wc = wc << 8 | dp->dir[si++];
+ }
+ wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM -> Unicode */
+ if (wc == 0) { di = 0; break; } /* Wrong char in the current code page? */
+ wc = put_utf(wc, &fno->altname[di], FF_SFN_BUF - di); /* Store it in Unicode */
+ if (wc == 0) { di = 0; break; } /* Buffer overflow? */
+ di += wc;
+#else /* ANSI/OEM output */
+ fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */
+#endif
+ }
+ fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */
+
+ if (fno->fname[0] == 0) { /* If LFN is invalid, altname[] needs to be copied to fname[] */
+ if (di == 0) { /* If LFN and SFN both are invalid, this object is inaccesible */
+ fno->fname[di++] = '?';
+ } else {
+ for (si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++) { /* Copy altname[] to fname[] with case information */
+ wc = (WCHAR)fno->altname[si];
+ if (wc == '.') lcf = NS_EXT;
+ if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20;
+ fno->fname[di] = (TCHAR)wc;
+ }
+ }
+ fno->fname[di] = 0; /* Terminate the LFN */
+ if (!dp->dir[DIR_NTres]) fno->altname[0] = 0; /* Altname is not needed if neither LFN nor case info is exist. */
+ }
+
+#else /* Non-LFN configuration */
+ si = di = 0;
+ while (si < 11) { /* Copy name body and extension */
+ c = (TCHAR)dp->dir[si++];
+ if (c == ' ') continue; /* Skip padding spaces */
+ if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */
+ if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */
+ fno->fname[di++] = c;
+ }
+ fno->fname[di] = 0;
+#endif
+
+ fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */
+ fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */
+ fno->ftime = ld_word(dp->dir + DIR_ModTime + 0); /* Time */
+ fno->fdate = ld_word(dp->dir + DIR_ModTime + 2); /* Date */
+}
+
+#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */
+
+
+
+#if FF_USE_FIND && FF_FS_MINIMIZE <= 1
+/*-----------------------------------------------------------------------*/
+/* Pattern matching */
+/*-----------------------------------------------------------------------*/
+
+static DWORD get_achar ( /* Get a character and advances ptr */
+ const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */
+)
+{
+ DWORD chr;
+
+
+#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode input */
+ chr = tchar2uni(ptr);
+ if (chr == 0xFFFFFFFF) chr = 0; /* Wrong UTF encoding is recognized as end of the string */
+ chr = ff_wtoupper(chr);
+
+#else /* ANSI/OEM input */
+ chr = (BYTE)*(*ptr)++; /* Get a byte */
+ if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */
+#if FF_CODE_PAGE == 0
+ if (ExCvt && chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */
+#elif FF_CODE_PAGE < 900
+ if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */
+#endif
+#if FF_CODE_PAGE == 0 || FF_CODE_PAGE >= 900
+ if (dbc_1st((BYTE)chr)) { /* Get DBC 2nd byte if needed */
+ chr = dbc_2nd((BYTE)**ptr) ? chr << 8 | (BYTE)*(*ptr)++ : 0;
+ }
+#endif
+
+#endif
+ return chr;
+}
+
+
+static int pattern_matching ( /* 0:not matched, 1:matched */
+ const TCHAR* pat, /* Matching pattern */
+ const TCHAR* nam, /* String to be tested */
+ int skip, /* Number of pre-skip chars (number of ?s) */
+ int inf /* Infinite search (* specified) */
+)
+{
+ const TCHAR *pp, *np;
+ DWORD pc, nc;
+ int nm, nx;
+
+
+ while (skip--) { /* Pre-skip name chars */
+ if (!get_achar(&nam)) return 0; /* Branch mismatched if less name chars */
+ }
+ if (*pat == 0 && inf) return 1; /* (short circuit) */
+
+ do {
+ pp = pat; np = nam; /* Top of pattern and name to match */
+ for (;;) {
+ if (*pp == '?' || *pp == '*') { /* Wildcard? */
+ nm = nx = 0;
+ do { /* Analyze the wildcard block */
+ if (*pp++ == '?') nm++; else nx = 1;
+ } while (*pp == '?' || *pp == '*');
+ if (pattern_matching(pp, np, nm, nx)) return 1; /* Test new branch (recurs upto number of wildcard blocks in the pattern) */
+ nc = *np; break; /* Branch mismatched */
+ }
+ pc = get_achar(&pp); /* Get a pattern char */
+ nc = get_achar(&np); /* Get a name char */
+ if (pc != nc) break; /* Branch mismatched? */
+ if (pc == 0) return 1; /* Branch matched? (matched at end of both strings) */
+ }
+ get_achar(&nam); /* nam++ */
+ } while (inf && nc); /* Retry until end of name if infinite search is specified */
+
+ return 0;
+}
+
+#endif /* FF_USE_FIND && FF_FS_MINIMIZE <= 1 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Pick a top segment and create the object name in directory form */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */
+ DIR* dp, /* Pointer to the directory object */
+ const TCHAR** path /* Pointer to pointer to the segment in the path string */
+)
+{
+#if FF_USE_LFN /* LFN configuration */
+ BYTE b, cf;
+ WCHAR wc, *lfn;
+ DWORD uc;
+ UINT i, ni, si, di;
+ const TCHAR *p;
+
+
+ /* Create LFN into LFN working buffer */
+ p = *path; lfn = dp->obj.fs->lfnbuf; di = 0;
+ for (;;) {
+ uc = tchar2uni(&p); /* Get a character */
+ if (uc == 0xFFFFFFFF) return FR_INVALID_NAME; /* Invalid code or UTF decode error */
+ if (uc >= 0x10000) lfn[di++] = (WCHAR)(uc >> 16); /* Store high surrogate if needed */
+ wc = (WCHAR)uc;
+ if (wc < ' ' || wc == '/' || wc == '\\') break; /* Break if end of the path or a separator is found */
+ if (wc < 0x80 && chk_chr("\"*:<>\?|\x7F", wc)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */
+ if (di >= FF_MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */
+ lfn[di++] = wc; /* Store the Unicode character */
+ }
+ while (*p == '/' || *p == '\\') p++; /* Skip duplicated separators if exist */
+ *path = p; /* Return pointer to the next segment */
+ cf = (wc < ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */
+
+#if FF_FS_RPATH != 0
+ if ((di == 1 && lfn[di - 1] == '.') ||
+ (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.')) { /* Is this segment a dot name? */
+ lfn[di] = 0;
+ for (i = 0; i < 11; i++) { /* Create dot name for SFN entry */
+ dp->fn[i] = (i < di) ? '.' : ' ';
+ }
+ dp->fn[i] = cf | NS_DOT; /* This is a dot entry */
+ return FR_OK;
+ }
+#endif
+ while (di) { /* Snip off trailing spaces and dots if exist */
+ wc = lfn[di - 1];
+ if (wc != ' ' && wc != '.') break;
+ di--;
+ }
+ lfn[di] = 0; /* LFN is created into the working buffer */
+ if (di == 0) return FR_INVALID_NAME; /* Reject null name */
+
+ /* Create SFN in directory form */
+ for (si = 0; lfn[si] == ' '; si++) ; /* Remove leading spaces */
+ if (si > 0 || lfn[si] == '.') cf |= NS_LOSS | NS_LFN; /* Is there any leading space or dot? */
+ while (di > 0 && lfn[di - 1] != '.') di--; /* Find last dot (di<=si: no extension) */
+
+ mem_set(dp->fn, ' ', 11);
+ i = b = 0; ni = 8;
+ for (;;) {
+ wc = lfn[si++]; /* Get an LFN character */
+ if (wc == 0) break; /* Break on end of the LFN */
+ if (wc == ' ' || (wc == '.' && si != di)) { /* Remove embedded spaces and dots */
+ cf |= NS_LOSS | NS_LFN;
+ continue;
+ }
+
+ if (i >= ni || si == di) { /* End of field? */
+ if (ni == 11) { /* Name extension overflow? */
+ cf |= NS_LOSS | NS_LFN;
+ break;
+ }
+ if (si != di) cf |= NS_LOSS | NS_LFN; /* Name body overflow? */
+ if (si > di) break; /* No name extension? */
+ si = di; i = 8; ni = 11; b <<= 2; /* Enter name extension */
+ continue;
+ }
+
+ if (wc >= 0x80) { /* Is this a non-ASCII character? */
+ cf |= NS_LFN; /* LFN entry needs to be created */
+#if FF_CODE_PAGE == 0
+ if (ExCvt) { /* At SBCS */
+ wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */
+ if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */
+ } else { /* At DBCS */
+ wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */
+ }
+#elif FF_CODE_PAGE < 900 /* SBCS cfg */
+ wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */
+ if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */
+#else /* DBCS cfg */
+ wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */
+#endif
+ }
+
+ if (wc >= 0x100) { /* Is this a DBC? */
+ if (i >= ni - 1) { /* Field overflow? */
+ cf |= NS_LOSS | NS_LFN;
+ i = ni; continue; /* Next field */
+ }
+ dp->fn[i++] = (BYTE)(wc >> 8); /* Put 1st byte */
+ } else { /* SBC */
+ if (wc == 0 || chk_chr("+,;=[]", wc)) { /* Replace illegal characters for SFN if needed */
+ wc = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */
+ } else {
+ if (IsUpper(wc)) { /* ASCII upper case? */
+ b |= 2;
+ }
+ if (IsLower(wc)) { /* ASCII lower case? */
+ b |= 1; wc -= 0x20;
+ }
+ }
+ }
+ dp->fn[i++] = (BYTE)wc;
+ }
+
+ if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */
+
+ if (ni == 8) b <<= 2; /* Shift capital flags if no extension */
+ if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* LFN entry needs to be created if composite capitals */
+ if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */
+ if (b & 0x01) cf |= NS_EXT; /* NT flag (Extension has small capital letters only) */
+ if (b & 0x04) cf |= NS_BODY; /* NT flag (Body has small capital letters only) */
+ }
+
+ dp->fn[NSFLAG] = cf; /* SFN is created into dp->fn[] */
+
+ return FR_OK;
+
+
+#else /* FF_USE_LFN : Non-LFN configuration */
+ BYTE c, d, *sfn;
+ UINT ni, si, i;
+ const char *p;
+
+ /* Create file name in directory form */
+ p = *path; sfn = dp->fn;
+ mem_set(sfn, ' ', 11);
+ si = i = 0; ni = 8;
+#if FF_FS_RPATH != 0
+ if (p[si] == '.') { /* Is this a dot entry? */
+ for (;;) {
+ c = (BYTE)p[si++];
+ if (c != '.' || si >= 3) break;
+ sfn[i++] = c;
+ }
+ if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME;
+ *path = p + si; /* Return pointer to the next segment */
+ sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */
+ return FR_OK;
+ }
+#endif
+ for (;;) {
+ c = (BYTE)p[si++]; /* Get a byte */
+ if (c <= ' ') break; /* Break if end of the path name */
+ if (c == '/' || c == '\\') { /* Break if a separator is found */
+ while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */
+ break;
+ }
+ if (c == '.' || i >= ni) { /* End of body or field overflow? */
+ if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Field overflow or invalid dot? */
+ i = 8; ni = 11; /* Enter file extension field */
+ continue;
+ }
+#if FF_CODE_PAGE == 0
+ if (ExCvt && c >= 0x80) { /* Is SBC extended character? */
+ c = ExCvt[c & 0x7F]; /* To upper SBC extended character */
+ }
+#elif FF_CODE_PAGE < 900
+ if (c >= 0x80) { /* Is SBC extended character? */
+ c = ExCvt[c & 0x7F]; /* To upper SBC extended character */
+ }
+#endif
+ if (dbc_1st(c)) { /* Check if it is a DBC 1st byte */
+ d = (BYTE)p[si++]; /* Get 2nd byte */
+ if (!dbc_2nd(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */
+ sfn[i++] = c;
+ sfn[i++] = d;
+ } else { /* SBC */
+ if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */
+ if (IsLower(c)) c -= 0x20; /* To upper */
+ sfn[i++] = c;
+ }
+ }
+ *path = p + si; /* Return pointer to the next segment */
+ if (i == 0) return FR_INVALID_NAME; /* Reject nul string */
+
+ if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */
+ sfn[NSFLAG] = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */
+
+ return FR_OK;
+#endif /* FF_USE_LFN */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Follow a file path */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */
+ DIR* dp, /* Directory object to return last directory and found object */
+ const TCHAR* path /* Full-path string to find a file or directory */
+)
+{
+ FRESULT res;
+ BYTE ns;
+ FATFS *fs = dp->obj.fs;
+
+
+#if FF_FS_RPATH != 0
+ if (*path != '/' && *path != '\\') { /* Without heading separator */
+ dp->obj.sclust = fs->cdir; /* Start from current directory */
+ } else
+#endif
+ { /* With heading separator */
+ while (*path == '/' || *path == '\\') path++; /* Strip heading separator */
+ dp->obj.sclust = 0; /* Start from root directory */
+ }
+#if FF_FS_EXFAT
+ dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */
+#if FF_FS_RPATH != 0
+ if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */
+ DIR dj;
+
+ dp->obj.c_scl = fs->cdc_scl;
+ dp->obj.c_size = fs->cdc_size;
+ dp->obj.c_ofs = fs->cdc_ofs;
+ res = load_obj_xdir(&dj, &dp->obj);
+ if (res != FR_OK) return res;
+ dp->obj.objsize = ld_dword(fs->dirbuf + XDIR_FileSize);
+ dp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ }
+#endif
+#endif
+
+ if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */
+ dp->fn[NSFLAG] = NS_NONAME;
+ res = dir_sdi(dp, 0);
+
+ } else { /* Follow path */
+ for (;;) {
+ res = create_name(dp, &path); /* Get a segment name of the path */
+ if (res != FR_OK) break;
+ res = dir_find(dp); /* Find an object with the segment name */
+ ns = dp->fn[NSFLAG];
+ if (res != FR_OK) { /* Failed to find the object */
+ if (res == FR_NO_FILE) { /* Object is not found */
+ if (FF_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */
+ if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */
+ dp->fn[NSFLAG] = NS_NONAME;
+ res = FR_OK;
+ } else { /* Could not find the object */
+ if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */
+ }
+ }
+ break;
+ }
+ if (ns & NS_LAST) break; /* Last segment matched. Function completed. */
+ /* Get into the sub-directory */
+ if (!(dp->obj.attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */
+ res = FR_NO_PATH; break;
+ }
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* Save containing directory information for next dir */
+ dp->obj.c_scl = dp->obj.sclust;
+ dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat;
+ dp->obj.c_ofs = dp->blk_ofs;
+ init_alloc_info(fs, &dp->obj); /* Open next directory */
+ } else
+#endif
+ {
+ dp->obj.sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */
+ }
+ }
+ }
+
+ return res;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Get logical drive number from path name */
+/*-----------------------------------------------------------------------*/
+
+static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive number or null pointer) */
+ const TCHAR** path /* Pointer to pointer to the path name */
+)
+{
+ const TCHAR *tp, *tt;
+ TCHAR tc;
+ int i, vol = -1;
+#if FF_STR_VOLUME_ID /* Find string volume ID */
+ const char *sp;
+ char c;
+#endif
+
+ tt = tp = *path;
+ if (!tp) return vol; /* Invalid path name? */
+ do tc = *tt++; while ((UINT)tc >= (FF_USE_LFN ? ' ' : '!') && tc != ':'); /* Find a colon in the path */
+
+ if (tc == ':') { /* DOS/Windows style volume ID? */
+ i = FF_VOLUMES;
+ if (IsDigit(*tp) && tp + 2 == tt) { /* Is there a numeric volume ID + colon? */
+ i = (int)*tp - '0'; /* Get the LD number */
+ }
+#if FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */
+ else {
+ i = 0;
+ do {
+ sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */
+ do { /* Compare the volume ID with path name */
+ c = *sp++; tc = *tp++;
+ if (IsLower(c)) c -= 0x20;
+ if (IsLower(tc)) tc -= 0x20;
+ } while (c && (TCHAR)c == tc);
+ } while ((c || tp != tt) && ++i < FF_VOLUMES); /* Repeat for each id until pattern match */
+ }
+#endif
+ if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */
+ vol = i; /* Drive number */
+ *path = tt; /* Snip the drive prefix off */
+ }
+ return vol;
+ }
+#if FF_STR_VOLUME_ID == 2 /* Unix style volume ID is enabled */
+ if (*tp == '/') {
+ i = 0;
+ do {
+ sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */
+ do { /* Compare the volume ID with path name */
+ c = *sp++; tc = *(++tp);
+ if (IsLower(c)) c -= 0x20;
+ if (IsLower(tc)) tc -= 0x20;
+ } while (c && (TCHAR)c == tc);
+ } while ((c || (tc != '/' && (UINT)tc >= (FF_USE_LFN ? ' ' : '!'))) && ++i < FF_VOLUMES); /* Repeat for each ID until pattern match */
+ if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */
+ vol = i; /* Drive number */
+ *path = tp; /* Snip the drive prefix off */
+ return vol;
+ }
+ }
+#endif
+ /* No drive prefix is found */
+#if FF_FS_RPATH != 0
+ vol = CurrVol; /* Default drive is current drive */
+#else
+ vol = 0; /* Default drive is 0 */
+#endif
+ return vol; /* Return the default drive */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Load a sector and check if it is an FAT VBR */
+/*-----------------------------------------------------------------------*/
+
+static BYTE check_fs ( /* 0:FAT, 1:exFAT, 2:Valid BS but not FAT, 3:Not a BS, 4:Disk error */
+ FATFS* fs, /* Filesystem object */
+ DWORD sect /* Sector# (lba) to load and check if it is an FAT-VBR or not */
+)
+{
+ fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */
+ if (move_window(fs, sect) != FR_OK) return 4; /* Load boot record */
+
+ if (ld_word(fs->win + BS_55AA) != 0xAA55) return 3; /* Check boot record signature (always here regardless of the sector size) */
+
+#if FF_FS_EXFAT
+ if (!mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; /* Check if exFAT VBR */
+#endif
+ if (fs->win[BS_JmpBoot] == 0xE9 || fs->win[BS_JmpBoot] == 0xEB || fs->win[BS_JmpBoot] == 0xE8) { /* Valid JumpBoot code? */
+ if (!mem_cmp(fs->win + BS_FilSysType, "FAT", 3)) return 0; /* Is it an FAT VBR? */
+ if (!mem_cmp(fs->win + BS_FilSysType32, "FAT32", 5)) return 0; /* Is it an FAT32 VBR? */
+ }
+ return 2; /* Valid BS but not FAT */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Determine logical drive number and mount the volume if needed */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred */
+ const TCHAR** path, /* Pointer to pointer to the path name (drive number) */
+ FATFS** rfs, /* Pointer to pointer to the found filesystem object */
+ BYTE mode /* !=0: Check write protection for write access */
+)
+{
+ BYTE fmt, *pt;
+ int vol;
+ DSTATUS stat;
+ DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4];
+ WORD nrsv;
+ FATFS *fs;
+ UINT i;
+
+
+ /* Get logical drive number */
+ *rfs = 0;
+ vol = get_ldnumber(path);
+ if (vol < 0) return FR_INVALID_DRIVE;
+
+ /* Check if the filesystem object is valid or not */
+ fs = FatFs[vol]; /* Get pointer to the filesystem object */
+ if (!fs) return FR_NOT_ENABLED; /* Is the filesystem object available? */
+#if FF_FS_REENTRANT
+ if (!lock_fs(fs)) return FR_TIMEOUT; /* Lock the volume */
+#endif
+ *rfs = fs; /* Return pointer to the filesystem object */
+
+ mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */
+ if (fs->fs_type != 0) { /* If the volume has been mounted */
+ stat = disk_status(fs->pdrv);
+ if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */
+ if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */
+ EFSPRINTF("WPEN1");
+ return FR_WRITE_PROTECTED;
+ }
+ return FR_OK; /* The filesystem object is valid */
+ }
+ }
+
+ /* The filesystem object is not valid. */
+ /* Following code attempts to mount the volume. (analyze BPB and initialize the filesystem object) */
+
+ fs->fs_type = 0; /* Clear the filesystem object */
+ fs->pdrv = LD2PD(vol); /* Bind the logical drive and a physical drive */
+ stat = disk_initialize(fs->pdrv); /* Initialize the physical drive */
+ if (stat & STA_NOINIT) { /* Check if the initialization succeeded */
+ EFSPRINTF("MDNR");
+ return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */
+ }
+ if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */
+ EFSPRINTF("WPEN2");
+ return FR_WRITE_PROTECTED;
+ }
+#if FF_MAX_SS != FF_MIN_SS /* Get sector size (multiple sector size cfg only) */
+ if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR;
+ if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR;
+#endif
+
+ /* Find an FAT partition on the drive. Supports only generic partitioning rules, FDISK (MBR) and SFD (w/o partition). */
+ bsect = 0;
+ fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT-VBR as SFD */
+ if (fmt == 2 || (fmt < 2 && LD2PT(vol) != 0)) { /* Not an FAT-VBR or forced partition number */
+ for (i = 0; i < 4; i++) { /* Get partition offset */
+ pt = fs->win + (MBR_Table + i * SZ_PTE);
+ br[i] = pt[PTE_System] ? ld_dword(pt + PTE_StLba) : 0;
+ }
+ i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */
+ if (i != 0) i--;
+ do { /* Find an FAT volume */
+ bsect = br[i];
+ fmt = bsect ? check_fs(fs, bsect) : 3; /* Check the partition */
+ } while (LD2PT(vol) == 0 && fmt >= 2 && ++i < 4);
+ }
+ if (fmt == 4) {
+ EFSPRINTF("BRNL");
+ return FR_DISK_ERR; /* An error occured in the disk I/O layer */
+ }
+ if (fmt >= 2) {
+ EFSPRINTF("NOFAT");
+ return FR_NO_FILESYSTEM; /* No FAT volume is found */
+ }
+
+ /* An FAT volume is found (bsect). Following code initializes the filesystem object */
+
+#if FF_FS_EXFAT
+ if (fmt == 1) {
+ QWORD maxlba;
+ DWORD so, cv, bcl;
+
+ for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */
+ if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM;
+
+ if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT version (must be version 1.0) */
+
+ if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) { /* (BPB_BytsPerSecEx must be equal to the physical sector size) */
+ EFSPRINTF("EXSPS");
+ return FR_NO_FILESYSTEM;
+ }
+
+ maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect; /* Last LBA + 1 of the volume */
+ if (maxlba >= 0x100000000) return FR_NO_FILESYSTEM; /* (It cannot be handled in 32-bit LBA) */
+
+ fs->fsize = ld_dword(fs->win + BPB_FatSzEx); /* Number of sectors per FAT */
+
+ fs->n_fats = fs->win[BPB_NumFATsEx]; /* Number of FATs */
+ if (fs->n_fats != 1) {
+ EFSPRINTF("EXFNF");
+ return FR_NO_FILESYSTEM; /* (Supports only 1 FAT) */
+ }
+
+ fs->csize = 1 << fs->win[BPB_SecPerClusEx]; /* Cluster size */
+ if (fs->csize == 0) return FR_NO_FILESYSTEM; /* (Must be 1..32768) */
+
+ nclst = ld_dword(fs->win + BPB_NumClusEx); /* Number of clusters */
+ if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM; /* (Too many clusters) */
+ fs->n_fatent = nclst + 2;
+
+ /* Boundaries and Limits */
+ fs->volbase = bsect;
+ fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx);
+ fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx);
+ if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM; /* (Volume size must not be smaller than the size requiered) */
+ fs->dirbase = ld_dword(fs->win + BPB_RootClusEx);
+
+ /* Get bitmap location and check if it is contiguous (implementation assumption) */
+ so = i = 0;
+ for (;;) { /* Find the bitmap entry in the root directory (in only first cluster) */
+ if (i == 0) {
+ if (so >= fs->csize) return FR_NO_FILESYSTEM; /* Not found? */
+ if (move_window(fs, clst2sect(fs, fs->dirbase) + so) != FR_OK) {
+ EFSPRINTF("EXBM1C");
+ return FR_DISK_ERR;
+ }
+ so++;
+ }
+ if (fs->win[i] == ET_BITMAP) break; /* Is it a bitmap entry? */
+ i = (i + SZDIRE) % SS(fs); /* Next entry */
+ }
+ bcl = ld_dword(fs->win + i + 20); /* Bitmap cluster */
+ if (bcl < 2 || bcl >= fs->n_fatent) {
+ EFSPRINTF("EXBMM");
+ return FR_NO_FILESYSTEM;
+ }
+ fs->bitbase = fs->database + fs->csize * (bcl - 2); /* Bitmap sector */
+ for (;;) { /* Check if bitmap is contiguous */
+ if (move_window(fs, fs->fatbase + bcl / (SS(fs) / 4)) != FR_OK) return FR_DISK_ERR;
+ cv = ld_dword(fs->win + bcl % (SS(fs) / 4) * 4);
+ if (cv == 0xFFFFFFFF) break; /* Last link? */
+ if (cv != ++bcl) {
+ EFSPRINTF("EXBMM");
+ return FR_NO_FILESYSTEM; /* Fragmented? */
+ }
+ }
+
+#if !FF_FS_READONLY
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */
+#endif
+ fmt = FS_EXFAT; /* FAT sub-type */
+ } else
+#endif /* FF_FS_EXFAT */
+ {
+ if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) {
+ EFSPRINTF("32SPS");
+ return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */
+ }
+
+ fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */
+ if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32);
+ fs->fsize = fasize;
+
+ fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */
+ if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */
+ fasize *= fs->n_fats; /* Number of sectors for FAT area */
+
+ fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */
+ if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */
+
+ fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */
+ if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */
+
+ tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */
+ if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32);
+
+ nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */
+ if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */
+
+ /* Determine the FAT sub type */
+ sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */
+ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
+ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */
+ if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
+ fmt = 0;
+ if (nclst <= MAX_FAT32) fmt = FS_FAT32;
+ if (nclst <= MAX_FAT16) fmt = FS_FAT16;
+ if (nclst <= MAX_FAT12) fmt = FS_FAT12;
+ if (fmt == 0) return FR_NO_FILESYSTEM;
+
+ /* Boundaries and Limits */
+ fs->n_fatent = nclst + 2; /* Number of FAT entries */
+ fs->volbase = bsect; /* Volume start sector */
+ fs->fatbase = bsect + nrsv; /* FAT start sector */
+ fs->database = bsect + sysect; /* Data start sector */
+ if (fmt == FS_FAT32) {
+ if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */
+ if (fs->n_rootdir != 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */
+ fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */
+ szbfat = fs->n_fatent * 4; /* (Needed FAT size) */
+ } else {
+ if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */
+ fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */
+ szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */
+ fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1);
+ }
+ if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */
+
+#if !FF_FS_READONLY
+ /* Get FSInfo if available */
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */
+ fs->fsi_flag = 0x80;
+#if (FF_FS_NOFSINFO & 3) != 3
+ if (fmt == FS_FAT32 /* Allow to update FSInfo only if BPB_FSInfo32 == 1 */
+ && ld_word(fs->win + BPB_FSInfo32) == 1
+ && move_window(fs, bsect + 1) == FR_OK)
+ {
+ fs->fsi_flag = 0;
+ if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSInfo data if available */
+ && ld_dword(fs->win + FSI_LeadSig) == 0x41615252
+ && ld_dword(fs->win + FSI_StrucSig) == 0x61417272)
+ {
+#if (FF_FS_NOFSINFO & 1) == 0
+ fs->free_clst = ld_dword(fs->win + FSI_Free_Count);
+#endif
+#if (FF_FS_NOFSINFO & 2) == 0
+ fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free);
+#endif
+ }
+ }
+#endif /* (FF_FS_NOFSINFO & 3) != 3 */
+#endif /* !FF_FS_READONLY */
+ }
+
+ fs->fs_type = fmt; /* FAT sub-type */
+ fs->id = ++Fsid; /* Volume mount ID */
+#if FF_USE_LFN == 1
+ fs->lfnbuf = LfnBuf; /* Static LFN working buffer */
+#if FF_FS_EXFAT
+ fs->dirbuf = DirBuf; /* Static directory block scratchpad buffer */
+#endif
+#endif
+#if FF_FS_RPATH != 0
+ fs->cdir = 0; /* Initialize current directory */
+#endif
+#if FF_FS_LOCK != 0 /* Clear file lock semaphores */
+ clear_lock(fs);
+#endif
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Check if the file/directory object is valid or not */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */
+ FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR object, to check validity */
+ FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */
+)
+{
+ FRESULT res = FR_INVALID_OBJECT;
+
+
+ if (obj && obj->fs && obj->fs->fs_type && obj->id == obj->fs->id) { /* Test if the object is valid */
+#if FF_FS_REENTRANT
+ if (lock_fs(obj->fs)) { /* Obtain the filesystem object */
+ if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */
+ res = FR_OK;
+ } else {
+ unlock_fs(obj->fs, FR_OK);
+ }
+ } else {
+ res = FR_TIMEOUT;
+ }
+#else
+ if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */
+ res = FR_OK;
+ }
+#endif
+ }
+ *rfs = (res == FR_OK) ? obj->fs : 0; /* Corresponding filesystem object */
+ return res;
+}
+
+
+
+
+/*---------------------------------------------------------------------------
+
+ Public Functions (FatFs API)
+
+----------------------------------------------------------------------------*/
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Mount/Unmount a Logical Drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mount (
+ FATFS* fs, /* Pointer to the filesystem object (NULL:unmount)*/
+ const TCHAR* path, /* Logical drive number to be mounted/unmounted */
+ BYTE opt /* Mode option 0:Do not mount (delayed mount), 1:Mount immediately */
+)
+{
+ FATFS *cfs;
+ int vol;
+ FRESULT res;
+ const TCHAR *rp = path;
+
+
+ /* Get logical drive number */
+ vol = get_ldnumber(&rp);
+ if (vol < 0) {
+ EFSPRINTF("IDRIVE!");
+ return FR_INVALID_DRIVE;
+ }
+ cfs = FatFs[vol]; /* Pointer to fs object */
+
+ if (cfs) {
+#if FF_FS_LOCK != 0
+ clear_lock(cfs);
+#endif
+#if FF_FS_REENTRANT /* Discard sync object of the current volume */
+ if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR;
+#endif
+ cfs->fs_type = 0; /* Clear old fs object */
+ }
+
+ if (fs) {
+ fs->fs_type = 0; /* Clear new fs object */
+#if FF_FS_REENTRANT /* Create sync object for the new volume */
+ if (!ff_cre_syncobj((BYTE)vol, &fs->sobj)) return FR_INT_ERR;
+#endif
+ }
+ FatFs[vol] = fs; /* Register new fs object */
+
+ if (opt == 0) return FR_OK; /* Do not mount now, it will be mounted later */
+
+ res = find_volume(&path, &fs, 0); /* Force mounted the volume */
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Open or Create a File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_open (
+ FIL* fp, /* Pointer to the blank file object */
+ const TCHAR* path, /* Pointer to the file name */
+ BYTE mode /* Access mode and file open mode flags */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+#if !FF_FS_READONLY
+ DWORD dw, cl, bcs, clst, sc;
+ FSIZE_t ofs;
+#endif
+ DEF_NAMBUF
+
+
+ if (!fp) return FR_INVALID_OBJECT;
+
+ /* Get logical drive number */
+ mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
+ res = find_volume(&path, &fs, mode);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+#if !FF_FS_READONLY /* Read/Write configuration */
+ if (res == FR_OK) {
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */
+ res = FR_INVALID_NAME;
+ }
+#if FF_FS_LOCK != 0
+ else {
+ res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Check if the file can be used */
+ }
+#endif
+ }
+ /* Create or Open a file */
+ if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
+ if (res != FR_OK) { /* No file, create new */
+ if (res == FR_NO_FILE) { /* There is no file to open, create a new entry */
+#if FF_FS_LOCK != 0
+ res = enq_lock() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES;
+#else
+ res = dir_register(&dj);
+#endif
+ }
+ mode |= FA_CREATE_ALWAYS; /* File is created */
+ }
+ else { /* Any object with the same name is already existing */
+ if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */
+ res = FR_DENIED;
+ } else {
+ if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */
+ }
+ }
+ if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate the file if overwrite mode */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ /* Get current allocation info */
+ fp->obj.fs = fs;
+ init_alloc_info(fs, &fp->obj);
+ /* Set directory entry block initial state */
+ mem_set(fs->dirbuf + 2, 0, 30); /* Clear 85 entry except for NumSec */
+ mem_set(fs->dirbuf + 38, 0, 26); /* Clear C0 entry except for NumName and NameHash */
+ fs->dirbuf[XDIR_Attr] = AM_ARC;
+ st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME());
+ fs->dirbuf[XDIR_GenFlags] = 1;
+ res = store_xdir(&dj);
+ if (res == FR_OK && fp->obj.sclust != 0) { /* Remove the cluster chain if exist */
+ res = remove_chain(&fp->obj, fp->obj.sclust, 0);
+ fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */
+ }
+ } else
+#endif
+ {
+ /* Set directory entry initial state */
+ cl = ld_clust(fs, dj.dir); /* Get current cluster chain */
+ st_dword(dj.dir + DIR_CrtTime, GET_FATTIME()); /* Set created time */
+ dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */
+ st_clust(fs, dj.dir, 0); /* Reset file allocation info */
+ st_dword(dj.dir + DIR_FileSize, 0);
+ fs->wflag = 1;
+ if (cl != 0) { /* Remove the cluster chain if exist */
+ dw = fs->winsect;
+ res = remove_chain(&dj.obj, cl, 0);
+ if (res == FR_OK) {
+ res = move_window(fs, dw);
+ fs->last_clst = cl - 1; /* Reuse the cluster hole */
+ }
+ }
+ }
+ }
+ }
+ else { /* Open an existing file */
+ if (res == FR_OK) { /* Is the object exsiting? */
+ if (dj.obj.attr & AM_DIR) { /* File open against a directory */
+ res = FR_NO_FILE;
+ } else {
+ if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */
+ res = FR_DENIED;
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED; /* Set file change flag if created or overwritten */
+ fp->dir_sect = fs->winsect; /* Pointer to the directory entry */
+ fp->dir_ptr = dj.dir;
+#if FF_FS_LOCK != 0
+ fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Lock the file for this session */
+ if (fp->obj.lockid == 0) res = FR_INT_ERR;
+#endif
+ }
+#else /* R/O configuration */
+ if (res == FR_OK) {
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it origin directory itself? */
+ res = FR_INVALID_NAME;
+ } else {
+ if (dj.obj.attr & AM_DIR) { /* Is it a directory? */
+ res = FR_NO_FILE;
+ }
+ }
+ }
+#endif
+
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fp->obj.c_scl = dj.obj.sclust; /* Get containing directory info */
+ fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
+ fp->obj.c_ofs = dj.blk_ofs;
+ init_alloc_info(fs, &fp->obj);
+ } else
+#endif
+ {
+ fp->obj.sclust = ld_clust(fs, dj.dir); /* Get object allocation info */
+ fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize);
+ }
+#if FF_USE_FASTSEEK
+ fp->cltbl = 0; /* Disable fast seek mode */
+#endif
+ fp->obj.fs = fs; /* Validate the file object */
+ fp->obj.id = fs->id;
+ fp->flag = mode; /* Set file access mode */
+ fp->err = 0; /* Clear error flag */
+ fp->sect = 0; /* Invalidate current data sector */
+ fp->fptr = 0; /* Set file pointer top of the file */
+#if !FF_FS_READONLY
+#if !FF_FS_TINY
+ mem_set(fp->buf, 0, sizeof fp->buf); /* Clear sector buffer */
+#endif
+ if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */
+ fp->fptr = fp->obj.objsize; /* Offset to seek */
+ bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */
+ clst = fp->obj.sclust; /* Follow the cluster chain */
+ for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) {
+ clst = get_fat(&fp->obj, clst);
+ if (clst <= 1) res = FR_INT_ERR;
+ if (clst == 0xFFFFFFFF) res = FR_DISK_ERR;
+ }
+ fp->clust = clst;
+ if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */
+ if ((sc = clst2sect(fs, clst)) == 0) {
+ res = FR_INT_ERR;
+ } else {
+ fp->sect = sc + (DWORD)(ofs / SS(fs));
+#if !FF_FS_TINY
+ if (disk_read(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR;
+#endif
+ }
+ }
+ }
+#endif
+ }
+
+ FREE_NAMBUF();
+ }
+
+ if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_read (
+ FIL* fp, /* Pointer to the file object */
+ void* buff, /* Pointer to data buffer */
+ UINT btr, /* Number of bytes to read */
+ UINT* br /* Pointer to number of bytes read */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, sect;
+ FSIZE_t remain;
+ UINT rcnt, cc, csect;
+ BYTE *rbuff = (BYTE*)buff;
+
+ UINT br_tmp;
+ if (!br)
+ br = &br_tmp;
+ *br = 0; /* Clear read byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) {
+ EFSPRINTF("FOV");
+ LEAVE_FF(fs, res); /* Check validity */
+ }
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+ remain = fp->obj.objsize - fp->fptr;
+ if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */
+
+ for ( ; btr; /* Repeat until btr bytes read */
+ btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += rcnt) {
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
+ if (csect == 0) { /* On the cluster boundary? */
+ if (fp->fptr == 0) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow cluster chain from the origin */
+ } else { /* Middle or end of the file */
+#if FF_USE_FASTSEEK
+ if (fp->cltbl) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ } else
+#endif
+ {
+ clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */
+ }
+ }
+ if (clst < 2) {
+ EFSPRINTF("CCHK");
+ ABORT(fs, FR_INT_ERR);
+ }
+ if (clst == 0xFFFFFFFF) {
+ EFSPRINTF("DSKC");
+ ABORT(fs, FR_DISK_ERR);
+ }
+ fp->clust = clst; /* Update current cluster */
+ }
+ sect = clst2sect(fs, fp->clust); /* Get current sector */
+ if (sect == 0) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+ cc = btr / SS(fs); /* When remaining bytes >= sector size, */
+ if (cc > 0) { /* Read maximum contiguous sectors directly */
+ if (csect + cc > fs->csize) { /* Clip at cluster boundary */
+ cc = fs->csize - csect;
+ }
+ if (disk_read(fs->pdrv, rbuff, sect, cc) != RES_OK) {
+ EFSPRINTF("RLIO");
+ ABORT(fs, FR_DISK_ERR);
+ }
+#if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */
+#if FF_FS_TINY
+ if (fs->wflag && fs->winsect - sect < cc) {
+ mem_cpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs));
+ }
+#else
+ if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) {
+ mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs));
+ }
+#endif
+#endif
+ rcnt = SS(fs) * cc; /* Number of bytes transferred */
+ continue;
+ }
+#if !FF_FS_TINY
+ if (fp->sect != sect) { /* Load data sector if not in cache */
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) {
+ EFSPRINTF("RDC");
+ ABORT(fs, FR_DISK_ERR);
+ }
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) {
+ EFSPRINTF("RSC");
+ ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
+ }
+ }
+#endif
+ fp->sect = sect;
+ }
+ rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
+ if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */
+#if FF_FS_TINY
+ if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
+ mem_cpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
+#else
+ mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
+#endif
+ }
+
+ LEAVE_FF(fs, FR_OK);
+}
+
+
+
+
+#ifdef FF_FASTFS
+/*-----------------------------------------------------------------------*/
+/* Fast Read Aligned Sized File Without a Cache */
+/*-----------------------------------------------------------------------*/
+#if FF_USE_FASTSEEK
+FRESULT f_read_fast (
+ FIL* fp, /* Pointer to the file object */
+ const void* buff, /* Pointer to the data to be written */
+ UINT btr /* Number of bytes to read */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ UINT csize_bytes;
+ DWORD clst;
+ DWORD wbytes;
+ UINT count;
+ FSIZE_t work_sector = 0;
+ FSIZE_t sector_base = 0;
+ BYTE *wbuff = (BYTE*)buff;
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) {
+ EFSPRINTF("FOV");
+ LEAVE_FF(fs, res); /* Check validity */
+ }
+
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+ FSIZE_t remain = fp->obj.objsize - fp->fptr;
+ if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */
+
+ csize_bytes = fs->csize * SS(fs);
+ DWORD csect = (UINT)((fp->fptr / SS(fs)) & (fs->csize - 1)); /* Sector offset in the cluster */
+
+ /* If inside a cluster, read the sectors and align to cluster. */
+ if (csect) {
+ wbytes = MIN(btr, (fs->csize - csect) * SS(fs));
+ f_read(fp, wbuff, wbytes, (void *)0);
+ wbuff += wbytes;
+ btr -= wbytes;
+ if (!btr)
+ goto out;
+ }
+
+ if (!fp->fptr) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow from the origin */
+ } else {
+ if (fp->cltbl) clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ else { EFSPRINTF("CLTBL"); ABORT(fs, FR_CLTBL_NO_INIT); }
+ }
+
+ if (clst < 2) { EFSPRINTF("CCHK"); ABORT(fs, FR_INT_ERR); }
+ else if (clst == 0xFFFFFFFF) { EFSPRINTF("DSKC"); ABORT(fs, FR_DISK_ERR); }
+
+ fp->clust = clst; /* Set working cluster */
+
+ wbytes = MIN(btr, csize_bytes);
+ sector_base = clst2sect(fs, fp->clust);
+ count = wbytes / SS(fs);
+ fp->fptr += wbytes;
+ btr -= wbytes;
+
+ if (!btr) { /* Final cluster/sectors read. */
+ if (disk_read(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ goto out;
+ }
+
+ while (btr) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+
+ if (clst < 2) { EFSPRINTF("CCHK2"); ABORT(fs, FR_INT_ERR); }
+ else if (clst == 0xFFFFFFFF) { EFSPRINTF("DSKC"); ABORT(fs, FR_DISK_ERR); }
+
+ fp->clust = clst;
+
+ work_sector = clst2sect(fs, fp->clust);
+ wbytes = MIN(btr, csize_bytes);
+ if ((work_sector - sector_base) == count) count += wbytes / SS(fs);
+ else {
+ if (disk_read(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ wbuff += count * SS(fs);
+
+ sector_base = work_sector;
+ count = wbytes / SS(fs);
+ }
+
+ fp->fptr += wbytes;
+ btr -= wbytes;
+
+ if (!btr) { /* Final cluster/sectors read. */
+ if (disk_read(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ }
+ }
+
+out:
+ LEAVE_FF(fs, FR_OK);
+}
+#endif
+#endif
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Write File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_write (
+ FIL* fp, /* Pointer to the file object */
+ const void* buff, /* Pointer to the data to be written */
+ UINT btw, /* Number of bytes to write */
+ UINT* bw /* Pointer to number of bytes written */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, sect;
+ UINT wcnt, cc, csect;
+ const BYTE *wbuff = (const BYTE*)buff;
+
+ UINT bw_tmp;
+ if (!bw)
+ bw = &bw_tmp;
+ *bw = 0; /* Clear write byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) {
+ EFSPRINTF("FOV");
+ LEAVE_FF(fs, res); /* Check validity */
+ }
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ /* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */
+ if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) {
+ btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr);
+ }
+
+ for ( ; btw; /* Repeat until all data written */
+ btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize) {
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */
+ if (csect == 0) { /* On the cluster boundary? */
+ if (fp->fptr == 0) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow from the origin */
+ if (clst == 0) { /* If no cluster is allocated, */
+ clst = create_chain(&fp->obj, 0); /* create a new cluster chain */
+ }
+ } else { /* On the middle or end of the file */
+#if FF_USE_FASTSEEK
+ if (fp->cltbl) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ } else
+#endif
+ {
+ clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */
+ }
+ }
+ if (clst == 0) {
+ EFSPRINTF("DSKFULL");
+ break; /* Could not allocate a new cluster (disk full) */
+ }
+ if (clst == 1) {
+ EFSPRINTF("CCHK");
+ ABORT(fs, FR_INT_ERR);
+ }
+ if (clst == 0xFFFFFFFF) {
+ EFSPRINTF("DERR");
+ ABORT(fs, FR_DISK_ERR);
+ }
+ fp->clust = clst; /* Update current cluster */
+ if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */
+ }
+#if FF_FS_TINY
+ if (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */
+#else
+ if (fp->flag & FA_DIRTY) { /* Write-back sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ sect = clst2sect(fs, fp->clust); /* Get current sector */
+ if (sect == 0) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+ cc = btw / SS(fs); /* When remaining bytes >= sector size, */
+ if (cc > 0) { /* Write maximum contiguous sectors directly */
+ if (csect + cc > fs->csize) { /* Clip at cluster boundary */
+ cc = fs->csize - csect;
+ }
+ if (disk_write(fs->pdrv, wbuff, sect, cc) != RES_OK) {
+ EFSPRINTF("WLIO");
+ ABORT(fs, FR_DISK_ERR);
+ }
+#if FF_FS_MINIMIZE <= 2
+#if FF_FS_TINY
+ if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
+ mem_cpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs));
+ fs->wflag = 0;
+ }
+#else
+ if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
+ mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs));
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+#endif
+ wcnt = SS(fs) * cc; /* Number of bytes transferred */
+ continue;
+ }
+#if FF_FS_TINY
+ if (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */
+ if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR);
+ fs->winsect = sect;
+ }
+#else
+ if (fp->sect != sect && /* Fill sector cache with file data */
+ fp->fptr < fp->obj.objsize &&
+ disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) {
+ ABORT(fs, FR_DISK_ERR);
+ }
+#endif
+ fp->sect = sect;
+ }
+ wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
+ if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */
+#if FF_FS_TINY
+ if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
+ mem_cpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */
+ fs->wflag = 1;
+#else
+ mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */
+ fp->flag |= FA_DIRTY;
+#endif
+ }
+
+ fp->flag |= FA_MODIFIED; /* Set file change flag */
+
+ LEAVE_FF(fs, FR_OK);
+}
+
+
+
+
+#ifdef FF_FASTFS
+/*-----------------------------------------------------------------------*/
+/* Fast Write Aligned Sized File Without a Cache */
+/*-----------------------------------------------------------------------*/
+#if FF_USE_FASTSEEK
+FRESULT f_write_fast (
+ FIL* fp, /* Pointer to the file object */
+ const void* buff, /* Pointer to the data to be written */
+ UINT btw /* Number of bytes to write */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ UINT csize_bytes;
+ DWORD clst;
+ DWORD wbytes;
+ UINT count;
+ FSIZE_t work_sector = 0;
+ FSIZE_t sector_base = 0;
+ BYTE *wbuff = (BYTE*)buff;
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) {
+ EFSPRINTF("FOV");
+ LEAVE_FF(fs, res); /* Check validity */
+ }
+
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+ /* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */
+ if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) {
+ btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr);
+ }
+
+ csize_bytes = fs->csize * SS(fs);
+ DWORD csect = (UINT)((fp->fptr / SS(fs)) & (fs->csize - 1)); /* Sector offset in the cluster */
+
+ /* If inside a cluster, write the sectors and align to cluster. */
+ if (csect) {
+ wbytes = MIN(btw, (fs->csize - csect) * SS(fs));
+ f_write(fp, wbuff, wbytes, (void *)0);
+ /* Ensure flushing of it. FatFS is not notified for next write if raw. */
+ f_sync(fp);
+ wbuff += wbytes;
+ btw -= wbytes;
+ if (!btw)
+ goto out;
+ }
+
+ if (!fp->fptr) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow from the origin */
+ } else {
+ if (fp->cltbl) clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ else { EFSPRINTF("CLTBL"); ABORT(fs, FR_CLTBL_NO_INIT); }
+ }
+
+ if (clst < 2) { EFSPRINTF("CCHK"); ABORT(fs, FR_INT_ERR); }
+ else if (clst == 0xFFFFFFFF) { EFSPRINTF("DSKC"); ABORT(fs, FR_DISK_ERR); }
+
+ fp->clust = clst; /* Set working cluster */
+
+ wbytes = MIN(btw, csize_bytes);
+ sector_base = clst2sect(fs, fp->clust);
+ count = wbytes / SS(fs);
+ fp->fptr += wbytes;
+ btw -= wbytes;
+
+ if (!btw) { /* Final cluster/sectors write. */
+ if (disk_write(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ goto out;
+ }
+
+ while (btw) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+
+ if (clst < 2) { EFSPRINTF("CCHK2"); ABORT(fs, FR_INT_ERR); }
+ else if (clst == 0xFFFFFFFF) { EFSPRINTF("DSKC"); ABORT(fs, FR_DISK_ERR); }
+
+ fp->clust = clst;
+
+ work_sector = clst2sect(fs, fp->clust);
+ wbytes = MIN(btw, csize_bytes);
+ if ((work_sector - sector_base) == count) count += wbytes / SS(fs);
+ else {
+ if (disk_write(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ wbuff += count * SS(fs);
+
+ sector_base = work_sector;
+ count = wbytes / SS(fs);
+ }
+
+ fp->fptr += wbytes;
+ btw -= wbytes;
+
+ if (!btw) { /* Final cluster/sectors write. */
+ if (disk_write(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+ }
+
+out:
+ fp->flag |= FA_MODIFIED; /* Set file change flag */
+
+ LEAVE_FF(fs, FR_OK);
+}
+#endif
+#endif
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Synchronize the File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_sync (
+ FIL* fp /* Pointer to the file object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD tm;
+ BYTE *dir;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) {
+ if (fp->flag & FA_MODIFIED) { /* Is there any change to the file? */
+#if !FF_FS_TINY
+ if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) LEAVE_FF(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ /* Update the directory entry */
+ tm = GET_FATTIME(); /* Modified time */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ res = fill_first_frag(&fp->obj); /* Fill first fragment on the FAT if needed */
+ if (res == FR_OK) {
+ res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */
+ }
+ if (res == FR_OK) {
+ DIR dj;
+ DEF_NAMBUF
+
+ INIT_NAMBUF(fs);
+ res = load_obj_xdir(&dj, &fp->obj); /* Load directory entry block */
+ if (res == FR_OK) {
+ fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */
+ fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation information */
+ st_dword(fs->dirbuf + XDIR_FstClus, fp->obj.sclust);
+ st_qword(fs->dirbuf + XDIR_FileSize, fp->obj.objsize);
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, fp->obj.objsize);
+ st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Update modified time */
+ fs->dirbuf[XDIR_ModTime10] = 0;
+ st_dword(fs->dirbuf + XDIR_AccTime, 0);
+ res = store_xdir(&dj); /* Restore it to the directory */
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ fp->flag &= (BYTE)~FA_MODIFIED;
+ }
+ }
+ FREE_NAMBUF();
+ }
+ } else
+#endif
+ {
+ res = move_window(fs, fp->dir_sect);
+ if (res == FR_OK) {
+ dir = fp->dir_ptr;
+ dir[DIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */
+ st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation information */
+ st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */
+ st_dword(dir + DIR_ModTime, tm); /* Update modified time */
+ st_word(dir + DIR_LstAccDate, 0);
+ fs->wflag = 1;
+ res = sync_fs(fs); /* Restore it to the directory */
+ fp->flag &= (BYTE)~FA_MODIFIED;
+ }
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Close File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_close (
+ FIL* fp /* Pointer to the file object to be closed */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+
+#if !FF_FS_READONLY
+ res = f_sync(fp); /* Flush cached data */
+ if (res == FR_OK)
+#endif
+ {
+ res = validate(&fp->obj, &fs); /* Lock volume */
+ if (res == FR_OK) {
+#if FF_FS_LOCK != 0
+ res = dec_lock(fp->obj.lockid); /* Decrement file open counter */
+ if (res == FR_OK) fp->obj.fs = 0; /* Invalidate file object */
+#else
+ fp->obj.fs = 0; /* Invalidate file object */
+#endif
+#if FF_FS_REENTRANT
+ unlock_fs(fs, FR_OK); /* Unlock volume */
+#endif
+ }
+ }
+ return res;
+}
+
+
+
+
+#if FF_FS_RPATH >= 1
+/*-----------------------------------------------------------------------*/
+/* Change Current Directory or Current Drive, Get Current Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_chdrive (
+ const TCHAR* path /* Drive number to set */
+)
+{
+ int vol;
+
+
+ /* Get logical drive number */
+ vol = get_ldnumber(&path);
+ if (vol < 0) return FR_INVALID_DRIVE;
+ CurrVol = (BYTE)vol; /* Set it as current volume */
+
+ return FR_OK;
+}
+
+
+
+FRESULT f_chdir (
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+#if FF_STR_VOLUME_ID == 2
+ UINT i;
+#endif
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the path */
+ if (res == FR_OK) { /* Follow completed */
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it the start directory itself? */
+ fs->cdir = dj.obj.sclust;
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->cdc_scl = dj.obj.c_scl;
+ fs->cdc_size = dj.obj.c_size;
+ fs->cdc_ofs = dj.obj.c_ofs;
+ }
+#endif
+ } else {
+ if (dj.obj.attr & AM_DIR) { /* It is a sub-directory */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->cdir = ld_dword(fs->dirbuf + XDIR_FstClus); /* Sub-directory cluster */
+ fs->cdc_scl = dj.obj.sclust; /* Save containing directory information */
+ fs->cdc_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
+ fs->cdc_ofs = dj.blk_ofs;
+ } else
+#endif
+ {
+ fs->cdir = ld_clust(fs, dj.dir); /* Sub-directory cluster */
+ }
+ } else {
+ res = FR_NO_PATH; /* Reached but a file */
+ }
+ }
+ }
+ FREE_NAMBUF();
+ if (res == FR_NO_FILE) res = FR_NO_PATH;
+#if FF_STR_VOLUME_ID == 2 /* Also current drive is changed at Unix style volume ID */
+ if (res == FR_OK) {
+ for (i = FF_VOLUMES - 1; i && fs != FatFs[i]; i--) ; /* Set current drive */
+ CurrVol = (BYTE)i;
+ }
+#endif
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+#if FF_FS_RPATH >= 2
+FRESULT f_getcwd (
+ TCHAR* buff, /* Pointer to the directory path */
+ UINT len /* Size of buff in unit of TCHAR */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ UINT i, n;
+ DWORD ccl;
+ TCHAR *tp = buff;
+#if FF_VOLUMES >= 2
+ UINT vl;
+#if FF_STR_VOLUME_ID
+ const char *vp;
+#endif
+#endif
+ FILINFO fno;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ buff[0] = 0; /* Set null string to get current volume */
+ res = find_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+
+ /* Follow parent directories and create the path */
+ i = len; /* Bottom of buffer (directory stack base) */
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* (Cannot do getcwd on exFAT and returns root path) */
+ dj.obj.sclust = fs->cdir; /* Start to follow upper directory from current directory */
+ while ((ccl = dj.obj.sclust) != 0) { /* Repeat while current directory is a sub-directory */
+ res = dir_sdi(&dj, 1 * SZDIRE); /* Get parent directory */
+ if (res != FR_OK) break;
+ res = move_window(fs, dj.sect);
+ if (res != FR_OK) break;
+ dj.obj.sclust = ld_clust(fs, dj.dir); /* Goto parent directory */
+ res = dir_sdi(&dj, 0);
+ if (res != FR_OK) break;
+ do { /* Find the entry links to the child directory */
+ res = DIR_READ_FILE(&dj);
+ if (res != FR_OK) break;
+ if (ccl == ld_clust(fs, dj.dir)) break; /* Found the entry */
+ res = dir_next(&dj, 0);
+ } while (res == FR_OK);
+ if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */
+ if (res != FR_OK) break;
+ get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */
+ for (n = 0; fno.fname[n]; n++) ; /* Name length */
+ if (i < n + 1) { /* Insufficient space to store the path name? */
+ res = FR_NOT_ENOUGH_CORE; break;
+ }
+ while (n) buff[--i] = fno.fname[--n]; /* Stack the name */
+ buff[--i] = '/';
+ }
+ }
+ if (res == FR_OK) {
+ if (i == len) buff[--i] = '/'; /* Is it the root-directory? */
+#if FF_VOLUMES >= 2 /* Put drive prefix */
+ vl = 0;
+#if FF_STR_VOLUME_ID >= 1 /* String volume ID */
+ for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ;
+ if (i >= n + 2) {
+ if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/';
+ for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ;
+ if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':';
+ vl++;
+ }
+#else /* Numeric volume ID */
+ if (i >= 3) {
+ *tp++ = (TCHAR)'0' + CurrVol;
+ *tp++ = (TCHAR)':';
+ vl = 2;
+ }
+#endif
+ if (vl == 0) res = FR_NOT_ENOUGH_CORE;
+#endif
+ /* Add current directory path */
+ if (res == FR_OK) {
+ do *tp++ = buff[i++]; while (i < len); /* Copy stacked path string */
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ *tp = 0;
+ LEAVE_FF(fs, res);
+}
+
+#endif /* FF_FS_RPATH >= 2 */
+#endif /* FF_FS_RPATH >= 1 */
+
+
+
+#if FF_FS_MINIMIZE <= 2
+/*-----------------------------------------------------------------------*/
+/* Seek File Read/Write Pointer */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_lseek (
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t ofs /* File pointer from top of file */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, bcs, nsect;
+ FSIZE_t ifptr;
+#if FF_USE_FASTSEEK
+ DWORD cl, pcl, ncl, tcl, dsc, tlen, ulen, *tbl;
+#endif
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) res = (FRESULT)fp->err;
+#if FF_FS_EXFAT && !FF_FS_READONLY
+ if (res == FR_OK && fs->fs_type == FS_EXFAT) {
+ res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */
+ }
+#endif
+ if (res != FR_OK) LEAVE_FF(fs, res);
+
+#if FF_USE_FASTSEEK
+ if (fp->cltbl) { /* Fast seek */
+ if (ofs == CREATE_LINKMAP) { /* Create CLMT */
+ tbl = fp->cltbl;
+ tlen = *tbl++; ulen = 2; /* Given table size and required table size */
+ cl = fp->obj.sclust; /* Origin of the chain */
+ if (cl != 0) {
+ do {
+ /* Get a fragment */
+ tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */
+ do {
+ pcl = cl; ncl++;
+ cl = get_fat(&fp->obj, cl);
+ if (cl <= 1) ABORT(fs, FR_INT_ERR);
+ if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ } while (cl == pcl + 1);
+ if (ulen <= tlen) { /* Store the length and top of the fragment */
+ *tbl++ = ncl; *tbl++ = tcl;
+ }
+ } while (cl < fs->n_fatent); /* Repeat until end of chain */
+ }
+ *fp->cltbl = ulen; /* Number of items used */
+ if (ulen <= tlen) {
+ *tbl = 0; /* Terminate table */
+ } else {
+ res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */
+ }
+ } else { /* Fast seek */
+ if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; /* Clip offset at the file size */
+ fp->fptr = ofs; /* Set file pointer */
+ if (ofs > 0) {
+ fp->clust = clmt_clust(fp, ofs - 1);
+ dsc = clst2sect(fs, fp->clust);
+ if (dsc == 0) ABORT(fs, FR_INT_ERR);
+ dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1);
+ if (fp->fptr % SS(fs) && dsc != fp->sect) { /* Refill sector cache if needed */
+#if !FF_FS_TINY
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */
+#endif
+ fp->sect = dsc;
+ }
+ }
+ }
+ } else
+#endif
+
+ /* Normal Seek */
+ {
+#if FF_FS_EXFAT
+ if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4 GiB - 1 if at FATxx */
+#endif
+ if (ofs > fp->obj.objsize && (FF_FS_READONLY || !(fp->flag & FA_WRITE))) { /* In read-only mode, clip offset with the file size */
+ ofs = fp->obj.objsize;
+ }
+ ifptr = fp->fptr;
+ fp->fptr = nsect = 0;
+ if (ofs > 0) {
+ bcs = (DWORD)fs->csize * SS(fs); /* Cluster size (byte) */
+ if (ifptr > 0 &&
+ (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */
+ fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); /* start from the current cluster */
+ ofs -= fp->fptr;
+ clst = fp->clust;
+ } else { /* When seek to back cluster, */
+ clst = fp->obj.sclust; /* start from the first cluster */
+#if !FF_FS_READONLY
+ if (clst == 0) { /* If no cluster chain, create a new chain */
+ clst = create_chain(&fp->obj, 0);
+ if (clst == 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->obj.sclust = clst;
+ }
+#endif
+ fp->clust = clst;
+ }
+ if (clst != 0) {
+ while (ofs > bcs) { /* Cluster following loop */
+ ofs -= bcs; fp->fptr += bcs;
+#if !FF_FS_READONLY
+ if (fp->flag & FA_WRITE) { /* Check if in write mode or not */
+ if (FF_FS_EXFAT && fp->fptr > fp->obj.objsize) { /* No FAT chain object needs correct objsize to generate FAT value */
+ fp->obj.objsize = fp->fptr;
+ fp->flag |= FA_MODIFIED;
+ }
+ clst = create_chain(&fp->obj, clst); /* Follow chain with forceed stretch */
+ if (clst == 0) { /* Clip file size in case of disk full */
+ ofs = 0; break;
+ }
+ } else
+#endif
+ {
+ clst = get_fat(&fp->obj, clst); /* Follow cluster chain if not in write mode */
+ }
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR);
+ fp->clust = clst;
+ }
+ fp->fptr += ofs;
+ if (ofs % SS(fs)) {
+ nsect = clst2sect(fs, clst); /* Current sector */
+ if (nsect == 0) ABORT(fs, FR_INT_ERR);
+ nsect += (DWORD)(ofs / SS(fs));
+ }
+ }
+ }
+ if (!FF_FS_READONLY && fp->fptr > fp->obj.objsize) { /* Set file change flag if the file size is extended */
+ fp->obj.objsize = fp->fptr;
+ fp->flag |= FA_MODIFIED;
+ }
+ if (fp->fptr % SS(fs) && nsect != fp->sect) { /* Fill sector cache if needed */
+#if !FF_FS_TINY
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
+#endif
+ fp->sect = nsect;
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+#ifdef FF_FASTFS
+#if FF_USE_FASTSEEK
+/*-----------------------------------------------------------------------*/
+/* Seek File Read/Write Pointer */
+/*-----------------------------------------------------------------------*/
+
+DWORD *f_expand_cltbl (
+ FIL* fp, /* Pointer to the file object */
+ UINT tblsz, /* Size of table */
+ DWORD *tbl, /* Table pointer */
+ FSIZE_t ofs /* File pointer from top of file */
+)
+{
+ if (fp->flag & FA_WRITE) f_lseek(fp, ofs); /* Expand file if write is enabled */
+ fp->cltbl = (DWORD *)tbl;
+ fp->cltbl[0] = tblsz;
+ if (f_lseek(fp, CREATE_LINKMAP)) { /* Create cluster link table */
+ fp->cltbl = (void *)0;
+ EFSPRINTF("CLTBLSZ");
+ return (void *)0;
+ }
+ f_lseek(fp, 0);
+
+ return fp->cltbl;
+}
+#endif
+#endif
+
+
+
+
+#if FF_FS_MINIMIZE <= 1
+/*-----------------------------------------------------------------------*/
+/* Create a Directory Object */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_opendir (
+ DIR* dp, /* Pointer to directory object to create */
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ if (!dp) return FR_INVALID_OBJECT;
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ dp->obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(dp, path); /* Follow the path to the directory */
+ if (res == FR_OK) { /* Follow completed */
+ if (!(dp->fn[NSFLAG] & NS_NONAME)) { /* It is not the origin directory itself */
+ if (dp->obj.attr & AM_DIR) { /* This object is a sub-directory */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ dp->obj.c_scl = dp->obj.sclust; /* Get containing directory inforamation */
+ dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat;
+ dp->obj.c_ofs = dp->blk_ofs;
+ init_alloc_info(fs, &dp->obj); /* Get object allocation info */
+ } else
+#endif
+ {
+ dp->obj.sclust = ld_clust(fs, dp->dir); /* Get object allocation info */
+ }
+ } else { /* This object is a file */
+ res = FR_NO_PATH;
+ }
+ }
+ if (res == FR_OK) {
+ dp->obj.id = fs->id;
+ res = dir_sdi(dp, 0); /* Rewind directory */
+#if FF_FS_LOCK != 0
+ if (res == FR_OK) {
+ if (dp->obj.sclust != 0) {
+ dp->obj.lockid = inc_lock(dp, 0); /* Lock the sub directory */
+ if (!dp->obj.lockid) res = FR_TOO_MANY_OPEN_FILES;
+ } else {
+ dp->obj.lockid = 0; /* Root directory need not to be locked */
+ }
+ }
+#endif
+ }
+ }
+ FREE_NAMBUF();
+ if (res == FR_NO_FILE) res = FR_NO_PATH;
+ }
+ if (res != FR_OK) dp->obj.fs = 0; /* Invalidate the directory object if function faild */
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Close Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_closedir (
+ DIR *dp /* Pointer to the directory object to be closed */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+
+
+ res = validate(&dp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) {
+#if FF_FS_LOCK != 0
+ if (dp->obj.lockid) res = dec_lock(dp->obj.lockid); /* Decrement sub-directory open counter */
+ if (res == FR_OK) dp->obj.fs = 0; /* Invalidate directory object */
+#else
+ dp->obj.fs = 0; /* Invalidate directory object */
+#endif
+#if FF_FS_REENTRANT
+ unlock_fs(fs, FR_OK); /* Unlock volume */
+#endif
+ }
+ return res;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read Directory Entries in Sequence */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_readdir (
+ DIR* dp, /* Pointer to the open directory object */
+ FILINFO* fno /* Pointer to file information to return */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = validate(&dp->obj, &fs); /* Check validity of the directory object */
+ if (res == FR_OK) {
+ if (!fno) {
+ res = dir_sdi(dp, 0); /* Rewind the directory object */
+ } else {
+ INIT_NAMBUF(fs);
+ res = DIR_READ_FILE(dp); /* Read an item */
+ if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory */
+ if (res == FR_OK) { /* A valid entry is found */
+ get_fileinfo(dp, fno); /* Get the object information */
+ res = dir_next(dp, 0); /* Increment index for next */
+ if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory now */
+ }
+ FREE_NAMBUF();
+ }
+ }
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if FF_USE_FIND
+/*-----------------------------------------------------------------------*/
+/* Find Next File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_findnext (
+ DIR* dp, /* Pointer to the open directory object */
+ FILINFO* fno /* Pointer to the file information structure */
+)
+{
+ FRESULT res;
+
+
+ for (;;) {
+ res = f_readdir(dp, fno); /* Get a directory item */
+ if (res != FR_OK || !fno || !fno->fname[0]) break; /* Terminate if any error or end of directory */
+ if (pattern_matching(dp->pat, fno->fname, 0, 0)) break; /* Test for the file name */
+#if FF_USE_LFN && FF_USE_FIND == 2
+ if (pattern_matching(dp->pat, fno->altname, 0, 0)) break; /* Test for alternative name if exist */
+#endif
+ }
+ return res;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Find First File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_findfirst (
+ DIR* dp, /* Pointer to the blank directory object */
+ FILINFO* fno, /* Pointer to the file information structure */
+ const TCHAR* path, /* Pointer to the directory to open */
+ const TCHAR* pattern /* Pointer to the matching pattern */
+)
+{
+ FRESULT res;
+
+
+ dp->pat = pattern; /* Save pointer to pattern string */
+ res = f_opendir(dp, path); /* Open the target directory */
+ if (res == FR_OK) {
+ res = f_findnext(dp, fno); /* Find the first item */
+ }
+ return res;
+}
+
+#endif /* FF_USE_FIND */
+
+
+
+#if FF_FS_MINIMIZE == 0
+/*-----------------------------------------------------------------------*/
+/* Get File Status */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_stat (
+ const TCHAR* path, /* Pointer to the file path */
+ FILINFO* fno /* Pointer to file information to return */
+)
+{
+ FRESULT res;
+ DIR dj;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &dj.obj.fs, 0);
+ if (res == FR_OK) {
+ INIT_NAMBUF(dj.obj.fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK) { /* Follow completed */
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* It is origin directory */
+ res = FR_INVALID_NAME;
+ } else { /* Found an object */
+ if (fno) get_fileinfo(&dj, fno);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(dj.obj.fs, res);
+}
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Get Number of Free Clusters */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_getfree (
+ const TCHAR* path, /* Logical drive number */
+ DWORD* nclst, /* Pointer to a variable to return number of free clusters */
+ FATFS** fatfs /* Pointer to return pointer to corresponding filesystem object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD nfree, clst, sect, stat;
+ UINT i;
+ FFOBJID obj;
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ if (fatfs) *fatfs = fs; /* Return ptr to the fs object */
+ /* If free_clst is valid, return it without full FAT scan */
+ if (fs->free_clst <= fs->n_fatent - 2) {
+ *nclst = fs->free_clst;
+ } else {
+ /* Scan FAT to obtain number of free clusters */
+ nfree = 0;
+ if (fs->fs_type == FS_FAT12) { /* FAT12: Scan bit field FAT entries */
+ clst = 2; obj.fs = fs;
+ do {
+ stat = get_fat(&obj, clst);
+ if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
+ if (stat == 1) { res = FR_INT_ERR; break; }
+ if (stat == 0) nfree++;
+ } while (++clst < fs->n_fatent);
+ } else {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan allocation bitmap */
+ BYTE bm;
+ UINT b;
+
+ clst = fs->n_fatent - 2; /* Number of clusters */
+ sect = fs->bitbase; /* Bitmap sector */
+ i = 0; /* Offset in the sector */
+ do { /* Counts numbuer of bits with zero in the bitmap */
+ if (i == 0) {
+ res = move_window(fs, sect++);
+ if (res != FR_OK) break;
+ }
+ for (b = 8, bm = fs->win[i]; b && clst; b--, clst--) {
+ if (!(bm & 1)) nfree++;
+ bm >>= 1;
+ }
+ i = (i + 1) % SS(fs);
+ } while (clst);
+ } else
+#endif
+ { /* FAT16/32: Scan WORD/DWORD FAT entries */
+ clst = fs->n_fatent; /* Number of entries */
+ sect = fs->fatbase; /* Top of the FAT */
+ i = 0; /* Offset in the sector */
+ do { /* Counts numbuer of entries with zero in the FAT */
+ if (i == 0) {
+ res = move_window(fs, sect++);
+ if (res != FR_OK) break;
+ }
+ if (fs->fs_type == FS_FAT16) {
+ if (ld_word(fs->win + i) == 0) nfree++;
+ i += 2;
+ } else {
+ if ((ld_dword(fs->win + i) & 0x0FFFFFFF) == 0) nfree++;
+ i += 4;
+ }
+ i %= SS(fs);
+ } while (--clst);
+ }
+ }
+ *nclst = nfree; /* Return the free clusters */
+ fs->free_clst = nfree; /* Now free_clst is valid */
+ fs->fsi_flag |= 1; /* FAT32: FSInfo is to be updated */
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Truncate File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_truncate (
+ FIL* fp /* Pointer to the file object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD ncl;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ if (fp->fptr < fp->obj.objsize) { /* Process when fptr is not on the eof */
+ if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */
+ res = remove_chain(&fp->obj, fp->obj.sclust, 0);
+ fp->obj.sclust = 0;
+ } else { /* When truncate a part of the file, remove remaining clusters */
+ ncl = get_fat(&fp->obj, fp->clust);
+ res = FR_OK;
+ if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (ncl == 1) res = FR_INT_ERR;
+ if (res == FR_OK && ncl < fs->n_fatent) {
+ res = remove_chain(&fp->obj, ncl, fp->clust);
+ }
+ }
+ fp->obj.objsize = fp->fptr; /* Set file size to current read/write point */
+ fp->flag |= FA_MODIFIED;
+#if !FF_FS_TINY
+ if (res == FR_OK && (fp->flag & FA_DIRTY)) {
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) {
+ res = FR_DISK_ERR;
+ } else {
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+ }
+#endif
+ if (res != FR_OK) ABORT(fs, res);
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Delete a File/Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_unlink (
+ const TCHAR* path /* Pointer to the file or directory path */
+)
+{
+ FRESULT res;
+ DIR dj, sdj;
+ DWORD dclst = 0;
+ FATFS *fs;
+#if FF_FS_EXFAT
+ FFOBJID obj;
+#endif
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, FA_WRITE);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (FF_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) {
+ res = FR_INVALID_NAME; /* Cannot remove dot entry */
+ }
+#if FF_FS_LOCK != 0
+ if (res == FR_OK) res = chk_lock(&dj, 2); /* Check if it is an open object */
+#endif
+ if (res == FR_OK) { /* The object is accessible */
+ if (dj.fn[NSFLAG] & NS_NONAME) {
+ res = FR_INVALID_NAME; /* Cannot remove the origin directory */
+ } else {
+ if (dj.obj.attr & AM_RDO) {
+ res = FR_DENIED; /* Cannot remove R/O object */
+ }
+ }
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ obj.fs = fs;
+ if (fs->fs_type == FS_EXFAT) {
+ init_alloc_info(fs, &obj);
+ dclst = obj.sclust;
+ } else
+#endif
+ {
+ dclst = ld_clust(fs, dj.dir);
+ }
+ if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory? */
+#if FF_FS_RPATH != 0
+ if (dclst == fs->cdir) { /* Is it the current directory? */
+ res = FR_DENIED;
+ } else
+#endif
+ {
+ sdj.obj.fs = fs; /* Open the sub-directory */
+ sdj.obj.sclust = dclst;
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ sdj.obj.objsize = obj.objsize;
+ sdj.obj.stat = obj.stat;
+ }
+#endif
+ res = dir_sdi(&sdj, 0);
+ if (res == FR_OK) {
+ res = DIR_READ_FILE(&sdj); /* Test if the directory is empty */
+ if (res == FR_OK) res = FR_DENIED; /* Not empty? */
+ if (res == FR_NO_FILE) res = FR_OK; /* Empty? */
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ res = dir_remove(&dj); /* Remove the directory entry */
+ if (res == FR_OK && dclst != 0) { /* Remove the cluster chain if exist */
+#if FF_FS_EXFAT
+ res = remove_chain(&obj, dclst, 0);
+#else
+ res = remove_chain(&dj.obj, dclst, 0);
+#endif
+ }
+ if (res == FR_OK) res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Create a Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mkdir (
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FFOBJID sobj;
+ FATFS *fs;
+ DWORD dcl, pcl, tm;
+ DEF_NAMBUF
+
+
+ res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK) res = FR_EXIST; /* Name collision? */
+ if (FF_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) { /* Invalid name? */
+ res = FR_INVALID_NAME;
+ }
+ if (res == FR_NO_FILE) { /* It is clear to create a new directory */
+ sobj.fs = fs; /* New object id to create a new chain */
+ dcl = create_chain(&sobj, 0); /* Allocate a cluster for the new directory */
+ res = FR_OK;
+ if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster? */
+ if (dcl == 1) res = FR_INT_ERR; /* Any insanity? */
+ if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; /* Disk error? */
+ tm = GET_FATTIME();
+ if (res == FR_OK) {
+ res = dir_clear(fs, dcl); /* Clean up the new table */
+ if (res == FR_OK) {
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* Create dot entries (FAT only) */
+ mem_set(fs->win + DIR_Name, ' ', 11); /* Create "." entry */
+ fs->win[DIR_Name] = '.';
+ fs->win[DIR_Attr] = AM_DIR;
+ st_dword(fs->win + DIR_ModTime, tm);
+ st_clust(fs, fs->win, dcl);
+ mem_cpy(fs->win + SZDIRE, fs->win, SZDIRE); /* Create ".." entry */
+ fs->win[SZDIRE + 1] = '.'; pcl = dj.obj.sclust;
+ st_clust(fs, fs->win + SZDIRE, pcl);
+ fs->wflag = 1;
+ }
+ res = dir_register(&dj); /* Register the object to the parent directoy */
+ }
+ }
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* Initialize directory entry block */
+ st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Created time */
+ st_dword(fs->dirbuf + XDIR_FstClus, dcl); /* Table start cluster */
+ st_dword(fs->dirbuf + XDIR_FileSize, (DWORD)fs->csize * SS(fs)); /* File size needs to be valid */
+ st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)fs->csize * SS(fs));
+ fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag */
+ fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ st_dword(dj.dir + DIR_ModTime, tm); /* Created time */
+ st_clust(fs, dj.dir, dcl); /* Table start cluster */
+ dj.dir[DIR_Attr] = AM_DIR; /* Attribute */
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ } else {
+ remove_chain(&sobj, dcl, 0); /* Could not register, remove the allocated cluster */
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Rename a File/Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_rename (
+ const TCHAR* path_old, /* Pointer to the object name to be renamed */
+ const TCHAR* path_new /* Pointer to the new name */
+)
+{
+ FRESULT res;
+ DIR djo, djn;
+ FATFS *fs;
+ BYTE buf[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir;
+ DWORD dw;
+ DEF_NAMBUF
+
+
+ get_ldnumber(&path_new); /* Snip the drive number of new name off */
+ res = find_volume(&path_old, &fs, FA_WRITE); /* Get logical drive of the old object */
+ if (res == FR_OK) {
+ djo.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&djo, path_old); /* Check old object */
+ if (res == FR_OK && (djo.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check validity of name */
+#if FF_FS_LOCK != 0
+ if (res == FR_OK) {
+ res = chk_lock(&djo, 2);
+ }
+#endif
+ if (res == FR_OK) { /* Object to be renamed is found */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* At exFAT volume */
+ BYTE nf, nn;
+ WORD nh;
+
+ mem_cpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */
+ mem_cpy(&djn, &djo, sizeof djo);
+ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
+ if (res == FR_OK) { /* Is new name already in use by any other object? */
+ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
+ }
+ if (res == FR_NO_FILE) { /* It is a valid path and no name collision */
+ res = dir_register(&djn); /* Register the new entry */
+ if (res == FR_OK) {
+ nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName];
+ nh = ld_word(fs->dirbuf + XDIR_NameHash);
+ mem_cpy(fs->dirbuf, buf, SZDIRE * 2); /* Restore 85+C0 entry */
+ fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn;
+ st_word(fs->dirbuf + XDIR_NameHash, nh);
+ if (!(fs->dirbuf[XDIR_Attr] & AM_DIR)) fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */
+/* Start of critical section where an interruption can cause a cross-link */
+ res = store_xdir(&djn);
+ }
+ }
+ } else
+#endif
+ { /* At FAT/FAT32 volume */
+ mem_cpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */
+ mem_cpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */
+ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
+ if (res == FR_OK) { /* Is new name already in use by any other object? */
+ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
+ }
+ if (res == FR_NO_FILE) { /* It is a valid path and no name collision */
+ res = dir_register(&djn); /* Register the new entry */
+ if (res == FR_OK) {
+ dir = djn.dir; /* Copy directory entry of the object except name */
+ mem_cpy(dir + 13, buf + 13, SZDIRE - 13);
+ dir[DIR_Attr] = buf[DIR_Attr];
+ if (!(dir[DIR_Attr] & AM_DIR)) dir[DIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */
+ fs->wflag = 1;
+ if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */
+ dw = clst2sect(fs, ld_clust(fs, dir));
+ if (dw == 0) {
+ res = FR_INT_ERR;
+ } else {
+/* Start of critical section where an interruption can cause a cross-link */
+ res = move_window(fs, dw);
+ dir = fs->win + SZDIRE * 1; /* Ptr to .. entry */
+ if (res == FR_OK && dir[1] == '.') {
+ st_clust(fs, dir, djn.obj.sclust);
+ fs->wflag = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ res = dir_remove(&djo); /* Remove old entry */
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+/* End of the critical section */
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_FS_MINIMIZE == 0 */
+#endif /* FF_FS_MINIMIZE <= 1 */
+#endif /* FF_FS_MINIMIZE <= 2 */
+
+
+
+#if FF_USE_CHMOD && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Change Attribute */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_chmod (
+ const TCHAR* path, /* Pointer to the file path */
+ BYTE attr, /* Attribute bits */
+ BYTE mask /* Attribute mask to change */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */
+ if (res == FR_OK) {
+ mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->dirbuf[XDIR_Attr] = (attr & mask) | (fs->dirbuf[XDIR_Attr] & (BYTE)~mask); /* Apply attribute change */
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ dj.dir[DIR_Attr] = (attr & mask) | (dj.dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Change Timestamp */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_utime (
+ const TCHAR* path, /* Pointer to the file/directory name */
+ const FILINFO* fno /* Pointer to the timestamp to be set */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ st_dword(fs->dirbuf + XDIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime);
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ st_dword(dj.dir + DIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime);
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* FF_USE_CHMOD && !FF_FS_READONLY */
+
+
+
+#if FF_USE_LABEL
+/*-----------------------------------------------------------------------*/
+/* Get Volume Label */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_getlabel (
+ const TCHAR* path, /* Logical drive number */
+ TCHAR* label, /* Buffer to store the volume label */
+ DWORD* vsn /* Variable to store the volume serial number */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ UINT si, di;
+ WCHAR wc;
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+
+ /* Get volume label */
+ if (res == FR_OK && label) {
+ dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */
+ res = dir_sdi(&dj, 0);
+ if (res == FR_OK) {
+ res = DIR_READ_LABEL(&dj); /* Find a volume label entry */
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ WCHAR hs;
+
+ for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */
+ wc = ld_word(dj.dir + XDIR_Label + si * 2);
+ if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */
+ hs = wc; continue;
+ }
+ wc = put_utf((DWORD)hs << 16 | wc, &label[di], 4);
+ if (wc == 0) { di = 0; break; }
+ di += wc;
+ hs = 0;
+ }
+ if (hs != 0) di = 0; /* Broken surrogate pair? */
+ label[di] = 0;
+ } else
+#endif
+ {
+ si = di = 0; /* Extract volume label from AM_VOL entry */
+ while (si < 11) {
+ wc = dj.dir[si++];
+#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode output */
+ if (dbc_1st((BYTE)wc) && si < 11) wc = wc << 8 | dj.dir[si++]; /* Is it a DBC? */
+ wc = ff_oem2uni(wc, CODEPAGE); /* Convert it into Unicode */
+ if (wc != 0) wc = put_utf(wc, &label[di], 4); /* Put it in Unicode */
+ if (wc == 0) { di = 0; break; }
+ di += wc;
+#else /* ANSI/OEM output */
+ label[di++] = (TCHAR)wc;
+#endif
+ }
+ do { /* Truncate trailing spaces */
+ label[di] = 0;
+ if (di == 0) break;
+ } while (label[--di] == ' ');
+ }
+ }
+ }
+ if (res == FR_NO_FILE) { /* No label entry and return nul string */
+ label[0] = 0;
+ res = FR_OK;
+ }
+ }
+
+ /* Get volume serial number */
+ if (res == FR_OK && vsn) {
+ res = move_window(fs, fs->volbase);
+ if (res == FR_OK) {
+ switch (fs->fs_type) {
+ case FS_EXFAT:
+ di = BPB_VolIDEx; break;
+
+ case FS_FAT32:
+ di = BS_VolID32; break;
+
+ default:
+ di = BS_VolID;
+ }
+ *vsn = ld_dword(fs->win + di);
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Set Volume Label */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_setlabel (
+ const TCHAR* label /* Volume label to set with heading logical drive number */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ BYTE dirvn[22];
+ UINT di;
+ WCHAR wc;
+ static const char badchr[] = "+.,;=[]/\\\"*:<>\?|\x7F"; /* [0..] for FAT, [7..] for exFAT */
+#if FF_USE_LFN
+ DWORD dc;
+#endif
+
+ /* Get logical drive */
+ res = find_volume(&label, &fs, FA_WRITE);
+ if (res != FR_OK) LEAVE_FF(fs, res);
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ mem_set(dirvn, 0, 22);
+ di = 0;
+ while ((UINT)*label >= ' ') { /* Create volume label */
+ dc = tchar2uni(&label); /* Get a Unicode character */
+ if (dc >= 0x10000) {
+ if (dc == 0xFFFFFFFF || di >= 10) { /* Wrong surrogate or buffer overflow */
+ dc = 0;
+ } else {
+ st_word(dirvn + di * 2, (WCHAR)(dc >> 16)); di++;
+ }
+ }
+ if (dc == 0 || chk_chr(badchr + 7, (int)dc) || di >= 11) { /* Check validity of the volume label */
+ LEAVE_FF(fs, FR_INVALID_NAME);
+ }
+ st_word(dirvn + di * 2, (WCHAR)dc); di++;
+ }
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ mem_set(dirvn, ' ', 11);
+ di = 0;
+ while ((UINT)*label >= ' ') { /* Create volume label */
+#if FF_USE_LFN
+ dc = tchar2uni(&label);
+ wc = (dc < 0x10000) ? ff_uni2oem(ff_wtoupper(dc), CODEPAGE) : 0;
+#else /* ANSI/OEM input */
+ wc = (BYTE)*label++;
+ if (dbc_1st((BYTE)wc)) wc = dbc_2nd((BYTE)*label) ? wc << 8 | (BYTE)*label++ : 0;
+ if (IsLower(wc)) wc -= 0x20; /* To upper ASCII characters */
+#if FF_CODE_PAGE == 0
+ if (ExCvt && wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */
+#elif FF_CODE_PAGE < 900
+ if (wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */
+#endif
+#endif
+ if (wc == 0 || chk_chr(badchr + 0, (int)wc) || di >= (UINT)((wc >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */
+ LEAVE_FF(fs, FR_INVALID_NAME);
+ }
+ if (wc >= 0x100) dirvn[di++] = (BYTE)(wc >> 8);
+ dirvn[di++] = (BYTE)wc;
+ }
+ if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */
+ while (di && dirvn[di - 1] == ' ') di--; /* Snip trailing spaces */
+ }
+
+ /* Set volume label */
+ dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */
+ res = dir_sdi(&dj, 0);
+ if (res == FR_OK) {
+ res = DIR_READ_LABEL(&dj); /* Get volume label entry */
+ if (res == FR_OK) {
+ if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) {
+ dj.dir[XDIR_NumLabel] = (BYTE)di; /* Change the volume label */
+ mem_cpy(dj.dir + XDIR_Label, dirvn, 22);
+ } else {
+ if (di != 0) {
+ mem_cpy(dj.dir, dirvn, 11); /* Change the volume label */
+ } else {
+ dj.dir[DIR_Name] = DDEM; /* Remove the volume label */
+ }
+ }
+ fs->wflag = 1;
+ res = sync_fs(fs);
+ } else { /* No volume label entry or an error */
+ if (res == FR_NO_FILE) {
+ res = FR_OK;
+ if (di != 0) { /* Create a volume label entry */
+ res = dir_alloc(&dj, 1); /* Allocate an entry */
+ if (res == FR_OK) {
+ mem_set(dj.dir, 0, SZDIRE); /* Clean the entry */
+ if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) {
+ dj.dir[XDIR_Type] = ET_VLABEL; /* Create volume label entry */
+ dj.dir[XDIR_NumLabel] = (BYTE)di;
+ mem_cpy(dj.dir + XDIR_Label, dirvn, 22);
+ } else {
+ dj.dir[DIR_Attr] = AM_VOL; /* Create volume label entry */
+ mem_cpy(dj.dir, dirvn, 11);
+ }
+ fs->wflag = 1;
+ res = sync_fs(fs);
+ }
+ }
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_USE_LABEL */
+
+
+
+#if FF_USE_EXPAND && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Allocate a Contiguous Blocks to the File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_expand (
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t fsz, /* File size to be expanded to */
+ BYTE opt /* Operation mode 0:Find and prepare or 1:Find and allocate */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD n, clst, stcl, scl, ncl, tcl, lclst;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (fsz == 0 || fp->obj.objsize != 0 || !(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED);
+#if FF_FS_EXFAT
+ if (fs->fs_type != FS_EXFAT && fsz >= 0x100000000) LEAVE_FF(fs, FR_DENIED); /* Check if in size limit */
+#endif
+ n = (DWORD)fs->csize * SS(fs); /* Cluster size */
+ tcl = (DWORD)(fsz / n) + ((fsz & (n - 1)) ? 1 : 0); /* Number of clusters required */
+ stcl = fs->last_clst; lclst = 0;
+ if (stcl < 2 || stcl >= fs->n_fatent) stcl = 2;
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ scl = find_bitmap(fs, stcl, tcl); /* Find a contiguous cluster block */
+ if (scl == 0) res = FR_DENIED; /* No contiguous cluster block was found */
+ if (scl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (res == FR_OK) { /* A contiguous free area is found */
+ if (opt) { /* Allocate it now */
+ res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */
+ lclst = scl + tcl - 1;
+ } else { /* Set it as suggested point for next allocation */
+ lclst = scl - 1;
+ }
+ }
+ } else
+#endif
+ {
+ scl = clst = stcl; ncl = 0;
+ for (;;) { /* Find a contiguous cluster block */
+ n = get_fat(&fp->obj, clst);
+ if (++clst >= fs->n_fatent) clst = 2;
+ if (n == 1) { res = FR_INT_ERR; break; }
+ if (n == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
+ if (n == 0) { /* Is it a free cluster? */
+ if (++ncl == tcl) break; /* Break if a contiguous cluster block is found */
+ } else {
+ scl = clst; ncl = 0; /* Not a free cluster */
+ }
+ if (clst == stcl) { res = FR_DENIED; break; } /* No contiguous cluster? */
+ }
+ if (res == FR_OK) { /* A contiguous free area is found */
+ if (opt) { /* Allocate it now */
+ for (clst = scl, n = tcl; n; clst++, n--) { /* Create a cluster chain on the FAT */
+ res = put_fat(fs, clst, (n == 1) ? 0xFFFFFFFF : clst + 1);
+ if (res != FR_OK) break;
+ lclst = clst;
+ }
+ } else { /* Set it as suggested point for next allocation */
+ lclst = scl - 1;
+ }
+ }
+ }
+
+ if (res == FR_OK) {
+ fs->last_clst = lclst; /* Set suggested start cluster to start next */
+ if (opt) { /* Is it allocated now? */
+ fp->obj.sclust = scl; /* Update object allocation information */
+ fp->obj.objsize = fsz;
+ if (FF_FS_EXFAT) fp->obj.stat = 2; /* Set status 'contiguous chain' */
+ fp->flag |= FA_MODIFIED;
+ if (fs->free_clst <= fs->n_fatent - 2) { /* Update FSINFO */
+ fs->free_clst -= tcl;
+ fs->fsi_flag |= 1;
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* FF_USE_EXPAND && !FF_FS_READONLY */
+
+
+
+#if FF_USE_FORWARD
+/*-----------------------------------------------------------------------*/
+/* Forward Data to the Stream Directly */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_forward (
+ FIL* fp, /* Pointer to the file object */
+ UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */
+ UINT btf, /* Number of bytes to forward */
+ UINT* bf /* Pointer to number of bytes forwarded */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, sect;
+ FSIZE_t remain;
+ UINT rcnt, csect;
+ BYTE *dbuf;
+
+
+ *bf = 0; /* Clear transfer byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ remain = fp->obj.objsize - fp->fptr;
+ if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */
+
+ for ( ; btf && (*func)(0, 0); /* Repeat until all data transferred or stream goes busy */
+ fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) {
+ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ if (csect == 0) { /* On the cluster boundary? */
+ clst = (fp->fptr == 0) ? /* On the top of the file? */
+ fp->obj.sclust : get_fat(&fp->obj, fp->clust);
+ if (clst <= 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ }
+ }
+ sect = clst2sect(fs, fp->clust); /* Get current data sector */
+ if (sect == 0) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+#if FF_FS_TINY
+ if (move_window(fs, sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window to the file data */
+ dbuf = fs->win;
+#else
+ if (fp->sect != sect) { /* Fill sector cache with file data */
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ }
+ dbuf = fp->buf;
+#endif
+ fp->sect = sect;
+ rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
+ if (rcnt > btf) rcnt = btf; /* Clip it by btr if needed */
+ rcnt = (*func)(dbuf + ((UINT)fp->fptr % SS(fs)), rcnt); /* Forward the file data */
+ if (rcnt == 0) ABORT(fs, FR_INT_ERR);
+ }
+
+ LEAVE_FF(fs, FR_OK);
+}
+#endif /* FF_USE_FORWARD */
+
+
+
+#if FF_USE_MKFS && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Create an FAT/exFAT volume */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mkfs (
+ const TCHAR* path, /* Logical drive number */
+ BYTE opt, /* Format option */
+ DWORD au, /* Size of allocation unit (cluster) [byte] */
+ void* work, /* Pointer to working buffer (null: use heap memory) */
+ UINT len /* Size of working buffer [byte] */
+)
+{
+ const UINT n_fats = 1; /* Number of FATs for FAT/FAT32 volume (1 or 2) */
+ const UINT n_rootdir = 512; /* Number of root directory entries for FAT volume */
+ static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */
+ static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */
+ BYTE fmt, sys, *buf, *pte, pdrv, part;
+ WORD ss; /* Sector size */
+ DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n;
+ DWORD b_vol, b_fat, b_data; /* Base LBA for volume, fat, data */
+ DWORD sz_vol, sz_rsv, sz_fat, sz_dir; /* Size for volume, fat, dir, data */
+ UINT i;
+ int vol;
+ DSTATUS stat;
+#if FF_USE_TRIM || FF_FS_EXFAT
+ DWORD tbl[3];
+#endif
+
+
+ /* Check mounted drive and clear work area */
+ vol = get_ldnumber(&path); /* Get target logical drive */
+ if (vol < 0) return FR_INVALID_DRIVE;
+ if (FatFs[vol]) FatFs[vol]->fs_type = 0; /* Clear the volume if mounted */
+ pdrv = LD2PD(vol); /* Physical drive */
+ part = LD2PT(vol); /* Partition (0:create as new, 1-4:get from partition table) */
+
+ /* Check physical drive status */
+ stat = disk_initialize(pdrv);
+ if (stat & STA_NOINIT) return FR_NOT_READY;
+ if (stat & STA_PROTECT) return FR_WRITE_PROTECTED;
+ if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK || !sz_blk || sz_blk > 32768 || (sz_blk & (sz_blk - 1))) sz_blk = 1; /* Erase block to align data area */
+#if FF_MAX_SS != FF_MIN_SS /* Get sector size of the medium if variable sector size cfg. */
+ if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR;
+ if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR;
+#else
+ ss = FF_MAX_SS;
+#endif
+ if ((au != 0 && au < ss) || au > 0x1000000 || (au & (au - 1))) return FR_INVALID_PARAMETER; /* Check if au is valid */
+ au /= ss; /* Cluster size in unit of sector */
+
+ /* Get working buffer */
+#if FF_USE_LFN == 3
+ if (!work) { /* Use heap memory for working buffer */
+ for (szb_buf = MAX_MALLOC, buf = 0; szb_buf >= ss && (buf = ff_memalloc(szb_buf)) == 0; szb_buf /= 2) ;
+ sz_buf = szb_buf / ss; /* Size of working buffer (sector) */
+ } else
+#endif
+ {
+ buf = (BYTE*)work; /* Working buffer */
+ sz_buf = len / ss; /* Size of working buffer (sector) */
+ szb_buf = sz_buf * ss; /* Size of working buffer (byte) */
+ }
+ if (!buf || sz_buf == 0) return FR_NOT_ENOUGH_CORE;
+
+ /* Determine where the volume to be located (b_vol, sz_vol) */
+ if (FF_MULTI_PARTITION && part != 0) {
+ /* Get partition information from partition table in the MBR */
+ if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load MBR */
+ if (ld_word(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */
+ pte = buf + (MBR_Table + (part - 1) * SZ_PTE);
+ if (pte[PTE_System] == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* No partition? */
+ b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */
+ sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */
+ } else {
+ /* Create a single-partition in this function */
+ if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ b_vol = (opt & FM_SFD) ? 0 : 63; /* Volume start sector */
+ if (sz_vol < b_vol) LEAVE_MKFS(FR_MKFS_ABORTED);
+ sz_vol -= b_vol; /* Volume size */
+ }
+ if (sz_vol < 128) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=128s */
+
+ /* Pre-determine the FAT type */
+ do {
+ if (FF_FS_EXFAT && (opt & FM_EXFAT)) { /* exFAT possible? */
+ if ((opt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || au > 128) { /* exFAT only, vol >= 64Ms or au > 128s ? */
+ fmt = FS_EXFAT; break;
+ }
+ }
+ if (au > 128) LEAVE_MKFS(FR_INVALID_PARAMETER); /* Too large au for FAT/FAT32 */
+ if (opt & FM_FAT32) { /* FAT32 possible? */
+ if ((opt & FM_ANY) == FM_FAT32 || !(opt & FM_FAT)) { /* FAT32 only or no-FAT? */
+ fmt = FS_FAT32; break;
+ }
+ }
+ if (!(opt & FM_FAT)) LEAVE_MKFS(FR_INVALID_PARAMETER); /* no-FAT? */
+ fmt = FS_FAT16;
+ } while (0);
+
+#if FF_FS_EXFAT
+ if (fmt == FS_EXFAT) { /* Create an exFAT volume */
+ DWORD szb_bit, szb_case, sum, nb, cl;
+ WCHAR ch, si;
+ UINT j, st;
+ BYTE b;
+
+ if (sz_vol < 0x1000) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */
+#if FF_USE_TRIM
+ tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area may be erased */
+ disk_ioctl(pdrv, CTRL_TRIM, tbl);
+#endif
+ /* Determine FAT location, data location and number of clusters */
+ if (au == 0) { /* au auto-selection */
+ au = 8;
+ if (sz_vol >= 0x80000) au = 64; /* >= 512Ks */
+ if (sz_vol >= 0x4000000) au = 256; /* >= 64Ms */
+ }
+ b_fat = b_vol + 32; /* FAT start at offset 32 */
+ sz_fat = ((sz_vol / au + 2) * 4 + ss - 1) / ss; /* Number of FAT sectors */
+ b_data = (b_fat + sz_fat + sz_blk - 1) & ~(sz_blk - 1); /* Align data area to the erase block boundary */
+ if (b_data - b_vol >= sz_vol / 2) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */
+ n_clst = (sz_vol - (b_data - b_vol)) / au; /* Number of clusters */
+ if (n_clst <16) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too few clusters? */
+ if (n_clst > MAX_EXFAT) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters? */
+
+ szb_bit = (n_clst + 7) / 8; /* Size of allocation bitmap */
+ tbl[0] = (szb_bit + au * ss - 1) / (au * ss); /* Number of allocation bitmap clusters */
+
+ /* Create a compressed up-case table */
+ sect = b_data + au * tbl[0]; /* Table start sector */
+ sum = 0; /* Table checksum to be stored in the 82 entry */
+ st = 0; si = 0; i = 0; j = 0; szb_case = 0;
+ do {
+ switch (st) {
+ case 0:
+ ch = (WCHAR)ff_wtoupper(si); /* Get an up-case char */
+ if (ch != si) {
+ si++; break; /* Store the up-case char if exist */
+ }
+ for (j = 1; (WCHAR)(si + j) && (WCHAR)(si + j) == ff_wtoupper((WCHAR)(si + j)); j++) ; /* Get run length of no-case block */
+ if (j >= 128) {
+ ch = 0xFFFF; st = 2; break; /* Compress the no-case block if run is >= 128 */
+ }
+ st = 1; /* Do not compress short run */
+ /* go to next case */
+ case 1:
+ ch = si++; /* Fill the short run */
+ if (--j == 0) st = 0;
+ break;
+
+ default:
+ ch = (WCHAR)j; si += (WCHAR)j; /* Number of chars to skip */
+ st = 0;
+ }
+ sum = xsum32(buf[i + 0] = (BYTE)ch, sum); /* Put it into the write buffer */
+ sum = xsum32(buf[i + 1] = (BYTE)(ch >> 8), sum);
+ i += 2; szb_case += 2;
+ if (si == 0 || i == szb_buf) { /* Write buffered data when buffer full or end of process */
+ n = (i + ss - 1) / ss;
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; i = 0;
+ }
+ } while (si);
+ tbl[1] = (szb_case + au * ss - 1) / (au * ss); /* Number of up-case table clusters */
+ tbl[2] = 1; /* Number of root dir clusters */
+
+ /* Initialize the allocation bitmap */
+ sect = b_data; nsect = (szb_bit + ss - 1) / ss; /* Start of bitmap and number of sectors */
+ nb = tbl[0] + tbl[1] + tbl[2]; /* Number of clusters in-use by system */
+ do {
+ mem_set(buf, 0, szb_buf);
+ for (i = 0; nb >= 8 && i < szb_buf; buf[i++] = 0xFF, nb -= 8) ;
+ for (b = 1; nb != 0 && i < szb_buf; buf[i] |= b, b <<= 1, nb--) ;
+ n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Initialize the FAT */
+ sect = b_fat; nsect = sz_fat; /* Start of FAT and number of FAT sectors */
+ j = nb = cl = 0;
+ do {
+ mem_set(buf, 0, szb_buf); i = 0; /* Clear work area and reset write index */
+ if (cl == 0) { /* Set entry 0 and 1 */
+ st_dword(buf + i, 0xFFFFFFF8); i += 4; cl++;
+ st_dword(buf + i, 0xFFFFFFFF); i += 4; cl++;
+ }
+ do { /* Create chains of bitmap, up-case and root dir */
+ while (nb != 0 && i < szb_buf) { /* Create a chain */
+ st_dword(buf + i, (nb > 1) ? cl + 1 : 0xFFFFFFFF);
+ i += 4; cl++; nb--;
+ }
+ if (nb == 0 && j < 3) nb = tbl[j++]; /* Next chain */
+ } while (nb != 0 && i < szb_buf);
+ n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Initialize the root directory */
+ mem_set(buf, 0, szb_buf);
+ buf[SZDIRE * 0 + 0] = ET_VLABEL; /* Volume label entry */
+ buf[SZDIRE * 1 + 0] = ET_BITMAP; /* Bitmap entry */
+ st_dword(buf + SZDIRE * 1 + 20, 2); /* cluster */
+ st_dword(buf + SZDIRE * 1 + 24, szb_bit); /* size */
+ buf[SZDIRE * 2 + 0] = ET_UPCASE; /* Up-case table entry */
+ st_dword(buf + SZDIRE * 2 + 4, sum); /* sum */
+ st_dword(buf + SZDIRE * 2 + 20, 2 + tbl[0]); /* cluster */
+ st_dword(buf + SZDIRE * 2 + 24, szb_case); /* size */
+ sect = b_data + au * (tbl[0] + tbl[1]); nsect = au; /* Start of the root directory and number of sectors */
+ do { /* Fill root directory sectors */
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ mem_set(buf, 0, ss);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Create two set of the exFAT VBR blocks */
+ sect = b_vol;
+ for (n = 0; n < 2; n++) {
+ /* Main record (+0) */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11); /* Boot jump code (x86), OEM name */
+ st_dword(buf + BPB_VolOfsEx, b_vol); /* Volume offset in the physical drive [sector] */
+ st_dword(buf + BPB_TotSecEx, sz_vol); /* Volume size [sector] */
+ st_dword(buf + BPB_FatOfsEx, b_fat - b_vol); /* FAT offset [sector] */
+ st_dword(buf + BPB_FatSzEx, sz_fat); /* FAT size [sector] */
+ st_dword(buf + BPB_DataOfsEx, b_data - b_vol); /* Data offset [sector] */
+ st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */
+ st_dword(buf + BPB_RootClusEx, 2 + tbl[0] + tbl[1]); /* Root dir cluster # */
+ st_dword(buf + BPB_VolIDEx, GET_FATTIME()); /* VSN */
+ st_word(buf + BPB_FSVerEx, 0x100); /* Filesystem version (1.00) */
+ for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */
+ for (buf[BPB_SecPerClusEx] = 0, i = au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */
+ buf[BPB_NumFATsEx] = 1; /* Number of FATs */
+ buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */
+ st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */
+ st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */
+ for (i = sum = 0; i < ss; i++) { /* VBR checksum */
+ if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum);
+ }
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ /* Extended bootstrap record (+1..+8) */
+ mem_set(buf, 0, ss);
+ st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */
+ for (j = 1; j < 9; j++) {
+ for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ }
+ /* OEM/Reserved record (+9..+10) */
+ mem_set(buf, 0, ss);
+ for ( ; j < 11; j++) {
+ for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ }
+ /* Sum record (+11) */
+ for (i = 0; i < ss; i += 4) st_dword(buf + i, sum); /* Fill with checksum value */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ }
+
+ } else
+#endif /* FF_FS_EXFAT */
+ { /* Create an FAT/FAT32 volume */
+ do {
+ pau = au;
+ /* Pre-determine number of clusters and FAT sub-type */
+ if (fmt == FS_FAT32) { /* FAT32 volume */
+ if (pau == 0) { /* au auto-selection */
+ n = sz_vol / 0x20000; /* Volume size in unit of 128KS */
+ for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */
+ }
+ n_clst = sz_vol / pau; /* Number of clusters */
+ sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* FAT size [sector] */
+ sz_rsv = 32; /* Number of reserved sectors */
+ sz_dir = 0; /* No static directory */
+ if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) LEAVE_MKFS(FR_MKFS_ABORTED);
+ } else { /* FAT volume */
+ if (pau == 0) { /* au auto-selection */
+ n = sz_vol / 0x1000; /* Volume size in unit of 4KS */
+ for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */
+ }
+ n_clst = sz_vol / pau;
+ if (n_clst > MAX_FAT12) {
+ n = n_clst * 2 + 4; /* FAT size [byte] */
+ } else {
+ fmt = FS_FAT12;
+ n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */
+ }
+ sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */
+ sz_rsv = 1; /* Number of reserved sectors */
+ sz_dir = (DWORD)n_rootdir * SZDIRE / ss; /* Rootdir size [sector] */
+ }
+ b_fat = b_vol + sz_rsv; /* FAT base */
+ b_data = b_fat + sz_fat * n_fats + sz_dir; /* Data base */
+
+ /* Align data base to erase block boundary (for flash memory media) */
+ n = ((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data; /* Next nearest erase block from current data base */
+ if (fmt == FS_FAT32) { /* FAT32: Move FAT base */
+ sz_rsv += n; b_fat += n;
+ } else { /* FAT: Expand FAT size */
+ sz_fat += n / n_fats;
+ }
+
+ /* Determine number of clusters and final check of validity of the FAT sub-type */
+ if (sz_vol < b_data + pau * 16 - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume */
+ n_clst = (sz_vol - sz_rsv - sz_fat * n_fats - sz_dir) / pau;
+ if (fmt == FS_FAT32) {
+ if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32 */
+ if (au == 0 && (au = pau / 2) != 0) continue; /* Adjust cluster size and retry */
+ LEAVE_MKFS(FR_MKFS_ABORTED);
+ }
+ }
+ if (fmt == FS_FAT16) {
+ if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */
+ if (au == 0 && (pau * 2) <= 64) {
+ au = pau * 2; continue; /* Adjust cluster size and retry */
+ }
+ if ((opt & FM_FAT32)) {
+ fmt = FS_FAT32; continue; /* Switch type to FAT32 and retry */
+ }
+ if (au == 0 && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */
+ LEAVE_MKFS(FR_MKFS_ABORTED);
+ }
+ if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */
+ if (au == 0 && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */
+ LEAVE_MKFS(FR_MKFS_ABORTED);
+ }
+ }
+ if (fmt == FS_FAT12 && n_clst > MAX_FAT12) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters for FAT12 */
+
+ /* Ok, it is the valid cluster configuration */
+ break;
+ } while (1);
+
+#if FF_USE_TRIM
+ tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area can be erased */
+ disk_ioctl(pdrv, CTRL_TRIM, tbl);
+#endif
+ /* Create FAT VBR */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */
+ st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */
+ buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */
+ st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */
+ buf[BPB_NumFATs] = (BYTE)n_fats; /* Number of FATs */
+ st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */
+ if (sz_vol < 0x10000) {
+ st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */
+ } else {
+ st_dword(buf + BPB_TotSec32, sz_vol); /* Volume size in 32-bit LBA */
+ }
+ buf[BPB_Media] = 0xF8; /* Media descriptor byte */
+ st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */
+ st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */
+ st_dword(buf + BPB_HiddSec, b_vol); /* Volume offset in the physical drive [sector] */
+ if (fmt == FS_FAT32) {
+ st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */
+ st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */
+ st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */
+ st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */
+ st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */
+ buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */
+ buf[BS_BootSig32] = 0x29; /* Extended boot signature */
+ mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */
+ } else {
+ st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */
+ st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */
+ buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */
+ buf[BS_BootSig] = 0x29; /* Extended boot signature */
+ mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */
+ }
+ st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */
+ if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the VBR sector */
+
+ /* Create FSINFO record if needed */
+ if (fmt == FS_FAT32) {
+ disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */
+ mem_set(buf, 0, ss);
+ st_dword(buf + FSI_LeadSig, 0x41615252);
+ st_dword(buf + FSI_StrucSig, 0x61417272);
+ st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */
+ st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */
+ st_word(buf + BS_55AA, 0xAA55);
+ disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */
+ disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */
+ }
+
+ /* Initialize FAT area */
+ mem_set(buf, 0, (UINT)szb_buf);
+ sect = b_fat; /* FAT start sector */
+ for (i = 0; i < n_fats; i++) { /* Initialize FATs each */
+ if (fmt == FS_FAT32) {
+ st_dword(buf + 0, 0xFFFFFFF8); /* Entry 0 */
+ st_dword(buf + 4, 0xFFFFFFFF); /* Entry 1 */
+ st_dword(buf + 8, 0x0FFFFFFF); /* Entry 2 (root directory) */
+ } else {
+ st_dword(buf + 0, (fmt == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* Entry 0 and 1 */
+ }
+ nsect = sz_fat; /* Number of FAT sectors */
+ do { /* Fill FAT sectors */
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ mem_set(buf, 0, ss);
+ sect += n; nsect -= n;
+ } while (nsect);
+ }
+
+ /* Initialize root directory (fill with zero) */
+ nsect = (fmt == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */
+ do {
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; nsect -= n;
+ } while (nsect);
+ }
+
+ /* Determine system ID in the partition table */
+ if (FF_FS_EXFAT && fmt == FS_EXFAT) {
+ sys = 0x07; /* HPFS/NTFS/exFAT */
+ } else {
+ if (fmt == FS_FAT32) {
+ sys = 0x0C; /* FAT32X */
+ } else {
+ if (sz_vol >= 0x10000) {
+ sys = 0x06; /* FAT12/16 (large) */
+ } else {
+ sys = (fmt == FS_FAT16) ? 0x04 : 0x01; /* FAT16 : FAT12 */
+ }
+ }
+ }
+
+ /* Update partition information */
+ if (FF_MULTI_PARTITION && part != 0) { /* Created in the existing partition */
+ /* Update system ID in the partition table */
+ if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Read the MBR */
+ buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys; /* Set system ID */
+ if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it back to the MBR */
+ } else { /* Created as a new single partition */
+ if (!(opt & FM_SFD)) { /* Create partition table if in FDISK format */
+ mem_set(buf, 0, ss);
+ st_word(buf + BS_55AA, 0xAA55); /* MBR signature */
+ pte = buf + MBR_Table; /* Create partition table for single partition in the drive */
+ pte[PTE_Boot] = 0; /* Boot indicator */
+ pte[PTE_StHead] = 1; /* Start head */
+ pte[PTE_StSec] = 1; /* Start sector */
+ pte[PTE_StCyl] = 0; /* Start cylinder */
+ pte[PTE_System] = sys; /* System type */
+ n = (b_vol + sz_vol) / (63 * 255); /* (End CHS may be invalid) */
+ pte[PTE_EdHead] = 254; /* End head */
+ pte[PTE_EdSec] = (BYTE)(((n >> 2) & 0xC0) | 63); /* End sector */
+ pte[PTE_EdCyl] = (BYTE)n; /* End cylinder */
+ st_dword(pte + PTE_StLba, b_vol); /* Start offset in LBA */
+ st_dword(pte + PTE_SizLba, sz_vol); /* Size in sectors */
+ if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the MBR */
+ }
+ }
+
+ if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+
+ LEAVE_MKFS(FR_OK);
+}
+
+
+
+#if FF_MULTI_PARTITION
+/*-----------------------------------------------------------------------*/
+/* Create Partition Table on the Physical Drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_fdisk (
+ BYTE pdrv, /* Physical drive number */
+ const DWORD* szt, /* Pointer to the size table for each partitions */
+ void* work /* Pointer to the working buffer (null: use heap memory) */
+)
+{
+ UINT i, n, sz_cyl, tot_cyl, b_cyl, e_cyl, p_cyl;
+ BYTE s_hd, e_hd, *p, *buf = (BYTE*)work;
+ DSTATUS stat;
+ DWORD sz_disk, sz_part, s_part;
+ FRESULT res;
+
+
+ stat = disk_initialize(pdrv);
+ if (stat & STA_NOINIT) return FR_NOT_READY;
+ if (stat & STA_PROTECT) return FR_WRITE_PROTECTED;
+ if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_disk)) return FR_DISK_ERR;
+
+ buf = (BYTE*)work;
+#if FF_USE_LFN == 3
+ if (!buf) buf = ff_memalloc(FF_MAX_SS); /* Use heap memory for working buffer */
+#endif
+ if (!buf) return FR_NOT_ENOUGH_CORE;
+
+ /* Determine the CHS without any consideration of the drive geometry */
+ for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2) ;
+ if (n == 256) n--;
+ e_hd = (BYTE)(n - 1);
+ sz_cyl = 63 * n;
+ tot_cyl = sz_disk / sz_cyl;
+
+ /* Create partition table */
+ mem_set(buf, 0, FF_MAX_SS);
+ p = buf + MBR_Table; b_cyl = 0;
+ for (i = 0; i < 4; i++, p += SZ_PTE) {
+ p_cyl = (szt[i] <= 100U) ? (DWORD)tot_cyl * szt[i] / 100 : szt[i] / sz_cyl; /* Number of cylinders */
+ if (p_cyl == 0) continue;
+ s_part = (DWORD)sz_cyl * b_cyl;
+ sz_part = (DWORD)sz_cyl * p_cyl;
+ if (i == 0) { /* Exclude first track of cylinder 0 */
+ s_hd = 1;
+ s_part += 63; sz_part -= 63;
+ } else {
+ s_hd = 0;
+ }
+ e_cyl = b_cyl + p_cyl - 1; /* End cylinder */
+ if (e_cyl >= tot_cyl) LEAVE_MKFS(FR_INVALID_PARAMETER);
+
+ /* Set partition table */
+ p[1] = s_hd; /* Start head */
+ p[2] = (BYTE)(((b_cyl >> 2) & 0xC0) | 1); /* Start sector */
+ p[3] = (BYTE)b_cyl; /* Start cylinder */
+ p[4] = 0x07; /* System type (temporary setting) */
+ p[5] = e_hd; /* End head */
+ p[6] = (BYTE)(((e_cyl >> 2) & 0xC0) | 63); /* End sector */
+ p[7] = (BYTE)e_cyl; /* End cylinder */
+ st_dword(p + 8, s_part); /* Start sector in LBA */
+ st_dword(p + 12, sz_part); /* Number of sectors */
+
+ /* Next partition */
+ b_cyl += p_cyl;
+ }
+ st_word(p, 0xAA55); /* MBR signature (always at offset 510) */
+
+ /* Write it to the MBR */
+ res = (disk_write(pdrv, buf, 0, 1) == RES_OK && disk_ioctl(pdrv, CTRL_SYNC, 0) == RES_OK) ? FR_OK : FR_DISK_ERR;
+ LEAVE_MKFS(res);
+}
+
+#endif /* FF_MULTI_PARTITION */
+#endif /* FF_USE_MKFS && !FF_FS_READONLY */
+
+
+
+
+#if FF_USE_STRFUNC
+#if FF_USE_LFN && FF_LFN_UNICODE && (FF_STRF_ENCODE < 0 || FF_STRF_ENCODE > 3)
+#error Wrong FF_STRF_ENCODE setting
+#endif
+/*-----------------------------------------------------------------------*/
+/* Get a String from the File */
+/*-----------------------------------------------------------------------*/
+
+TCHAR* f_gets (
+ TCHAR* buff, /* Pointer to the string buffer to read */
+ int len, /* Size of string buffer (items) */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ int nc = 0;
+ TCHAR *p = buff;
+ BYTE s[4];
+ UINT rc;
+ DWORD dc;
+#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE <= 2
+ WCHAR wc;
+#endif
+#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE == 3
+ UINT ct;
+#endif
+
+#if FF_USE_LFN && FF_LFN_UNICODE /* With code conversion (Unicode API) */
+ /* Make a room for the character and terminator */
+ if (FF_LFN_UNICODE == 1) len -= (FF_STRF_ENCODE == 0) ? 1 : 2;
+ if (FF_LFN_UNICODE == 2) len -= (FF_STRF_ENCODE == 0) ? 3 : 4;
+ if (FF_LFN_UNICODE == 3) len -= 1;
+ while (nc < len) {
+#if FF_STRF_ENCODE == 0 /* Read a character in ANSI/OEM */
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ wc = s[0];
+ if (dbc_1st((BYTE)wc)) {
+ f_read(fp, s, 1, &rc);
+ if (rc != 1 || !dbc_2nd(s[0])) continue;
+ wc = wc << 8 | s[0];
+ }
+ dc = ff_oem2uni(wc, CODEPAGE);
+ if (dc == 0) continue;
+#elif FF_STRF_ENCODE == 1 || FF_STRF_ENCODE == 2 /* Read a character in UTF-16LE/BE */
+ f_read(fp, s, 2, &rc);
+ if (rc != 2) break;
+ dc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1];
+ if (IsSurrogateL(dc)) continue;
+ if (IsSurrogateH(dc)) {
+ f_read(fp, s, 2, &rc);
+ if (rc != 2) break;
+ wc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1];
+ if (!IsSurrogateL(wc)) continue;
+ dc = ((dc & 0x3FF) + 0x40) << 10 | (wc & 0x3FF);
+ }
+#else /* Read a character in UTF-8 */
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ dc = s[0];
+ if (dc >= 0x80) { /* Multi-byte character? */
+ ct = 0;
+ if ((dc & 0xE0) == 0xC0) { dc &= 0x1F; ct = 1; } /* 2-byte? */
+ if ((dc & 0xF0) == 0xE0) { dc &= 0x0F; ct = 2; } /* 3-byte? */
+ if ((dc & 0xF8) == 0xF0) { dc &= 0x07; ct = 3; } /* 4-byte? */
+ if (ct == 0) continue;
+ f_read(fp, s, ct, &rc); /* Get trailing bytes */
+ if (rc != ct) break;
+ rc = 0;
+ do { /* Merge trailing bytes */
+ if ((s[rc] & 0xC0) != 0x80) break;
+ dc = dc << 6 | (s[rc] & 0x3F);
+ } while (++rc < ct);
+ if (rc != ct || dc < 0x80 || IsSurrogate(dc) || dc >= 0x110000) continue; /* Wrong encoding? */
+ }
+#endif
+ if (FF_USE_STRFUNC == 2 && dc == '\r') continue; /* Strip \r off if needed */
+#if FF_LFN_UNICODE == 1 || FF_LFN_UNICODE == 3 /* Output it in UTF-16/32 encoding */
+ if (FF_LFN_UNICODE == 1 && dc >= 0x10000) { /* Out of BMP at UTF-16? */
+ *p++ = (TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */
+ dc = 0xDC00 | (dc & 0x3FF); /* Make low surrogate */
+ }
+ *p++ = (TCHAR)dc; nc++;
+ if (dc == '\n') break; /* End of line? */
+#elif FF_LFN_UNICODE == 2 /* Output it in UTF-8 encoding */
+ if (dc < 0x80) { /* 1-byte */
+ *p++ = (TCHAR)dc;
+ nc++;
+ if (dc == '\n') break; /* End of line? */
+ } else {
+ if (dc < 0x800) { /* 2-byte */
+ *p++ = (TCHAR)(0xC0 | (dc >> 6 & 0x1F));
+ *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F));
+ nc += 2;
+ } else {
+ if (dc < 0x10000) { /* 3-byte */
+ *p++ = (TCHAR)(0xE0 | (dc >> 12 & 0x0F));
+ *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F));
+ *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F));
+ nc += 3;
+ } else { /* 4-byte */
+ *p++ = (TCHAR)(0xF0 | (dc >> 18 & 0x07));
+ *p++ = (TCHAR)(0x80 | (dc >> 12 & 0x3F));
+ *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F));
+ *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F));
+ nc += 4;
+ }
+ }
+ }
+#endif
+ }
+
+#else /* Byte-by-byte without any conversion (ANSI/OEM API) */
+ len -= 1; /* Make a room for the terminator */
+ while (nc < len) {
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ dc = s[0];
+ if (FF_USE_STRFUNC == 2 && dc == '\r') continue;
+ *p++ = (TCHAR)dc; nc++;
+ if (dc == '\n') break;
+ }
+#endif
+
+ *p = 0; /* Terminate the string */
+ return nc ? buff : 0; /* When no data read due to EOF or error, return with error. */
+}
+
+
+
+
+#if !FF_FS_READONLY
+#include
+/*-----------------------------------------------------------------------*/
+/* Put a Character to the File */
+/*-----------------------------------------------------------------------*/
+
+typedef struct { /* Putchar output buffer and work area */
+ FIL *fp; /* Ptr to the writing file */
+ int idx, nchr; /* Write index of buf[] (-1:error), number of encoding units written */
+#if FF_USE_LFN && FF_LFN_UNICODE == 1
+ WCHAR hs;
+#elif FF_USE_LFN && FF_LFN_UNICODE == 2
+ BYTE bs[4];
+ UINT wi, ct;
+#endif
+ BYTE buf[64]; /* Write buffer */
+} putbuff;
+
+
+static void putc_bfd ( /* Buffered write with code conversion */
+ putbuff* pb,
+ TCHAR c
+)
+{
+ UINT n;
+ int i, nc;
+#if FF_USE_LFN && FF_LFN_UNICODE
+ WCHAR hs, wc;
+#if FF_LFN_UNICODE == 2
+ DWORD dc;
+ TCHAR *tp;
+#endif
+#endif
+
+ if (FF_USE_STRFUNC == 2 && c == '\n') { /* LF -> CRLF conversion */
+ putc_bfd(pb, '\r');
+ }
+
+ i = pb->idx; /* Write index of pb->buf[] */
+ if (i < 0) return;
+ nc = pb->nchr; /* Write unit counter */
+
+#if FF_USE_LFN && FF_LFN_UNICODE
+#if FF_LFN_UNICODE == 1 /* UTF-16 input */
+ if (IsSurrogateH(c)) {
+ pb->hs = c; return;
+ }
+ hs = pb->hs; pb->hs = 0;
+ if (hs != 0) {
+ if (!IsSurrogateL(c)) hs = 0;
+ } else {
+ if (IsSurrogateL(c)) return;
+ }
+ wc = c;
+#elif FF_LFN_UNICODE == 2 /* UTF-8 input */
+ for (;;) {
+ if (pb->ct == 0) { /* Out of multi-byte sequence? */
+ pb->bs[pb->wi = 0] = (BYTE)c; /* Save 1st byte */
+ if ((BYTE)c < 0x80) break; /* 1-byte? */
+ if (((BYTE)c & 0xE0) == 0xC0) pb->ct = 1; /* 2-byte? */
+ if (((BYTE)c & 0xF0) == 0xE0) pb->ct = 2; /* 3-byte? */
+ if (((BYTE)c & 0xF1) == 0xF0) pb->ct = 3; /* 4-byte? */
+ return;
+ } else { /* In the multi-byte sequence */
+ if (((BYTE)c & 0xC0) != 0x80) { /* Broken sequence? */
+ pb->ct = 0; continue;
+ }
+ pb->bs[++pb->wi] = (BYTE)c; /* Save the trailing byte */
+ if (--pb->ct == 0) break; /* End of multi-byte sequence? */
+ return;
+ }
+ }
+ tp = (TCHAR*)pb->bs;
+ dc = tchar2uni(&tp); /* UTF-8 ==> UTF-16 */
+ if (dc == 0xFFFFFFFF) return;
+ wc = (WCHAR)dc;
+ hs = (WCHAR)(dc >> 16);
+#elif FF_LFN_UNICODE == 3 /* UTF-32 input */
+ if (IsSurrogate(c) || c >= 0x110000) return;
+ if (c >= 0x10000) {
+ hs = (WCHAR)(0xD800 | ((c >> 10) - 0x40)); /* Make high surrogate */
+ wc = 0xDC00 | (c & 0x3FF); /* Make low surrogate */
+ } else {
+ hs = 0;
+ wc = (WCHAR)c;
+ }
+#endif
+
+#if FF_STRF_ENCODE == 1 /* Write a character in UTF-16LE */
+ if (hs != 0) {
+ st_word(&pb->buf[i], hs);
+ i += 2;
+ nc++;
+ }
+ st_word(&pb->buf[i], wc);
+ i += 2;
+#elif FF_STRF_ENCODE == 2 /* Write a character in UTF-16BE */
+ if (hs != 0) {
+ pb->buf[i++] = (BYTE)(hs >> 8);
+ pb->buf[i++] = (BYTE)hs;
+ nc++;
+ }
+ pb->buf[i++] = (BYTE)(wc >> 8);
+ pb->buf[i++] = (BYTE)wc;
+#elif FF_STRF_ENCODE == 3 /* Write it in UTF-8 */
+ if (hs != 0) { /* 4-byte */
+ nc += 3;
+ hs = (hs & 0x3FF) + 0x40;
+ pb->buf[i++] = (BYTE)(0xF0 | hs >> 8);
+ pb->buf[i++] = (BYTE)(0x80 | (hs >> 2 & 0x3F));
+ pb->buf[i++] = (BYTE)(0x80 | (hs & 3) << 4 | (wc >> 6 & 0x0F));
+ pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F));
+ } else {
+ if (wc < 0x80) { /* 1-byte */
+ pb->buf[i++] = (BYTE)wc;
+ } else {
+ if (wc < 0x800) { /* 2-byte */
+ nc += 1;
+ pb->buf[i++] = (BYTE)(0xC0 | wc >> 6);
+ } else { /* 3-byte */
+ nc += 2;
+ pb->buf[i++] = (BYTE)(0xE0 | wc >> 12);
+ pb->buf[i++] = (BYTE)(0x80 | (wc >> 6 & 0x3F));
+ }
+ pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F));
+ }
+ }
+#else /* Write it in ANSI/OEM */
+ if (hs != 0) return;
+ wc = ff_uni2oem(wc, CODEPAGE); /* UTF-16 ==> ANSI/OEM */
+ if (wc == 0) return;
+ if (wc >= 0x100) {
+ pb->buf[i++] = (BYTE)(wc >> 8); nc++;
+ }
+ pb->buf[i++] = (BYTE)wc;
+#endif
+
+#else /* ANSI/OEM input (without re-encode) */
+ pb->buf[i++] = (BYTE)c;
+#endif
+
+ if (i >= (int)(sizeof pb->buf) - 4) { /* Write buffered characters to the file */
+ f_write(pb->fp, pb->buf, (UINT)i, &n);
+ i = (n == (UINT)i) ? 0 : -1;
+ }
+ pb->idx = i;
+ pb->nchr = nc + 1;
+}
+
+
+static int putc_flush ( /* Flush left characters in the buffer */
+ putbuff* pb
+)
+{
+ UINT nw;
+
+ if ( pb->idx >= 0 /* Flush buffered characters to the file */
+ && f_write(pb->fp, pb->buf, (UINT)pb->idx, &nw) == FR_OK
+ && (UINT)pb->idx == nw) return pb->nchr;
+ return EOF;
+}
+
+
+static void putc_init ( /* Initialize write buffer */
+ putbuff* pb,
+ FIL* fp
+)
+{
+ mem_set(pb, 0, sizeof (putbuff));
+ pb->fp = fp;
+}
+
+
+
+int f_putc (
+ TCHAR c, /* A character to be output */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ putbuff pb;
+
+
+ putc_init(&pb, fp);
+ putc_bfd(&pb, c); /* Put the character */
+ return putc_flush(&pb);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Put a String to the File */
+/*-----------------------------------------------------------------------*/
+
+int f_puts (
+ const TCHAR* str, /* Pointer to the string to be output */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ putbuff pb;
+
+
+ putc_init(&pb, fp);
+ while (*str) putc_bfd(&pb, *str++); /* Put the string */
+ return putc_flush(&pb);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Put a Formatted String to the File */
+/*-----------------------------------------------------------------------*/
+
+int f_printf (
+ FIL* fp, /* Pointer to the file object */
+ const TCHAR* fmt, /* Pointer to the format string */
+ ... /* Optional arguments... */
+)
+{
+ va_list arp;
+ putbuff pb;
+ BYTE f, r;
+ UINT i, j, w;
+ DWORD v;
+ TCHAR c, d, str[32], *p;
+
+
+ putc_init(&pb, fp);
+
+ va_start(arp, fmt);
+
+ for (;;) {
+ c = *fmt++;
+ if (c == 0) break; /* End of string */
+ if (c != '%') { /* Non escape character */
+ putc_bfd(&pb, c);
+ continue;
+ }
+ w = f = 0;
+ c = *fmt++;
+ if (c == '0') { /* Flag: '0' padding */
+ f = 1; c = *fmt++;
+ } else {
+ if (c == '-') { /* Flag: left justified */
+ f = 2; c = *fmt++;
+ }
+ }
+ if (c == '*') { /* Minimum width by argument */
+ w = va_arg(arp, int);
+ c = *fmt++;
+ } else {
+ while (IsDigit(c)) { /* Minimum width */
+ w = w * 10 + c - '0';
+ c = *fmt++;
+ }
+ }
+ if (c == 'l' || c == 'L') { /* Type prefix: Size is long int */
+ f |= 4; c = *fmt++;
+ }
+ if (c == 0) break;
+ d = c;
+ if (IsLower(d)) d -= 0x20;
+ switch (d) { /* Atgument type is... */
+ case 'S' : /* String */
+ p = va_arg(arp, TCHAR*);
+ for (j = 0; p[j]; j++) ;
+ if (!(f & 2)) { /* Right padded */
+ while (j++ < w) putc_bfd(&pb, ' ') ;
+ }
+ while (*p) putc_bfd(&pb, *p++) ; /* String body */
+ while (j++ < w) putc_bfd(&pb, ' ') ; /* Left padded */
+ continue;
+
+ case 'C' : /* Character */
+ putc_bfd(&pb, (TCHAR)va_arg(arp, int)); continue;
+
+ case 'B' : /* Unsigned binary */
+ r = 2; break;
+
+ case 'O' : /* Unsigned octal */
+ r = 8; break;
+
+ case 'D' : /* Signed decimal */
+ case 'U' : /* Unsigned decimal */
+ r = 10; break;
+
+ case 'X' : /* Unsigned hexdecimal */
+ r = 16; break;
+
+ default: /* Unknown type (pass-through) */
+ putc_bfd(&pb, c); continue;
+ }
+
+ /* Get an argument and put it in numeral */
+ v = (f & 4) ? (DWORD)va_arg(arp, long) : ((d == 'D') ? (DWORD)(long)va_arg(arp, int) : (DWORD)va_arg(arp, unsigned int));
+ if (d == 'D' && (v & 0x80000000)) {
+ v = 0 - v;
+ f |= 8;
+ }
+ i = 0;
+ do {
+ d = (TCHAR)(v % r); v /= r;
+ if (d > 9) d += (c == 'x') ? 0x27 : 0x07;
+ str[i++] = d + '0';
+ } while (v && i < sizeof str / sizeof *str);
+ if (f & 8) str[i++] = '-';
+ j = i; d = (f & 1) ? '0' : ' ';
+ if (!(f & 2)) {
+ while (j++ < w) putc_bfd(&pb, d); /* Right pad */
+ }
+ do {
+ putc_bfd(&pb, str[--i]); /* Number body */
+ } while (i);
+ while (j++ < w) putc_bfd(&pb, d); /* Left pad */
+ }
+
+ va_end(arp);
+
+ return putc_flush(&pb);
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_USE_STRFUNC */
+
+
+
+#if FF_CODE_PAGE == 0
+/*-----------------------------------------------------------------------*/
+/* Set Active Codepage for the Path Name */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_setcp (
+ WORD cp /* Value to be set as active code page */
+)
+{
+ static const WORD validcp[] = { 437, 720, 737, 771, 775, 850, 852, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0};
+ static const BYTE* const tables[] = {Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0};
+ UINT i;
+
+
+ for (i = 0; validcp[i] != 0 && validcp[i] != cp; i++) ; /* Find the code page */
+ if (validcp[i] != cp) return FR_INVALID_PARAMETER; /* Not found? */
+
+ CodePage = cp;
+ if (cp >= 900) { /* DBCS */
+ ExCvt = 0;
+ DbcTbl = tables[i];
+ } else { /* SBCS */
+ ExCvt = tables[i];
+ DbcTbl = 0;
+ }
+ return FR_OK;
+}
+#endif /* FF_CODE_PAGE == 0 */
+
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ff.h b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ff.h
new file mode 100644
index 00000000..68dc7ab6
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ff.h
@@ -0,0 +1,391 @@
+/*----------------------------------------------------------------------------/
+/ FatFs - Generic FAT Filesystem module R0.13c /
+/-----------------------------------------------------------------------------/
+/
+/ Copyright (C) 2018, 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 86604 /* Revision ID */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../../utils/types.h" /* Basic integer types */
+#include "ffconf.h" /* FatFs configuration options */
+
+#if FF_DEFINED != FFCONF_DEF
+#error Wrong configuration file (ffconf.h).
+#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 resolution 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 variables */
+
+#if FF_FS_EXFAT
+typedef QWORD FSIZE_t;
+#else
+typedef DWORD FSIZE_t;
+#endif
+
+
+
+/* Filesystem object structure (FATFS) */
+
+typedef struct {
+ BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
+ 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] */
+ DWORD volbase; /* Volume base sector */
+ DWORD fatbase; /* FAT base sector */
+ DWORD dirbase; /* Root directory base sector/cluster */
+ DWORD database; /* Data base sector */
+#if FF_FS_EXFAT
+ DWORD bitbase; /* Allocation bitmap base sector */
+#endif
+ DWORD winsect; /* Current sector appearing in the win[] */
+} 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 {
+#if !FF_FS_TINY
+ BYTE buf[FF_MAX_SS]; /* File private data read/write window */
+#endif
+ 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) */
+ DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
+#if !FF_FS_READONLY
+ DWORD 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
+} FIL;
+
+
+
+/* Directory object structure (DIR) */
+
+typedef struct {
+ FFOBJID obj; /* Object identifier */
+ DWORD dptr; /* Current read/write offset */
+ DWORD clust; /* Current cluster */
+ DWORD 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;
+
+
+
+/* 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 */
+#ifdef FF_FASTFS
+ FR_INVALID_PARAMETER, /* (19) Given parameter is invalid */
+ FR_CLTBL_NO_INIT /* (20) The cluster table for fast seek/read/write was not created */
+#else
+ FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
+#endif
+} 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 */
+#ifdef FF_FASTFS
+FRESULT f_read_fast (FIL* fp, const void* buff, UINT btr); /* Fast read data from the file */
+FRESULT f_write_fast (FIL* fp, const void* buff, UINT btw); /* Fast write data to the file */
+#endif
+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 */
+#ifdef FF_FASTFS
+DWORD *f_expand_cltbl (FIL* fp, UINT tblsz, DWORD *tbl, FSIZE_t ofs); /* Expand file and populate cluster table */
+#endif
+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, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */
+FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, 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 */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffconf.h b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffconf.h
new file mode 100644
index 00000000..3d01d8b0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffconf.h
@@ -0,0 +1,292 @@
+/*---------------------------------------------------------------------------/
+/ FatFs Functional Configurations
+/---------------------------------------------------------------------------*/
+
+#define FFCONF_DEF 86604 /* Revision ID */
+
+/*---------------------------------------------------------------------------/
+/ Function Configurations
+/---------------------------------------------------------------------------*/
+
+#define FF_FS_READONLY 0
+/* 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 2
+/* 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 2
+/* 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_FASTFS 1
+
+#ifdef FF_FASTFS
+#define FF_USE_FASTSEEK 1
+#else
+#define FF_USE_FASTSEEK 0
+#endif
+/* 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 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() 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 0
+#define FF_VOLUME_STRS "sd"
+/* 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_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. */
+
+
+#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.
+*/
+
+
+
+/*---------------------------------------------------------------------------/
+/ 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 1
+/* 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 function. 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 at read-only configuration (FF_FS_READONLY = 1). */
+
+
+#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. */
+
+
+#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 ---*/
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffsystem.c b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffsystem.c
new file mode 100644
index 00000000..8485c07d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffsystem.c
@@ -0,0 +1,39 @@
+/*------------------------------------------------------------------------*/
+/* Sample Code of OS Dependent Functions for FatFs */
+/* (C) ChaN, 2018 */
+/* (C) CTCaer, 2018 */
+/*------------------------------------------------------------------------*/
+
+
+#include "ff.h"
+#include "../../utils/types.h"
+#include
+
+
+#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
+
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffunicode.c b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffunicode.c
new file mode 100644
index 00000000..9f039637
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/libs/fatfs/ffunicode.c
@@ -0,0 +1,625 @@
+/*------------------------------------------------------------------------*/
+/* Unicode handling functions for FatFs R0.13c */
+/*------------------------------------------------------------------------*/
+/* This module will occupy a huge memory in the .const section when the /
+/ FatFs is configured for LFN with DBCS. If the system has any Unicode /
+/ utilitiy for the code conversion, this module should be modified to use /
+/ that function to avoid silly memory consumption. /
+/-------------------------------------------------------------------------*/
+/*
+/ Copyright (C) 2018, 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.
+*/
+
+
+#include "ff.h"
+
+#if FF_USE_LFN /* This module will be blanked at non-LFN configuration */
+
+#if FF_DEFINED != 86604 /* Revision ID */
+#error Wrong include file (ff.h).
+#endif
+
+#define MERGE2(a, b) a ## b
+#define CVTBL(tbl, cp) MERGE2(tbl, cp)
+
+/*------------------------------------------------------------------------*/
+/* Code Conversion Tables */
+/*------------------------------------------------------------------------*/
+
+#if FF_CODE_PAGE == 437 || FF_CODE_PAGE == 0
+static const WCHAR uc437[] = { /* CP437(U.S.) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 720 || FF_CODE_PAGE == 0
+static const WCHAR uc720[] = { /* CP720(Arabic) to Unicode conversion table */
+ 0x0000, 0x0000, 0x00E9, 0x00E2, 0x0000, 0x00E0, 0x0000, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0651, 0x0652, 0x00F4, 0x00A4, 0x0640, 0x00FB, 0x00F9, 0x0621, 0x0622, 0x0623, 0x0624, 0x00A3, 0x0625, 0x0626, 0x0627,
+ 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 0x0641, 0x00B5, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A,
+ 0x2261, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 737 || FF_CODE_PAGE == 0
+static const WCHAR uc737[] = { /* CP737(Greek) to Unicode conversion table */
+ 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0,
+ 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8,
+ 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, 0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E,
+ 0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 771 || FF_CODE_PAGE == 0
+static const WCHAR uc771[] = { /* CP771(KBL) to Unicode conversion table */
+ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x0104, 0x0105, 0x010C, 0x010D,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ 0x0118, 0x0119, 0x0116, 0x0117, 0x012E, 0x012F, 0x0160, 0x0161, 0x0172, 0x0173, 0x016A, 0x016B, 0x017D, 0x017E, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 775 || FF_CODE_PAGE == 0
+static const WCHAR uc775[] = { /* CP775(Baltic) to Unicode conversion table */
+ 0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, 0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4,
+ 0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, 0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, 0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D,
+ 0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, 0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, 0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019,
+ 0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, 0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 850 || FF_CODE_PAGE == 0
+static const WCHAR uc850[] = { /* CP850(Latin 1) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+ 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580,
+ 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4,
+ 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 852 || FF_CODE_PAGE == 0
+static const WCHAR uc852[] = { /* CP852(Latin 2) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106,
+ 0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+ 0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580,
+ 0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4,
+ 0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 855 || FF_CODE_PAGE == 0
+static const WCHAR uc855[] = { /* CP855(Cyrillic) to Unicode conversion table */
+ 0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408,
+ 0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A,
+ 0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+ 0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580,
+ 0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116,
+ 0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 857 || FF_CODE_PAGE == 0
+static const WCHAR uc857[] = { /* CP857(Turkish) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0131, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x0130, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x015E, 0x015F,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x011E, 0x011F, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+ 0x00BA, 0x00AA, 0x00CA, 0x00CB, 0x00C8, 0x0000, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580,
+ 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x0000, 0x00D7, 0x00DA, 0x00DB, 0x00D9, 0x00EC, 0x00FF, 0x00AF, 0x00B4,
+ 0x00AD, 0x00B1, 0x0000, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 860 || FF_CODE_PAGE == 0
+static const WCHAR uc860[] = { /* CP860(Portuguese) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E3, 0x00E0, 0x00C1, 0x00E7, 0x00EA, 0x00CA, 0x00E8, 0x00CD, 0x00D4, 0x00EC, 0x00C3, 0x00C2,
+ 0x00C9, 0x00C0, 0x00C8, 0x00F4, 0x00F5, 0x00F2, 0x00DA, 0x00F9, 0x00CC, 0x00D5, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x20A7, 0x00D3,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00D2, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 861 || FF_CODE_PAGE == 0
+static const WCHAR uc861[] = { /* CP861(Icelandic) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00D0, 0x00F0, 0x00DE, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00FE, 0x00FB, 0x00DD, 0x00FD, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00C1, 0x00CD, 0x00D3, 0x00DA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 862 || FF_CODE_PAGE == 0
+static const WCHAR uc862[] = { /* CP862(Hebrew) to Unicode conversion table */
+ 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
+ 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 863 || FF_CODE_PAGE == 0
+static const WCHAR uc863[] = { /* CP863(Canadian French) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00C2, 0x00E0, 0x00B6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x2017, 0x00C0,
+ 0x00C9, 0x00C8, 0x00CA, 0x00F4, 0x00CB, 0x00CF, 0x00FB, 0x00F9, 0x00A4, 0x00D4, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x00DB, 0x0192,
+ 0x00A6, 0x00B4, 0x00F3, 0x00FA, 0x00A8, 0x00BB, 0x00B3, 0x00AF, 0x00CE, 0x3210, 0x00AC, 0x00BD, 0x00BC, 0x00BE, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2219,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 864 || FF_CODE_PAGE == 0
+static const WCHAR uc864[] = { /* CP864(Arabic) to Unicode conversion table */
+ 0x00B0, 0x00B7, 0x2219, 0x221A, 0x2592, 0x2500, 0x2502, 0x253C, 0x2524, 0x252C, 0x251C, 0x2534, 0x2510, 0x250C, 0x2514, 0x2518,
+ 0x03B2, 0x221E, 0x03C6, 0x00B1, 0x00BD, 0x00BC, 0x2248, 0x00AB, 0x00BB, 0xFEF7, 0xFEF8, 0x0000, 0x0000, 0xFEFB, 0xFEFC, 0x0000,
+ 0x00A0, 0x00AD, 0xFE82, 0x00A3, 0x00A4, 0xFE84, 0x0000, 0x20AC, 0xFE8E, 0xFE8F, 0xFE95, 0xFE99, 0x060C, 0xFE9D, 0xFEA1, 0xFEA5,
+ 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0xFED1, 0x061B, 0xFEB1, 0xFEB5, 0xFEB9, 0x061F,
+ 0x00A2, 0xFE80, 0xFE81, 0xFE83, 0xFE85, 0xFECA, 0xFE8B, 0xFE8D, 0xFE91, 0xFE93, 0xFE97, 0xFE9B, 0xFE9F, 0xFEA3, 0xFEA7, 0xFEA9,
+ 0xFEAB, 0xFEAD, 0xFEAF, 0xFEB3, 0xFEB7, 0xFEBB, 0xFEBF, 0xFEC1, 0xFEC5, 0xFECB, 0xFECF, 0x00A6, 0x00AC, 0x00F7, 0x00D7, 0xFEC9,
+ 0x0640, 0xFED3, 0xFED7, 0xFEDB, 0xFEDF, 0xFEE3, 0xFEE7, 0xFEEB, 0xFEED, 0xFEEF, 0xFEF3, 0xFEBD, 0xFECC, 0xFECE, 0xFECD, 0xFEE1,
+ 0xFE7D, 0x0651, 0xFEE5, 0xFEE9, 0xFEEC, 0xFEF0, 0xFEF2, 0xFED0, 0xFED5, 0xFEF5, 0xFEF6, 0xFEDD, 0xFED9, 0xFEF1, 0x25A0, 0x0000
+};
+#endif
+#if FF_CODE_PAGE == 865 || FF_CODE_PAGE == 0
+static const WCHAR uc865[] = { /* CP865(Nordic) to Unicode conversion table */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+ 0x00C5, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00A4,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 866 || FF_CODE_PAGE == 0
+static const WCHAR uc866[] = { /* CP866(Russian) to Unicode conversion table */
+ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0
+};
+#endif
+#if FF_CODE_PAGE == 869 || FF_CODE_PAGE == 0
+static const WCHAR uc869[] = { /* CP869(Greek 2) to Unicode conversion table */
+ 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x0386, 0x00B7, 0x00B7, 0x00AC, 0x00A6, 0x2018, 0x2019, 0x0388, 0x2015, 0x0389,
+ 0x038A, 0x03AA, 0x038C, 0x00B7, 0x00B7, 0x038E, 0x03AB, 0x00A9, 0x038F, 0x00B2, 0x00B3, 0x03AC, 0x00A3, 0x03AD, 0x03AE, 0x03AF,
+ 0x03CA, 0x0390, 0x03CC, 0x03CD, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x00BD, 0x0398, 0x0399, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x039A, 0x039B, 0x039C, 0x039D, 0x2563, 0x2551, 0x2557, 0x255D, 0x039E, 0x039F, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0A30, 0x03A1, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x03A3,
+ 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x2518, 0x250C, 0x2588, 0x2584, 0x03B4, 0x03B5, 0x2580,
+ 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x0384,
+ 0x00AD, 0x00B1, 0x03C5, 0x03C6, 0x03C7, 0x00A7, 0x03C8, 0x0385, 0x00B0, 0x00A8, 0x03C9, 0x03CB, 0x03B0, 0x03CE, 0x25A0, 0x00A0
+};
+#endif
+
+
+
+
+/*------------------------------------------------------------------------*/
+/* OEM <==> Unicode conversions for static code page configuration */
+/* SBCS fixed code page */
+/*------------------------------------------------------------------------*/
+
+#if FF_CODE_PAGE != 0 && FF_CODE_PAGE < 900
+WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */
+ DWORD uni, /* UTF-16 encoded character to be converted */
+ WORD cp /* Code page for the conversion */
+)
+{
+ WCHAR c = 0;
+ const WCHAR *p = CVTBL(uc, FF_CODE_PAGE);
+
+
+ if (uni < 0x80) { /* ASCII? */
+ c = (WCHAR)uni;
+
+ } else { /* Non-ASCII */
+ if (uni < 0x10000 && cp == FF_CODE_PAGE) { /* Is it in BMP and valid code page? */
+ for (c = 0; c < 0x80 && uni != p[c]; c++) ;
+ c = (c + 0x80) & 0xFF;
+ }
+ }
+
+ return c;
+}
+
+WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */
+ WCHAR oem, /* OEM code to be converted */
+ WORD cp /* Code page for the conversion */
+)
+{
+ WCHAR c = 0;
+ const WCHAR *p = CVTBL(uc, FF_CODE_PAGE);
+
+
+ if (oem < 0x80) { /* ASCII? */
+ c = oem;
+
+ } else { /* Extended char */
+ if (cp == FF_CODE_PAGE) { /* Is it a valid code page? */
+ if (oem < 0x100) c = p[oem - 0x80];
+ }
+ }
+
+ return c;
+}
+
+#endif
+
+
+
+/*------------------------------------------------------------------------*/
+/* OEM <==> Unicode conversions for static code page configuration */
+/* DBCS fixed code page */
+/*------------------------------------------------------------------------*/
+
+#if FF_CODE_PAGE >= 900
+WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */
+ DWORD uni, /* UTF-16 encoded character to be converted */
+ WORD cp /* Code page for the conversion */
+)
+{
+ const WCHAR *p;
+ WCHAR c = 0, uc;
+ UINT i = 0, n, li, hi;
+
+
+ if (uni < 0x80) { /* ASCII? */
+ c = (WCHAR)uni;
+
+ } else { /* Non-ASCII */
+ if (uni < 0x10000 && cp == FF_CODE_PAGE) { /* Is it in BMP and valid code page? */
+ uc = (WCHAR)uni;
+ p = CVTBL(uni2oem, FF_CODE_PAGE);
+ hi = sizeof CVTBL(uni2oem, FF_CODE_PAGE) / 4 - 1;
+ li = 0;
+ for (n = 16; n; n--) {
+ i = li + (hi - li) / 2;
+ if (uc == p[i * 2]) break;
+ if (uc > p[i * 2]) {
+ li = i;
+ } else {
+ hi = i;
+ }
+ }
+ if (n != 0) c = p[i * 2 + 1];
+ }
+ }
+
+ return c;
+}
+
+
+WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */
+ WCHAR oem, /* OEM code to be converted */
+ WORD cp /* Code page for the conversion */
+)
+{
+ const WCHAR *p;
+ WCHAR c = 0;
+ UINT i = 0, n, li, hi;
+
+
+ if (oem < 0x80) { /* ASCII? */
+ c = oem;
+
+ } else { /* Extended char */
+ if (cp == FF_CODE_PAGE) { /* Is it valid code page? */
+ p = CVTBL(oem2uni, FF_CODE_PAGE);
+ hi = sizeof CVTBL(oem2uni, FF_CODE_PAGE) / 4 - 1;
+ li = 0;
+ for (n = 16; n; n--) {
+ i = li + (hi - li) / 2;
+ if (oem == p[i * 2]) break;
+ if (oem > p[i * 2]) {
+ li = i;
+ } else {
+ hi = i;
+ }
+ }
+ if (n != 0) c = p[i * 2 + 1];
+ }
+ }
+
+ return c;
+}
+#endif
+
+
+
+/*------------------------------------------------------------------------*/
+/* OEM <==> Unicode conversions for dynamic code page configuration */
+/*------------------------------------------------------------------------*/
+
+#if FF_CODE_PAGE == 0
+
+static const WORD cp_code[] = { 437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 0};
+static const WCHAR* const cp_table[] = {uc437, uc720, uc737, uc771, uc775, uc850, uc852, uc855, uc857, uc860, uc861, uc862, uc863, uc864, uc865, uc866, uc869, 0};
+
+
+WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */
+ DWORD uni, /* UTF-16 encoded character to be converted */
+ WORD cp /* Code page for the conversion */
+)
+{
+ const WCHAR *p;
+ WCHAR c = 0, uc;
+ UINT i, n, li, hi;
+
+
+ if (uni < 0x80) { /* ASCII? */
+ c = (WCHAR)uni;
+
+ } else { /* Non-ASCII */
+ if (uni < 0x10000) { /* Is it in BMP? */
+ uc = (WCHAR)uni;
+ p = 0;
+ if (cp < 900) { /* SBCS */
+ for (i = 0; cp_code[i] != 0 && cp_code[i] != cp; i++) ; /* Get conversion table */
+ p = cp_table[i];
+ if (p) { /* Is it valid code page ? */
+ for (c = 0; c < 0x80 && uc != p[c]; c++) ; /* Find OEM code in the table */
+ c = (c + 0x80) & 0xFF;
+ }
+ } else { /* DBCS */
+ switch (cp) { /* Get conversion table */
+ case 932 : p = uni2oem932; hi = sizeof uni2oem932 / 4 - 1; break;
+ case 936 : p = uni2oem936; hi = sizeof uni2oem936 / 4 - 1; break;
+ case 949 : p = uni2oem949; hi = sizeof uni2oem949 / 4 - 1; break;
+ case 950 : p = uni2oem950; hi = sizeof uni2oem950 / 4 - 1; break;
+ }
+ if (p) { /* Is it valid code page? */
+ li = 0;
+ for (n = 16; n; n--) { /* Find OEM code */
+ i = li + (hi - li) / 2;
+ if (uc == p[i * 2]) break;
+ if (uc > p[i * 2]) {
+ li = i;
+ } else {
+ hi = i;
+ }
+ }
+ if (n != 0) c = p[i * 2 + 1];
+ }
+ }
+ }
+ }
+
+ return c;
+}
+
+
+WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */
+ WCHAR oem, /* OEM code to be converted (DBC if >=0x100) */
+ WORD cp /* Code page for the conversion */
+)
+{
+ const WCHAR *p;
+ WCHAR c = 0;
+ UINT i, n, li, hi;
+
+
+ if (oem < 0x80) { /* ASCII? */
+ c = oem;
+
+ } else { /* Extended char */
+ p = 0;
+ if (cp < 900) { /* SBCS */
+ for (i = 0; cp_code[i] != 0 && cp_code[i] != cp; i++) ; /* Get table */
+ p = cp_table[i];
+ if (p) { /* Is it a valid CP ? */
+ if (oem < 0x100) c = p[oem - 0x80];
+ }
+ } else { /* DBCS */
+ switch (cp) {
+ case 932 : p = oem2uni932; hi = sizeof oem2uni932 / 4 - 1; break;
+ case 936 : p = oem2uni936; hi = sizeof oem2uni936 / 4 - 1; break;
+ case 949 : p = oem2uni949; hi = sizeof oem2uni949 / 4 - 1; break;
+ case 950 : p = oem2uni950; hi = sizeof oem2uni950 / 4 - 1; break;
+ }
+ if (p) {
+ li = 0;
+ for (n = 16; n; n--) {
+ i = li + (hi - li) / 2;
+ if (oem == p[i * 2]) break;
+ if (oem > p[i * 2]) {
+ li = i;
+ } else {
+ hi = i;
+ }
+ }
+ if (n != 0) c = p[i * 2 + 1];
+ }
+ }
+ }
+
+ return c;
+}
+#endif
+
+
+
+/*------------------------------------------------------------------------*/
+/* Unicode up-case conversion */
+/*------------------------------------------------------------------------*/
+
+DWORD ff_wtoupper ( /* Returns up-converted code point */
+ DWORD uni /* Unicode code point to be up-converted */
+)
+{
+ const WORD *p;
+ WORD uc, bc, nc, cmd;
+ static const WORD cvt1[] = { /* Compressed up conversion table for U+0000 - U+0FFF */
+ /* Basic Latin */
+ 0x0061,0x031A,
+ /* Latin-1 Supplement */
+ 0x00E0,0x0317,
+ 0x00F8,0x0307,
+ 0x00FF,0x0001,0x0178,
+ /* Latin Extended-A */
+ 0x0100,0x0130,
+ 0x0132,0x0106,
+ 0x0139,0x0110,
+ 0x014A,0x012E,
+ 0x0179,0x0106,
+ /* Latin Extended-B */
+ 0x0180,0x004D,0x0243,0x0181,0x0182,0x0182,0x0184,0x0184,0x0186,0x0187,0x0187,0x0189,0x018A,0x018B,0x018B,0x018D,0x018E,0x018F,0x0190,0x0191,0x0191,0x0193,0x0194,0x01F6,0x0196,0x0197,0x0198,0x0198,0x023D,0x019B,0x019C,0x019D,0x0220,0x019F,0x01A0,0x01A0,0x01A2,0x01A2,0x01A4,0x01A4,0x01A6,0x01A7,0x01A7,0x01A9,0x01AA,0x01AB,0x01AC,0x01AC,0x01AE,0x01AF,0x01AF,0x01B1,0x01B2,0x01B3,0x01B3,0x01B5,0x01B5,0x01B7,0x01B8,0x01B8,0x01BA,0x01BB,0x01BC,0x01BC,0x01BE,0x01F7,0x01C0,0x01C1,0x01C2,0x01C3,0x01C4,0x01C5,0x01C4,0x01C7,0x01C8,0x01C7,0x01CA,0x01CB,0x01CA,
+ 0x01CD,0x0110,
+ 0x01DD,0x0001,0x018E,
+ 0x01DE,0x0112,
+ 0x01F3,0x0003,0x01F1,0x01F4,0x01F4,
+ 0x01F8,0x0128,
+ 0x0222,0x0112,
+ 0x023A,0x0009,0x2C65,0x023B,0x023B,0x023D,0x2C66,0x023F,0x0240,0x0241,0x0241,
+ 0x0246,0x010A,
+ /* IPA Extensions */
+ 0x0253,0x0040,0x0181,0x0186,0x0255,0x0189,0x018A,0x0258,0x018F,0x025A,0x0190,0x025C,0x025D,0x025E,0x025F,0x0193,0x0261,0x0262,0x0194,0x0264,0x0265,0x0266,0x0267,0x0197,0x0196,0x026A,0x2C62,0x026C,0x026D,0x026E,0x019C,0x0270,0x0271,0x019D,0x0273,0x0274,0x019F,0x0276,0x0277,0x0278,0x0279,0x027A,0x027B,0x027C,0x2C64,0x027E,0x027F,0x01A6,0x0281,0x0282,0x01A9,0x0284,0x0285,0x0286,0x0287,0x01AE,0x0244,0x01B1,0x01B2,0x0245,0x028D,0x028E,0x028F,0x0290,0x0291,0x01B7,
+ /* Greek, Coptic */
+ 0x037B,0x0003,0x03FD,0x03FE,0x03FF,
+ 0x03AC,0x0004,0x0386,0x0388,0x0389,0x038A,
+ 0x03B1,0x0311,
+ 0x03C2,0x0002,0x03A3,0x03A3,
+ 0x03C4,0x0308,
+ 0x03CC,0x0003,0x038C,0x038E,0x038F,
+ 0x03D8,0x0118,
+ 0x03F2,0x000A,0x03F9,0x03F3,0x03F4,0x03F5,0x03F6,0x03F7,0x03F7,0x03F9,0x03FA,0x03FA,
+ /* Cyrillic */
+ 0x0430,0x0320,
+ 0x0450,0x0710,
+ 0x0460,0x0122,
+ 0x048A,0x0136,
+ 0x04C1,0x010E,
+ 0x04CF,0x0001,0x04C0,
+ 0x04D0,0x0144,
+ /* Armenian */
+ 0x0561,0x0426,
+
+ 0x0000 /* EOT */
+ };
+ static const WORD cvt2[] = { /* Compressed up conversion table for U+1000 - U+FFFF */
+ /* Phonetic Extensions */
+ 0x1D7D,0x0001,0x2C63,
+ /* Latin Extended Additional */
+ 0x1E00,0x0196,
+ 0x1EA0,0x015A,
+ /* Greek Extended */
+ 0x1F00,0x0608,
+ 0x1F10,0x0606,
+ 0x1F20,0x0608,
+ 0x1F30,0x0608,
+ 0x1F40,0x0606,
+ 0x1F51,0x0007,0x1F59,0x1F52,0x1F5B,0x1F54,0x1F5D,0x1F56,0x1F5F,
+ 0x1F60,0x0608,
+ 0x1F70,0x000E,0x1FBA,0x1FBB,0x1FC8,0x1FC9,0x1FCA,0x1FCB,0x1FDA,0x1FDB,0x1FF8,0x1FF9,0x1FEA,0x1FEB,0x1FFA,0x1FFB,
+ 0x1F80,0x0608,
+ 0x1F90,0x0608,
+ 0x1FA0,0x0608,
+ 0x1FB0,0x0004,0x1FB8,0x1FB9,0x1FB2,0x1FBC,
+ 0x1FCC,0x0001,0x1FC3,
+ 0x1FD0,0x0602,
+ 0x1FE0,0x0602,
+ 0x1FE5,0x0001,0x1FEC,
+ 0x1FF3,0x0001,0x1FFC,
+ /* Letterlike Symbols */
+ 0x214E,0x0001,0x2132,
+ /* Number forms */
+ 0x2170,0x0210,
+ 0x2184,0x0001,0x2183,
+ /* Enclosed Alphanumerics */
+ 0x24D0,0x051A,
+ 0x2C30,0x042F,
+ /* Latin Extended-C */
+ 0x2C60,0x0102,
+ 0x2C67,0x0106, 0x2C75,0x0102,
+ /* Coptic */
+ 0x2C80,0x0164,
+ /* Georgian Supplement */
+ 0x2D00,0x0826,
+ /* Full-width */
+ 0xFF41,0x031A,
+
+ 0x0000 /* EOT */
+ };
+
+
+ if (uni < 0x10000) { /* Is it in BMP? */
+ uc = (WORD)uni;
+ p = uc < 0x1000 ? cvt1 : cvt2;
+ for (;;) {
+ bc = *p++; /* Get the block base */
+ if (bc == 0 || uc < bc) break; /* Not matched? */
+ nc = *p++; cmd = nc >> 8; nc &= 0xFF; /* Get processing command and block size */
+ if (uc < bc + nc) { /* In the block? */
+ switch (cmd) {
+ case 0: uc = p[uc - bc]; break; /* Table conversion */
+ case 1: uc -= (uc - bc) & 1; break; /* Case pairs */
+ case 2: uc -= 16; break; /* Shift -16 */
+ case 3: uc -= 32; break; /* Shift -32 */
+ case 4: uc -= 48; break; /* Shift -48 */
+ case 5: uc -= 26; break; /* Shift -26 */
+ case 6: uc += 8; break; /* Shift +8 */
+ case 7: uc -= 80; break; /* Shift -80 */
+ case 8: uc -= 0x1C60; break; /* Shift -0x1C60 */
+ }
+ break;
+ }
+ if (cmd == 0) p += nc; /* Skip table if needed */
+ }
+ uni = uc;
+ }
+
+ return uni;
+}
+
+#endif /* #if FF_USE_LFN */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/main.c b/Source/Atmosphere-MTC-Unlock/emummc/source/main.c
new file mode 100644
index 00000000..a06101a9
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/main.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include "nx/svc.h"
+#include "nx/smc.h"
+#include "soc/clock.h"
+#include "soc/i2c.h"
+#include "emuMMC/emummc.h"
+#include "emuMMC/emummc_ctx.h"
+#include "FS/FS_offsets.h"
+#include "utils/fatal.h"
+
+// Prototypes
+void __init();
+void __initheap(void);
+void setup_hooks(void);
+void __libc_init_array(void);
+void setup_nintendo_paths(void);
+void hook_function(uintptr_t source, uintptr_t target);
+
+void *__stack_top;
+uintptr_t text_base;
+size_t fs_code_size;
+u8 *fs_rw_mapping = NULL;
+Handle self_proc_handle = 0;
+char inner_heap[INNER_HEAP_SIZE];
+size_t inner_heap_size = INNER_HEAP_SIZE;
+
+extern char _start;
+extern char __argdata__;
+
+// Nintendo Path
+static char nintendo_path[0x80] = "Nintendo";
+
+// 1.0.0 requires special path handling because it has separate album and contents paths.
+#define FS_100_ALBUM_PATH 0
+#define FS_100_CONTENTS_PATH 1
+static char nintendo_path_album_100[0x100] = "/Nintendo/Album";
+static char nintendo_path_contents_100[0x100] = "/Nintendo/Contents";
+
+// FS offsets
+static const fs_offsets_t *fs_offsets;
+
+// Defined by linkerscript
+#define INJECTED_SIZE ((uintptr_t)&__argdata__ - (uintptr_t)&_start)
+#define INJECT_OFFSET(type, offset) (type)(text_base + INJECTED_SIZE + offset)
+#define FS_CODE_BASE INJECT_OFFSET(uintptr_t, 0)
+
+#define GENERATE_ADD(register, register_target, value) (0x91000000 | value << 10 | register << 5 | register_target)
+#define GENERATE_ADRP(register, page_addr) (0x90000000 | ((((page_addr) >> 12) & 0x3) << 29) | ((((page_addr) >> 12) & 0x1FFFFC) << 3) | ((register) & 0x1F))
+#define GENERATE_BRANCH(source, destination) (0x14000000 | ((((destination) - (source)) >> 2) & 0x3FFFFFF))
+#define GENERATE_NOP() (0xD503201F)
+
+#define INJECT_HOOK(offset, destination) hook_function(INJECT_OFFSET(uintptr_t, offset), (uintptr_t)&destination)
+#define INJECT_HOOK_RELATIVE(offset, relative_destination) hook_function(INJECT_OFFSET(uintptr_t, offset), INJECT_OFFSET(uintptr_t, offset) + relative_destination)
+#define INJECT_NOP(offset) write_nop(INJECT_OFFSET(uintptr_t, offset))
+
+// emuMMC
+extern _sdmmc_accessor_gc sdmmc_accessor_gc;
+extern _sdmmc_accessor_sd sdmmc_accessor_sd;
+extern _sdmmc_accessor_nand sdmmc_accessor_nand;
+extern _lock_mutex lock_mutex;
+extern _unlock_mutex unlock_mutex;
+extern void *sd_mutex;
+extern void *nand_mutex;
+extern volatile int *active_partition;
+extern volatile Handle *sdmmc_das_handle;
+
+// Storage
+volatile __attribute__((aligned(0x1000))) emuMMC_ctx_t emuMMC_ctx = {
+ .magic = EMUMMC_STORAGE_MAGIC,
+ .id = 0,
+ .fs_ver = FS_VER_MAX,
+
+ // SD Default Metadata
+ .SD_Type = emuMMC_SD_Raw,
+ .SD_StoragePartitionOffset = 0,
+
+ // EMMC Default Metadata
+ .EMMC_Type = emuMMC_EMMC,
+ .EMMC_StoragePartitionOffset = 0,
+
+ // File Default Path
+ .storagePath = "",
+};
+
+// TODO: move into another file
+typedef struct
+{
+ void *_0x0;
+ void *_0x8;
+ void *_0x10;
+ void *_0x18;
+ void *_0x20;
+ void *_0x28;
+ void *_0x30;
+ void *_0x38;
+ void *_0x40;
+ Result (*set_min_v_clock_rate)(void *, uint32_t, uint32_t);
+} nn_clkrst_session_vt_t;
+
+typedef struct
+{
+ nn_clkrst_session_vt_t *vt;
+} nn_clkrst_session_t;
+
+Result clkrst_set_min_v_clock_rate(nn_clkrst_session_t **_this, uint32_t clk_rate)
+{
+ Result rc = (*_this)->vt->set_min_v_clock_rate((void *)*_this, clk_rate, clk_rate);
+
+ if (rc == 0x6C0 || rc == 0)
+ { // TODO #define
+ return 0;
+ }
+ if (rc != 0xAC0)
+ { // TODO #define
+ fatal_abort(Fatal_BadResult);
+ }
+
+ return rc;
+}
+
+void __initheap(void)
+{
+ void *addr = inner_heap;
+ size_t size = inner_heap_size;
+
+ /* Newlib Heap Management */
+ extern char *fake_heap_start;
+ extern char *fake_heap_end;
+
+ fake_heap_start = (char *)addr;
+ fake_heap_end = (char *)addr + size;
+}
+
+static void _receive_process_handle_thread(void *_session_handle) {
+ Result rc;
+
+ // Convert the argument to a handle copy we can use.
+ Handle session_handle = *(Handle*)_session_handle;
+
+ // Receive the request from the client thread.
+ memset(armGetTls(), 0, 0x10);
+ s32 idx = 0;
+ rc = svcReplyAndReceive(&idx, &session_handle, 1, INVALID_HANDLE, UINT64_MAX);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ // Set the process handle.
+ self_proc_handle = ((u32 *)armGetTls())[3];
+
+ // Close the session.
+ svcCloseHandle(session_handle);
+
+ // Terminate ourselves.
+ svcExitThread();
+
+ // This code will never execute.
+ while (true);
+}
+
+static void _init_process_handle(void) {
+ Result rc;
+ u8 temp_thread_stack[0x1000];
+
+ // Create a new session to transfer our process handle to ourself
+ Handle server_handle, client_handle;
+ rc = svcCreateSession(&server_handle, &client_handle, 0, 0);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ // Create a new thread to receive our handle.
+ Handle thread_handle;
+ rc = svcCreateThread(&thread_handle, _receive_process_handle_thread, &server_handle, temp_thread_stack + sizeof(temp_thread_stack), 0x20, 3);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ // Start the new thread.
+ rc = svcStartThread(thread_handle);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ // Send the message.
+ static const u32 SendProcessHandleMessage[4] = { 0x00000000, 0x80000000, 0x00000002, CUR_PROCESS_HANDLE };
+ memcpy(armGetTls(), SendProcessHandleMessage, sizeof(SendProcessHandleMessage));
+ svcSendSyncRequest(client_handle);
+
+ // Close the session handle.
+ svcCloseHandle(client_handle);
+
+ // Wait for the thread to be done.
+ rc = svcWaitSynchronizationSingle(thread_handle, UINT64_MAX);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ // Close the thread handle.
+ svcCloseHandle(thread_handle);
+}
+
+static void _map_fs_rw(void) {
+ Result rc;
+
+ do {
+ fs_rw_mapping = (u8 *)(smcGenerateRandomU64() & 0xFFFFFF000ull);
+ rc = svcMapProcessMemory(fs_rw_mapping, self_proc_handle, FS_CODE_BASE, fs_code_size);
+ } while (rc == 0xDC01 || rc == 0xD401);
+
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+}
+
+static void _unmap_fs_rw(void) {
+ Result rc = svcUnmapProcessMemory(fs_rw_mapping, self_proc_handle, FS_CODE_BASE, fs_code_size);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ fs_rw_mapping = NULL;
+}
+
+static void _write32(uintptr_t source, u32 value) {
+ *((u32 *)(fs_rw_mapping + (source - FS_CODE_BASE))) = value;
+}
+
+void hook_function(uintptr_t source, uintptr_t target)
+{
+ u32 branch_opcode = GENERATE_BRANCH(source, target);
+ _write32(source, branch_opcode);
+}
+
+void write_nop(uintptr_t source)
+{
+ _write32(source, GENERATE_NOP());
+}
+
+void write_adrp_add(int reg, uintptr_t pc, uintptr_t add_rel_offset, intptr_t destination)
+{
+ uintptr_t add_opcode_location = pc + add_rel_offset;
+
+ intptr_t offset = (destination & 0xFFFFF000) - (pc & 0xFFFFF000);
+ uint32_t opcode_adrp = GENERATE_ADRP(reg, offset);
+ uint32_t opcode_add = GENERATE_ADD(reg, reg, (destination & 0x00000FFF));
+
+ _write32(pc, opcode_adrp);
+ _write32(add_opcode_location, opcode_add);
+}
+
+void setup_hooks(void)
+{
+ // rtld
+ INJECT_HOOK_RELATIVE(fs_offsets->rtld, fs_offsets->rtld_destination);
+ // sdmmc_wrapper_read hook
+ INJECT_HOOK(fs_offsets->sdmmc_wrapper_read, sdmmc_wrapper_read);
+ // sdmmc_wrapper_write hook
+ INJECT_HOOK(fs_offsets->sdmmc_wrapper_write, sdmmc_wrapper_write);
+ // sdmmc_wrapper_controller_open hook
+ if (fs_offsets->sdmmc_accessor_controller_open)
+ INJECT_HOOK(fs_offsets->sdmmc_accessor_controller_open, sdmmc_wrapper_controller_open);
+ // sdmmc_wrapper_controller_close hook
+ INJECT_HOOK(fs_offsets->sdmmc_accessor_controller_close, sdmmc_wrapper_controller_close);
+
+ // On 8.0.0+, we need to hook the regulator setup, because
+ // otherwise it will abort because we have already turned it on.
+ if (emuMMC_ctx.fs_ver >= FS_VER_8_0_0)
+ {
+ INJECT_HOOK(fs_offsets->clkrst_set_min_v_clock_rate, clkrst_set_min_v_clock_rate);
+ }
+}
+
+void populate_function_pointers(void)
+{
+ // Accessor getters
+ sdmmc_accessor_gc = INJECT_OFFSET(_sdmmc_accessor_gc, fs_offsets->sdmmc_accessor_gc);
+ sdmmc_accessor_sd = INJECT_OFFSET(_sdmmc_accessor_sd, fs_offsets->sdmmc_accessor_sd);
+ sdmmc_accessor_nand = INJECT_OFFSET(_sdmmc_accessor_nand, fs_offsets->sdmmc_accessor_nand);
+
+ // MutexLock functions
+ lock_mutex = INJECT_OFFSET(_lock_mutex, fs_offsets->lock_mutex);
+ unlock_mutex = INJECT_OFFSET(_unlock_mutex, fs_offsets->unlock_mutex);
+
+ // Other
+ sd_mutex = INJECT_OFFSET(void *, fs_offsets->sd_mutex);
+ nand_mutex = INJECT_OFFSET(void *, fs_offsets->nand_mutex);
+ active_partition = INJECT_OFFSET(volatile int *, fs_offsets->active_partition);
+ sdmmc_das_handle = INJECT_OFFSET(volatile Handle *, fs_offsets->sdmmc_das_handle);
+}
+
+void write_nops(void)
+{
+ // On 7.0.0+, we need to attach to device address space ourselves.
+ // This patches an abort that happens when Nintendo's code sees SD
+ // is already attached
+ if (emuMMC_ctx.fs_ver >= FS_VER_7_0_0)
+ {
+ INJECT_NOP(fs_offsets->sd_das_init);
+ }
+}
+
+static void load_emummc_ctx(void)
+{
+ exo_emummc_config_t config;
+ static struct
+ {
+ char storage_path[sizeof(emuMMC_ctx.storagePath)];
+ char nintendo_path[sizeof(nintendo_path)];
+ } __attribute__((aligned(0x1000))) paths;
+
+ int x = smcGetEmummcConfig(EXO_EMUMMC_MMC_NAND, &config, &paths);
+ if (x != 0)
+ {
+ fatal_abort(Fatal_GetConfig);
+ }
+
+ if (config.base_cfg.magic == EMUMMC_STORAGE_MAGIC)
+ {
+ emuMMC_ctx.magic = config.base_cfg.magic;
+ emuMMC_ctx.id = config.base_cfg.id;
+ emuMMC_ctx.EMMC_Type = (enum emuMMC_Type)config.base_cfg.type;
+ emuMMC_ctx.fs_ver = (enum FS_VER)config.base_cfg.fs_version;
+ if (emuMMC_ctx.EMMC_Type == emuMMC_SD_Raw)
+ {
+ emuMMC_ctx.EMMC_StoragePartitionOffset = config.partition_cfg.start_sector;
+ }
+ else if (emuMMC_ctx.EMMC_Type == emuMMC_SD_File)
+ {
+ memcpy((void *)emuMMC_ctx.storagePath, paths.storage_path, sizeof(emuMMC_ctx.storagePath) - 1);
+ emuMMC_ctx.storagePath[sizeof(emuMMC_ctx.storagePath) - 1] = 0;
+ }
+ memcpy(nintendo_path, paths.nintendo_path, sizeof(nintendo_path) - 1);
+ nintendo_path[sizeof(nintendo_path) - 1] = 0;
+ if (strcmp(nintendo_path, "") == 0)
+ {
+ snprintf(nintendo_path, sizeof(nintendo_path), "emummc/Nintendo_%04x", emuMMC_ctx.id);
+ }
+ }
+ else
+ {
+ fatal_abort(Fatal_GetConfig);
+ }
+}
+
+void setup_nintendo_paths(void)
+{
+ if (emuMMC_ctx.fs_ver > FS_VER_1_0_0)
+ {
+ for (int i = 0; fs_offsets->nintendo_paths[i].adrp_offset; i++)
+ {
+ intptr_t nintendo_path_location = (intptr_t)&nintendo_path;
+ uintptr_t fs_adrp_opcode_location = INJECT_OFFSET(uintptr_t, fs_offsets->nintendo_paths[i].adrp_offset);
+ write_adrp_add(fs_offsets->nintendo_paths[i].opcode_reg, fs_adrp_opcode_location, fs_offsets->nintendo_paths[i].add_rel_offset, nintendo_path_location);
+ }
+ }
+ else
+ {
+ // 1.0.0 needs special handling because it uses two paths.
+ // Do album path
+ {
+ snprintf(nintendo_path_album_100, sizeof(nintendo_path_album_100), "/%s/Album", nintendo_path);
+ intptr_t nintendo_album_path_location = (intptr_t)&nintendo_path_album_100;
+ uintptr_t fs_adrp_opcode_location = INJECT_OFFSET(uintptr_t, fs_offsets->nintendo_paths[FS_100_ALBUM_PATH].adrp_offset);
+ write_adrp_add(fs_offsets->nintendo_paths[FS_100_ALBUM_PATH].opcode_reg, fs_adrp_opcode_location, fs_offsets->nintendo_paths[FS_100_ALBUM_PATH].add_rel_offset, nintendo_album_path_location);
+ }
+
+ // Do contents path
+ {
+ snprintf(nintendo_path_contents_100, sizeof(nintendo_path_contents_100), "/%s/Contents", nintendo_path);
+ intptr_t nintendo_contents_path_location = (intptr_t)&nintendo_path_contents_100;
+ uintptr_t fs_adrp_opcode_location = INJECT_OFFSET(uintptr_t, fs_offsets->nintendo_paths[FS_100_CONTENTS_PATH].adrp_offset);
+ write_adrp_add(fs_offsets->nintendo_paths[FS_100_CONTENTS_PATH].opcode_reg, fs_adrp_opcode_location, fs_offsets->nintendo_paths[FS_100_CONTENTS_PATH].add_rel_offset, nintendo_contents_path_location);
+ }
+ }
+}
+
+// inject main func
+void __init()
+{
+ // Call constructors.
+ __libc_init_array();
+
+ MemoryInfo meminfo;
+ u32 pageinfo;
+ svcQueryMemory(&meminfo, &pageinfo, (u64)&_start);
+
+ text_base = meminfo.addr;
+
+ // Get code size
+ svcQueryMemory(&meminfo, &pageinfo, FS_CODE_BASE);
+ fs_code_size = meminfo.size;
+
+ load_emummc_ctx();
+
+ fs_offsets = get_fs_offsets(emuMMC_ctx.fs_ver);
+
+ _init_process_handle();
+ _map_fs_rw();
+ setup_hooks();
+ populate_function_pointers();
+ write_nops();
+ setup_nintendo_paths();
+ _unmap_fs_rw();
+
+ clock_enable_i2c5();
+ i2c_init();
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/cache.h b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/cache.h
new file mode 100644
index 00000000..f3a646d2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/cache.h
@@ -0,0 +1,41 @@
+/**
+ * @file cache.h
+ * @brief AArch64 cache operations.
+ * @author plutoo
+ * @copyright libnx Authors
+ */
+#pragma once
+#include "../utils/types.h"
+
+/**
+ * @brief Performs a data cache flush on the specified buffer.
+ * @param addr Address of the buffer.
+ * @param size Size of the buffer, in bytes.
+ * @remarks Cache flush is defined as Clean + Invalidate.
+ * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
+ */
+void armDCacheFlush(void* addr, size_t size);
+
+/**
+ * @brief Performs a data cache clean on the specified buffer.
+ * @param addr Address of the buffer.
+ * @param size Size of the buffer, in bytes.
+ * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
+ */
+void armDCacheClean(void* addr, size_t size);
+
+/**
+ * @brief Performs an instruction cache invalidation clean on the specified buffer.
+ * @param addr Address of the buffer.
+ * @param size Size of the buffer, in bytes.
+ * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
+ */
+void armICacheInvalidate(void* addr, size_t size);
+
+/**
+ * @brief Performs a data cache zeroing operation on the specified buffer.
+ * @param addr Address of the buffer.
+ * @param size Size of the buffer, in bytes.
+ * @note The start and end addresses of the buffer are forcibly rounded to cache line boundaries (read from CTR_EL0 system register).
+ */
+void armDCacheZero(void* addr, size_t size);
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/cache.s b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/cache.s
new file mode 100644
index 00000000..ce1d0703
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/cache.s
@@ -0,0 +1,128 @@
+/**
+ * @file cache.s
+ * @copyright libnx Authors
+ */
+
+.macro CODE_BEGIN name
+ .section .text.\name, "ax", %progbits
+ .global \name
+ .type \name, %function
+ .align 2
+ .cfi_startproc
+\name:
+.endm
+
+.macro CODE_END
+ .cfi_endproc
+.endm
+
+CODE_BEGIN armDCacheFlush
+ add x1, x1, x0
+ mrs x8, CTR_EL0
+ lsr x8, x8, #16
+ and x8, x8, #0xf
+ mov x9, #4
+ lsl x9, x9, x8
+ sub x10, x9, #1
+ bic x8, x0, x10
+ mov x10, x1
+
+ mov w1, #1
+ mrs x0, tpidrro_el0
+ strb w1, [x0, #0x104]
+
+armDCacheFlush_L0:
+ dc civac, x8
+ add x8, x8, x9
+ cmp x8, x10
+ bcc armDCacheFlush_L0
+
+ dsb sy
+
+ strb wzr, [x0, #0x104]
+
+ ret
+CODE_END
+
+CODE_BEGIN armDCacheClean
+ add x1, x1, x0
+ mrs x8, CTR_EL0
+ lsr x8, x8, #16
+ and x8, x8, #0xf
+ mov x9, #4
+ lsl x9, x9, x8
+ sub x10, x9, #1
+ bic x8, x0, x10
+ mov x10, x1
+
+ mov w1, #1
+ mrs x0, tpidrro_el0
+ strb w1, [x0, #0x104]
+
+armDCacheClean_L0:
+ dc cvac, x8
+ add x8, x8, x9
+ cmp x8, x10
+ bcc armDCacheClean_L0
+
+ dsb sy
+
+ strb wzr, [x0, #0x104]
+
+ ret
+CODE_END
+
+CODE_BEGIN armICacheInvalidate
+ add x1, x1, x0
+ mrs x8, CTR_EL0
+ and x8, x8, #0xf
+ mov x9, #4
+ lsl x9, x9, x8
+ sub x10, x9, #1
+ bic x8, x0, x10
+ mov x10, x1
+
+ mov w1, #1
+ mrs x0, tpidrro_el0
+ strb w1, [x0, #0x104]
+
+armICacheInvalidate_L0:
+ ic ivau, x8
+ add x8, x8, x9
+ cmp x8, x10
+ bcc armICacheInvalidate_L0
+
+ dsb sy
+
+ strb wzr, [x0, #0x104]
+
+ ret
+CODE_END
+
+CODE_BEGIN armDCacheZero
+ add x1, x1, x0
+ mrs x8, CTR_EL0
+ lsr x8, x8, #16
+ and x8, x8, #0xf
+ mov x9, #4
+ lsl x9, x9, x8
+ sub x10, x9, #1
+ bic x8, x0, x10
+ mov x10, x1
+
+ mov w1, #1
+ mrs x0, tpidrro_el0
+ strb w1, [x0, #0x104]
+
+armDCacheZero_L0:
+ dc zva, x8
+ add x8, x8, x9
+ cmp x8, x10
+ bcc armDCacheZero_L0
+
+ dsb sy
+
+ strb wzr, [x0, #0x104]
+
+ ret
+CODE_END
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/counter.h b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/counter.h
new file mode 100644
index 00000000..072be641
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/counter.h
@@ -0,0 +1,46 @@
+/**
+ * @file counter.h
+ * @brief AArch64 system counter-timer.
+ * @author fincs
+ * @copyright libnx Authors
+ */
+#pragma once
+#include "../utils/types.h"
+
+/**
+ * @brief Gets the current system tick.
+ * @return The current system tick.
+ */
+static inline u64 armGetSystemTick(void) {
+ u64 ret;
+ __asm__ __volatile__ ("mrs %x[data], cntpct_el0" : [data] "=r" (ret));
+ return ret;
+}
+
+/**
+ * @brief Gets the system counter-timer frequency
+ * @return The system counter-timer frequency, in Hz.
+ */
+static inline u64 armGetSystemTickFreq(void) {
+ u64 ret;
+ __asm__ ("mrs %x[data], cntfrq_el0" : [data] "=r" (ret));
+ return ret;
+}
+
+/**
+ * @brief Converts from nanoseconds to CPU ticks unit.
+ * @param ns Time in nanoseconds.
+ * @return Time in CPU ticks.
+ */
+static inline u64 armNsToTicks(u64 ns) {
+ return (ns * 12) / 625;
+}
+
+/**
+ * @brief Converts from CPU ticks unit to nanoseconds.
+ * @param tick Time in ticks.
+ * @return Time in nanoseconds.
+ */
+static inline u64 armTicksToNs(u64 tick) {
+ return (tick * 625) / 12;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/dynamic.c b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/dynamic.c
new file mode 100644
index 00000000..f1a24f88
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/dynamic.c
@@ -0,0 +1,45 @@
+/**
+ * @file dynamic.c
+ * @copyright libnx Authors
+ */
+
+#include
+#include "../utils/types.h"
+#include
+
+void __nx_dynamic(uintptr_t base, const Elf64_Dyn* dyn)
+{
+ const Elf64_Rela* rela = NULL;
+ u64 relasz = 0;
+
+ for (; dyn->d_tag != DT_NULL; dyn++)
+ {
+ switch (dyn->d_tag)
+ {
+ case DT_RELA:
+ rela = (const Elf64_Rela*)(base + dyn->d_un.d_ptr);
+ break;
+ case DT_RELASZ:
+ relasz = dyn->d_un.d_val / sizeof(Elf64_Rela);
+ break;
+ }
+ }
+
+ if (rela == NULL) {
+ while(true)
+ ;
+ }
+
+ for (; relasz--; rela++)
+ {
+ switch (ELF64_R_TYPE(rela->r_info))
+ {
+ case R_AARCH64_RELATIVE:
+ {
+ u64* ptr = (u64*)(base + rela->r_offset);
+ *ptr = base + rela->r_addend;
+ break;
+ }
+ }
+ }
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/smc.c b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/smc.c
new file mode 100644
index 00000000..b07c6e72
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/smc.c
@@ -0,0 +1,217 @@
+/**
+ * @file smc.c
+ * @copyright libnx Authors
+ */
+
+#include
+#include
+#include "smc.h"
+#include "../utils/fatal.h"
+
+void smcRebootToRcm(void)
+{
+ SecmonArgs args;
+ args.X[0] = 0xC3000401; /* smcSetConfig */
+ args.X[1] = SplConfigItem_NeedsReboot; /* Exosphere reboot */
+ args.X[3] = 1; /* Perform reboot to RCM. */
+ svcCallSecureMonitor(&args);
+}
+
+void smcRebootToIramPayload(void)
+{
+ SecmonArgs args;
+ args.X[0] = 0xC3000401; /* smcSetConfig */
+ args.X[1] = SplConfigItem_NeedsReboot; /* Exosphere reboot */
+ args.X[3] = 2; /* Perform reboot to payload at 0x40010000 in IRAM. */
+ svcCallSecureMonitor(&args);
+}
+
+void smcPerformShutdown(void)
+{
+ SecmonArgs args;
+ args.X[0] = 0xC3000401; /* smcSetConfig */
+ args.X[1] = SplConfigItem_NeedsShutdown; /* Exosphere shutdown */
+ args.X[3] = 1; /* Perform shutdown. */
+ svcCallSecureMonitor(&args);
+}
+
+Result smcGetConfig(SplConfigItem config_item, u64 *out_config)
+{
+ SecmonArgs args;
+ args.X[0] = 0xC3000002; /* smcGetConfig */
+ args.X[1] = (u64)config_item; /* config item */
+ Result rc = svcCallSecureMonitor(&args);
+ if (rc == 0)
+ {
+ if (args.X[0] == 0)
+ {
+ if (out_config)
+ {
+ *out_config = args.X[1];
+ }
+ }
+ else
+ {
+ /* SPL result n = SMC result n */
+ rc = (26u | ((u32)args.X[0] << 9u));
+ }
+ }
+ return rc;
+}
+
+SplHardwareType splGetHardwareType(void)
+{
+ u64 value;
+ Result rc = smcGetConfig(SplConfigItem_HardwareType, &value);
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+ return (SplHardwareType)value;
+}
+
+SplSocType splGetSocType(void)
+{
+ static SplSocType soc_type;
+ static bool soc_type_set = false;
+
+ if (soc_type_set)
+ return soc_type;
+
+ switch (splGetHardwareType())
+ {
+ case SplHardwareType_Icosa:
+ case SplHardwareType_Copper:
+ soc_type = SplSocType_Erista;
+ break;
+ case SplHardwareType_Hoag:
+ case SplHardwareType_Iowa:
+ case SplHardwareType_Calcio:
+ case SplHardwareType_Five:
+ soc_type = SplSocType_Mariko;
+ break;
+ default:
+ fatal_abort(Fatal_InvalidEnum);
+ }
+
+ soc_type_set = true;
+
+ return soc_type;
+}
+
+
+Result smcCopyToIram(uintptr_t iram_addr, const void *src_addr, u32 size)
+{
+ SecmonArgs args;
+ args.X[0] = 0xF0000201; /* smcAmsIramCopy */
+ args.X[1] = (u64)src_addr; /* DRAM address */
+ args.X[2] = (u64)iram_addr; /* IRAM address */
+ args.X[3] = size; /* Amount to copy */
+ args.X[4] = 1; /* 1 = Write */
+ Result rc = svcCallSecureMonitor(&args);
+ if (rc == 0)
+ {
+ if (args.X[0] != 0)
+ {
+ /* SPL result n = SMC result n */
+ rc = (26u | ((u32)args.X[0] << 9u));
+ }
+ }
+ return rc;
+}
+
+Result smcCopyFromIram(void *dst_addr, uintptr_t iram_addr, u32 size)
+{
+ SecmonArgs args;
+ args.X[0] = 0xF0000201; /* smcAmsIramCopy */
+ args.X[1] = (u64)dst_addr; /* DRAM address */
+ args.X[2] = (u64)iram_addr; /* IRAM address */
+ args.X[3] = size; /* Amount to copy */
+ args.X[4] = 0; /* 0 = Read */
+ Result rc = svcCallSecureMonitor(&args);
+ if (rc == 0)
+ {
+ if (args.X[0] != 0)
+ {
+ /* SPL result n = SMC result n */
+ rc = (26u | ((u32)args.X[0] << 9u));
+ }
+ }
+ return rc;
+}
+
+Result smcReadWriteRegister(u32 phys_addr, u32 value, u32 mask)
+{
+ SecmonArgs args;
+ args.X[0] = 0xF0000002; /* smcAmsReadWriteRegister */
+ args.X[1] = phys_addr; /* MMIO address */
+ args.X[2] = mask; /* mask */
+ args.X[3] = value; /* value */
+ Result rc = svcCallSecureMonitor(&args);
+ if (rc == 0)
+ {
+ if (args.X[0] != 0)
+ {
+ /* SPL result n = SMC result n */
+ rc = (26u | ((u32)args.X[0] << 9u));
+ }
+ }
+ return rc;
+}
+
+Result smcGetEmummcConfig(exo_emummc_mmc_t mmc_id, exo_emummc_config_t *out_cfg, void *out_paths)
+{
+ SecmonArgs args;
+ args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */
+ args.X[1] = mmc_id;
+ args.X[2] = (u64)out_paths; /* out path */
+ Result rc = svcCallSecureMonitor(&args);
+ if (rc == 0)
+ {
+ if (args.X[0] != 0)
+ {
+ /* SPL result n = SMC result n */
+ rc = (26u | ((u32)args.X[0] << 9u));
+ }
+ if (rc == 0)
+ {
+ memcpy(out_cfg, &args.X[1], sizeof(*out_cfg));
+ }
+ }
+ return rc;
+
+}
+
+Result smcGenerateRandomBytes(void *dst, u32 size)
+{
+ SecmonArgs args;
+ args.X[0] = 0xC3000006; /* smcGenerateRandomBytes */
+ args.X[1] = size;
+ Result rc = svcCallSecureMonitor(&args);
+ if (rc == 0)
+ {
+ if (args.X[0] != 0)
+ {
+ /* SPL result n = SMC result n */
+ rc = (26u | ((u32)args.X[0] << 9u));
+ }
+ if (rc == 0)
+ {
+ memcpy(dst, &args.X[1], size);
+ }
+ }
+ return rc;
+}
+
+u64 smcGenerateRandomU64(void)
+{
+ u64 random;
+
+ Result rc = smcGenerateRandomBytes(&random, sizeof(random));
+ if (rc != 0)
+ {
+ fatal_abort(Fatal_BadResult);
+ }
+
+ return random;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/smc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/smc.h
new file mode 100644
index 00000000..08572002
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/smc.h
@@ -0,0 +1,105 @@
+/**
+ * @file smc.h
+ * @brief Wrappers for secure monitor calls.
+ * @copyright libnx Authors
+ */
+#pragma once
+#include "../utils/types.h"
+#include "svc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ SplConfigItem_DisableProgramVerification = 1,
+ SplConfigItem_DramId = 2,
+ SplConfigItem_SecurityEngineIrqNumber = 3,
+ SplConfigItem_Version = 4,
+ SplConfigItem_HardwareType = 5,
+ SplConfigItem_IsRetail = 6,
+ SplConfigItem_IsRecoveryBoot = 7,
+ SplConfigItem_DeviceId = 8,
+ SplConfigItem_BootReason = 9,
+ SplConfigItem_MemoryArrange = 10,
+ SplConfigItem_IsDebugMode = 11,
+ SplConfigItem_KernelMemoryConfiguration = 12,
+ SplConfigItem_IsChargerHiZModeEnabled = 13,
+ SplConfigItem_IsKiosk = 14,
+ SplConfigItem_NewHardwareType = 15,
+ SplConfigItem_NewKeyGeneration = 16,
+ SplConfigItem_Package2Hash = 17,
+
+ SplConfigItem_ExosphereVersion = 65000,
+ SplConfigItem_NeedsReboot = 65001,
+ SplConfigItem_NeedsShutdown = 65002,
+ SplConfigItem_ExosphereVerHash = 65003,
+ SplConfigItem_HasRcmBugPatch = 65004,
+} SplConfigItem;
+
+typedef enum {
+ SplSocType_Erista = 0,
+ SplSocType_Mariko = 1,
+} SplSocType;
+
+typedef enum {
+ SplHardwareType_Icosa = 0,
+ SplHardwareType_Copper = 1,
+ SplHardwareType_Hoag = 2,
+ SplHardwareType_Iowa = 3,
+ SplHardwareType_Calcio = 4,
+ SplHardwareType_Five = 5,
+} SplHardwareType;
+
+typedef enum {
+ EXO_EMUMMC_TYPE_NONE = 0,
+ EXO_EMUMMC_TYPE_PARTITION = 1,
+ EXO_EMUMMC_TYPE_FILES = 2,
+} exo_emummc_type_t;
+
+typedef enum {
+ EXO_EMUMMC_MMC_NAND = 0,
+ EXO_EMUMMC_MMC_SD = 1,
+ EXO_EMUMMC_MMC_GC = 2,
+} exo_emummc_mmc_t;
+
+typedef struct {
+ uint32_t magic;
+ uint32_t type;
+ uint32_t id;
+ uint32_t fs_version;
+} exo_emummc_base_config_t;
+
+typedef struct {
+ uint64_t start_sector;
+} exo_emummc_partition_config_t;
+
+typedef struct {
+ exo_emummc_base_config_t base_cfg;
+ union {
+ exo_emummc_partition_config_t partition_cfg;
+ };
+} exo_emummc_config_t;
+
+Result smcGetConfig(SplConfigItem config_item, u64 *out_config);
+
+SplHardwareType splGetHardwareType(void);
+SplSocType splGetSocType(void);
+
+void smcRebootToRcm(void);
+void smcRebootToIramPayload(void);
+void smcPerformShutdown(void);
+
+Result smcCopyToIram(uintptr_t iram_addr, const void *src_addr, u32 size);
+Result smcCopyFromIram(void *dst_addr, uintptr_t iram_addr, u32 size);
+
+Result smcReadWriteRegister(u32 phys_addr, u32 value, u32 mask);
+
+Result smcGetEmummcConfig(exo_emummc_mmc_t mmc_id, exo_emummc_config_t *out_cfg, void *out_paths);
+
+Result smcGenerateRandomBytes(void *dst, u32 size);
+u64 smcGenerateRandomU64(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/start.s b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/start.s
new file mode 100644
index 00000000..4a20ed0e
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/start.s
@@ -0,0 +1,131 @@
+/**
+ * @file start.s
+ * @copyright libnx Authors
+ */
+
+.macro push_all
+ SUB SP, SP, #0x100
+ STP X29, X30, [SP, #0x0]
+ STP X27, X28, [SP, #0x10]
+ STP X25, X26, [SP, #0x20]
+ STP X23, X24, [SP, #0x30]
+ STP X21, X22, [SP, #0x40]
+ STP X19, X20, [SP, #0x50]
+ STP X17, X18, [SP, #0x60]
+ STP X15, X16, [SP, #0x70]
+ STP X13, X14, [SP, #0x80]
+ STP X11, X12, [SP, #0x90]
+ STP X9, X10, [SP, #0xA0]
+ STP X7, X8, [SP, #0xB0]
+ STP X5, X6, [SP, #0xC0]
+ STP X3, X4, [SP, #0xD0]
+ STP X1, X2, [SP, #0xE0]
+ STR X0, [SP, #0xF0]
+.endm
+
+.macro pop_all
+ LDR X0, [SP, #0xF0]
+ LDP X1, X2, [SP, #0xE0]
+ LDP X3, X4, [SP, #0xD0]
+ LDP X5, X6, [SP, #0xC0]
+ LDP X7, X8, [SP, #0xB0]
+ LDP X9, X10, [SP, #0xA0]
+ LDP X11, X12, [SP, #0x90]
+ LDP X13, X14, [SP, #0x80]
+ LDP X15, X16, [SP, #0x70]
+ LDP X17, X18, [SP, #0x60]
+ LDP X19, X20, [SP, #0x50]
+ LDP X21, X22, [SP, #0x40]
+ LDP X23, X24, [SP, #0x30]
+ LDP X25, X26, [SP, #0x20]
+ LDP X27, X28, [SP, #0x10]
+ LDP X29, X30, [SP, #0x0]
+ ADD SP, SP, #0x100
+.endm
+
+.section ".crt0","ax"
+.global _start
+_start:
+ B startup
+.org _start+0xc
+ B sdmmc_wrapper_read
+.org _start+0x18
+ B sdmmc_wrapper_write
+.org _start+0x80
+
+.section ".crt0","ax"
+startup:
+ # Save LR
+ MOV X7, X30
+
+ # Retrieve ASLR Base
+ BL +4
+ SUB X6, X30, #0x88
+
+ # Context Ptr and MainThread Handle
+ MOV X5, X0
+ MOV X4, X1
+
+ # Inject start
+ push_all
+
+ MOV W0, #0xFFFF8001
+ ADR X1, __rodata_start
+ ADR X2, __data_start
+ SUB X2, X2, X1
+ MOV X3, #1
+ SVC 0x73
+
+ MOV W0, #0xFFFF8001
+ ADR X1, __data_start
+ ADR X2, __argdata__
+ SUB X2, X2, X1
+ MOV X3, #3
+ SVC 0x73
+
+ pop_all
+
+ MOV X27, X7
+ MOV X25, X5
+ MOV X26, X4
+
+ # Clear .bss
+ ADRP X0, __bss_start__
+ ADRP X1, __bss_end__
+ ADD X0, X0, #:lo12:__bss_start__
+ ADD X1, X1, #:lo12:__bss_end__
+ SUB X1, X1, X0
+ ADD X1, X1, #7
+ BIC X1, X1, #7
+
+bss_loop:
+ STR XZR, [X0], #8
+ SUBS X1, X1, #8
+ BNE bss_loop
+
+ # Store SP
+ MOV X1, SP
+ ADRP X0, __stack_top
+ STR X1, [X0, #:lo12:__stack_top]
+
+ # Process _DYNAMIC Section
+ MOV X0, X6
+ ADRP X1, _DYNAMIC
+ ADD X1, X1, #:lo12:_DYNAMIC
+ BL __nx_dynamic
+
+ # TODO: handle in code
+ MOV X0, X25
+ MOV X1, X26
+ MOV X2, X27
+
+ BL __initheap
+ BL __init
+
+ MOV X0, X25
+ MOV X1, X26
+ MOV X30, X27
+
+ # FS main
+ ADRP X16, __argdata__
+ BR X16
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/svc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/svc.h
new file mode 100644
index 00000000..32066639
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/svc.h
@@ -0,0 +1,220 @@
+/**
+ * @file svc.h
+ * @brief Wrappers for kernel syscalls.
+ * @copyright libnx Authors
+ */
+#pragma once
+#include "../utils/types.h"
+
+/// Memory information structure.
+typedef struct {
+ u64 addr; ///< Base address.
+ u64 size; ///< Size.
+ u32 type; ///< Memory type (see lower 8 bits of \ref MemoryState).
+ u32 attr; ///< Memory attributes (see \ref MemoryAttribute).
+ u32 perm; ///< Memory permissions (see \ref Permission).
+ u32 device_refcount; ///< Device reference count.
+ u32 ipc_refcount; ///< IPC reference count.
+ u32 padding; ///< Padding.
+} MemoryInfo;
+
+/// Memory permission bitmasks.
+typedef enum {
+ Perm_None = 0, ///< No permissions.
+ Perm_R = BIT(0), ///< Read permission.
+ Perm_W = BIT(1), ///< Write permission.
+ Perm_X = BIT(2), ///< Execute permission.
+ Perm_Rw = Perm_R | Perm_W, ///< Read/write permissions.
+ Perm_Rx = Perm_R | Perm_X, ///< Read/execute permissions.
+ Perm_DontCare = BIT(28), ///< Don't care
+} Permission;
+
+/// Secure monitor arguments.
+typedef struct {
+ u64 X[8]; ///< Values of X0 through X7.
+} SecmonArgs;
+
+_Static_assert(sizeof(SecmonArgs) == 0x40, "SecmonArgs definition");
+
+#define DeviceName_SDMMC1A 19
+#define DeviceName_SDMMC2A 20
+#define DeviceName_SDMMC3A 21
+#define DeviceName_SDMMC4A 22
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Returns a virtual address mapped to a given IO range.
+ * @return Result code.
+ * @note Syscall number 0x55.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ * @warning Only exists on [10.0.0+]. For older versions use \ref svcLegacyQueryIoMapping.
+ */
+Result svcQueryIoMapping(u64* virtaddr, u64* out_size, u64 physaddr, u64 size);
+
+/**
+ * @brief Returns a virtual address mapped to a given IO range.
+ * @return Result code.
+ * @note Syscall number 0x55.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ * @warning Only exists on [1.0.0-9.2.0]. For newer versions use \ref svcQueryIoMapping.
+ */
+Result svcLegacyQueryIoMapping(u64* virtaddr, u64 physaddr, u64 size);
+
+/**
+ * @brief Attaches a device address space to a device.
+ * @return Result code.
+ * @note Syscall number 0x57.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+Result svcAttachDeviceAddressSpace(u64 device, Handle handle);
+
+/**
+ * @brief Query information about an address. Will always fetch the lowest page-aligned mapping that contains the provided address.
+ * @param[out] meminfo_ptr \ref MemoryInfo structure which will be filled in.
+ * @param[out] pageinfo Page information which will be filled in.
+ * @param[in] addr Address to query.
+ * @return Result code.
+ * @note Syscall number 0x06.
+ */
+Result svcQueryMemory(MemoryInfo* meminfo_ptr, u32 *pageinfo, u64 addr);
+
+/**
+ * @brief Sets the memory permissions for the specified memory with the supplied process handle.
+ * @param[in] proc Process handle.
+ * @param[in] addr Address of the memory.
+ * @param[in] size Size of the memory.
+ * @param[in] perm Permissions (see \ref Permission).
+ * @return Result code.
+ * @remark This returns an error (0xD801) when \p perm is >0x5, hence -WX and RWX are not allowed.
+ * @note Syscall number 0x73.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+Result svcSetProcessMemoryPermission(Handle proc, u64 addr, u64 size, u32 perm);
+
+/**
+ * @brief Set the memory permissions of a (page-aligned) range of memory.
+ * @param[in] addr Start address of the range.
+ * @param[in] size Size of the range, in bytes.
+ * @param[in] perm Permissions (see \ref Permission).
+ * @return Result code.
+ * @remark Perm_X is not allowed. Setting write-only is not allowed either (Perm_W).
+ * This can be used to move back and forth between Perm_None, Perm_R and Perm_Rw.
+ * @note Syscall number 0x01.
+ */
+Result svcSetMemoryPermission(void* addr, u64 size, u32 perm);
+
+/**
+ * @brief Creates a thread.
+ * @return Result code.
+ * @note Syscall number 0x08.
+ */
+Result svcCreateThread(Handle* out, void* entry, void* arg, void* stack_top, int prio, int cpuid);
+
+/**
+ * @brief Starts a freshly created thread.
+ * @return Result code.
+ * @note Syscall number 0x09.
+ */
+Result svcStartThread(Handle handle);
+
+/**
+ * @brief Exits the current thread.
+ * @note Syscall number 0x0A.
+ */
+void __attribute__((noreturn)) svcExitThread(void);
+
+/**
+ * @brief Closes a handle, decrementing the reference count of the corresponding kernel object.
+ * This might result in the kernel freeing the object.
+ * @param handle Handle to close.
+ * @return Result code.
+ * @note Syscall number 0x16.
+ */
+Result svcCloseHandle(Handle handle);
+
+/**
+ * @brief Waits on one or more synchronization objects, optionally with a timeout.
+ * @return Result code.
+ * @note Syscall number 0x18.
+ * @note \p handleCount must not be greater than \ref MAX_WAIT_OBJECTS. This is a Horizon kernel limitation.
+ * @note This is the raw syscall, which can be cancelled by \ref svcCancelSynchronization or other means. \ref waitHandles or \ref waitMultiHandle should normally be used instead.
+ */
+Result svcWaitSynchronization(s32* index, const Handle* handles, s32 handleCount, u64 timeout);
+
+/**
+ * @brief Waits on a single synchronization object, optionally with a timeout.
+ * @return Result code.
+ * @note Wrapper for \ref svcWaitSynchronization.
+ * @note This is the raw syscall, which can be cancelled by \ref svcCancelSynchronization or other means. \ref waitSingleHandle should normally be used instead.
+ */
+static inline Result svcWaitSynchronizationSingle(Handle handle, u64 timeout) {
+ s32 tmp;
+ return svcWaitSynchronization(&tmp, &handle, 1, timeout);
+}
+
+/**
+ * @brief Creates an IPC session.
+ * @return Result code.
+ * @note Syscall number 0x40.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+Result svcCreateSession(Handle *server_handle, Handle *client_handle, u32 unk0, u64 unk1);//unk* are normally 0?
+
+/**
+ * @brief Sends an IPC synchronization request to a session.
+ * @return Result code.
+ * @note Syscall number 0x21.
+ */
+Result svcSendSyncRequest(Handle session);
+
+/**
+ * @brief Performs IPC input/output.
+ * @return Result code.
+ * @note Syscall number 0x43.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+Result svcReplyAndReceive(s32* index, const Handle* handles, s32 handleCount, Handle replyTarget, u64 timeout);
+
+/**
+ * @brief Maps the src address from the supplied process handle into the current process.
+ * @param[in] dst Address to which map the memory in the current process.
+ * @param[in] proc Process handle.
+ * @param[in] src Source mapping address.
+ * @param[in] size Size of the memory.
+ * @return Result code.
+ * @remark This allows mapping code and rodata with RW- permission.
+ * @note Syscall number 0x74.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+Result svcMapProcessMemory(void* dst, Handle proc, u64 src, u64 size);
+
+/**
+ * @brief Undoes the effects of \ref svcMapProcessMemory.
+ * @param[in] dst Destination mapping address
+ * @param[in] proc Process handle.
+ * @param[in] src Address of the memory in the process.
+ * @param[in] size Size of the memory.
+ * @return Result code.
+ * @remark This allows mapping code and rodata with RW- permission.
+ * @note Syscall number 0x75.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+Result svcUnmapProcessMemory(void* dst, Handle proc, u64 src, u64 size);
+
+/**
+ * @brief Calls a secure monitor function (TrustZone, EL3).
+ * @param regs Arguments to pass to the secure monitor.
+ * @return Return value from the secure monitor.
+ * @note Syscall number 0x7F.
+ * @warning This is a privileged syscall. Use \ref envIsSyscallHinted to check if it is available.
+ */
+u64 svcCallSecureMonitor(SecmonArgs* regs);
+
+#ifdef __cplusplus
+}
+#endif
+
+///@}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/nx/svc.s b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/svc.s
new file mode 100644
index 00000000..fc03f96c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/nx/svc.s
@@ -0,0 +1,136 @@
+/**
+ * @file svc.s
+ * @copyright libnx Authors
+ */
+
+.macro SVC_BEGIN name
+ .section .text.\name, "ax", %progbits
+ .global \name
+ .type \name, %function
+ .align 2
+ .cfi_startproc
+\name:
+.endm
+
+.macro SVC_END
+ .cfi_endproc
+.endm
+
+SVC_BEGIN svcQueryIoMapping
+ STP X0, X1, [SP, #-16]!
+ SVC 0x55
+ LDP X3, X4, [SP], #16
+ STR X1, [X3]
+ STR X2, [X4]
+ RET
+SVC_END
+
+SVC_BEGIN svcLegacyQueryIoMapping
+ STR X0, [SP, #-16]!
+ SVC 0x55
+ LDR X2, [SP], #16
+ STR X1, [X2]
+ RET
+SVC_END
+
+SVC_BEGIN svcAttachDeviceAddressSpace
+ SVC 0x57
+ RET
+SVC_END
+
+SVC_BEGIN svcQueryMemory
+ STR X1, [SP, #-16]!
+ SVC 0x6
+ LDR X2, [SP], #16
+ STR W1, [X2]
+ RET
+SVC_END
+
+SVC_BEGIN svcSetMemoryPermission
+ SVC 0x2
+ RET
+SVC_END
+
+SVC_BEGIN svcSetProcessMemoryPermission
+ SVC 0x73
+ RET
+SVC_END
+
+SVC_BEGIN svcCreateThread
+ STR X0, [SP, #-16]!
+ SVC 0x8
+ LDR X2, [SP], #16
+ STR W1, [X2]
+ RET
+SVC_END
+
+SVC_BEGIN svcStartThread
+ SVC 0x9
+ RET
+SVC_END
+
+SVC_BEGIN svcExitThread
+ SVC 0xA
+ RET
+SVC_END
+
+SVC_BEGIN svcCloseHandle
+ SVC 0x16
+ RET
+SVC_END
+
+SVC_BEGIN svcWaitSynchronization
+ STR X0, [SP, #-16]!
+ SVC 0x18
+ LDR X2, [SP], #16
+ STR W1, [X2]
+ RET
+SVC_END
+
+SVC_BEGIN svcCreateSession
+ STP X0, X1, [SP, #-16]!
+ SVC 0x40
+ LDP X3, X4, [SP], #16
+ STR W1, [X3]
+ STR W2, [X4]
+ RET
+SVC_END
+
+SVC_BEGIN svcSendSyncRequest
+ SVC 0x21
+ RET
+SVC_END
+
+SVC_BEGIN svcReplyAndReceive
+ STR X0, [SP, #-16]!
+ SVC 0x43
+ LDR X2, [SP], #16
+ STR W1, [X2]
+ RET
+SVC_END
+
+SVC_BEGIN svcMapProcessMemory
+ SVC 0x74
+ RET
+SVC_END
+
+SVC_BEGIN svcUnmapProcessMemory
+ SVC 0x75
+ RET
+SVC_END
+
+SVC_BEGIN svcCallSecureMonitor
+ STR X0, [SP, #-16]!
+ MOV X8, X0
+ LDP X0, X1, [X8]
+ LDP X2, X3, [X8, #0x10]
+ LDP X4, X5, [X8, #0x20]
+ LDP X6, X7, [X8, #0x30]
+ SVC 0x7F
+ LDR X8, [SP], #16
+ STP X0, X1, [X8]
+ STP X2, X3, [X8, #0x10]
+ STP X4, X5, [X8, #0x20]
+ STP X6, X7, [X8, #0x30]
+ RET
+SVC_END
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/power/max77620.h b/Source/Atmosphere-MTC-Unlock/emummc/source/power/max77620.h
new file mode 100644
index 00000000..26ea8559
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/power/max77620.h
@@ -0,0 +1,340 @@
+/*
+ * Defining registers address and its bit definitions of MAX77620 and MAX20024
+ *
+ * Copyright (c) 2016 NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#ifndef _MFD_MAX77620_H_
+#define _MFD_MAX77620_H_
+
+#define MAX77620_I2C_ADDR 0x3C
+
+/* GLOBAL, PMIC, GPIO, FPS, ONOFFC, CID Registers */
+#define MAX77620_REG_CNFGGLBL1 0x00
+#define MAX77620_CNFGGLBL1_LBDAC_EN (1 << 7)
+#define MAX77620_CNFGGLBL1_MPPLD (1 << 6)
+#define MAX77620_CNFGGLBL1_LBHYST ((1 << 5) | (1 << 4))
+#define MAX77620_CNFGGLBL1_LBHYST_100 (0 << 4)
+#define MAX77620_CNFGGLBL1_LBHYST_200 (1 << 4)
+#define MAX77620_CNFGGLBL1_LBHYST_300 (2 << 4)
+#define MAX77620_CNFGGLBL1_LBHYST_400 (3 << 4)
+#define MAX77620_CNFGGLBL1_LBDAC_MASK 0x0E
+#define MAX77620_CNFGGLBL1_LBDAC_2700 (0 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_2800 (1 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_2900 (2 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_3000 (3 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_3100 (4 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_3200 (5 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_3300 (6 << 1)
+#define MAX77620_CNFGGLBL1_LBDAC_3400 (7 << 1)
+#define MAX77620_CNFGGLBL1_LBRSTEN (1 << 0)
+
+#define MAX77620_REG_CNFGGLBL2 0x01
+#define MAX77620_REG_CNFGGLBL3 0x02
+#define MAX77620_WDTC_MASK 0x3
+#define MAX77620_WDTOFFC (1 << 4)
+#define MAX77620_WDTSLPC (1 << 3)
+#define MAX77620_WDTEN (1 << 2)
+#define MAX77620_TWD_MASK 0x3
+#define MAX77620_TWD_2s 0x0
+#define MAX77620_TWD_16s 0x1
+#define MAX77620_TWD_64s 0x2
+#define MAX77620_TWD_128s 0x3
+
+#define MAX77620_REG_CNFG1_32K 0x03
+#define MAX77620_CNFG1_32K_OUT0_EN (1 << 2)
+
+#define MAX77620_REG_CNFGBBC 0x04
+#define MAX77620_CNFGBBC_ENABLE (1 << 0)
+#define MAX77620_CNFGBBC_CURRENT_MASK 0x06
+#define MAX77620_CNFGBBC_CURRENT_SHIFT 1
+#define MAX77620_CNFGBBC_VOLTAGE_MASK 0x18
+#define MAX77620_CNFGBBC_VOLTAGE_SHIFT 3
+#define MAX77620_CNFGBBC_LOW_CURRENT_DISABLE (1 << 5)
+#define MAX77620_CNFGBBC_RESISTOR_MASK 0xC0
+#define MAX77620_CNFGBBC_RESISTOR_SHIFT 6
+#define MAX77620_CNFGBBC_RESISTOR_100 (0 << MAX77620_CNFGBBC_RESISTOR_SHIFT)
+#define MAX77620_CNFGBBC_RESISTOR_1K (1 << MAX77620_CNFGBBC_RESISTOR_SHIFT)
+#define MAX77620_CNFGBBC_RESISTOR_3K (2 << MAX77620_CNFGBBC_RESISTOR_SHIFT)
+#define MAX77620_CNFGBBC_RESISTOR_6K (3 << MAX77620_CNFGBBC_RESISTOR_SHIFT)
+
+#define MAX77620_REG_IRQTOP 0x05
+#define MAX77620_IRQ_TOP_GLBL_MASK (1 << 7)
+#define MAX77620_IRQ_TOP_SD_MASK (1 << 6)
+#define MAX77620_IRQ_TOP_LDO_MASK (1 << 5)
+#define MAX77620_IRQ_TOP_GPIO_MASK (1 << 4)
+#define MAX77620_IRQ_TOP_RTC_MASK (1 << 3)
+#define MAX77620_IRQ_TOP_32K_MASK (1 << 2)
+#define MAX77620_IRQ_TOP_ONOFF_MASK (1 << 1)
+
+#define MAX77620_REG_INTLBT 0x06
+#define MAX77620_REG_IRQTOPM 0x0D
+#define MAX77620_IRQ_LBM_MASK (1 << 3)
+#define MAX77620_IRQ_TJALRM1_MASK (1 << 2)
+#define MAX77620_IRQ_TJALRM2_MASK (1 << 1)
+
+#define MAX77620_REG_IRQSD 0x07
+#define MAX77620_REG_IRQ_LVL2_L0_7 0x08
+#define MAX77620_REG_IRQ_LVL2_L8 0x09
+#define MAX77620_REG_IRQ_LVL2_GPIO 0x0A
+#define MAX77620_REG_ONOFFIRQ 0x0B
+#define MAX77620_REG_NVERC 0x0C
+
+#define MAX77620_REG_INTENLBT 0x0E
+#define MAX77620_GLBLM_MASK (1 << 0)
+
+#define MAX77620_REG_IRQMASKSD 0x0F
+#define MAX77620_REG_IRQ_MSK_L0_7 0x10
+#define MAX77620_REG_IRQ_MSK_L8 0x11
+#define MAX77620_REG_ONOFFIRQM 0x12
+#define MAX77620_REG_STATLBT 0x13
+#define MAX77620_REG_STATSD 0x14
+#define MAX77620_REG_ONOFFSTAT 0x15
+
+/* SD and LDO Registers */
+#define MAX77620_REG_SD0 0x16
+#define MAX77620_REG_SD1 0x17
+#define MAX77620_REG_SD2 0x18
+#define MAX77620_REG_SD3 0x19
+#define MAX77620_REG_SD4 0x1A
+#define MAX77620_SDX_VOLT_MASK 0xFF
+#define MAX77620_SD0_VOLT_MASK 0x3F
+#define MAX77620_SD1_VOLT_MASK 0x7F
+#define MAX77620_LDO_VOLT_MASK 0x3F
+#define MAX77620_REG_DVSSD0 0x1B
+#define MAX77620_REG_DVSSD1 0x1C
+#define MAX77620_REG_SD0_CFG 0x1D
+#define MAX77620_REG_SD1_CFG 0x1E
+#define MAX77620_REG_SD2_CFG 0x1F
+#define MAX77620_REG_SD3_CFG 0x20
+#define MAX77620_REG_SD4_CFG 0x21
+#define MAX77620_REG_SD_CFG2 0x22
+#define MAX77620_REG_LDO0_CFG 0x23
+#define MAX77620_REG_LDO0_CFG2 0x24
+#define MAX77620_REG_LDO1_CFG 0x25
+#define MAX77620_REG_LDO1_CFG2 0x26
+#define MAX77620_REG_LDO2_CFG 0x27
+#define MAX77620_REG_LDO2_CFG2 0x28
+#define MAX77620_REG_LDO3_CFG 0x29
+#define MAX77620_REG_LDO3_CFG2 0x2A
+#define MAX77620_REG_LDO4_CFG 0x2B
+#define MAX77620_REG_LDO4_CFG2 0x2C
+#define MAX77620_REG_LDO5_CFG 0x2D
+#define MAX77620_REG_LDO5_CFG2 0x2E
+#define MAX77620_REG_LDO6_CFG 0x2F
+#define MAX77620_REG_LDO6_CFG2 0x30
+#define MAX77620_REG_LDO7_CFG 0x31
+#define MAX77620_REG_LDO7_CFG2 0x32
+#define MAX77620_REG_LDO8_CFG 0x33
+#define MAX77620_REG_LDO8_CFG2 0x34
+#define MAX77620_LDO_POWER_MODE_MASK 0xC0
+#define MAX77620_LDO_POWER_MODE_SHIFT 6
+#define MAX77620_POWER_MODE_NORMAL 3
+#define MAX77620_POWER_MODE_LPM 2
+#define MAX77620_POWER_MODE_GLPM 1
+#define MAX77620_POWER_MODE_DISABLE 0
+#define MAX20024_LDO_CFG2_MPOK_MASK (1 << 2)
+#define MAX77620_LDO_CFG2_ADE_MASK (1 << 1)
+#define MAX77620_LDO_CFG2_ADE_DISABLE (0 << 1)
+#define MAX77620_LDO_CFG2_ADE_ENABLE (1 << 1)
+#define MAX77620_LDO_CFG2_SS_MASK (1 << 0)
+#define MAX77620_LDO_CFG2_SS_FAST (1 << 0)
+#define MAX77620_LDO_CFG2_SS_SLOW 0
+
+#define MAX77620_REG_LDO_CFG3 0x35
+#define MAX77620_TRACK4_MASK (1 << 5)
+#define MAX77620_TRACK4_SHIFT 5
+
+#define MAX77620_LDO_SLEW_RATE_MASK 0x1
+
+#define MAX77620_REG_GPIO0 0x36
+#define MAX77620_REG_GPIO1 0x37
+#define MAX77620_REG_GPIO2 0x38
+#define MAX77620_REG_GPIO3 0x39
+#define MAX77620_REG_GPIO4 0x3A
+#define MAX77620_REG_GPIO5 0x3B
+#define MAX77620_REG_GPIO6 0x3C
+#define MAX77620_REG_GPIO7 0x3D
+#define MAX77620_REG_PUE_GPIO 0x3E
+#define MAX77620_REG_PDE_GPIO 0x3F
+#define MAX77620_REG_AME_GPIO 0x40
+#define MAX77620_CNFG_GPIO_DRV_MASK (1 << 0)
+#define MAX77620_CNFG_GPIO_DRV_PUSHPULL (1 << 0)
+#define MAX77620_CNFG_GPIO_DRV_OPENDRAIN (0 << 0)
+#define MAX77620_CNFG_GPIO_DIR_MASK (1 << 1)
+#define MAX77620_CNFG_GPIO_DIR_INPUT (1 << 1)
+#define MAX77620_CNFG_GPIO_DIR_OUTPUT (0 << 1)
+#define MAX77620_CNFG_GPIO_INPUT_VAL_MASK (1 << 2)
+#define MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK (1 << 3)
+#define MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH (1 << 3)
+#define MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW (0 << 3)
+#define MAX77620_CNFG_GPIO_INT_MASK (0x3 << 4)
+#define MAX77620_CNFG_GPIO_INT_FALLING (1 << 4)
+#define MAX77620_CNFG_GPIO_INT_RISING (1 << 5)
+#define MAX77620_CNFG_GPIO_DBNC_MASK (0x3 << 6)
+#define MAX77620_CNFG_GPIO_DBNC_None (0x0 << 6)
+#define MAX77620_CNFG_GPIO_DBNC_8ms (0x1 << 6)
+#define MAX77620_CNFG_GPIO_DBNC_16ms (0x2 << 6)
+#define MAX77620_CNFG_GPIO_DBNC_32ms (0x3 << 6)
+
+#define MAX77620_REG_ONOFFCNFG1 0x41
+#define MAX77620_ONOFFCNFG1_SFT_RST (1 << 7)
+#define MAX77620_ONOFFCNFG1_MRT_MASK 0x38
+#define MAX77620_ONOFFCNFG1_MRT_SHIFT 0x3
+#define MAX77620_ONOFFCNFG1_SLPEN (1 << 2)
+#define MAX77620_ONOFFCNFG1_PWR_OFF (1 << 1)
+#define MAX20024_ONOFFCNFG1_CLRSE 0x18
+
+#define MAX77620_REG_ONOFFCNFG2 0x42
+#define MAX77620_ONOFFCNFG2_SFT_RST_WK (1 << 7)
+#define MAX77620_ONOFFCNFG2_WD_RST_WK (1 << 6)
+#define MAX77620_ONOFFCNFG2_SLP_LPM_MSK (1 << 5)
+#define MAX77620_ONOFFCNFG2_WK_ALARM1 (1 << 2)
+#define MAX77620_ONOFFCNFG2_WK_EN0 (1 << 0)
+
+/* FPS Registers */
+#define MAX77620_REG_FPS_CFG0 0x43
+#define MAX77620_REG_FPS_CFG1 0x44
+#define MAX77620_REG_FPS_CFG2 0x45
+#define MAX77620_REG_FPS_LDO0 0x46
+#define MAX77620_REG_FPS_LDO1 0x47
+#define MAX77620_REG_FPS_LDO2 0x48
+#define MAX77620_REG_FPS_LDO3 0x49
+#define MAX77620_REG_FPS_LDO4 0x4A
+#define MAX77620_REG_FPS_LDO5 0x4B
+#define MAX77620_REG_FPS_LDO6 0x4C
+#define MAX77620_REG_FPS_LDO7 0x4D
+#define MAX77620_REG_FPS_LDO8 0x4E
+#define MAX77620_REG_FPS_SD0 0x4F
+#define MAX77620_REG_FPS_SD1 0x50
+#define MAX77620_REG_FPS_SD2 0x51
+#define MAX77620_REG_FPS_SD3 0x52
+#define MAX77620_REG_FPS_SD4 0x53
+#define MAX77620_REG_FPS_NONE 0
+#define MAX77620_FPS_SRC_MASK 0xC0
+#define MAX77620_FPS_SRC_SHIFT 6
+#define MAX77620_FPS_PU_PERIOD_MASK 0x38
+#define MAX77620_FPS_PU_PERIOD_SHIFT 3
+#define MAX77620_FPS_PD_PERIOD_MASK 0x07
+#define MAX77620_FPS_PD_PERIOD_SHIFT 0
+
+/* Minimum and maximum FPS period time (in microseconds) are
+ * different for MAX77620 and Max20024.
+ */
+#define MAX77620_FPS_COUNT 3
+
+#define MAX77620_FPS_PERIOD_MIN_US 40
+#define MAX20024_FPS_PERIOD_MIN_US 20
+
+#define MAX77620_FPS_PERIOD_MAX_US 2560
+#define MAX20024_FPS_PERIOD_MAX_US 5120
+
+#define MAX77620_REG_FPS_GPIO1 0x54
+#define MAX77620_REG_FPS_GPIO2 0x55
+#define MAX77620_REG_FPS_GPIO3 0x56
+#define MAX77620_FPS_TIME_PERIOD_MASK 0x38
+#define MAX77620_FPS_TIME_PERIOD_SHIFT 3
+#define MAX77620_FPS_EN_SRC_MASK 0x06
+#define MAX77620_FPS_EN_SRC_SHIFT 1
+#define MAX77620_FPS_ENFPS_SW_MASK 0x01
+#define MAX77620_FPS_ENFPS_SW 0x01
+
+#define MAX77620_REG_FPS_RSO 0x57
+#define MAX77620_REG_CID0 0x58
+#define MAX77620_REG_CID1 0x59
+#define MAX77620_REG_CID2 0x5A
+#define MAX77620_REG_CID3 0x5B
+#define MAX77620_REG_CID4 0x5C
+#define MAX77620_REG_CID5 0x5D
+
+#define MAX77620_REG_DVSSD4 0x5E
+#define MAX20024_REG_MAX_ADD 0x70
+
+#define MAX77620_CID_DIDM_MASK 0xF0
+#define MAX77620_CID_DIDM_SHIFT 4
+
+/* CNCG2SD */
+#define MAX77620_SD_CNF2_ROVS_EN_SD1 (1 << 1)
+#define MAX77620_SD_CNF2_ROVS_EN_SD0 (1 << 2)
+
+/* Device Identification Metal */
+#define MAX77620_CID5_DIDM(n) (((n) >> 4) & 0xF)
+/* Device Indentification OTP */
+#define MAX77620_CID5_DIDO(n) ((n) & 0xF)
+
+/* SD CNFG1 */
+#define MAX77620_SD_SR_MASK 0xC0
+#define MAX77620_SD_SR_SHIFT 6
+#define MAX77620_SD_POWER_MODE_MASK 0x30
+#define MAX77620_SD_POWER_MODE_SHIFT 4
+#define MAX77620_SD_CFG1_ADE_MASK (1 << 3)
+#define MAX77620_SD_CFG1_ADE_DISABLE 0
+#define MAX77620_SD_CFG1_ADE_ENABLE (1 << 3)
+#define MAX77620_SD_FPWM_MASK 0x04
+#define MAX77620_SD_FPWM_SHIFT 2
+#define MAX77620_SD_FSRADE_MASK 0x01
+#define MAX77620_SD_FSRADE_SHIFT 0
+#define MAX77620_SD_CFG1_FPWM_SD_MASK (1 << 2)
+#define MAX77620_SD_CFG1_FPWM_SD_SKIP 0
+#define MAX77620_SD_CFG1_FPWM_SD_FPWM (1 << 2)
+#define MAX20024_SD_CFG1_MPOK_MASK (1 << 1)
+#define MAX77620_SD_CFG1_FSRADE_SD_MASK (1 << 0)
+#define MAX77620_SD_CFG1_FSRADE_SD_DISABLE 0
+#define MAX77620_SD_CFG1_FSRADE_SD_ENABLE (1 << 0)
+
+#define MAX77620_IRQ_LVL2_GPIO_EDGE0 (1 << 0)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE1 (1 << 1)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE2 (1 << 2)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE3 (1 << 3)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE4 (1 << 4)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE5 (1 << 5)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE6 (1 << 6)
+#define MAX77620_IRQ_LVL2_GPIO_EDGE7 (1 << 7)
+
+/* Interrupts */
+enum {
+ MAX77620_IRQ_TOP_GLBL, /* Low-Battery */
+ MAX77620_IRQ_TOP_SD, /* SD power fail */
+ MAX77620_IRQ_TOP_LDO, /* LDO power fail */
+ MAX77620_IRQ_TOP_GPIO, /* TOP GPIO internal int to MAX77620 */
+ MAX77620_IRQ_TOP_RTC, /* RTC */
+ MAX77620_IRQ_TOP_32K, /* 32kHz oscillator */
+ MAX77620_IRQ_TOP_ONOFF, /* ON/OFF oscillator */
+ MAX77620_IRQ_LBT_MBATLOW, /* Thermal alarm status, > 120C */
+ MAX77620_IRQ_LBT_TJALRM1, /* Thermal alarm status, > 120C */
+ MAX77620_IRQ_LBT_TJALRM2, /* Thermal alarm status, > 140C */
+};
+
+/* GPIOs */
+enum {
+ MAX77620_GPIO0,
+ MAX77620_GPIO1,
+ MAX77620_GPIO2,
+ MAX77620_GPIO3,
+ MAX77620_GPIO4,
+ MAX77620_GPIO5,
+ MAX77620_GPIO6,
+ MAX77620_GPIO7,
+ MAX77620_GPIO_NR,
+};
+
+/* FPS Source */
+enum max77620_fps_src {
+ MAX77620_FPS_SRC_0,
+ MAX77620_FPS_SRC_1,
+ MAX77620_FPS_SRC_2,
+ MAX77620_FPS_SRC_NONE,
+ MAX77620_FPS_SRC_DEF,
+};
+
+enum max77620_chip_id {
+ MAX77620,
+ MAX20024,
+};
+
+#endif /* _MFD_MAX77620_H_ */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/power/max7762x.c b/Source/Atmosphere-MTC-Unlock/emummc/source/power/max7762x.c
new file mode 100644
index 00000000..1e090bf5
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/power/max7762x.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include "max7762x.h"
+#include "max77620.h"
+#include "../soc/i2c.h"
+#include "../utils/util.h"
+
+#define REGULATOR_SD 0
+#define REGULATOR_LDO 1
+
+typedef struct _max77620_regulator_t
+{
+ u8 type;
+ const char *name;
+ u8 reg_sd;
+
+ u32 mv_step;
+ u32 mv_min;
+ u32 mv_default;
+ u32 mv_max;
+
+ u8 volt_addr;
+ u8 cfg_addr;
+
+ u8 volt_mask;
+ u8 enable_mask;
+ u8 enable_shift;
+ u8 status_mask;
+
+ u8 fps_addr;
+ u8 fps_src;
+ u8 pd_period;
+ u8 pu_period;
+} max77620_regulator_t;
+
+static const max77620_regulator_t _pmic_regulators[] = {
+ { REGULATOR_SD, "sd0", 0x16, 12500, 600000, 625000, 1400000, MAX77620_REG_SD0, MAX77620_REG_SD0_CFG, MAX77620_SD0_VOLT_MASK, MAX77620_SD_POWER_MODE_MASK, MAX77620_SD_POWER_MODE_SHIFT, 0x80, MAX77620_REG_FPS_SD0, 1, 7, 1 },
+ { REGULATOR_SD, "sd1", 0x17, 12500, 600000, 1125000, 1125000, MAX77620_REG_SD1, MAX77620_REG_SD1_CFG, MAX77620_SD1_VOLT_MASK, MAX77620_SD_POWER_MODE_MASK, MAX77620_SD_POWER_MODE_SHIFT, 0x40, MAX77620_REG_FPS_SD1, 0, 1, 5 },
+ { REGULATOR_SD, "sd2", 0x18, 12500, 600000, 1325000, 1350000, MAX77620_REG_SD2, MAX77620_REG_SD2_CFG, MAX77620_SDX_VOLT_MASK, MAX77620_SD_POWER_MODE_MASK, MAX77620_SD_POWER_MODE_SHIFT, 0x20, MAX77620_REG_FPS_SD2, 1, 5, 2 },
+ { REGULATOR_SD, "sd3", 0x19, 12500, 600000, 1800000, 1800000, MAX77620_REG_SD3, MAX77620_REG_SD3_CFG, MAX77620_SDX_VOLT_MASK, MAX77620_SD_POWER_MODE_MASK, MAX77620_SD_POWER_MODE_SHIFT, 0x10, MAX77620_REG_FPS_SD3, 0, 3, 3 },
+ { REGULATOR_LDO, "ldo0", 0x00, 25000, 800000, 1200000, 1200000, MAX77620_REG_LDO0_CFG, MAX77620_REG_LDO0_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO0, 3, 7, 0 },
+ { REGULATOR_LDO, "ldo1", 0x00, 25000, 800000, 1050000, 1050000, MAX77620_REG_LDO1_CFG, MAX77620_REG_LDO1_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO1, 3, 7, 0 },
+ { REGULATOR_LDO, "ldo2", 0x00, 50000, 800000, 1800000, 3300000, MAX77620_REG_LDO2_CFG, MAX77620_REG_LDO2_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO2, 3, 7, 0 },
+ { REGULATOR_LDO, "ldo3", 0x00, 50000, 800000, 3100000, 3100000, MAX77620_REG_LDO3_CFG, MAX77620_REG_LDO3_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO3, 3, 7, 0 },
+ { REGULATOR_LDO, "ldo4", 0x00, 12500, 800000, 850000, 850000, MAX77620_REG_LDO4_CFG, MAX77620_REG_LDO4_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO4, 0, 7, 1 },
+ { REGULATOR_LDO, "ldo5", 0x00, 50000, 800000, 1800000, 1800000, MAX77620_REG_LDO5_CFG, MAX77620_REG_LDO5_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO5, 3, 7, 0 },
+ { REGULATOR_LDO, "ldo6", 0x00, 50000, 800000, 2900000, 2900000, MAX77620_REG_LDO6_CFG, MAX77620_REG_LDO6_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO6, 3, 7, 0 },
+ { REGULATOR_LDO, "ldo7", 0x00, 50000, 800000, 1050000, 1050000, MAX77620_REG_LDO7_CFG, MAX77620_REG_LDO7_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO7, 1, 4, 3 },
+ { REGULATOR_LDO, "ldo8", 0x00, 50000, 800000, 1050000, 1050000, MAX77620_REG_LDO8_CFG, MAX77620_REG_LDO8_CFG2, MAX77620_LDO_VOLT_MASK, MAX77620_LDO_POWER_MODE_MASK, MAX77620_LDO_POWER_MODE_SHIFT, 0x00, MAX77620_REG_FPS_LDO8, 3, 7, 0 }
+};
+
+int max77620_regulator_get_status(u32 id)
+{
+ if (id > REGULATOR_MAX)
+ return 0;
+
+ const max77620_regulator_t *reg = &_pmic_regulators[id];
+
+ if (reg->type == REGULATOR_SD)
+ return (i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_STATSD) & reg->status_mask) ? 0 : 1;
+ return (i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, reg->cfg_addr) & 8) ? 1 : 0;
+}
+
+int max77620_regulator_config_fps(u32 id)
+{
+ if (id > REGULATOR_MAX)
+ return 0;
+
+ const max77620_regulator_t *reg = &_pmic_regulators[id];
+
+ i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, reg->fps_addr,
+ (reg->fps_src << MAX77620_FPS_SRC_SHIFT) | (reg->pu_period << MAX77620_FPS_PU_PERIOD_SHIFT) | (reg->pd_period));
+
+ return 1;
+}
+
+int max77620_regulator_set_voltage(u32 id, u32 mv)
+{
+ if (id > REGULATOR_MAX)
+ return 0;
+
+ const max77620_regulator_t *reg = &_pmic_regulators[id];
+
+ if (mv < reg->mv_min || mv > reg->mv_max)
+ return 0;
+
+ u32 mult = (mv + reg->mv_step - 1 - reg->mv_min) / reg->mv_step;
+ u8 val = i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, reg->volt_addr);
+ val = (val & ~reg->volt_mask) | (mult & reg->volt_mask);
+ i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, reg->volt_addr, val);
+ usleep(1000);
+
+ return 1;
+}
+
+int max77620_regulator_enable(u32 id, int enable)
+{
+ if (id > REGULATOR_MAX)
+ return 0;
+
+ const max77620_regulator_t *reg = &_pmic_regulators[id];
+
+ u32 addr = reg->type == REGULATOR_SD ? reg->cfg_addr : reg->volt_addr;
+ u8 val = i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, addr);
+ if (enable)
+ val = (val & ~reg->enable_mask) | ((MAX77620_POWER_MODE_NORMAL << reg->enable_shift) & reg->enable_mask);
+ else
+ val &= ~reg->enable_mask;
+ i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, addr, val);
+ usleep(1000);
+
+ return 1;
+}
+
+int max77620_regulator_set_volt_and_flags(u32 id, u32 mv, u8 flags)
+{
+ if (id > REGULATOR_MAX)
+ return 0;
+
+ const max77620_regulator_t *reg = &_pmic_regulators[id];
+
+ if (mv < reg->mv_min || mv > reg->mv_max)
+ return 0;
+
+ u32 mult = (mv + reg->mv_step - 1 - reg->mv_min) / reg->mv_step;
+ u8 val = ((flags << reg->enable_shift) & ~reg->volt_mask) | (mult & reg->volt_mask);
+ i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, reg->volt_addr, val);
+ usleep(1000);
+
+ return 1;
+}
+
+void max77620_config_default()
+{
+ for (u32 i = 1; i <= REGULATOR_MAX; i++)
+ {
+ i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_CID4);
+ max77620_regulator_config_fps(i);
+ max77620_regulator_set_voltage(i, _pmic_regulators[i].mv_default);
+ if (_pmic_regulators[i].fps_src != MAX77620_FPS_SRC_NONE)
+ max77620_regulator_enable(i, 1);
+ }
+ i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_SD_CFG2, 4);
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/power/max7762x.h b/Source/Atmosphere-MTC-Unlock/emummc/source/power/max7762x.h
new file mode 100644
index 00000000..c8b5c530
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/power/max7762x.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _MAX7762X_H_
+#define _MAX7762X_H_
+
+#include "../utils/types.h"
+
+/*
+* Switch Power domains (max77620):
+* Name | Usage | uV step | uV min | uV default | uV max | Init
+*-------+---------------+---------+--------+------------+---------+------------------
+* sd0 | SoC | 12500 | 600000 | 625000 | 1400000 | 1.125V (pkg1.1)
+* sd1 | SDRAM | 12500 | 600000 | 1125000 | 1125000 | 1.1V (pkg1.1)
+* sd2 | ldo{0-1, 7-8} | 12500 | 600000 | 1325000 | 1350000 | 1.325V (pcv)
+* sd3 | 1.8V general | 12500 | 600000 | 1800000 | 1800000 |
+* ldo0 | Display Panel | 25000 | 800000 | 1200000 | 1200000 | 1.2V (pkg1.1)
+* ldo1 | XUSB, PCIE | 25000 | 800000 | 1050000 | 1050000 | 1.05V (pcv)
+* ldo2 | SDMMC1 | 50000 | 800000 | 1800000 | 3300000 |
+* ldo3 | GC ASIC | 50000 | 800000 | 3100000 | 3100000 | 3.1V (pcv)
+* ldo4 | RTC | 12500 | 800000 | 850000 | 850000 |
+* ldo5 | GC ASIC | 50000 | 800000 | 1800000 | 1800000 | 1.8V (pcv)
+* ldo6 | Touch, ALS | 50000 | 800000 | 2900000 | 2900000 | 2.9V
+* ldo7 | XUSB | 50000 | 800000 | 1050000 | 1050000 |
+* ldo8 | XUSB, DC | 50000 | 800000 | 1050000 | 1050000 |
+*/
+
+/*
+* MAX77620_AME_GPIO: control GPIO modes (bits 0 - 7 correspond to GPIO0 - GPIO7); 0 -> GPIO, 1 -> alt-mode
+* MAX77620_REG_GPIOx: 0x9 sets output and enable
+*/
+
+/*! MAX77620 partitions. */
+#define REGULATOR_SD0 0
+#define REGULATOR_SD1 1
+#define REGULATOR_SD2 2
+#define REGULATOR_SD3 3
+#define REGULATOR_LDO0 4
+#define REGULATOR_LDO1 5
+#define REGULATOR_LDO2 6
+#define REGULATOR_LDO3 7
+#define REGULATOR_LDO4 8
+#define REGULATOR_LDO5 9
+#define REGULATOR_LDO6 10
+#define REGULATOR_LDO7 11
+#define REGULATOR_LDO8 12
+#define REGULATOR_MAX 12
+
+#define MAX77621_CPU_I2C_ADDR 0x1B
+#define MAX77621_GPU_I2C_ADDR 0x1C
+
+#define MAX77621_VOUT_REG 0
+#define MAX77621_VOUT_DVC_REG 1
+#define MAX77621_CONTROL1_REG 2
+#define MAX77621_CONTROL2_REG 3
+
+/* MAX77621_VOUT */
+#define MAX77621_VOUT_ENABLE (1 << 7)
+#define MAX77621_VOUT_MASK 0x7F
+#define MAX77621_VOUT_0_95V 0x37
+#define MAX77621_VOUT_1_09V 0x4F
+
+/* MAX77621_VOUT_DVC_DVS */
+#define MAX77621_DVS_VOUT_MASK 0x7F
+
+/* MAX77621_CONTROL1 */
+#define MAX77621_SNS_ENABLE (1 << 7)
+#define MAX77621_FPWM_EN_M (1 << 6)
+#define MAX77621_NFSR_ENABLE (1 << 5)
+#define MAX77621_AD_ENABLE (1 << 4)
+#define MAX77621_BIAS_ENABLE (1 << 3)
+#define MAX77621_FREQSHIFT_9PER (1 << 2)
+
+#define MAX77621_RAMP_12mV_PER_US 0x0
+#define MAX77621_RAMP_25mV_PER_US 0x1
+#define MAX77621_RAMP_50mV_PER_US 0x2
+#define MAX77621_RAMP_200mV_PER_US 0x3
+#define MAX77621_RAMP_MASK 0x3
+
+/* MAX77621_CONTROL2 */
+#define MAX77621_WDTMR_ENABLE (1 << 6)
+#define MAX77621_DISCH_ENBABLE (1 << 5)
+#define MAX77621_FT_ENABLE (1 << 4)
+#define MAX77621_T_JUNCTION_120 (1 << 7)
+
+#define MAX77621_CKKADV_TRIP_DISABLE 0xC
+#define MAX77621_CKKADV_TRIP_75mV_PER_US 0x0
+#define MAX77621_CKKADV_TRIP_150mV_PER_US 0x4
+#define MAX77621_CKKADV_TRIP_75mV_PER_US_HIST_DIS 0x8
+
+#define MAX77621_INDUCTOR_MIN_30_PER 0x0
+#define MAX77621_INDUCTOR_NOMINAL 0x1
+#define MAX77621_INDUCTOR_PLUS_30_PER 0x2
+#define MAX77621_INDUCTOR_PLUS_60_PER 0x3
+
+int max77620_regulator_get_status(u32 id);
+int max77620_regulator_config_fps(u32 id);
+int max77620_regulator_set_voltage(u32 id, u32 mv);
+int max77620_regulator_enable(u32 id, int enable);
+int max77620_regulator_set_volt_and_flags(u32 id, u32 mv, u8 flags);
+void max77620_config_default();
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/clock.c b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/clock.c
new file mode 100644
index 00000000..541cd163
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/clock.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include "../soc/clock.h"
+#include "../soc/t210.h"
+#include "../utils/util.h"
+#include "../emmc/sdmmc.h"
+
+static const sclock_t _clock_i2c5 = {
+ CLK_RST_CONTROLLER_RST_DEVICES_H, CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_CONTROLLER_CLK_SOURCE_I2C5, 0xF, 0, 4 //81.6MHz -> 400KHz
+};
+
+static sclock_t _clock_sdmmc_legacy_tm = {
+ CLK_RST_CONTROLLER_RST_DEVICES_Y, CLK_RST_CONTROLLER_CLK_OUT_ENB_Y, CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM, 1, 4, 66
+};
+
+void clock_enable(const sclock_t *clk)
+{
+ // Put clock into reset.
+ CLOCK(clk->reset) = (CLOCK(clk->reset) & ~(1 << clk->index)) | (1 << clk->index);
+ // Disable.
+ CLOCK(clk->enable) &= ~(1 << clk->index);
+ // Configure clock source if required.
+ if (clk->source)
+ CLOCK(clk->source) = clk->clk_div | (clk->clk_src << 29);
+ // Enable.
+ CLOCK(clk->enable) = (CLOCK(clk->enable) & ~(1 << clk->index)) | (1 << clk->index);
+ usleep(2);
+
+ // Take clock off reset.
+ CLOCK(clk->reset) &= ~(1 << clk->index);
+}
+
+void clock_disable(const sclock_t *clk)
+{
+ // Put clock into reset.
+ CLOCK(clk->reset) = (CLOCK(clk->reset) & ~(1 << clk->index)) | (1 << clk->index);
+ // Disable.
+ CLOCK(clk->enable) &= ~(1 << clk->index);
+}
+
+void clock_enable_i2c5()
+{
+ clock_enable(&_clock_i2c5);
+}
+
+void clock_disable_i2c5()
+{
+ clock_disable(&_clock_i2c5);
+}
+
+static void _clock_enable_pllc4()
+{
+ if ((CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE) & (PLLCX_BASE_ENABLE | PLLCX_BASE_LOCK | 0xFFFFFF))
+ == (PLLCX_BASE_ENABLE | PLLCX_BASE_LOCK | (104 << 8) | 4))
+ return;
+
+ // Enable Phase and Frequency lock detection.
+ //CLOCK(CLK_RST_CONTROLLER_PLLC4_MISC) = PLLC4_MISC_EN_LCKDET;
+
+ // Disable PLL and IDDQ in case they are on.
+ CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE) &= ~PLLCX_BASE_ENABLE;
+ CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE) &= ~PLLC4_BASE_IDDQ;
+ (void)CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE);
+ usleep(10);
+
+ // Set PLLC4 dividers.
+ CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE) = (104 << 8) | 4; // DIVM: 4, DIVP: 1.
+
+ // Enable PLLC4 and wait for Phase and Frequency lock.
+ CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE) |= PLLCX_BASE_ENABLE;
+ (void)CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE);
+ while (!(CLOCK(CLK_RST_CONTROLLER_PLLC4_BASE) & PLLCX_BASE_LOCK))
+ ;
+
+ msleep(1); // Wait a bit for PLL to stabilize.
+}
+
+#define L_SWR_SDMMC1_RST (1 << 14)
+#define L_SWR_SDMMC2_RST (1 << 9)
+#define L_SWR_SDMMC4_RST (1 << 15)
+#define U_SWR_SDMMC3_RST (1 << 5)
+
+#define L_CLK_ENB_SDMMC1 (1 << 14)
+#define L_CLK_ENB_SDMMC2 (1 << 9)
+#define L_CLK_ENB_SDMMC4 (1 << 15)
+#define U_CLK_ENB_SDMMC3 (1 << 5)
+
+#define L_SET_SDMMC1_RST (1 << 14)
+#define L_SET_SDMMC2_RST (1 << 9)
+#define L_SET_SDMMC4_RST (1 << 15)
+#define U_SET_SDMMC3_RST (1 << 5)
+
+#define L_CLR_SDMMC1_RST (1 << 14)
+#define L_CLR_SDMMC2_RST (1 << 9)
+#define L_CLR_SDMMC4_RST (1 << 15)
+#define U_CLR_SDMMC3_RST (1 << 5)
+
+#define L_SET_CLK_ENB_SDMMC1 (1 << 14)
+#define L_SET_CLK_ENB_SDMMC2 (1 << 9)
+#define L_SET_CLK_ENB_SDMMC4 (1 << 15)
+#define U_SET_CLK_ENB_SDMMC3 (1 << 5)
+
+#define L_CLR_CLK_ENB_SDMMC1 (1 << 14)
+#define L_CLR_CLK_ENB_SDMMC2 (1 << 9)
+#define L_CLR_CLK_ENB_SDMMC4 (1 << 15)
+#define U_CLR_CLK_ENB_SDMMC3 (1 << 5)
+
+static int _clock_sdmmc_is_reset(u32 id)
+{
+ switch (id)
+ {
+ case SDMMC_1:
+ return CLOCK(CLK_RST_CONTROLLER_RST_DEVICES_L) & L_SWR_SDMMC1_RST;
+ case SDMMC_2:
+ return CLOCK(CLK_RST_CONTROLLER_RST_DEVICES_L) & L_SWR_SDMMC2_RST;
+ case SDMMC_3:
+ return CLOCK(CLK_RST_CONTROLLER_RST_DEVICES_U) & U_SWR_SDMMC3_RST;
+ case SDMMC_4:
+ return CLOCK(CLK_RST_CONTROLLER_RST_DEVICES_L) & L_SWR_SDMMC4_RST;
+ }
+ return 0;
+}
+
+static void _clock_sdmmc_set_reset(u32 id)
+{
+ switch (id)
+ {
+ case SDMMC_1:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = L_SET_SDMMC1_RST;
+ break;
+ case SDMMC_2:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = L_SET_SDMMC2_RST;
+ break;
+ case SDMMC_3:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_U_SET) = U_SET_SDMMC3_RST;
+ break;
+ case SDMMC_4:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = L_SET_SDMMC4_RST;
+ break;
+ }
+}
+
+static void _clock_sdmmc_clear_reset(u32 id)
+{
+ switch (id)
+ {
+ case SDMMC_1:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_CLR) = L_CLR_SDMMC1_RST;
+ break;
+ case SDMMC_2:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_CLR) = L_CLR_SDMMC2_RST;
+ break;
+ case SDMMC_3:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_U_CLR) = U_CLR_SDMMC3_RST;
+ break;
+ case SDMMC_4:
+ CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_CLR) = L_CLR_SDMMC4_RST;
+ break;
+ }
+}
+
+static int _clock_sdmmc_is_enabled(u32 id)
+{
+ switch (id)
+ {
+ case SDMMC_1:
+ return CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_L) & L_CLK_ENB_SDMMC1;
+ case SDMMC_2:
+ return CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_L) & L_CLK_ENB_SDMMC2;
+ case SDMMC_3:
+ return CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_U) & U_CLK_ENB_SDMMC3;
+ case SDMMC_4:
+ return CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_L) & L_CLK_ENB_SDMMC4;
+ }
+ return 0;
+}
+
+static void _clock_sdmmc_set_enable(u32 id)
+{
+ switch (id)
+ {
+ case SDMMC_1:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_SET) = L_SET_CLK_ENB_SDMMC1;
+ break;
+ case SDMMC_2:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_SET) = L_SET_CLK_ENB_SDMMC2;
+ break;
+ case SDMMC_3:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_U_SET) = U_SET_CLK_ENB_SDMMC3;
+ break;
+ case SDMMC_4:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_SET) = L_SET_CLK_ENB_SDMMC4;
+ break;
+ }
+}
+
+static void _clock_sdmmc_clear_enable(u32 id)
+{
+ switch (id)
+ {
+ case SDMMC_1:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = L_CLR_CLK_ENB_SDMMC1;
+ break;
+ case SDMMC_2:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = L_CLR_CLK_ENB_SDMMC2;
+ break;
+ case SDMMC_3:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_U_CLR) = U_CLR_CLK_ENB_SDMMC3;
+ break;
+ case SDMMC_4:
+ CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = L_CLR_CLK_ENB_SDMMC4;
+ break;
+ }
+}
+
+static void _clock_sdmmc_config_legacy_tm()
+{
+ sclock_t *clk = &_clock_sdmmc_legacy_tm;
+ if (!(CLOCK(clk->enable) & (1 << clk->index)))
+ clock_enable(clk);
+}
+
+typedef struct _clock_sdmmc_t
+{
+ u32 clock;
+ u32 real_clock;
+} clock_sdmmc_t;
+
+static clock_sdmmc_t _clock_sdmmc_table[4] = { 0 };
+
+#define SDMMC_CLOCK_SRC_PLLP_OUT0 0x0
+#define SDMMC_CLOCK_SRC_PLLC4_OUT2 0x3
+#define SDMMC4_CLOCK_SRC_PLLC4_OUT2_LJ 0x1
+
+static int _clock_sdmmc_config_clock_host(u32 *pclock, u32 id, u32 val)
+{
+ u32 divisor = 0;
+ u32 source = SDMMC_CLOCK_SRC_PLLP_OUT0;
+
+ if (id > SDMMC_4)
+ return 0;
+
+ // Get IO clock divisor.
+ switch (val)
+ {
+ case 25000:
+ *pclock = 24728;
+ divisor = 31; // 16.5 div.
+ break;
+ case 26000:
+ *pclock = 25500;
+ divisor = 30; // 16 div.
+ break;
+ case 40800:
+ *pclock = 40800;
+ divisor = 18; // 10 div.
+ break;
+ case 50000:
+ *pclock = 48000;
+ divisor = 15; // 8.5 div.
+ break;
+ case 52000:
+ *pclock = 51000;
+ divisor = 14; // 8 div.
+ break;
+ case 100000:
+ source = SDMMC_CLOCK_SRC_PLLC4_OUT2;
+ *pclock = 99840;
+ divisor = 2; // 2 div.
+ break;
+ case 164000:
+ *pclock = 163200;
+ divisor = 3; // 2.5 div.
+ break;
+ case 200000:
+ switch (id)
+ {
+ case SDMMC_1:
+ source = SDMMC_CLOCK_SRC_PLLC4_OUT2;
+ break;
+ case SDMMC_2:
+ source = SDMMC4_CLOCK_SRC_PLLC4_OUT2_LJ;
+ break;
+ case SDMMC_3:
+ source = SDMMC_CLOCK_SRC_PLLC4_OUT2;
+ break;
+ case SDMMC_4:
+ source = SDMMC4_CLOCK_SRC_PLLC4_OUT2_LJ;
+ break;
+ }
+ *pclock = 199680;
+ divisor = 0; // 1 div.
+ break;
+ default:
+ *pclock = 24728;
+ divisor = 31; // 16.5 div.
+ }
+
+ _clock_sdmmc_table[id].clock = val;
+ _clock_sdmmc_table[id].real_clock = *pclock;
+
+ // PLLC4 and LEGACY_TM clocks are already initialized,
+ // because we init at the first eMMC read.
+ // // Enable PLLC4 if in use by any SDMMC.
+ // if (source)
+ // _clock_enable_pllc4();
+
+ // // Set SDMMC legacy timeout clock.
+ // _clock_sdmmc_config_legacy_tm();
+
+
+ // Set SDMMC clock.
+ switch (id)
+ {
+ case SDMMC_1:
+ CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1) = (source << 29) | divisor;
+ break;
+ case SDMMC_2:
+ CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2) = (source << 29) | divisor;
+ break;
+ case SDMMC_3:
+ CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3) = (source << 29) | divisor;
+ break;
+ case SDMMC_4:
+ CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4) = (source << 29) | divisor;
+ break;
+ }
+
+ return 1;
+}
+
+void clock_sdmmc_config_clock_source(u32 *pclock, u32 id, u32 val)
+{
+ if (_clock_sdmmc_table[id].clock == val)
+ {
+ *pclock = _clock_sdmmc_table[id].real_clock;
+ }
+ else
+ {
+ int is_enabled = _clock_sdmmc_is_enabled(id);
+ if (is_enabled)
+ _clock_sdmmc_clear_enable(id);
+ _clock_sdmmc_config_clock_host(pclock, id, val);
+ if (is_enabled)
+ _clock_sdmmc_set_enable(id);
+ _clock_sdmmc_is_reset(id);
+ }
+}
+
+void clock_sdmmc_get_card_clock_div(u32 *pclock, u16 *pdivisor, u32 type)
+{
+ // Get Card clock divisor.
+ switch (type)
+ {
+ case SDHCI_TIMING_MMC_ID: // Actual IO Freq: 380.59 KHz.
+ *pclock = 26000;
+ *pdivisor = 66;
+ break;
+ case SDHCI_TIMING_MMC_LS26:
+ *pclock = 26000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_MMC_HS52:
+ *pclock = 52000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_MMC_HS200:
+ case SDHCI_TIMING_MMC_HS400:
+ case SDHCI_TIMING_UHS_SDR104:
+ *pclock = 200000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_SD_ID: // Actual IO Freq: 380.43 KHz.
+ *pclock = 25000;
+ *pdivisor = 64;
+ break;
+ case SDHCI_TIMING_SD_DS12:
+ case SDHCI_TIMING_UHS_SDR12:
+ *pclock = 25000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_SD_HS25:
+ case SDHCI_TIMING_UHS_SDR25:
+ *pclock = 50000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_UHS_SDR50:
+ *pclock = 100000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_UHS_SDR82:
+ *pclock = 164000;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_UHS_DDR50:
+ *pclock = 40800;
+ *pdivisor = 1;
+ break;
+ case SDHCI_TIMING_MMC_HS102: // Actual IO Freq: 99.84 MHz.
+ *pclock = 200000;
+ *pdivisor = 2;
+ break;
+ }
+}
+
+int clock_sdmmc_is_not_reset_and_enabled(u32 id)
+{
+ return !_clock_sdmmc_is_reset(id) && _clock_sdmmc_is_enabled(id);
+}
+
+void clock_sdmmc_enable(u32 id, u32 val)
+{
+ u32 clock = 0;
+
+ if (_clock_sdmmc_is_enabled(id))
+ _clock_sdmmc_clear_enable(id);
+ _clock_sdmmc_set_reset(id);
+ _clock_sdmmc_config_clock_host(&clock, id, val);
+ _clock_sdmmc_set_enable(id);
+ _clock_sdmmc_is_reset(id);
+ usleep((100000 + clock - 1) / clock);
+ _clock_sdmmc_clear_reset(id);
+ _clock_sdmmc_is_reset(id);
+}
+
+void clock_sdmmc_disable(u32 id)
+{
+ _clock_sdmmc_set_reset(id);
+ _clock_sdmmc_clear_enable(id);
+ _clock_sdmmc_is_reset(id);
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/clock.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/clock.h
new file mode 100644
index 00000000..73171b11
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/clock.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _CLOCK_H_
+#define _CLOCK_H_
+
+#include "../utils/types.h"
+
+/*! Clock registers. */
+#define CLK_RST_CONTROLLER_RST_SOURCE 0x0
+#define CLK_RST_CONTROLLER_RST_DEVICES_L 0x4
+#define CLK_RST_CONTROLLER_RST_DEVICES_H 0x8
+#define CLK_RST_CONTROLLER_RST_DEVICES_U 0xC
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_L 0x10
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_H 0x14
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_U 0x18
+#define CLK_RST_CONTROLLER_CCLK_BURST_POLICY 0x20
+#define CLK_RST_CONTROLLER_SUPER_CCLK_DIVIDER 0x24
+#define CLK_RST_CONTROLLER_SCLK_BURST_POLICY 0x28
+#define CLK_RST_CONTROLLER_SUPER_SCLK_DIVIDER 0x2C
+#define CLK_RST_CONTROLLER_CLK_SYSTEM_RATE 0x30
+#define CLK_RST_CONTROLLER_MISC_CLK_ENB 0x48
+#define CLK_RST_CONTROLLER_OSC_CTRL 0x50
+#define CLK_RST_CONTROLLER_PLLC_BASE 0x80
+#define CLK_RST_CONTROLLER_PLLC_OUT 0x84
+#define CLK_RST_CONTROLLER_PLLC_MISC 0x88
+#define CLK_RST_CONTROLLER_PLLC_MISC_1 0x8C
+#define CLK_RST_CONTROLLER_PLLM_BASE 0x90
+#define CLK_RST_CONTROLLER_PLLM_MISC1 0x98
+#define CLK_RST_CONTROLLER_PLLM_MISC2 0x9C
+#define CLK_RST_CONTROLLER_PLLP_BASE 0xA0
+#define CLK_RST_CONTROLLER_PLLD_BASE 0xD0
+#define CLK_RST_CONTROLLER_PLLD_MISC1 0xD8
+#define CLK_RST_CONTROLLER_PLLD_MISC 0xDC
+#define CLK_RST_CONTROLLER_PLLX_BASE 0xE0
+#define CLK_RST_CONTROLLER_PLLX_MISC 0xE4
+#define CLK_RST_CONTROLLER_PLLE_BASE 0xE8
+#define CLK_RST_CONTROLLER_PLLE_MISC 0xEC
+#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA 0xF8
+#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB 0xFC
+#define CLK_RST_CONTROLLER_CLK_SOURCE_PWM 0x110
+#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C1 0x124
+#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C5 0x128
+#define CLK_RST_CONTROLLER_CLK_SOURCE_DISP1 0x138
+#define CLK_RST_CONTROLLER_CLK_SOURCE_VI 0x148
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1 0x150
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2 0x154
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4 0x164
+#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTA 0x178
+#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTB 0x17C
+#define CLK_RST_CONTROLLER_CLK_SOURCE_HOST1X 0x180
+#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C2 0x198
+#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC 0x19C
+#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTC 0x1A0
+#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C3 0x1B8
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3 0x1BC
+#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTD 0x1C0
+#define CLK_RST_CONTROLLER_CLK_SOURCE_CSITE 0x1D4
+#define CLK_RST_CONTROLLER_CLK_SOURCE_TSEC 0x1F4
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X 0x280
+#define CLK_RST_CONTROLLER_CLK_ENB_X_SET 0x284
+#define CLK_RST_CONTROLLER_CLK_ENB_X_CLR 0x288
+#define CLK_RST_CONTROLLER_RST_DEVICES_X 0x28C
+#define CLK_RST_CONTROLLER_RST_DEV_X_SET 0x290
+#define CLK_RST_CONTROLLER_RST_DEV_X_CLR 0x294
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_Y 0x298
+#define CLK_RST_CONTROLLER_CLK_ENB_Y_SET 0x29C
+#define CLK_RST_CONTROLLER_CLK_ENB_Y_CLR 0x2A0
+#define CLK_RST_CONTROLLER_RST_DEVICES_Y 0x2A4
+#define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2A8
+#define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2AC
+#define CLK_RST_CONTROLLER_RST_DEV_L_SET 0x300
+#define CLK_RST_CONTROLLER_RST_DEV_L_CLR 0x304
+#define CLK_RST_CONTROLLER_RST_DEV_H_SET 0x308
+#define CLK_RST_CONTROLLER_RST_DEV_H_CLR 0x30C
+#define CLK_RST_CONTROLLER_RST_DEV_U_SET 0x310
+#define CLK_RST_CONTROLLER_RST_DEV_U_CLR 0x314
+#define CLK_RST_CONTROLLER_CLK_ENB_L_SET 0x320
+#define CLK_RST_CONTROLLER_CLK_ENB_L_CLR 0x324
+#define CLK_RST_CONTROLLER_CLK_ENB_H_SET 0x328
+#define CLK_RST_CONTROLLER_CLK_ENB_H_CLR 0x32C
+#define CLK_RST_CONTROLLER_CLK_ENB_U_SET 0x330
+#define CLK_RST_CONTROLLER_CLK_ENB_U_CLR 0x334
+#define CLK_RST_CONTROLLER_RST_DEVICES_V 0x358
+#define CLK_RST_CONTROLLER_RST_DEVICES_W 0x35C
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_V 0x360
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_W 0x364
+#define CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2 0x388
+#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRC 0x3A0
+#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD 0x3A4
+#define CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT 0x3B4
+#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C4 0x3C4
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SYS 0x400
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SOR1 0x410
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SE 0x42C
+#define CLK_RST_CONTROLLER_RST_DEV_V_CLR 0x434
+#define CLK_RST_CONTROLLER_CLK_ENB_V_SET 0x440
+#define CLK_RST_CONTROLLER_CLK_ENB_V_CLR 0x444
+#define CLK_RST_CONTROLLER_CLK_ENB_W_SET 0x448
+#define CLK_RST_CONTROLLER_CLK_ENB_W_CLR 0x44C
+#define CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET 0x450
+#define CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR 0x454
+#define CLK_RST_CONTROLLER_UTMIP_PLL_CFG2 0x488
+#define CLK_RST_CONTROLLER_PLLE_AUX 0x48C
+#define CLK_RST_CONTROLLER_AUDIO_SYNC_CLK_I2S0 0x4A0
+#define CLK_RST_CONTROLLER_PLLX_MISC_3 0x518
+#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRE 0x554
+#define CLK_RST_CONTROLLER_SPARE_REG0 0x55C
+#define CLK_RST_CONTROLLER_PLLC4_BASE 0x5A4
+#define CLK_RST_CONTROLLER_PLLC4_MISC 0x5A8
+#define CLK_RST_CONTROLLER_PLLC_MISC_2 0x5D0
+#define CLK_RST_CONTROLLER_PLLC4_OUT 0x5E4
+#define CLK_RST_CONTROLLER_PLLMB_BASE 0x5E8
+#define CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP 0x620
+#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C6 0x65C
+#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL 0x664
+#define CLK_RST_CONTROLLER_CLK_SOURCE_UART_FST_MIPI_CAL 0x66C
+#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM 0x694
+#define CLK_RST_CONTROLLER_CLK_SOURCE_NVENC 0x6A0
+#define CLK_RST_CONTROLLER_SE_SUPER_CLK_DIVIDER 0x704
+#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTAPE 0x710
+
+#define CLK_NO_SOURCE 0x0
+
+/*! PLL control and status bits */
+#define PLLCX_BASE_ENABLE (1 << 30)
+#define PLLCX_BASE_REF_DIS (1 << 29)
+#define PLLCX_BASE_LOCK (1 << 27)
+
+#define PLLC4_MISC_EN_LCKDET (1 << 30)
+#define PLLC4_BASE_IDDQ (1 << 18)
+#define PLLC4_OUT3_CLKEN (1 << 1)
+#define PLLC4_OUT3_RSTN_CLR (1 << 0)
+
+/*! Generic clock descriptor. */
+typedef struct _sclock_t
+{
+ u32 reset;
+ u32 enable;
+ u32 source;
+ u8 index;
+ u8 clk_src;
+ u8 clk_div;
+} sclock_t;
+
+/*! Generic clock enable/disable. */
+void clock_enable(const sclock_t *clk);
+void clock_disable(const sclock_t *clk);
+
+/*! Clock control for specific hardware portions. */
+void clock_enable_i2c5();
+void clock_disable_i2c5();
+void clock_sdmmc_config_clock_source(u32 *pclock, u32 id, u32 val);
+void clock_sdmmc_get_card_clock_div(u32 *pclock, u16 *pdivisor, u32 type);
+int clock_sdmmc_is_not_reset_and_enabled(u32 id);
+void clock_sdmmc_enable(u32 id, u32 val);
+void clock_sdmmc_disable(u32 id);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/gpio.c b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/gpio.c
new file mode 100644
index 00000000..b84b0d25
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/gpio.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include "../soc/gpio.h"
+#include "../soc/t210.h"
+
+#define GPIO_BANK_IDX(port) ((port) >> 2)
+
+#define GPIO_CNF_OFFSET(port) (0x00 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_OE_OFFSET(port) (0x10 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_OUT_OFFSET(port) (0x20 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_IN_OFFSET(port) (0x30 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_STA_OFFSET(port) (0x40 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_ENB_OFFSET(port) (0x50 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_LVL_OFFSET(port) (0x60 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_CLR_OFFSET(port) (0x70 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+
+#define GPIO_CNF_MASKED_OFFSET(port) (0x80 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_OE_MASKED_OFFSET(port) (0x90 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_OUT_MASKED_OFFSET(port) (0xA0 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_STA_MASKED_OFFSET(port) (0xC0 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_ENB_MASKED_OFFSET(port) (0xD0 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+#define GPIO_INT_LVL_MASKED_OFFSET(port) (0xE0 + (((port) >> 2) << 8) + (((port) % 4) << 2))
+
+#define GPIO_IRQ_BANK1 32
+#define GPIO_IRQ_BANK2 33
+#define GPIO_IRQ_BANK3 34
+#define GPIO_IRQ_BANK4 35
+#define GPIO_IRQ_BANK5 55
+#define GPIO_IRQ_BANK6 87
+#define GPIO_IRQ_BANK7 89
+#define GPIO_IRQ_BANK8 125
+
+static u8 gpio_bank_irq_ids[8] = {
+ GPIO_IRQ_BANK1, GPIO_IRQ_BANK2, GPIO_IRQ_BANK3, GPIO_IRQ_BANK4,
+ GPIO_IRQ_BANK5, GPIO_IRQ_BANK6, GPIO_IRQ_BANK7, GPIO_IRQ_BANK8
+};
+
+void gpio_config(u32 port, u32 pins, int mode)
+{
+ u32 offset = GPIO_CNF_OFFSET(port);
+
+ if (mode)
+ GPIO(offset) |= pins;
+ else
+ GPIO(offset) &= ~pins;
+
+ (void)GPIO(offset); // Commit the write.
+}
+
+void gpio_output_enable(u32 port, u32 pins, int enable)
+{
+ u32 port_offset = GPIO_OE_OFFSET(port);
+
+ if (enable)
+ GPIO(port_offset) |= pins;
+ else
+ GPIO(port_offset) &= ~pins;
+
+ (void)GPIO(port_offset); // Commit the write.
+}
+
+void gpio_write(u32 port, u32 pins, int high)
+{
+ u32 port_offset = GPIO_OUT_OFFSET(port);
+
+ if (high)
+ GPIO(port_offset) |= pins;
+ else
+ GPIO(port_offset) &= ~pins;
+
+ (void)GPIO(port_offset); // Commit the write.
+}
+
+int gpio_read(u32 port, u32 pins)
+{
+ u32 port_offset = GPIO_IN_OFFSET(port);
+
+ return (GPIO(port_offset) & pins) ? 1 : 0;
+}
+
+static void _gpio_interrupt_clear(u32 port, u32 pins)
+{
+ u32 port_offset = GPIO_INT_CLR_OFFSET(port);
+
+ GPIO(port_offset) |= pins;
+
+ (void)GPIO(port_offset); // Commit the write.
+}
+
+int gpio_interrupt_status(u32 port, u32 pins)
+{
+ u32 port_offset = GPIO_INT_STA_OFFSET(port);
+ u32 enabled = GPIO(GPIO_INT_ENB_OFFSET(port)) & pins;
+
+ int status = ((GPIO(port_offset) & pins) && enabled) ? 1 : 0;
+
+ // Clear the interrupt status.
+ if (status)
+ _gpio_interrupt_clear(port, pins);
+
+ return status;
+}
+
+void gpio_interrupt_enable(u32 port, u32 pins, int enable)
+{
+ u32 port_offset = GPIO_INT_ENB_OFFSET(port);
+
+ // Clear any possible stray interrupt.
+ _gpio_interrupt_clear(port, pins);
+
+ if (enable)
+ GPIO(port_offset) |= pins;
+ else
+ GPIO(port_offset) &= ~pins;
+
+ (void)GPIO(port_offset); // Commit the write.
+}
+
+void gpio_interrupt_level(u32 port, u32 pins, int high, int edge, int delta)
+{
+ u32 port_offset = GPIO_INT_LVL_OFFSET(port);
+
+ u32 val = GPIO(port_offset);
+
+ if (high)
+ val |= pins;
+ else
+ val &= ~pins;
+
+ if (edge)
+ val |= pins << 8;
+ else
+ val &= ~(pins << 8);
+
+ if (delta)
+ val |= pins << 16;
+ else
+ val &= ~(pins << 16);
+
+ GPIO(port_offset) = val;
+
+ (void)GPIO(port_offset); // Commit the write.
+
+ // Clear any possible stray interrupt.
+ _gpio_interrupt_clear(port, pins);
+}
+
+u32 gpio_get_bank_irq_id(u32 port)
+{
+ u32 bank_idx = GPIO_BANK_IDX(port);
+
+ return gpio_bank_irq_ids[bank_idx];
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/gpio.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/gpio.h
new file mode 100644
index 00000000..a7f999db
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/gpio.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2019 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _GPIO_H_
+#define _GPIO_H_
+
+#include "../utils/types.h"
+
+#define GPIO_MODE_SPIO 0
+#define GPIO_MODE_GPIO 1
+
+#define GPIO_OUTPUT_DISABLE 0
+#define GPIO_OUTPUT_ENABLE 1
+
+#define GPIO_IRQ_DISABLE 0
+#define GPIO_IRQ_ENABLE 1
+
+#define GPIO_LOW 0
+#define GPIO_HIGH 1
+#define GPIO_FALLING 0
+#define GPIO_RISING 1
+
+#define GPIO_LEVEL 0
+#define GPIO_EDGE 1
+
+#define GPIO_CONFIGURED_EDGE 0
+#define GPIO_ANY_EDGE_CHANGE 1
+
+/*! GPIO pins (0-7 for each port). */
+#define GPIO_PIN_0 (1 << 0)
+#define GPIO_PIN_1 (1 << 1)
+#define GPIO_PIN_2 (1 << 2)
+#define GPIO_PIN_3 (1 << 3)
+#define GPIO_PIN_4 (1 << 4)
+#define GPIO_PIN_5 (1 << 5)
+#define GPIO_PIN_6 (1 << 6)
+#define GPIO_PIN_7 (1 << 7)
+
+/*! GPIO ports (A-EE). */
+#define GPIO_PORT_A 0
+#define GPIO_PORT_B 1
+#define GPIO_PORT_C 2
+#define GPIO_PORT_D 3
+#define GPIO_PORT_E 4
+#define GPIO_PORT_F 5
+#define GPIO_PORT_G 6
+#define GPIO_PORT_H 7
+#define GPIO_PORT_I 8
+#define GPIO_PORT_J 9
+#define GPIO_PORT_K 10
+#define GPIO_PORT_L 11
+#define GPIO_PORT_M 12
+#define GPIO_PORT_N 13
+#define GPIO_PORT_O 14
+#define GPIO_PORT_P 15
+#define GPIO_PORT_Q 16
+#define GPIO_PORT_R 17
+#define GPIO_PORT_S 18
+#define GPIO_PORT_T 19
+#define GPIO_PORT_U 20
+#define GPIO_PORT_V 21
+#define GPIO_PORT_W 22
+#define GPIO_PORT_X 23
+#define GPIO_PORT_Y 24
+#define GPIO_PORT_Z 25
+#define GPIO_PORT_AA 26
+#define GPIO_PORT_BB 27
+#define GPIO_PORT_CC 28
+#define GPIO_PORT_DD 29
+#define GPIO_PORT_EE 30
+
+void gpio_config(u32 port, u32 pins, int mode);
+void gpio_output_enable(u32 port, u32 pins, int enable);
+void gpio_write(u32 port, u32 pins, int high);
+int gpio_read(u32 port, u32 pins);
+int gpio_interrupt_status(u32 port, u32 pins);
+void gpio_interrupt_enable(u32 port, u32 pins, int enable);
+void gpio_interrupt_level(u32 port, u32 pins, int high, int edge, int delta);
+u32 gpio_get_bank_irq_id(u32 port);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/i2c.c b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/i2c.c
new file mode 100644
index 00000000..32ef078b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/i2c.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018-2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+
+#include "i2c.h"
+#include "../utils/util.h"
+#include "t210.h"
+
+#define I2C_PACKET_PROT_I2C (1 << 4)
+#define I2C_HEADER_CONT_XFER (1 << 15)
+#define I2C_HEADER_REP_START (1 << 16)
+#define I2C_HEADER_IE_ENABLE (1 << 17)
+#define I2C_HEADER_READ (1 << 19)
+
+#define I2C_CNFG (0x00 / 4)
+#define CMD1_WRITE (0 << 6)
+#define CMD1_READ (1 << 6)
+#define NORMAL_MODE_GO (1 << 9)
+#define PACKET_MODE_GO (1 << 10)
+#define NEW_MASTER_FSM (1 << 11)
+#define DEBOUNCE_CNT_4T (2 << 12)
+
+#define I2C_CMD_ADDR0 (0x04 / 4)
+#define ADDR0_WRITE 0
+#define ADDR0_READ 1
+
+#define I2C_CMD_DATA1 (0x0C / 4)
+#define I2C_CMD_DATA2 (0x10 / 4)
+
+#define I2C_STATUS (0x1C / 4)
+#define I2C_STATUS_NOACK (0xF << 0)
+#define I2C_STATUS_BUSY (1 << 8)
+
+#define I2C_TX_FIFO (0x50 / 4)
+#define I2C_RX_FIFO (0x54 / 4)
+
+#define I2C_FIFO_CONTROL (0x5C / 4)
+#define RX_FIFO_FLUSH (1 << 0)
+#define TX_FIFO_FLUSH (1 << 1)
+
+#define I2C_FIFO_STATUS (0x60 / 4)
+#define RX_FIFO_FULL_CNT (0xF << 0)
+#define TX_FIFO_EMPTY_CNT (0xF << 4)
+
+#define I2C_INT_EN (0x64 / 4)
+#define I2C_INT_STATUS (0x68 / 4)
+#define I2C_INT_SOURCE (0x70 / 4)
+#define RX_FIFO_DATA_REQ (1 << 0)
+#define TX_FIFO_DATA_REQ (1 << 1)
+#define ARB_LOST (1 << 2)
+#define NO_ACK (1 << 3)
+#define RX_FIFO_UNDER (1 << 4)
+#define TX_FIFO_OVER (1 << 5)
+#define ALL_PACKETS_COMPLETE (1 << 6)
+#define PACKET_COMPLETE (1 << 7)
+#define BUS_CLEAR_DONE (1 << 11)
+
+#define I2C_CLK_DIVISOR (0x6C / 4)
+
+#define I2C_BUS_CLEAR_CONFIG (0x84 / 4)
+#define BC_ENABLE (1 << 0)
+#define BC_TERMINATE (1 << 1)
+
+#define I2C_BUS_CLEAR_STATUS (0x88 / 4)
+
+#define I2C_CONFIG_LOAD (0x8C / 4)
+#define MSTR_CONFIG_LOAD (1 << 0)
+#define TIMEOUT_CONFIG_LOAD (1 << 2)
+
+static const u64 i2c_addrs[] = {
+ 0x7000C000, // I2C_1.
+ 0x7000C400, // I2C_2.
+ 0x7000C500, // I2C_3.
+ 0x7000C700, // I2C_4.
+ 0x7000D000, // I2C_5.
+ 0x7000D100 // I2C_6.
+};
+
+static void _i2c_load_cfg_wait(vu32 *base)
+{
+ base[I2C_CONFIG_LOAD] = (1 << 5) | TIMEOUT_CONFIG_LOAD | MSTR_CONFIG_LOAD;
+ for (u32 i = 0; i < 20; i++)
+ {
+ usleep(1);
+ if (!(base[I2C_CONFIG_LOAD] & MSTR_CONFIG_LOAD))
+ break;
+ }
+}
+
+static int _i2c_send_single(u32 i2c_idx, u32 dev_addr, u8 *buf, u32 size)
+{
+ if (size > 4)
+ return 0;
+
+ u32 tmp = 0;
+ memcpy(&tmp, buf, size);
+
+ vu32 *base = (vu32 *)QueryIoMapping(i2c_addrs[I2C_5], 0x1000);
+
+ // Set device address and send mode.
+ base[I2C_CMD_ADDR0] = dev_addr << 1 | ADDR0_WRITE;
+ base[I2C_CMD_DATA1] = tmp; //Set value.
+ // Set size and send mode.
+ base[I2C_CNFG] = ((size - 1) << 1) | DEBOUNCE_CNT_4T | NEW_MASTER_FSM | CMD1_WRITE;
+
+ // Load configuration.
+ _i2c_load_cfg_wait(base);
+
+ // Initiate transaction on normal mode.
+ base[I2C_CNFG] = (base[I2C_CNFG] & 0xFFFFF9FF) | NORMAL_MODE_GO;
+
+ u64 timeout = get_tmr_ms() + 100;
+ while (base[I2C_STATUS] & I2C_STATUS_BUSY)
+ {
+ if (get_tmr_ms() > timeout)
+ return 0;
+ }
+
+ if (base[I2C_STATUS] & I2C_STATUS_NOACK)
+ return 0;
+
+ return 1;
+}
+
+static int _i2c_recv_single(u32 i2c_idx, u8 *buf, u32 size, u32 dev_addr)
+{
+ if (size > 4)
+ return 0;
+
+ vu32 *base = (vu32 *)QueryIoMapping(i2c_addrs[I2C_5], 0x1000);
+ // Set device address and recv mode.
+ base[I2C_CMD_ADDR0] = (dev_addr << 1) | ADDR0_READ;
+
+ // Set size and recv mode.
+ base[I2C_CNFG] = ((size - 1) << 1) | DEBOUNCE_CNT_4T | NEW_MASTER_FSM | CMD1_READ;
+
+ // Load configuration.
+ _i2c_load_cfg_wait(base);
+
+ // Initiate transaction on normal mode.
+ base[I2C_CNFG] = (base[I2C_CNFG] & 0xFFFFF9FF) | NORMAL_MODE_GO;
+
+ u64 timeout = get_tmr_ms() + 100;
+ while (base[I2C_STATUS] & I2C_STATUS_BUSY)
+ {
+ if (get_tmr_ms() > timeout)
+ return 0;
+ }
+
+ if (base[I2C_STATUS] & I2C_STATUS_NOACK)
+ return 0;
+
+ u32 tmp = base[I2C_CMD_DATA1]; // Get LS value.
+ if (size > 4)
+ {
+ memcpy(buf, &tmp, 4);
+ tmp = base[I2C_CMD_DATA2]; // Get MS value.
+ memcpy(buf + 4, &tmp, size - 4);
+ }
+ else
+ memcpy(buf, &tmp, size);
+
+ return 1;
+}
+
+void i2c_init()
+{
+ vu32 *base = (vu32 *)QueryIoMapping(i2c_addrs[I2C_5], 0x1000);
+
+ base[I2C_CLK_DIVISOR] = (5 << 16) | 1; // SF mode Div: 6, HS mode div: 2.
+ base[I2C_BUS_CLEAR_CONFIG] = (9 << 16) | BC_TERMINATE | BC_ENABLE;
+
+ // Load configuration.
+ _i2c_load_cfg_wait(base);
+
+ for (u32 i = 0; i < 10; i++)
+ {
+ if (base[I2C_INT_STATUS] & BUS_CLEAR_DONE)
+ break;
+ }
+
+ (vu32)base[I2C_BUS_CLEAR_STATUS];
+ base[I2C_INT_STATUS] = base[I2C_INT_STATUS];
+}
+
+int i2c_send_buf_small(u32 i2c_idx, u32 dev_addr, u32 reg, u8 *buf, u32 size)
+{
+ u8 tmp[4];
+
+ if (size > 3)
+ return 0;
+
+ tmp[0] = reg;
+ memcpy(tmp + 1, buf, size);
+
+ return _i2c_send_single(i2c_idx, dev_addr, tmp, size + 1);
+}
+
+int i2c_recv_buf_small(u8 *buf, u32 size, u32 i2c_idx, u32 dev_addr, u32 reg)
+{
+ int res = _i2c_send_single(i2c_idx, dev_addr, (u8 *)®, 1);
+ if (res)
+ res = _i2c_recv_single(i2c_idx, buf, size, dev_addr);
+ return res;
+}
+
+int i2c_send_byte(u32 i2c_idx, u32 dev_addr, u32 reg, u8 val)
+{
+ return i2c_send_buf_small(i2c_idx, dev_addr, reg, &val, 1);
+}
+
+u8 i2c_recv_byte(u32 i2c_idx, u32 dev_addr, u32 reg)
+{
+ u8 tmp = 0;
+ i2c_recv_buf_small(&tmp, 1, i2c_idx, dev_addr, reg);
+ return tmp;
+}
+
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/i2c.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/i2c.h
new file mode 100644
index 00000000..095974fe
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/i2c.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2020 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _I2C_H_
+#define _I2C_H_
+
+#include "../utils/types.h"
+
+#define I2C_1 0
+#define I2C_2 1
+#define I2C_3 2
+#define I2C_4 3
+#define I2C_5 4
+#define I2C_6 5
+
+void i2c_init();
+int i2c_send_buf_small(u32 i2c_idx, u32 dev_addr, u32 reg, u8 *buf, u32 size);
+int i2c_recv_buf_small(u8 *buf, u32 size, u32 i2c_idx, u32 dev_addr, u32 reg);
+int i2c_send_byte(u32 i2c_idx, u32 dev_addr, u32 reg, u8 val);
+u8 i2c_recv_byte(u32 i2c_idx, u32 dev_addr, u32 reg);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pinmux.c b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pinmux.c
new file mode 100644
index 00000000..5966be20
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pinmux.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include "../soc/pinmux.h"
+#include "../soc/t210.h"
+
+void pinmux_config_i2c(u32 idx)
+{
+ PINMUX_AUX(PINMUX_AUX_X_I2C_SCL(idx)) = PINMUX_INPUT_ENABLE;
+ PINMUX_AUX(PINMUX_AUX_X_I2C_SDA(idx)) = PINMUX_INPUT_ENABLE;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pinmux.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pinmux.h
new file mode 100644
index 00000000..977d9b4d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pinmux.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _PINMUX_H_
+#define _PINMUX_H_
+
+#include "../utils/types.h"
+
+/*! APB MISC registers. */
+#define APB_MISC_GP_SDMMC1_CLK_LPBK_CONTROL 0x8D4
+#define APB_MISC_GP_SDMMC3_CLK_LPBK_CONTROL 0x8D8
+#define APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL 0xA98
+#define APB_MISC_GP_VGPIO_GPIO_MUX_SEL 0xB74
+
+/*! Pinmux registers. */
+#define PINMUX_AUX_SDMMC1_CLK 0x00
+#define PINMUX_AUX_SDMMC1_CMD 0x04
+#define PINMUX_AUX_SDMMC1_DAT3 0x08
+#define PINMUX_AUX_SDMMC1_DAT2 0x0C
+#define PINMUX_AUX_SDMMC1_DAT1 0x10
+#define PINMUX_AUX_SDMMC1_DAT0 0x14
+#define PINMUX_AUX_SDMMC3_CLK 0x1C
+#define PINMUX_AUX_SDMMC3_CMD 0x20
+#define PINMUX_AUX_SDMMC3_DAT0 0x24
+#define PINMUX_AUX_SDMMC3_DAT1 0x28
+#define PINMUX_AUX_SDMMC3_DAT2 0x2C
+#define PINMUX_AUX_SDMMC3_DAT3 0x30
+#define PINMUX_AUX_DMIC3_CLK 0xB4
+#define PINMUX_AUX_UART2_TX 0xF4
+#define PINMUX_AUX_UART3_TX 0x104
+#define PINMUX_AUX_WIFI_EN 0x1B4
+#define PINMUX_AUX_WIFI_RST 0x1B8
+#define PINMUX_AUX_NFC_EN 0x1D0
+#define PINMUX_AUX_NFC_INT 0x1D4
+#define PINMUX_AUX_LCD_BL_PWM 0x1FC
+#define PINMUX_AUX_LCD_BL_EN 0x200
+#define PINMUX_AUX_LCD_RST 0x204
+#define PINMUX_AUX_GPIO_PE6 0x248
+#define PINMUX_AUX_GPIO_PH6 0x250
+#define PINMUX_AUX_GPIO_PZ1 0x280
+/* Only in T210B01 */
+#define PINMUX_AUX_SDMMC2_DAT0 0x294
+#define PINMUX_AUX_SDMMC2_DAT1 0x298
+#define PINMUX_AUX_SDMMC2_DAT2 0x29C
+#define PINMUX_AUX_SDMMC2_DAT3 0x2A0
+#define PINMUX_AUX_SDMMC2_DAT4 0x2A4
+#define PINMUX_AUX_SDMMC2_DAT5 0x2A8
+#define PINMUX_AUX_SDMMC2_DAT6 0x2AC
+#define PINMUX_AUX_SDMMC2_DAT7 0x2B0
+#define PINMUX_AUX_SDMMC2_CLK 0x2B4
+#define PINMUX_AUX_SDMMC2_CMD 0x2BC
+
+/*! 0:UART-A, 1:UART-B, 3:UART-C, 3:UART-D */
+#define PINMUX_AUX_UARTX_TX(x) (0xE4 + 0x10 * (x))
+#define PINMUX_AUX_UARTX_RX(x) (0xE8 + 0x10 * (x))
+#define PINMUX_AUX_UARTX_RTS(x) (0xEC + 0x10 * (x))
+#define PINMUX_AUX_UARTX_CTS(x) (0xF0 + 0x10 * (x))
+/*! 0:GEN1, 1:GEN2, 2:GEN3, 3:CAM, 4:PWR */
+#define PINMUX_AUX_X_I2C_SCL(x) (0xBC + 8 * (x))
+#define PINMUX_AUX_X_I2C_SDA(x) (0xC0 + 8 * (x))
+
+#define PINMUX_FUNC_MASK (3 << 0)
+
+#define PINMUX_PULL_MASK (3 << 2)
+#define PINMUX_PULL_NONE (0 << 2)
+#define PINMUX_PULL_DOWN (1 << 2)
+#define PINMUX_PULL_UP (2 << 2)
+
+#define PINMUX_TRISTATE (1 << 4)
+#define PINMUX_PARKED (1 << 5)
+#define PINMUX_INPUT_ENABLE (1 << 6)
+#define PINMUX_LOCK (1 << 7)
+#define PINMUX_LPDR (1 << 8)
+#define PINMUX_HSM (1 << 9)
+
+#define PINMUX_IO_HV (1 << 10)
+#define PINMUX_OPEN_DRAIN (1 << 11)
+#define PINMUX_SCHMT (1 << 12)
+
+#define PINMUX_DRIVE_MASK (3 << 13)
+#define PINMUX_DRIVE_1X (0 << 13)
+#define PINMUX_DRIVE_2X (1 << 13)
+#define PINMUX_DRIVE_3X (2 << 13)
+#define PINMUX_DRIVE_4X (3 << 13)
+
+void pinmux_config_i2c(u32 idx);
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pmc.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pmc.h
new file mode 100644
index 00000000..8cd91616
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pmc.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (c) 2018 st4rk
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _PMC_H_
+#define _PMC_H_
+
+/*! PMC registers. */
+#define APBDEV_PMC_CNTRL 0x0
+#define PMC_CNTRL_MAIN_RST (1 << 4)
+#define APBDEV_PMC_SEC_DISABLE 0x4
+#define APBDEV_PMC_PWRGATE_TOGGLE 0x30
+#define APBDEV_PMC_PWRGATE_STATUS 0x38
+#define APBDEV_PMC_NO_IOPOWER 0x44
+#define PMC_NO_IOPOWER_SDMMC1_IO_EN (1 << 12)
+#define APBDEV_PMC_SCRATCH0 0x50
+#define APBDEV_PMC_SCRATCH1 0x54
+#define APBDEV_PMC_SCRATCH20 0xA0
+#define APBDEV_PMC_PWR_DET_VAL 0xE4
+#define PMC_PWR_DET_SDMMC1_IO_EN (1 << 12)
+#define APBDEV_PMC_DDR_PWR 0xE8
+#define APBDEV_PMC_CRYPTO_OP 0xF4
+#define PMC_CRYPTO_OP_SE_ENABLE 0
+#define PMC_CRYPTO_OP_SE_DISABLE 1
+#define APBDEV_PMC_SCRATCH33 0x120
+#define APBDEV_PMC_SCRATCH40 0x13C
+#define APBDEV_PMC_OSC_EDPD_OVER 0x1A4
+#define PMC_OSC_EDPD_OVER_OSC_CTRL_OVER 0x400000
+#define APBDEV_PMC_RST_STATUS 0x1B4
+#define APBDEV_PMC_IO_DPD_REQ 0x1B8
+#define APBDEV_PMC_IO_DPD2_REQ 0x1C0
+#define APBDEV_PMC_VDDP_SEL 0x1CC
+#define APBDEV_PMC_DDR_CFG 0x1D0
+#define APBDEV_PMC_SCRATCH45 0x234
+#define APBDEV_PMC_SCRATCH46 0x238
+#define APBDEV_PMC_SCRATCH49 0x244
+#define APBDEV_PMC_TSC_MULT 0x2B4
+#define APBDEV_PMC_SEC_DISABLE2 0x2C4
+#define APBDEV_PMC_WEAK_BIAS 0x2C8
+#define APBDEV_PMC_REG_SHORT 0x2CC
+#define APBDEV_PMC_SEC_DISABLE3 0x2D8
+#define APBDEV_PMC_SECURE_SCRATCH21 0x334
+#define PMC_FUSE_PRIVATEKEYDISABLE_TZ_STICKY_BIT 0x10
+#define APBDEV_PMC_SECURE_SCRATCH32 0x360
+#define APBDEV_PMC_SECURE_SCRATCH49 0x3A4
+#define APBDEV_PMC_CNTRL2 0x440
+#define PMC_CNTRL2_HOLD_CKE_LOW_EN 0x1000
+#define APBDEV_PMC_IO_DPD3_REQ 0x45C
+#define APBDEV_PMC_IO_DPD4_REQ 0x464
+#define APBDEV_PMC_UTMIP_PAD_CFG1 0x4C4
+#define APBDEV_PMC_UTMIP_PAD_CFG3 0x4CC
+#define APBDEV_PMC_DDR_CNTRL 0x4E4
+#define APBDEV_PMC_SEC_DISABLE4 0x5B0
+#define APBDEV_PMC_SEC_DISABLE5 0x5B4
+#define APBDEV_PMC_SEC_DISABLE6 0x5B8
+#define APBDEV_PMC_SEC_DISABLE7 0x5BC
+#define APBDEV_PMC_SEC_DISABLE8 0x5C0
+#define APBDEV_PMC_SCRATCH188 0x810
+#define APBDEV_PMC_SCRATCH190 0x818
+#define APBDEV_PMC_SCRATCH200 0x840
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pmc_lp0_t210.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pmc_lp0_t210.h
new file mode 100644
index 00000000..641d9f73
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/pmc_lp0_t210.h
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) 2010-2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#ifndef _TEGRA210_PMC_H_
+#define _TEGRA210_PMC_H_
+
+#include "../utils/types.h"
+
+struct tegra_pmc_regs
+{
+ u32 cntrl;
+ u32 sec_disable;
+ u32 pmc_swrst;
+ u32 wake_mask;
+ u32 wake_lvl;
+ u32 wake_status;
+ u32 sw_wake_status;
+ u32 dpd_pads_oride;
+ u32 dpd_sample;
+ u32 dpd_enable;
+ u32 pwrgate_timer_off;
+ u32 clamp_status;
+ u32 pwrgate_toggle;
+ u32 remove_clamping_cmd;
+ u32 pwrgate_status;
+ u32 pwrgood_timer;
+ u32 blink_timer;
+ u32 no_iopower;
+ u32 pwr_det;
+ u32 pwr_det_latch;
+ u32 scratch0;
+ u32 scratch1;
+ u32 scratch2;
+ u32 scratch3;
+ u32 scratch4;
+ u32 scratch5;
+ u32 scratch6;
+ u32 scratch7;
+ u32 scratch8;
+ u32 scratch9;
+ u32 scratch10;
+ u32 scratch11;
+ u32 scratch12;
+ u32 scratch13;
+ u32 scratch14;
+ u32 scratch15;
+ u32 scratch16;
+ u32 scratch17;
+ u32 scratch18;
+ u32 scratch19;
+ u32 odmdata;
+ u32 scratch21;
+ u32 scratch22;
+ u32 scratch23;
+ u32 secure_scratch0;
+ u32 secure_scratch1;
+ u32 secure_scratch2;
+ u32 secure_scratch3;
+ u32 secure_scratch4;
+ u32 secure_scratch5;
+ u32 cpupwrgood_timer;
+ u32 cpupwroff_timer;
+ u32 pg_mask;
+ u32 pg_mask_1;
+ u32 auto_wake_lvl;
+ u32 auto_wake_lvl_mask;
+ u32 wake_delay;
+ u32 pwr_det_val;
+ u32 ddr_pwr;
+ u32 usb_debounce_del;
+ u32 usb_a0;
+ u32 crypto_op;
+ u32 pllp_wb0_override;
+ u32 scratch24;
+ u32 scratch25;
+ u32 scratch26;
+ u32 scratch27;
+ u32 scratch28;
+ u32 scratch29;
+ u32 scratch30;
+ u32 scratch31;
+ u32 scratch32;
+ u32 scratch33;
+ u32 scratch34;
+ u32 scratch35;
+ u32 scratch36;
+ u32 scratch37;
+ u32 scratch38;
+ u32 scratch39;
+ u32 scratch40;
+ u32 scratch41;
+ u32 scratch42;
+ u32 bondout_mirror[3];
+ u32 sys_33v_en;
+ u32 bondout_mirror_access;
+ u32 gate;
+ u32 wake2_mask;
+ u32 wake2_lvl;
+ u32 wake2_status;
+ u32 sw_wake2_status;
+ u32 auto_wake2_lvl_mask;
+ u32 pg_mask_2;
+ u32 pg_mask_ce1;
+ u32 pg_mask_ce2;
+ u32 pg_mask_ce3;
+ u32 pwrgate_timer_ce[7];
+ u32 pcx_edpd_cntrl;
+ u32 osc_edpd_over;
+ u32 clk_out_cntrl;
+ u32 sata_pwrgt;
+ u32 sensor_ctrl;
+ u32 rst_status;
+ u32 io_dpd_req;
+ u32 io_dpd_status;
+ u32 io_dpd2_req;
+ u32 io_dpd2_status;
+ u32 sel_dpd_tim;
+ u32 vddp_sel;
+ u32 ddr_cfg;
+ u32 e_no_vttgen;
+ u8 _rsv0[4];
+ u32 pllm_wb0_override_freq;
+ u32 test_pwrgate;
+ u32 pwrgate_timer_mult;
+ u32 dis_sel_dpd;
+ u32 utmip_uhsic_triggers;
+ u32 utmip_uhsic_saved_state;
+ u32 utmip_pad_cfg;
+ u32 utmip_term_pad_cfg;
+ u32 utmip_uhsic_sleep_cfg;
+ u32 utmip_uhsic_sleepwalk_cfg;
+ u32 utmip_sleepwalk_p[3];
+ u32 uhsic_sleepwalk_p0;
+ u32 utmip_uhsic_status;
+ u32 utmip_uhsic_fake;
+ u32 bondout_mirror3[5 - 3];
+ u32 secure_scratch6;
+ u32 secure_scratch7;
+ u32 scratch43;
+ u32 scratch44;
+ u32 scratch45;
+ u32 scratch46;
+ u32 scratch47;
+ u32 scratch48;
+ u32 scratch49;
+ u32 scratch50;
+ u32 scratch51;
+ u32 scratch52;
+ u32 scratch53;
+ u32 scratch54;
+ u32 scratch55;
+ u32 scratch0_eco;
+ u32 por_dpd_ctrl;
+ u32 scratch2_eco;
+ u32 utmip_uhsic_line_wakeup;
+ u32 utmip_bias_master_cntrl;
+ u32 utmip_master_config;
+ u32 td_pwrgate_inter_part_timer;
+ u32 utmip_uhsic2_triggers;
+ u32 utmip_uhsic2_saved_state;
+ u32 utmip_uhsic2_sleep_cfg;
+ u32 utmip_uhsic2_sleepwalk_cfg;
+ u32 uhsic2_sleepwalk_p1;
+ u32 utmip_uhsic2_status;
+ u32 utmip_uhsic2_fake;
+ u32 utmip_uhsic2_line_wakeup;
+ u32 utmip_master2_config;
+ u32 utmip_uhsic_rpd_cfg;
+ u32 pg_mask_ce0;
+ u32 pg_mask3[5 - 3];
+ u32 pllm_wb0_override2;
+ u32 tsc_mult;
+ u32 cpu_vsense_override;
+ u32 glb_amap_cfg;
+ u32 sticky_bits;
+ u32 sec_disable2;
+ u32 weak_bias;
+ u32 reg_short;
+ u32 pg_mask_andor;
+ u8 _rsv1[0x2c];
+ u32 secure_scratch8; /* offset 0x300 */
+ u32 secure_scratch9;
+ u32 secure_scratch10;
+ u32 secure_scratch11;
+ u32 secure_scratch12;
+ u32 secure_scratch13;
+ u32 secure_scratch14;
+ u32 secure_scratch15;
+ u32 secure_scratch16;
+ u32 secure_scratch17;
+ u32 secure_scratch18;
+ u32 secure_scratch19;
+ u32 secure_scratch20;
+ u32 secure_scratch21;
+ u32 secure_scratch22;
+ u32 secure_scratch23;
+ u32 secure_scratch24;
+ u32 secure_scratch25;
+ u32 secure_scratch26;
+ u32 secure_scratch27;
+ u32 secure_scratch28;
+ u32 secure_scratch29;
+ u32 secure_scratch30;
+ u32 secure_scratch31;
+ u32 secure_scratch32;
+ u32 secure_scratch33;
+ u32 secure_scratch34;
+ u32 secure_scratch35;
+ u32 secure_scratch36;
+ u32 secure_scratch37;
+ u32 secure_scratch38;
+ u32 secure_scratch39;
+ u32 secure_scratch40;
+ u32 secure_scratch41;
+ u32 secure_scratch42;
+ u32 secure_scratch43;
+ u32 secure_scratch44;
+ u32 secure_scratch45;
+ u32 secure_scratch46;
+ u32 secure_scratch47;
+ u32 secure_scratch48;
+ u32 secure_scratch49;
+ u32 secure_scratch50;
+ u32 secure_scratch51;
+ u32 secure_scratch52;
+ u32 secure_scratch53;
+ u32 secure_scratch54;
+ u32 secure_scratch55;
+ u32 secure_scratch56;
+ u32 secure_scratch57;
+ u32 secure_scratch58;
+ u32 secure_scratch59;
+ u32 secure_scratch60;
+ u32 secure_scratch61;
+ u32 secure_scratch62;
+ u32 secure_scratch63;
+ u32 secure_scratch64;
+ u32 secure_scratch65;
+ u32 secure_scratch66;
+ u32 secure_scratch67;
+ u32 secure_scratch68;
+ u32 secure_scratch69;
+ u32 secure_scratch70;
+ u32 secure_scratch71;
+ u32 secure_scratch72;
+ u32 secure_scratch73;
+ u32 secure_scratch74;
+ u32 secure_scratch75;
+ u32 secure_scratch76;
+ u32 secure_scratch77;
+ u32 secure_scratch78;
+ u32 secure_scratch79;
+ u32 _rsv0x420[8];
+ u32 cntrl2; /* 0x440 */
+ u32 _rsv0x444[2];
+ u32 event_counter; /* 0x44C */
+ u32 fuse_control;
+ u32 scratch1_eco;
+ u32 _rsv0x458[1];
+ u32 io_dpd3_req; /* 0x45C */
+ u32 io_dpd3_status;
+ u32 io_dpd4_req;
+ u32 io_dpd4_status;
+ u32 _rsv0x46C[30];
+ u32 ddr_cntrl; /* 0x4E4 */
+ u32 _rsv0x4E8[70];
+ u32 scratch56; /* 0x600 */
+ u32 scratch57;
+ u32 scratch58;
+ u32 scratch59;
+ u32 scratch60;
+ u32 scratch61;
+ u32 scratch62;
+ u32 scratch63;
+ u32 scratch64;
+ u32 scratch65;
+ u32 scratch66;
+ u32 scratch67;
+ u32 scratch68;
+ u32 scratch69;
+ u32 scratch70;
+ u32 scratch71;
+ u32 scratch72;
+ u32 scratch73;
+ u32 scratch74;
+ u32 scratch75;
+ u32 scratch76;
+ u32 scratch77;
+ u32 scratch78;
+ u32 scratch79;
+ u32 scratch80;
+ u32 scratch81;
+ u32 scratch82;
+ u32 scratch83;
+ u32 scratch84;
+ u32 scratch85;
+ u32 scratch86;
+ u32 scratch87;
+ u32 scratch88;
+ u32 scratch89;
+ u32 scratch90;
+ u32 scratch91;
+ u32 scratch92;
+ u32 scratch93;
+ u32 scratch94;
+ u32 scratch95;
+ u32 scratch96;
+ u32 scratch97;
+ u32 scratch98;
+ u32 scratch99;
+ u32 scratch100;
+ u32 scratch101;
+ u32 scratch102;
+ u32 scratch103;
+ u32 scratch104;
+ u32 scratch105;
+ u32 scratch106;
+ u32 scratch107;
+ u32 scratch108;
+ u32 scratch109;
+ u32 scratch110;
+ u32 scratch111;
+ u32 scratch112;
+ u32 scratch113;
+ u32 scratch114;
+ u32 scratch115;
+ u32 scratch116;
+ u32 scratch117;
+ u32 scratch118;
+ u32 scratch119;
+ u32 scratch120; /* 0x700 */
+ u32 scratch121;
+ u32 scratch122;
+ u32 scratch123;
+ u32 scratch124;
+ u32 scratch125;
+ u32 scratch126;
+ u32 scratch127;
+ u32 scratch128;
+ u32 scratch129;
+ u32 scratch130;
+ u32 scratch131;
+ u32 scratch132;
+ u32 scratch133;
+ u32 scratch134;
+ u32 scratch135;
+ u32 scratch136;
+ u32 scratch137;
+ u32 scratch138;
+ u32 scratch139;
+ u32 scratch140;
+ u32 scratch141;
+ u32 scratch142;
+ u32 scratch143;
+ u32 scratch144;
+ u32 scratch145;
+ u32 scratch146;
+ u32 scratch147;
+ u32 scratch148;
+ u32 scratch149;
+ u32 scratch150;
+ u32 scratch151;
+ u32 scratch152;
+ u32 scratch153;
+ u32 scratch154;
+ u32 scratch155;
+ u32 scratch156;
+ u32 scratch157;
+ u32 scratch158;
+ u32 scratch159;
+ u32 scratch160;
+ u32 scratch161;
+ u32 scratch162;
+ u32 scratch163;
+ u32 scratch164;
+ u32 scratch165;
+ u32 scratch166;
+ u32 scratch167;
+ u32 scratch168;
+ u32 scratch169;
+ u32 scratch170;
+ u32 scratch171;
+ u32 scratch172;
+ u32 scratch173;
+ u32 scratch174;
+ u32 scratch175;
+ u32 scratch176;
+ u32 scratch177;
+ u32 scratch178;
+ u32 scratch179;
+ u32 scratch180;
+ u32 scratch181;
+ u32 scratch182;
+ u32 scratch183;
+ u32 scratch184;
+ u32 scratch185;
+ u32 scratch186;
+ u32 scratch187;
+ u32 scratch188;
+ u32 scratch189;
+ u32 scratch190;
+ u32 scratch191;
+ u32 scratch192;
+ u32 scratch193;
+ u32 scratch194;
+ u32 scratch195;
+ u32 scratch196;
+ u32 scratch197;
+ u32 scratch198;
+ u32 scratch199;
+ u32 scratch200;
+ u32 scratch201;
+ u32 scratch202;
+ u32 scratch203;
+ u32 scratch204;
+ u32 scratch205;
+ u32 scratch206;
+ u32 scratch207;
+ u32 scratch208;
+ u32 scratch209;
+ u32 scratch210;
+ u32 scratch211;
+ u32 scratch212;
+ u32 scratch213;
+ u32 scratch214;
+ u32 scratch215;
+ u32 scratch216;
+ u32 scratch217;
+ u32 scratch218;
+ u32 scratch219;
+ u32 scratch220;
+ u32 scratch221;
+ u32 scratch222;
+ u32 scratch223;
+ u32 scratch224;
+ u32 scratch225;
+ u32 scratch226;
+ u32 scratch227;
+ u32 scratch228;
+ u32 scratch229;
+ u32 scratch230;
+ u32 scratch231;
+ u32 scratch232;
+ u32 scratch233;
+ u32 scratch234;
+ u32 scratch235;
+ u32 scratch236;
+ u32 scratch237;
+ u32 scratch238;
+ u32 scratch239;
+ u32 scratch240;
+ u32 scratch241;
+ u32 scratch242;
+ u32 scratch243;
+ u32 scratch244;
+ u32 scratch245;
+ u32 scratch246;
+ u32 scratch247;
+ u32 scratch248;
+ u32 scratch249;
+ u32 scratch250;
+ u32 scratch251;
+ u32 scratch252;
+ u32 scratch253;
+ u32 scratch254;
+ u32 scratch255;
+ u32 scratch256;
+ u32 scratch257;
+ u32 scratch258;
+ u32 scratch259;
+ u32 scratch260;
+ u32 scratch261;
+ u32 scratch262;
+ u32 scratch263;
+ u32 scratch264;
+ u32 scratch265;
+ u32 scratch266;
+ u32 scratch267;
+ u32 scratch268;
+ u32 scratch269;
+ u32 scratch270;
+ u32 scratch271;
+ u32 scratch272;
+ u32 scratch273;
+ u32 scratch274;
+ u32 scratch275;
+ u32 scratch276;
+ u32 scratch277;
+ u32 scratch278;
+ u32 scratch279;
+ u32 scratch280;
+ u32 scratch281;
+ u32 scratch282;
+ u32 scratch283;
+ u32 scratch284;
+ u32 scratch285;
+ u32 scratch286;
+ u32 scratch287;
+ u32 scratch288;
+ u32 scratch289;
+ u32 scratch290;
+ u32 scratch291;
+ u32 scratch292;
+ u32 scratch293;
+ u32 scratch294;
+ u32 scratch295;
+ u32 scratch296;
+ u32 scratch297;
+ u32 scratch298;
+ u32 scratch299; /* 0x9CC */
+ u32 _rsv0x9D0[50];
+ u32 secure_scratch80; /* 0xa98 */
+ u32 secure_scratch81;
+ u32 secure_scratch82;
+ u32 secure_scratch83;
+ u32 secure_scratch84;
+ u32 secure_scratch85;
+ u32 secure_scratch86;
+ u32 secure_scratch87;
+ u32 secure_scratch88;
+ u32 secure_scratch89;
+ u32 secure_scratch90;
+ u32 secure_scratch91;
+ u32 secure_scratch92;
+ u32 secure_scratch93;
+ u32 secure_scratch94;
+ u32 secure_scratch95;
+ u32 secure_scratch96;
+ u32 secure_scratch97;
+ u32 secure_scratch98;
+ u32 secure_scratch99;
+ u32 secure_scratch100;
+ u32 secure_scratch101;
+ u32 secure_scratch102;
+ u32 secure_scratch103;
+ u32 secure_scratch104;
+ u32 secure_scratch105;
+ u32 secure_scratch106;
+ u32 secure_scratch107;
+ u32 secure_scratch108;
+ u32 secure_scratch109;
+ u32 secure_scratch110;
+ u32 secure_scratch111;
+ u32 secure_scratch112;
+ u32 secure_scratch113;
+ u32 secure_scratch114;
+ u32 secure_scratch115;
+ u32 secure_scratch116;
+ u32 secure_scratch117;
+ u32 secure_scratch118;
+ u32 secure_scratch119;
+};
+
+#endif /* _TEGRA210_PMC_H_ */
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/soc/t210.h b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/t210.h
new file mode 100644
index 00000000..8e6f3cfa
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/soc/t210.h
@@ -0,0 +1,123 @@
+/*
+* Copyright (c) 2018 naehrwert
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms and conditions of the GNU General Public License,
+* version 2, as published by the Free Software Foundation.
+*
+* This program is distributed in the hope 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, see .
+*/
+
+#ifndef _T210_H_
+#define _T210_H_
+
+#include "../utils/types.h"
+
+intptr_t QueryIoMapping(u64 addr, u64 size);
+
+#define TMR_BASE 0x60005000
+#define CLOCK_BASE 0x60006000
+#define GPIO_BASE 0x6000D000
+#define GPIO_1_BASE (GPIO_BASE)
+#define GPIO_2_BASE (GPIO_BASE + 0x100)
+#define GPIO_3_BASE (GPIO_BASE + 0x200)
+#define GPIO_4_BASE (GPIO_BASE + 0x300)
+#define GPIO_5_BASE (GPIO_BASE + 0x400)
+#define GPIO_6_BASE (GPIO_BASE + 0x500)
+#define GPIO_7_BASE (GPIO_BASE + 0x600)
+#define GPIO_8_BASE (GPIO_BASE + 0x700)
+#define APB_MISC_BASE 0x70000000
+#define PINMUX_AUX_BASE 0x70003000
+#define PWM_BASE 0x7000A000
+#define RTC_BASE 0x7000E000
+#define PMC_BASE 0x7000E400
+
+#define _REG(base, off) *(vu32 *)(QueryIoMapping((u64)base, 0) + (off))
+#define _REG_IO(base, off, size) *(vu32 *)(QueryIoMapping((u64)base, size) + (off))
+
+#define RTC(off) _REG_IO(RTC_BASE, off, 0x4000)
+#define TMR(off) _REG_IO(TMR_BASE, off, 0x3FF)
+#define CLOCK(off) _REG_IO(CLOCK_BASE, off, 0x1000)
+
+#define PMC(off) _REG_IO(PMC_BASE, off, 0x1000) // ??????????
+
+#define APB_MISC(off) _REG_IO(APB_MISC_BASE, off, 0x4000)
+#define PINMUX_AUX(off) _REG_IO(APB_MISC_BASE, off + (PINMUX_AUX_BASE - APB_MISC_BASE), 0x4000)
+
+#define GPIO(off) _REG_IO(GPIO_BASE, off, 0x1000)
+#define GPIO_1(off) _REG_IO(GPIO_BASE, off + (GPIO_1_BASE - GPIO_BASE), 0x1000)
+#define GPIO_2(off) _REG_IO(GPIO_BASE, off + (GPIO_2_BASE - GPIO_BASE), 0x1000)
+#define GPIO_3(off) _REG_IO(GPIO_BASE, off + (GPIO_3_BASE - GPIO_BASE), 0x1000)
+#define GPIO_4(off) _REG_IO(GPIO_BASE, off + (GPIO_4_BASE - GPIO_BASE), 0x1000)
+#define GPIO_5(off) _REG_IO(GPIO_BASE, off + (GPIO_5_BASE - GPIO_BASE), 0x1000)
+#define GPIO_6(off) _REG_IO(GPIO_BASE, off + (GPIO_6_BASE - GPIO_BASE), 0x1000)
+#define GPIO_7(off) _REG_IO(GPIO_BASE, off + (GPIO_7_BASE - GPIO_BASE), 0x1000)
+#define GPIO_8(off) _REG_IO(GPIO_BASE, off + (GPIO_8_BASE - GPIO_BASE), 0x1000)
+
+#define HOST1X(off) _REG(HOST1X_BASE, off)
+#define BPMP_CACHE_CTRL(off) _REG(BPMP_CACHE_BASE, off)
+#define DISPLAY_A(off) _REG(DISPLAY_A_BASE, off)
+#define DSI(off) _REG(DSI_BASE, off)
+#define VIC(off) _REG(VIC_BASE, off)
+#define TSEC(off) _REG(TSEC_BASE, off)
+#define SOR1(off) _REG(SOR1_BASE, off)
+#define FLOW_CTLR(off) _REG(FLOW_CTLR_BASE, off)
+#define SYSREG(off) _REG(SYSREG_BASE, off)
+#define SB(off) _REG(SB_BASE, off)
+#define EXCP_VEC(off) _REG(EXCP_VEC_BASE, off)
+#define PWM(off) _REG(PWM_BASE, off)
+#define SYSCTR0(off) _REG(SYSCTR0_BASE, off)
+#define FUSE(off) _REG(FUSE_BASE, off)
+#define KFUSE(off) _REG(KFUSE_BASE, off)
+#define SE(off) _REG(SE_BASE, off)
+#define MC(off) _REG(MC_BASE, off)
+#define EMC(off) _REG(EMC_BASE, off)
+#define MIPI_CAL(off) _REG(MIPI_CAL_BASE, off)
+#define I2S(off) _REG(I2S_BASE, off)
+#define CL_DVFS(off) _REG(CL_DVFS_BASE, off)
+#define TEST_REG(off) _REG(0x0, off)
+
+/*! Misc registers. */
+#define APB_MISC_PP_STRAPPING_OPT_A 0x08
+#define APB_MISC_PP_PINMUX_GLOBAL 0x40
+#define APB_MISC_GP_LCD_BL_PWM_CFGPADCTRL 0xA34
+#define APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL 0xA98
+#define APB_MISC_GP_EMMC2_PAD_CFGPADCTRL 0xA9C
+#define APB_MISC_GP_EMMC4_PAD_CFGPADCTRL 0xAB4
+#define APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL 0xABC
+#define APB_MISC_GP_WIFI_EN_CFGPADCTRL 0xB64
+#define APB_MISC_GP_WIFI_RST_CFGPADCTRL 0xB68
+
+/*! System registers. */
+#define AHB_ARBITRATION_XBAR_CTRL 0xE0
+#define AHB_AHB_SPARE_REG 0x110
+
+/*! RTC registers. */
+#define APBDEV_RTC_SECONDS 0x8
+#define APBDEV_RTC_SHADOW_SECONDS 0xC
+#define APBDEV_RTC_MILLI_SECONDS 0x10
+
+/*! TMR registers. */
+#define TIMERUS_CNTR_1US (0x10 + 0x0)
+#define TIMERUS_USEC_CFG (0x10 + 0x4)
+#define TIMER_TMR9_TMR_PTV 0x80
+#define TIMER_EN (1 << 31)
+#define TIMER_PER_EN (1 << 30)
+#define TIMER_WDT4_CONFIG (0x100 + 0x80)
+#define TIMER_SRC(TMR) (TMR & 0xF)
+#define TIMER_PER(PER) ((PER & 0xFF) << 4)
+#define TIMER_SYSRESET_EN (1 << 14)
+#define TIMER_PMCRESET_EN (1 << 15)
+#define TIMER_WDT4_COMMAND (0x108 + 0x80)
+#define TIMER_START_CNT (1 << 0)
+#define TIMER_CNT_DISABLE (1 << 1)
+#define TIMER_WDT4_UNLOCK_PATTERN (0x10C + 0x80)
+#define TIMER_MAGIC_PTRN 0xC45A
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/utils/fatal.c b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/fatal.c
new file mode 100644
index 00000000..0c912b60
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/fatal.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#include
+#include "fatal.h"
+
+void __attribute__((noreturn)) fatal_abort(enum FatalReason abortReason)
+{
+ atmosphere_fatal_error_ctx error_ctx;
+ memset(&error_ctx, 0, sizeof(atmosphere_fatal_error_ctx));
+
+ // Basic error storage for Atmosphere
+ // TODO: Maybe include a small reboot2payload stub?
+ error_ctx.magic = ATMOSPHERE_REBOOT_TO_FATAL_MAGIC;
+ error_ctx.title_id = 0x0100000000000000; // FS
+ error_ctx.error_desc = abortReason;
+
+ // Copy fatal context
+ smcCopyToIram(ATMOSPHERE_FATAL_ERROR_ADDR, &error_ctx, sizeof(atmosphere_fatal_error_ctx));
+
+ // Reboot to RCM
+ smcRebootToRcm();
+
+ while (true)
+ ; // Should never be reached
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/utils/fatal.h b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/fatal.h
new file mode 100644
index 00000000..977c713e
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/fatal.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019 m4xw
+ * Copyright (c) 2019 Atmosphere-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#pragma once
+#include "../nx/smc.h"
+
+enum FatalReason
+{
+ Fatal_InitMMC = 0,
+ Fatal_InitSD,
+ Fatal_InvalidAccessor,
+ Fatal_ReadNoAccessor,
+ Fatal_WriteNoAccessor,
+ Fatal_IoMappingLegacy,
+ Fatal_UnknownVersion,
+ Fatal_BadResult,
+ Fatal_GetConfig,
+ Fatal_OpenAccessor,
+ Fatal_CloseAccessor,
+ Fatal_IoMapping,
+ Fatal_FatfsMount,
+ Fatal_FatfsFileOpen,
+ Fatal_FatfsMemExhaustion,
+ Fatal_InvalidEnum,
+ Fatal_Max
+};
+
+#define AMS_FATAL_ERROR_MAX_STACKTRACE 0x20
+#define AMS_FATAL_ERROR_MAX_STACKDUMP 0x100
+
+/* Atmosphere reboot-to-fatal-error. */
+typedef struct
+{
+ uint32_t magic;
+ uint32_t error_desc;
+ uint64_t title_id;
+ union {
+ uint64_t gprs[32];
+ struct
+ {
+ uint64_t _gprs[29];
+ uint64_t fp;
+ uint64_t lr;
+ uint64_t sp;
+ };
+ };
+ uint64_t pc;
+ uint64_t module_base;
+ uint32_t pstate;
+ uint32_t afsr0;
+ uint32_t afsr1;
+ uint32_t esr;
+ uint64_t far;
+ uint64_t report_identifier; /* Normally just system tick. */
+ uint64_t stack_trace_size;
+ uint64_t stack_dump_size;
+ uint64_t stack_trace[AMS_FATAL_ERROR_MAX_STACKTRACE];
+ uint8_t stack_dump[AMS_FATAL_ERROR_MAX_STACKDUMP];
+} atmosphere_fatal_error_ctx;
+
+/* "AFE1" */
+#define ATMOSPHERE_REBOOT_TO_FATAL_MAGIC 0x31454641
+/* "AFE0" */
+#define ATMOSPHERE_REBOOT_TO_FATAL_MAGIC_0 0x30454641
+
+#define ATMOSPHERE_FATAL_ERROR_ADDR 0x4003E000
+#define ATMOSPHERE_FATAL_ERROR_CONTEXT ((volatile atmosphere_fatal_error_ctx *)(ATMOSPHERE_FATAL_ERROR_ADDR))
+
+void __attribute__((noreturn)) fatal_abort(enum FatalReason abortReason);
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/utils/types.h b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/types.h
new file mode 100644
index 00000000..42bb1fad
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/types.h
@@ -0,0 +1,95 @@
+/*
+* Copyright (c) 2018 naehrwert
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms and conditions of the GNU General Public License,
+* version 2, as published by the Free Software Foundation.
+*
+* This program is distributed in the hope 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, see .
+*/
+
+#ifndef _TYPES_H_
+#define _TYPES_H_
+
+#include
+#include
+
+#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define OFFSET_OF(t, m) ((u32)&((t *)NULL)->m)
+#define CONTAINER_OF(mp, t, mn) ((t *)((u32)mp - OFFSET_OF(t, mn)))
+
+/// Creates a bitmask from a bit number.
+#ifndef BIT
+#define BIT(n) (1U<<(n))
+#endif
+
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int16_t SHORT;
+typedef int32_t s32;
+typedef int32_t INT;
+typedef int64_t LONG;
+typedef int64_t s64;
+typedef uint8_t u8;
+typedef uint8_t BYTE;
+typedef uint16_t u16;
+typedef uint16_t WORD;
+typedef uint16_t WCHAR;
+typedef uint32_t u32;
+typedef uint32_t UINT;
+typedef uint32_t DWORD;
+typedef uint64_t QWORD;
+typedef uint64_t u64;
+typedef volatile uint8_t vu8;
+typedef volatile uint16_t vu16;
+typedef volatile uint32_t vu32;
+
+typedef u32 Handle; ///< Kernel object handle.
+typedef u32 Result; ///< Function error code result type.
+
+#define INVALID_HANDLE ((Handle) 0)
+#define CUR_PROCESS_HANDLE ((Handle) 0xFFFF8001)
+
+#define BOOT_CFG_AUTOBOOT_EN (1 << 0)
+#define BOOT_CFG_FROM_LAUNCH (1 << 1)
+#define BOOT_CFG_SEPT_RUN (1 << 7)
+
+#define EXTRA_CFG_KEYS (1 << 0)
+#define EXTRA_CFG_PAYLOAD (1 << 1)
+#define EXTRA_CFG_MODULE (1 << 2)
+
+typedef struct __attribute__((__packed__)) _boot_cfg_t
+{
+ u8 boot_cfg;
+ u8 autoboot;
+ u8 autoboot_list;
+ u8 extra_cfg;
+ u8 rsvd[128];
+} boot_cfg_t;
+
+typedef struct __attribute__((__packed__)) _ipl_ver_meta_t
+{
+ u32 magic;
+ u32 version;
+ u16 rsvd0;
+ u16 rsvd1;
+} ipl_ver_meta_t;
+
+typedef struct __attribute__((__packed__)) _reloc_meta_t
+{
+ u32 start;
+ u32 stack;
+ u32 end;
+ u32 ep;
+} reloc_meta_t;
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/utils/util.c b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/util.c
new file mode 100644
index 00000000..e2fa527a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/util.c
@@ -0,0 +1,119 @@
+/*
+* Copyright (c) 2018 naehrwert
+* Copyright (C) 2018 CTCaer
+* Copyright (C) 2019 M4xw
+* Copyright (c) 2019 Atmosphere-NX
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms and conditions of the GNU General Public License,
+* version 2, as published by the Free Software Foundation.
+*
+* This program is distributed in the hope 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, see .
+*/
+
+#include "util.h"
+#include "fatal.h"
+#include "types.h"
+#include "../nx/counter.h"
+#include "../nx/svc.h"
+#include "../soc/t210.h"
+
+typedef struct _io_mapping_t
+{
+ u64 phys;
+ u64 virt;
+ u64 size;
+} io_mapping_t;
+static io_mapping_t io_mapping_list[10] = {0}; // Max 10 Mappings
+#define IO_MAPPING_COUNT (sizeof(io_mapping_list) / sizeof(io_mapping_t))
+
+static inline uintptr_t _GetIoMapping(u64 io_addr, u64 io_size)
+{
+ u64 vaddr;
+ u64 aligned_addr = (io_addr & ~0xFFFul);
+ u64 aligned_size = io_size + (io_addr - aligned_addr);
+
+ if (emuMMC_ctx.fs_ver >= FS_VER_10_0_0) {
+ u64 out_size;
+ if (svcQueryIoMapping(&vaddr, &out_size, aligned_addr, aligned_size) != 0) {
+ fatal_abort(Fatal_IoMapping);
+ }
+ } else {
+ if (svcLegacyQueryIoMapping(&vaddr, aligned_addr, aligned_size) != 0) {
+ fatal_abort(Fatal_IoMappingLegacy);
+ }
+ }
+
+ return (uintptr_t)(vaddr + (io_addr - aligned_addr));
+}
+
+intptr_t QueryIoMapping(u64 addr, u64 size)
+{
+ for (int i = 0; i < IO_MAPPING_COUNT; i++)
+ {
+ if (io_mapping_list[i].phys == addr && io_mapping_list[i].size == size)
+ {
+ return io_mapping_list[i].virt;
+ }
+ }
+
+ u64 ioMap = _GetIoMapping(addr, size);
+
+ for (int i = 0; i < IO_MAPPING_COUNT; i++)
+ {
+ if (io_mapping_list[i].phys == 0 && io_mapping_list[i].virt == 0 && io_mapping_list[i].size == 0) // First empty
+ {
+ io_mapping_list[i].virt = ioMap;
+ io_mapping_list[i].phys = addr;
+ io_mapping_list[i].size = size;
+ break;
+ }
+ }
+
+ return (intptr_t)ioMap;
+}
+
+u64 get_tmr_s()
+{
+ return armTicksToNs(armGetSystemTick()) / 1e+9;
+}
+
+u64 get_tmr_ms()
+{
+ return armTicksToNs(armGetSystemTick()) / 1000000;
+}
+
+u64 get_tmr_us()
+{
+ return armTicksToNs(armGetSystemTick()) / 1000;
+}
+
+// TODO: Figure if Sleep or Busy loop
+void msleep(u64 milliseconds)
+{
+ u64 now = get_tmr_ms();
+ while (((u64)get_tmr_ms() - now) < milliseconds)
+ ;
+ //svcSleepThread(1000000 * milliseconds);
+}
+
+// TODO: Figure if Sleep or Busy loop
+void usleep(u64 microseconds)
+{
+ u64 now = get_tmr_us();
+ while (((u64)get_tmr_us() - now) < microseconds)
+ ;
+ //svcSleepThread(1000 * microseconds);
+}
+
+void exec_cfg(u32 *base, const cfg_op_t *ops, u32 num_ops)
+{
+ for (u32 i = 0; i < num_ops; i++)
+ base[ops[i].off] = ops[i].val;
+}
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/source/utils/util.h b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/util.h
new file mode 100644
index 00000000..0c36d06a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/source/utils/util.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (C) 2018 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+#ifndef _UTIL_H_
+#define _UTIL_H_
+
+#include "types.h"
+#include "../emuMMC/emummc_ctx.h"
+
+intptr_t QueryIoMapping(u64 addr, u64 size);
+#define byte_swap_32(num) (((num >> 24) & 0xff) | ((num << 8) & 0xff0000) | \
+ ((num >> 8 )& 0xff00) | ((num << 24) & 0xff000000))
+
+typedef struct _cfg_op_t
+{
+ u32 off;
+ u32 val;
+} cfg_op_t;
+
+u64 get_tmr_us();
+u64 get_tmr_ms();
+u64 get_tmr_s();
+void usleep(u64 ticks);
+void msleep(u64 milliseconds);
+void exec_cfg(u32 *base, const cfg_op_t *ops, u32 num_ops);
+
+static inline void *armGetTls(void) {
+ void *ret;
+ __asm__ __volatile__("MRS %x[data], TPIDRRO_EL0" : [data]"=r"(ret));
+ return ret;
+}
+
+extern volatile emuMMC_ctx_t emuMMC_ctx;
+
+#endif
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/tools/fs_ida_nintendo_folder_xref_formatter.au3 b/Source/Atmosphere-MTC-Unlock/emummc/tools/fs_ida_nintendo_folder_xref_formatter.au3
new file mode 100644
index 00000000..269ffc8c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/tools/fs_ida_nintendo_folder_xref_formatter.au3
@@ -0,0 +1,52 @@
+ParseClipboard()
+
+Func FormatLineData($sLineData, $sLineDataAdd)
+ Local $lineData = StringReplace($sLineData, @TAB, " ")
+ Local $lineDataADRP, $lineDataADD
+ Local $isADRP = false
+ $lineData = StringReplace($lineData, "Up o sub_71000" , "0x")
+ $isADRP = StringInStr($lineData, "ADRP")
+
+ Local $targetRegister = StringSplit(StringSplit($lineData, "X")[2], ",")[1]
+ $lineData = StringSplit($lineData, " ")[1]
+ $lineDataAddr = StringSplit($lineData, "+")[1]
+ $lineDataAddition = StringSplit($lineData, "+")[2]
+
+ $lineDataAddr = StringReplace($lineDataAddr, "0x" , "")
+ $lineDataAddition = StringReplace($lineDataAddition, "0x" , "")
+ $addrADRP = Dec($lineDataAddr) + Dec($lineDataAddition)
+
+ $lineDataADRP = "0x" & Hex($addrADRP)
+
+ Local $lineDataADD = StringReplace($sLineDataAdd, @TAB, " ")
+ Local $isADD = false
+ $lineDataADD = StringReplace($lineDataADD, "Up o sub_71000" , "0x")
+ $isADD = StringInStr($lineData, "ADD")
+
+ $lineDataADD = StringSplit($lineDataADD, " ")[1]
+ $lineDataAddAddr = StringSplit($lineDataADD, "+")[1]
+ $lineDataAddAddition = StringSplit($lineDataADD, "+")[2]
+
+ $lineDataAddAddr = StringReplace($lineDataAddAddr, "0x" , "")
+ $lineDataAddAddition = StringReplace($lineDataAddAddition, "0x" , "")
+ $addrADD = Dec($lineDataAddAddr) + Dec($lineDataAddAddition)
+ $addrADD = $addrADD - $addrADRP
+ $lineDataADD = "0x" & Hex($addrADD)
+
+ Return @TAB & "{.opcode_reg = " & $targetRegister & ", .adrp_offset = " & $lineDataADRP & ", .add_rel_offset = " & $lineDataADD & "}, \" & @LF
+EndFunc
+
+Func ParseClipboard()
+ Local $sData = ClipGet()
+ Local $oData = ""
+ Local $sLineData = StringSplit(StringReplace($sData, @CRLF, @LF), @LF)
+ For $i = 2 to UBound($sLineData) - 2 Step 2
+ Local $lineData = FormatLineData($sLineData[$i], $sLineData[$i+1])
+ ;ConsoleWrite($lineData)
+ $oData = $oData & $lineData
+ Next
+
+ $oData = "{ \" & @LF & $oData & @TAB & "{.opcode_reg = 0, .adrp_offset = 0, .add_rel_offset = 0}, \" & @LF & "}" & @LF
+ ;ConsoleWrite($oData)
+ ClipPut($oData)
+EndFunc
diff --git a/Source/Atmosphere-MTC-Unlock/emummc/tools/kip1converter.py b/Source/Atmosphere-MTC-Unlock/emummc/tools/kip1converter.py
new file mode 100644
index 00000000..79b20154
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/emummc/tools/kip1converter.py
@@ -0,0 +1,53 @@
+# Modified kip1 conversion script, originally by jakibaki
+# Used for dev purposes, will be replaced in the future
+
+from struct import pack, unpack
+from sys import argv
+
+f = open(argv[1], "rb")
+
+header_start = f.read(0x20)
+
+section_names = [".text", ".rodata", ".data", ".bss"]
+
+sections = []
+for i in range(6):
+ section_bytes = f.read(0x10)
+ section = {}
+
+ if i < len(section_names):
+ section["Name"] = section_names[i]
+
+ section["OutOffset"], section["DecompressedSize"], section["CompressedSize"], section["Attribute"] = unpack("IIII", section_bytes)
+ sections.append(section)
+ print(section)
+
+assert (sections[3]["OutOffset"] + sections[3]["DecompressedSize"]) % 0x1000 == 0
+
+kernel_caps = []
+for i in range(0x20):
+ val, = unpack("I", f.read(4))
+ kernel_caps.append(val)
+
+f.seek(0x100)
+
+for i in range(3):
+ section = sections[i]
+ section["Buffer"] = f.read(section["DecompressedSize"])
+print(f.read())
+
+f.close()
+
+f = open(argv[2], "wb")
+
+for i in range(3):
+ section = sections[i]
+ f.seek(section["OutOffset"])
+ f.write(section["Buffer"])
+
+f.seek(sections[3]["OutOffset"])
+f.write(b'\0' * sections[3]["DecompressedSize"])
+
+caps = open("emummc.caps", "wb")
+for i in range(0x20):
+ caps.write(pack("I", kernel_caps[i]))
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/Makefile
new file mode 100644
index 00000000..87603f7c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/Makefile
@@ -0,0 +1,41 @@
+ATMOSPHERE_BUILD_CONFIGS :=
+all: nx_release
+
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+
+define ATMOSPHERE_ADD_TARGET
+
+ATMOSPHERE_BUILD_CONFIGS += $(strip $1)
+
+$(strip $1):
+ @echo "Building $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/exosphere.mk ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+clean-$(strip $1):
+ @echo "Cleaning $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/exosphere.mk clean ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+endef
+
+define ATMOSPHERE_ADD_TARGETS
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_release, release, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4)" \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, debug, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, audit, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 \
+))
+
+endef
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, nx-hac-001, arm-cortex-a57,))
+
+clean: $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS),clean-$(config))
+
+.PHONY: all clean $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS), $(config) clean-$(config))
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/exosphere.mk b/Source/Atmosphere-MTC-Unlock/exosphere/exosphere.mk
new file mode 100644
index 00000000..b5608b41
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/exosphere.mk
@@ -0,0 +1,75 @@
+#---------------------------------------------------------------------------------
+# pull in common atmosphere configuration
+#---------------------------------------------------------------------------------
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+include $(CURRENT_DIRECTORY)/../libraries/config/common.mk
+
+all: $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/exosphere.bin $(CURRENT_DIRECTORY)/warmboot/$(ATMOSPHERE_BOOT_OUT_DIR)/warmboot.bin $(CURRENT_DIRECTORY)/mariko_fatal/$(ATMOSPHERE_OUT_DIR)/mariko_fatal.bin
+
+$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/exosphere.bin: $(CURRENT_DIRECTORY)/loader_stub/$(ATMOSPHERE_OUT_DIR)/loader_stub.bin $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)
+ @cp $(CURRENT_DIRECTORY)/loader_stub/$(ATMOSPHERE_OUT_DIR)/loader_stub.bin $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/exosphere.bin
+ @printf LENY >> $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/exosphere.bin
+ @echo "Built exosphere.bin..."
+
+$(CURRENT_DIRECTORY)/loader_stub/$(ATMOSPHERE_OUT_DIR)/loader_stub.bin: check_loader_stub
+ @$(SILENTCMD)echo "Checked loader stub."
+
+$(CURRENT_DIRECTORY)/program/$(ATMOSPHERE_OUT_DIR)/program.lz4: check_program
+ @$(SILENTCMD)echo "Checked program."
+
+$(CURRENT_DIRECTORY)/warmboot/$(ATMOSPHERE_BOOT_OUT_DIR)/warmboot.bin: check_warmboot
+ @$(SILENTCMD)echo "Checked warmboot."
+
+$(CURRENT_DIRECTORY)/mariko_fatal/$(ATMOSPHERE_OUT_DIR)/mariko_fatal.bin: check_mariko_fatal
+ @$(SILENTCMD)echo "Checked mariko fatal."
+
+$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a: check_lib
+ @$(SILENTCMD)echo "Checked library."
+
+ifneq ($(strip $(ATMOSPHERE_LIBRARY_DIR)),$(strip $(ATMOSPHERE_BOOT_LIBRARY_DIR)))
+$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_BOOT_LIBRARY_DIR)/libexosphere.a: check_boot_lib
+ @$(SILENTCMD)echo "Checked boot library."
+endif
+
+check_loader_stub: $(CURRENT_DIRECTORY)/program/$(ATMOSPHERE_OUT_DIR)/program.lz4
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/loader_stub -f $(CURRENT_DIRECTORY)/loader_stub/loader_stub.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_EXOSPHERE_PROGRAM=1
+
+check_program: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_BOOT_LIBRARY_DIR)/libexosphere.a
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/program -f $(CURRENT_DIRECTORY)/program/program.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE=1
+
+check_mariko_fatal: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/mariko_fatal -f $(CURRENT_DIRECTORY)/mariko_fatal/mariko_fatal.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE=1
+
+check_warmboot: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_BOOT_LIBRARY_DIR)/libexosphere.a
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/warmboot -f $(CURRENT_DIRECTORY)/warmboot/warmboot.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE=1 ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+
+ifeq ($(ATMOSPHERE_CHECKED_LIBEXOSPHERE),1)
+check_lib:
+else
+check_lib:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk
+endif
+
+ifeq ($(ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE),1)
+check_boot_lib:
+else
+check_boot_lib:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+endif
+
+$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR):
+ @[ -d $@ ] || mkdir -p $@
+
+clean:
+ @echo clean ...
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/loader_stub -f $(CURRENT_DIRECTORY)/loader_stub/loader_stub.mk clean
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/program -f $(CURRENT_DIRECTORY)/program/program.mk clean
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/mariko_fatal -f $(CURRENT_DIRECTORY)/mariko_fatal/mariko_fatal.mk clean
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/warmboot -f $(CURRENT_DIRECTORY)/warmboot/warmboot.mk clean ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk clean
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk clean ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))"
+ @rm -fr $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)
+ @for i in $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done
+
+.PHONY: all clean check_lib check_boot_lib check_loader_stub check_program check_mariko_fatal check_warmboot
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/Makefile
new file mode 100644
index 00000000..54bd3168
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/Makefile
@@ -0,0 +1,41 @@
+ATMOSPHERE_BUILD_CONFIGS :=
+all: nx_release
+
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+
+define ATMOSPHERE_ADD_TARGET
+
+ATMOSPHERE_BUILD_CONFIGS += $(strip $1)
+
+$(strip $1):
+ @echo "Building $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/loader_stub.mk ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+clean-$(strip $1):
+ @echo "Cleaning $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/loader_stub.mk clean ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+endef
+
+define ATMOSPHERE_ADD_TARGETS
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_release, release, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4)" \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, debug, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, audit, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 \
+))
+
+endef
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, nx-hac-001, arm-cortex-a57,))
+
+clean: $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS),clean-$(config))
+
+.PHONY: all clean $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS), $(config) clean-$(config))
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.ld b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.ld
new file mode 100644
index 00000000..bb8239d8
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.ld
@@ -0,0 +1,182 @@
+OUTPUT_ARCH(aarch64)
+ENTRY(_start)
+
+MEMORY
+{
+ NULL : ORIGIN = 0, LENGTH = 4K
+ ldr_stub : ORIGIN = 0x040030000, LENGTH = 128K
+}
+
+SECTIONS
+{
+ /* =========== CODE section =========== */
+ PROVIDE(__start__ = 0x040030000);
+ . = __start__;
+ __code_start = . ;
+
+ .crt0 :
+ {
+ KEEP (*(.crt0 .crt0.*))
+ . = ALIGN(8);
+ } >ldr_stub
+
+ .text :
+ {
+ *(.text.unlikely .text.*_unlikely .text.unlikely.*)
+ *(.text.exit .text.exit.*)
+ *(.text.startup .text.startup.*)
+ *(.text.hot .text.hot.*)
+ *(.text .stub .text.* .gnu.linkonce.t.*)
+ . = ALIGN(8);
+ } >ldr_stub
+
+ .init :
+ {
+ KEEP( *(.init) )
+ . = ALIGN(8);
+ } >ldr_stub
+
+ .plt :
+ {
+ *(.plt)
+ *(.iplt)
+ . = ALIGN(8);
+ } >ldr_stub
+
+ .fini :
+ {
+ KEEP( *(.fini) )
+ . = ALIGN(8);
+ } >ldr_stub
+
+
+ /* =========== RODATA section =========== */
+ . = ALIGN(8);
+ __rodata_start = . ;
+
+ .rodata :
+ {
+ *(.rodata .rodata.* .gnu.linkonce.r.*)
+ . = ALIGN(8);
+ } >ldr_stub
+
+ .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >ldr_stub
+ .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >ldr_stub
+ .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >ldr_stub
+ .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >ldr_stub
+
+ .hash : { *(.hash) } >ldr_stub
+
+ /* =========== DATA section =========== */
+ . = ALIGN(8);
+ __data_start = . ;
+
+ .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >ldr_stub
+ .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >ldr_stub
+ .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >ldr_stub
+ .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >ldr_stub
+
+ .preinit_array ALIGN(8) :
+ {
+ PROVIDE (__preinit_array_start = .);
+ KEEP (*(.preinit_array))
+ PROVIDE (__preinit_array_end = .);
+ } >ldr_stub
+
+ .init_array ALIGN(8) :
+ {
+ PROVIDE (__init_array_start = .);
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array))
+ PROVIDE (__init_array_end = .);
+ } >ldr_stub
+
+ .fini_array ALIGN(8) :
+ {
+ PROVIDE (__fini_array_start = .);
+ KEEP (*(.fini_array))
+ KEEP (*(SORT(.fini_array.*)))
+ PROVIDE (__fini_array_end = .);
+ } >ldr_stub
+
+ .ctors ALIGN(8) :
+ {
+ KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+ KEEP (*(SORT(.ctors.*)))
+ KEEP (*(.ctors))
+ } >ldr_stub
+
+ .dtors ALIGN(8) :
+ {
+ KEEP (*crtbegin.o(.dtors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+ KEEP (*(SORT(.dtors.*)))
+ KEEP (*(.dtors))
+ } >ldr_stub
+
+ __got_start__ = .;
+
+ .got : { *(.got) *(.igot) } >ldr_stub
+ .got.plt : { *(.got.plt) *(.igot.plt) } >ldr_stub
+
+ __got_end__ = .;
+
+ .data ALIGN(8) :
+ {
+ *(.data .data.* .gnu.linkonce.d.*)
+ SORT(CONSTRUCTORS)
+ } >ldr_stub
+
+ __bss_start__ = .;
+ .bss ALIGN(8) :
+ {
+ *(.dynbss)
+ *(.bss .bss.* .gnu.linkonce.b.*)
+ *(COMMON)
+ . = ALIGN(16);
+ } >ldr_stub
+ __bss_end__ = .;
+
+ __end__ = ABSOLUTE(.) ;
+
+ /* ==================
+ ==== Metadata ====
+ ================== */
+
+ /* Discard sections that difficult post-processing */
+ /DISCARD/ : { *(.group .comment .note .interp) }
+
+ /* Stabs debugging sections. */
+ .stab 0 : { *(.stab) }
+ .stabstr 0 : { *(.stabstr) }
+ .stab.excl 0 : { *(.stab.excl) }
+ .stab.exclstr 0 : { *(.stab.exclstr) }
+ .stab.index 0 : { *(.stab.index) }
+ .stab.indexstr 0 : { *(.stab.indexstr) }
+
+ /* DWARF debug sections.
+ Symbols in the DWARF debugging sections are relative to the beginning
+ of the section so we begin them at 0. */
+
+ /* DWARF 1 */
+ .debug 0 : { *(.debug) }
+ .line 0 : { *(.line) }
+
+ /* GNU DWARF 1 extensions */
+ .debug_srcinfo 0 : { *(.debug_srcinfo) }
+ .debug_sfnames 0 : { *(.debug_sfnames) }
+
+ /* DWARF 1.1 and DWARF 2 */
+ .debug_aranges 0 : { *(.debug_aranges) }
+ .debug_pubnames 0 : { *(.debug_pubnames) }
+
+ /* DWARF 2 */
+ .debug_info 0 : { *(.debug_info) }
+ .debug_abbrev 0 : { *(.debug_abbrev) }
+ .debug_line 0 : { *(.debug_line) }
+ .debug_frame 0 : { *(.debug_frame) }
+ .debug_str 0 : { *(.debug_str) }
+ .debug_loc 0 : { *(.debug_loc) }
+ .debug_macinfo 0 : { *(.debug_macinfo) }
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.mk b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.mk
new file mode 100644
index 00000000..7bffe7dc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.mk
@@ -0,0 +1,117 @@
+#---------------------------------------------------------------------------------
+# pull in common atmosphere configuration
+#---------------------------------------------------------------------------------
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+include $(CURRENT_DIRECTORY)/../../libraries/config/templates/exosphere.mk
+
+#---------------------------------------------------------------------------------
+# no real need to edit anything past this point unless you need to add additional
+# rules for different file extensions
+#---------------------------------------------------------------------------------
+ifneq ($(__RECURSIVE__),1)
+#---------------------------------------------------------------------------------
+
+export ATMOSPHERE_TOPDIR := $(CURRENT_DIRECTORY)
+
+export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) $(CURDIR)/include \
+ $(foreach dir,$(DATA),$(CURDIR)/$(dir))
+
+CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c)
+CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp)
+SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s)
+BINFILES :=
+
+#---------------------------------------------------------------------------------
+# 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 .,_,$(subst -,_,$(BINFILES))))
+
+export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
+ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
+ -I.
+
+export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR))
+
+.PHONY: clean all check_lib check_exo_program
+
+#---------------------------------------------------------------------------------
+all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a $(CURRENT_DIRECTORY)/../program/$(ATMOSPHERE_OUT_DIR)/program.lz4
+ @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/$(notdir $(ATMOSPHERE_TOPDIR)) \
+ DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \
+ --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \
+ -f $(THIS_MAKEFILE)
+
+$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a: check_lib
+ @$(SILENTCMD)echo "Checked library."
+
+$(CURRENT_DIRECTORY)/../program/$(ATMOSPHERE_OUT_DIR)/program.lz4: check_exo_program
+ @$(SILENTCMD)echo "Checked exosphere program."
+
+ifeq ($(ATMOSPHERE_CHECKED_EXOSPHERE_PROGRAM),1)
+check_exo_program:
+else
+check_exo_program: check_lib
+ @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/../program -f $(CURRENT_DIRECTORY)/../program/program.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1
+endif
+
+ifeq ($(ATMOSPHERE_CHECKED_LIBEXOSPHERE),1)
+check_lib:
+else
+check_lib:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk
+endif
+
+$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR):
+ @[ -d $@ ] || mkdir -p $@
+
+#---------------------------------------------------------------------------------
+clean:
+ @echo clean ...
+ @rm -fr $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_OUT_DIR)
+
+#---------------------------------------------------------------------------------
+else
+
+DEPENDS := $(OFILES:.o=.d)
+
+#---------------------------------------------------------------------------------
+# main targets
+#---------------------------------------------------------------------------------
+
+$(OUTPUT).bin : $(OUTPUT).elf
+ $(OBJCOPY) -S -O binary --set-section-flags .bss=alloc,load,contents $< $@
+ @echo built ... $(notdir $@)
+
+$(OUTPUT).elf : $(OFILES)
+
+$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a
+
+secmon_loader_main.o: CXXFLAGS += --embed-dir="$(CURRENT_DIRECTORY)/../program/$(ATMOSPHERE_OUT_DIR)/"
+
+%.elf:
+ @echo linking $(notdir $@)
+ $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
+ @$(NM) -CSn $@ > $(notdir $*.lst)
+
+$(OFILES_SRC) : $(OFILES_BIN)
+
+-include $(DEPENDS)
+
+#---------------------------------------------------------------------------------------
+endif
+#---------------------------------------------------------------------------------------
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.specs b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.specs
new file mode 100644
index 00000000..7326f34d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/loader_stub.specs
@@ -0,0 +1,4 @@
+%rename link old_link
+
+*link:
+%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /loader_stub.ld) --gc-sections --nmagic
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_error.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_error.cpp
new file mode 100644
index 00000000..ba2bef58
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_error.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "secmon_loader_error.hpp"
+
+namespace ams::diag {
+
+ NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line) {
+ AMS_UNUSED(expr, func, line, file);
+ ams::secmon::loader::ErrorReboot();
+ }
+
+ NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const char *format, ...) {
+ AMS_UNUSED(expr, func, line, file, format);
+ ams::secmon::loader::ErrorReboot();
+ }
+
+ NORETURN void AbortImpl() {
+ ams::secmon::loader::ErrorReboot();
+ }
+
+}
+
+namespace ams::secmon::loader {
+
+ NORETURN void ErrorReboot() {
+ /* Invalidate the security engine. */
+ /* TODO */
+
+ /* Reboot. */
+ while (true) {
+ wdt::Reboot();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_error.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_error.hpp
new file mode 100644
index 00000000..f3a9c61e
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_error.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#pragma once
+
+namespace ams::secmon::loader {
+
+ NORETURN void ErrorReboot();
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_main.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_main.cpp
new file mode 100644
index 00000000..782d7ac1
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_main.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "secmon_loader_uncompress.hpp"
+
+namespace ams::secmon::loader {
+
+ namespace {
+
+ constexpr const u8 SecmonProgramLz4[] = {
+ #embed
+ };
+
+ constexpr const u8 SecmonBootCodeLz4[] = {
+ #embed
+ };
+
+ }
+
+ NORETURN void UncompressAndExecute() {
+ /* Uncompress the program image. */
+ Uncompress(secmon::MemoryRegionPhysicalTzramFullProgramImage.GetPointer(), secmon::MemoryRegionPhysicalTzramFullProgramImage.GetSize(), SecmonProgramLz4, sizeof(SecmonProgramLz4));
+
+ /* Copy the boot image to the end of IRAM */
+ u8 *relocated_boot_code = secmon::MemoryRegionPhysicalIramBootCodeImage.GetEndPointer() - sizeof(SecmonBootCodeLz4);
+ std::memcpy(relocated_boot_code, SecmonBootCodeLz4, sizeof(SecmonBootCodeLz4));
+
+ /* Uncompress the boot image. */
+ Uncompress(secmon::MemoryRegionPhysicalIramBootCodeImage.GetPointer(), secmon::MemoryRegionPhysicalIramBootCodeImage.GetSize(), relocated_boot_code, sizeof(SecmonBootCodeLz4));
+
+ /* Jump to the boot image. */
+ reinterpret_cast(secmon::MemoryRegionPhysicalIramBootCodeImage.GetAddress())();
+
+ /* We will never reach this point. */
+ __builtin_unreachable();
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_uncompress.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_uncompress.cpp
new file mode 100644
index 00000000..6791d2dd
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_uncompress.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "secmon_loader_uncompress.hpp"
+
+namespace ams::secmon::loader {
+
+ namespace {
+
+ class Lz4Uncompressor {
+ private:
+ const u8 *m_src;
+ size_t m_src_size;
+ size_t m_src_offset;
+ u8 *m_dst;
+ size_t m_dst_size;
+ size_t m_dst_offset;
+ public:
+ Lz4Uncompressor(void *dst, size_t dst_size, const void *src, size_t src_size) : m_src(static_cast(src)), m_src_size(src_size), m_src_offset(0), m_dst(static_cast(dst)), m_dst_size(dst_size), m_dst_offset(0) {
+ /* ... */
+ }
+
+ void Uncompress() {
+ while (true) {
+ /* Read a control byte. */
+ const u8 control = this->ReadByte();
+
+ /* Copy what it specifies we should copy. */
+ this->Copy(this->GetCopySize(control >> 4));
+
+ /* If we've exceeded size, we're done. */
+ if (m_src_offset >= m_src_size) {
+ break;
+ }
+
+ /* Read the wide copy offset. */
+ u16 wide_offset = this->ReadByte();
+ AMS_ABORT_UNLESS(this->CanRead());
+ wide_offset |= (this->ReadByte() << 8);
+
+ /* Determine the copy size. */
+ const size_t wide_copy_size = this->GetCopySize(control & 0xF);
+
+ /* Copy bytes. */
+ const size_t end_offset = m_dst_offset + wide_copy_size + 4;
+ for (size_t cur_offset = m_dst_offset; cur_offset < end_offset; m_dst_offset = (++cur_offset)) {
+ AMS_ABORT_UNLESS(wide_offset <= cur_offset);
+
+ m_dst[cur_offset] = m_dst[cur_offset - wide_offset];
+ }
+ }
+ }
+ private:
+ u8 ReadByte() {
+ return m_src[m_src_offset++];
+ }
+
+ bool CanRead() const {
+ return m_src_offset < m_src_size;
+ }
+
+ size_t GetCopySize(u8 control) {
+ size_t size = control;
+
+ if (control >= 0xF) {
+ do {
+ AMS_ABORT_UNLESS(this->CanRead());
+ control = this->ReadByte();
+ size += control;
+ } while (control == 0xFF);
+ }
+
+ return size;
+ }
+
+ void Copy(size_t size) {
+ __builtin_memcpy(m_dst + m_dst_offset, m_src + m_src_offset, size);
+ m_dst_offset += size;
+ m_src_offset += size;
+ }
+ };
+
+ }
+
+ void Uncompress(void *dst, size_t dst_size, const void *src, size_t src_size) {
+ /* Create an execute a decompressor. */
+ Lz4Uncompressor(dst, dst_size, src, src_size).Uncompress();
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_uncompress.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_uncompress.hpp
new file mode 100644
index 00000000..aac20537
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/secmon_loader_uncompress.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#pragma once
+
+namespace ams::secmon::loader {
+
+ void Uncompress(void *dst, size_t dst_size, const void *src, size_t src_size);
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/start.s b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/start.s
new file mode 100644
index 00000000..6640ac2d
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/loader_stub/source/start.s
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+/* For some reason GAS doesn't know about it, even with .cpu cortex-a57 */
+#define cpuactlr_el1 s3_1_c15_c2_0
+#define cpuectlr_el1 s3_1_c15_c2_1
+
+.macro RESET_CORE
+ mov x0, #(1 << 63)
+ msr cpuactlr_el1, x0 /* disable regional clock gating */
+ isb
+ mov x0, #3
+ msr rmr_el3, x0
+ isb
+ dsb sy
+ /* Nintendo forgot to copy-paste the branch instruction below. */
+ 1:
+ wfi
+ b 1b
+.endm
+
+.macro ERRATUM_INVALIDATE_BTB_AT_BOOT
+/* Nintendo copy-pasted https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/nvidia/tegra/common/aarch64/tegra_helpers.S#L312 */
+ /*
+ * Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+ /* The following comments are mine. */
+
+ /*
+ Enable invalidates of branch target buffer, then flush
+ the entire instruction cache at the local level, and
+ with the reg change, the branch target buffer, then disable
+ invalidates of the branch target buffer again.
+ */
+ mrs x0, cpuactlr_el1
+ orr x0, x0, #1
+ msr cpuactlr_el1, x0
+
+ dsb sy
+ isb
+ ic iallu
+ dsb sy
+ isb
+
+ mrs x0, cpuactlr_el1
+ bic x0, x0, #1
+ msr cpuactlr_el1, x0
+
+.rept 7
+ nop /* wait long enough for the write to cpuactlr_el1 to have completed */
+.endr
+
+ /* if the OS lock is set, disable it and request a warm reset */
+ mrs x0, oslsr_el1
+ ands x0, x0, #2
+ b.eq 2f
+ mov x0, xzr
+ msr oslar_el1, x0
+
+ RESET_CORE
+
+.rept 65
+ nop /* guard against speculative excecution */
+.endr
+
+ 2:
+ /* set the OS lock */
+ mov x0, #1
+ msr oslar_el1, x0
+.endm
+
+.section .crt0.text.start, "ax", %progbits
+.align 6
+.global _start
+_start:
+ /* mask all interrupts */
+ msr daifset, #0xF
+
+ /* Fixup hardware erratum */
+ ERRATUM_INVALIDATE_BTB_AT_BOOT
+
+ /* Set the stack pointer to a temporary location. */
+ ldr x20, =0x7C020000
+ mov sp, x20
+
+ /* Uncompress the program and iram boot code images. */
+ b _ZN3ams6secmon6loader20UncompressAndExecuteEv
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/Makefile
new file mode 100644
index 00000000..8cda1a75
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/Makefile
@@ -0,0 +1,41 @@
+ATMOSPHERE_BUILD_CONFIGS :=
+all: nx_release
+
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+
+define ATMOSPHERE_ADD_TARGET
+
+ATMOSPHERE_BUILD_CONFIGS += $(strip $1)
+
+$(strip $1):
+ @echo "Building $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/mariko_fatal.mk ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+clean-$(strip $1):
+ @echo "Cleaning $(strip $1)"
+ @$$(MAKE) -f $(CURRENT_DIRECTORY)/mariko_fatal.mk clean ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+endef
+
+define ATMOSPHERE_ADD_TARGETS
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_release, release, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4)" \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, debug, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, audit, $(strip $2), $(strip $3), \
+ ATMOSPHERE_BUILD_SETTINGS="$(strip $4) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 \
+))
+
+endef
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, nx-hac-001, arm-cortex-a57,))
+
+clean: $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS),clean-$(config))
+
+.PHONY: all clean $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS), $(config) clean-$(config))
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.ld b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.ld
new file mode 100644
index 00000000..a89dd410
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.ld
@@ -0,0 +1,227 @@
+OUTPUT_ARCH(aarch64)
+ENTRY(_start)
+
+MEMORY
+{
+ NULL : ORIGIN = 0, LENGTH = 4K
+ mariko_tzram : ORIGIN = 0x1F00D0000, LENGTH = 128K
+}
+
+
+SECTIONS
+{
+ /* =========== CODE section =========== */
+ PROVIDE(__start__ = ORIGIN(mariko_tzram));
+ . = __start__;
+ __code_start = . ;
+
+ .crt0 :
+ {
+ KEEP (*(.crt0 .crt0.*))
+ KEEP (fatal_crt0_cpp.o(.text*))
+ *(.crt0.rodata*)
+ fatal_crt0_cpp.o(.rodata*)
+ *(.crt0.data*)
+ fatal_crt0_cpp.o(.data*)
+ . = ALIGN(8);
+ } >mariko_tzram
+
+ .preinit_array ALIGN(8) :
+ {
+ PROVIDE (__preinit_array_start = .);
+ KEEP (*(.preinit_array))
+ PROVIDE (__preinit_array_end = .);
+ } >mariko_tzram
+
+ .init_array ALIGN(8) :
+ {
+ PROVIDE (__init_array_start = .);
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array))
+ PROVIDE (__init_array_end = .);
+ } >mariko_tzram
+
+ .fini_array ALIGN(8) :
+ {
+ PROVIDE (__fini_array_start = .);
+ KEEP (*(.fini_array))
+ KEEP (*(SORT(.fini_array.*)))
+ PROVIDE (__fini_array_end = .);
+ } >mariko_tzram
+
+ .ctors ALIGN(8) :
+ {
+ KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+ KEEP (*(SORT(.ctors.*)))
+ KEEP (*(.ctors))
+ } >mariko_tzram
+
+ .dtors ALIGN(8) :
+ {
+ KEEP (*crtbegin.o(.dtors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+ KEEP (*(SORT(.dtors.*)))
+ KEEP (*(.dtors))
+ } >mariko_tzram
+
+ .text :
+ {
+ *(.text.unlikely .text.*_unlikely .text.unlikely.*)
+ *(.text.exit .text.exit.*)
+ *(.text.startup .text.startup.*)
+ *(.text.hot .text.hot.*)
+ *(.text .stub .text.* .gnu.linkonce.t.*)
+ . = ALIGN(8);
+ } >mariko_tzram
+
+ .init :
+ {
+ KEEP( *(.init) )
+ . = ALIGN(8);
+ } >mariko_tzram
+
+ .plt :
+ {
+ *(.plt)
+ *(.iplt)
+ . = ALIGN(8);
+ } >mariko_tzram
+
+ .fini :
+ {
+ KEEP( *(.fini) )
+ . = ALIGN(8);
+ } >mariko_tzram
+
+
+ /* =========== RODATA section =========== */
+ . = ALIGN(8);
+ __rodata_start = . ;
+
+ .rodata :
+ {
+ *(.rodata .rodata.* .gnu.linkonce.r.*)
+ . = ALIGN(8);
+ } >mariko_tzram
+
+ .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >mariko_tzram
+ .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >mariko_tzram
+ .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >mariko_tzram
+ .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >mariko_tzram
+
+ .hash : { *(.hash) } >mariko_tzram
+
+ /* =========== DATA section =========== */
+ . = ALIGN(8);
+ __data_start = . ;
+
+ .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >mariko_tzram
+ .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >mariko_tzram
+ .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >mariko_tzram
+ .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >mariko_tzram
+
+ .preinit_array ALIGN(8) :
+ {
+ PROVIDE (__preinit_array_start = .);
+ KEEP (*(.preinit_array))
+ PROVIDE (__preinit_array_end = .);
+ } >mariko_tzram
+
+ .init_array ALIGN(8) :
+ {
+ PROVIDE (__init_array_start = .);
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array))
+ PROVIDE (__init_array_end = .);
+ } >mariko_tzram
+
+ .fini_array ALIGN(8) :
+ {
+ PROVIDE (__fini_array_start = .);
+ KEEP (*(.fini_array))
+ KEEP (*(SORT(.fini_array.*)))
+ PROVIDE (__fini_array_end = .);
+ } >mariko_tzram
+
+ .ctors ALIGN(8) :
+ {
+ KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+ KEEP (*(SORT(.ctors.*)))
+ KEEP (*(.ctors))
+ } >mariko_tzram
+
+ .dtors ALIGN(8) :
+ {
+ KEEP (*crtbegin.o(.dtors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+ KEEP (*(SORT(.dtors.*)))
+ KEEP (*(.dtors))
+ } >mariko_tzram
+
+ __got_start__ = .;
+
+ .got : { *(.got) *(.igot) } >mariko_tzram
+ .got.plt : { *(.got.plt) *(.igot.plt) } >mariko_tzram
+
+ __got_end__ = .;
+
+ .data ALIGN(8) :
+ {
+ *(.data .data.* .gnu.linkonce.d.*)
+ SORT(CONSTRUCTORS)
+ } >mariko_tzram
+
+ __bss_start__ = .;
+ .bss ALIGN(8) :
+ {
+ *(.dynbss)
+ *(.bss .bss.* .gnu.linkonce.b.*)
+ *(COMMON)
+ . = ALIGN(16);
+ } >mariko_tzram
+ __bss_end__ = .;
+
+ __end__ = ABSOLUTE(.) ;
+
+ /* ==================
+ ==== Metadata ====
+ ================== */
+
+ /* Discard sections that difficult post-processing */
+ /DISCARD/ : { *(.group .comment .note .interp) }
+
+ /* Stabs debugging sections. */
+ .stab 0 : { *(.stab) }
+ .stabstr 0 : { *(.stabstr) }
+ .stab.excl 0 : { *(.stab.excl) }
+ .stab.exclstr 0 : { *(.stab.exclstr) }
+ .stab.index 0 : { *(.stab.index) }
+ .stab.indexstr 0 : { *(.stab.indexstr) }
+
+ /* DWARF debug sections.
+ Symbols in the DWARF debugging sections are relative to the beginning
+ of the section so we begin them at 0. */
+
+ /* DWARF 1 */
+ .debug 0 : { *(.debug) }
+ .line 0 : { *(.line) }
+
+ /* GNU DWARF 1 extensions */
+ .debug_srcinfo 0 : { *(.debug_srcinfo) }
+ .debug_sfnames 0 : { *(.debug_sfnames) }
+
+ /* DWARF 1.1 and DWARF 2 */
+ .debug_aranges 0 : { *(.debug_aranges) }
+ .debug_pubnames 0 : { *(.debug_pubnames) }
+
+ /* DWARF 2 */
+ .debug_info 0 : { *(.debug_info) }
+ .debug_abbrev 0 : { *(.debug_abbrev) }
+ .debug_line 0 : { *(.debug_line) }
+ .debug_frame 0 : { *(.debug_frame) }
+ .debug_str 0 : { *(.debug_str) }
+ .debug_loc 0 : { *(.debug_loc) }
+ .debug_macinfo 0 : { *(.debug_macinfo) }
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.mk b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.mk
new file mode 100644
index 00000000..3e37b939
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.mk
@@ -0,0 +1,115 @@
+#---------------------------------------------------------------------------------
+# pull in common atmosphere configuration
+#---------------------------------------------------------------------------------
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+include $(CURRENT_DIRECTORY)/../../libraries/config/templates/exosphere.mk
+
+#---------------------------------------------------------------------------------
+# no real need to edit anything past this point unless you need to add additional
+# rules for different file extensions
+#---------------------------------------------------------------------------------
+ifneq ($(__RECURSIVE__),1)
+#---------------------------------------------------------------------------------
+
+export ATMOSPHERE_TOPDIR := $(CURRENT_DIRECTORY)
+
+export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) $(CURDIR)/include \
+ $(foreach dir,$(DATA),$(CURDIR)/$(dir))
+
+CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c)
+CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp)
+SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s)
+BINFILES :=
+
+#---------------------------------------------------------------------------------
+# 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 .,_,$(subst -,_,$(BINFILES))))
+
+export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
+ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
+ -I.
+
+export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR))
+
+.PHONY: clean all check_lib
+
+#---------------------------------------------------------------------------------
+all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a
+ @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/$(notdir $(ATMOSPHERE_TOPDIR)) \
+ DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \
+ --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \
+ -f $(THIS_MAKEFILE)
+
+$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a: check_lib
+ @$(SILENTCMD)echo "Checked library."
+
+ifeq ($(ATMOSPHERE_CHECKED_LIBEXOSPHERE),1)
+check_lib:
+else
+check_lib:
+ @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/libexosphere.mk
+endif
+
+$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR):
+ @[ -d $@ ] || mkdir -p $@
+
+#---------------------------------------------------------------------------------
+clean:
+ @echo clean ...
+ @rm -fr $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_OUT_DIR)
+
+#---------------------------------------------------------------------------------
+else
+
+DEPENDS := $(OFILES:.o=.d)
+
+#---------------------------------------------------------------------------------
+# main targets
+#---------------------------------------------------------------------------------
+
+$(OUTPUT).bin : $(OUTPUT).elf
+ $(OBJCOPY) -S -O binary --set-section-flags .bss=alloc,load,contents $< $@
+ @echo built ... $(notdir $@)
+
+$(OUTPUT).elf : $(OFILES)
+
+$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a
+
+%.elf:
+ @echo linking $(notdir $@)
+ $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
+ @$(NM) -CSn $@ > $(notdir $*.lst)
+
+$(OFILES_SRC) : $(HFILES_BIN)
+
+diskio.o ff.o: CFLAGS += -Wno-error -Wno-unused-parameter -Wno-implicit-fallthrough
+
+#---------------------------------------------------------------------------------
+# you need a rule like this for each extension you use as binary data
+#---------------------------------------------------------------------------------
+%.bin.o %_bin.h: %.bin
+#---------------------------------------------------------------------------------
+ @echo $(notdir $<)
+ @$(bin2o)
+
+-include $(DEPENDS)
+
+#---------------------------------------------------------------------------------------
+endif
+#---------------------------------------------------------------------------------------
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.specs b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.specs
new file mode 100644
index 00000000..dec84676
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/mariko_fatal.specs
@@ -0,0 +1,4 @@
+%rename link old_link
+
+*link:
+%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /mariko_fatal.ld) --gc-sections --nmagic
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_abort_impl.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_abort_impl.cpp
new file mode 100644
index 00000000..a049cdc0
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_abort_impl.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+
+namespace ams::diag {
+
+ NORETURN void AbortImpl() {
+ AMS_SECMON_LOG("AbortImpl was called\n");
+ AMS_LOG_FLUSH();
+ reg::Write(0x4, 0xAAAAAAAA);
+
+ /* TODO: Reboot */
+ AMS_INFINITE_LOOP();
+ }
+
+ #include
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_crt0.s b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_crt0.s
new file mode 100644
index 00000000..b4bd57bc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_crt0.s
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+.section .crt0.text.start, "ax", %progbits
+.align 6
+.global _start
+_start:
+ /* Set the stack pointer to a temporary location. */
+ ldr x20, =0x1F00FC000
+ mov sp, x20
+
+ /* Save any arguments we may have. */
+ stp x0, x1, [sp, #-16]!
+
+ /* Initialize all memory to expected state. */
+ ldr x0, =__bss_start__
+ ldr x1, =__bss_end__
+ bl _ZN3ams6secmon5fatal10InitializeEmm
+
+ /* Restore any arguments we may have. */
+ ldp x0, x1, [sp], #16
+
+ /* Jump to the fatal program. */
+ ldr x16, =_ZN3ams6secmon5fatal4MainEv
+ br x16
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_crt0_cpp.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_crt0_cpp.cpp
new file mode 100644
index 00000000..5d5ed4e8
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_crt0_cpp.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+
+extern "C" void __libc_init_array();
+
+namespace ams::secmon::fatal {
+
+ void Initialize(uintptr_t bss_start, size_t bss_end) {
+ /* Clear bss. */
+ std::memset(reinterpret_cast(bss_start), 0, bss_end - bss_start);
+
+ /* Call init array. */
+ __libc_init_array();
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_device_page_table.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_device_page_table.cpp
new file mode 100644
index 00000000..7b3d5494
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_device_page_table.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+
+namespace ams::secmon::fatal {
+
+ namespace {
+
+ /* Definitions. */
+ constexpr size_t PageDirectorySize = mmu::PageSize;
+ constexpr size_t PageTableSize = mmu::PageSize;
+ static_assert(PageDirectorySize == mmu::PageSize);
+
+ using DeviceVirtualAddress = u64;
+
+ constexpr size_t AsidCount = 0x80;
+ constexpr size_t PhysicalAddressBits = 34;
+ constexpr size_t PhysicalAddressMask = (1ul << PhysicalAddressBits) - 1ul;
+ constexpr size_t DeviceVirtualAddressBits = 34;
+ constexpr size_t DeviceVirtualAddressMask = (1ul << DeviceVirtualAddressBits) - 1ul;
+
+ constexpr size_t DevicePageBits = 12;
+ constexpr size_t DevicePageSize = (1ul << DevicePageBits);
+ static_assert(DevicePageSize == mmu::PageSize);
+
+ constexpr size_t DeviceLargePageBits = 22;
+ constexpr size_t DeviceLargePageSize = (1ul << DeviceLargePageBits);
+ static_assert(DeviceLargePageSize % DevicePageSize == 0);
+
+ constexpr size_t DeviceRegionBits = 32;
+ constexpr size_t DeviceRegionSize = (1ul << DeviceRegionBits);
+ static_assert(DeviceRegionSize % DeviceLargePageSize == 0);
+
+ constexpr const uintptr_t MC = secmon::MemoryRegionVirtualDeviceMemoryController.GetAddress();
+
+ constexpr size_t TableCount = (1ul << DeviceVirtualAddressBits) / DeviceRegionSize;
+
+ consteval u32 EncodeAsidRegisterValue(u8 asid) {
+ u32 value = 0x80000000u;
+ for (size_t t = 0; t < TableCount; t++) {
+ value |= (asid << (BITSIZEOF(u8) * t));
+ }
+ return value;
+ }
+
+ constexpr u8 SdmmcAsid = 1;
+ constexpr u8 DcAsid = 2;
+
+ constexpr u32 SdmmcAsidRegisterValue = EncodeAsidRegisterValue(SdmmcAsid);
+ constexpr u32 DcAsidRegisterValue = EncodeAsidRegisterValue(DcAsid);
+
+ constexpr dd::PhysicalAddress DcL0PageTablePhysical = MemoryRegionPhysicalDramDcL0DevicePageTable.GetAddress();
+ constexpr dd::PhysicalAddress SdmmcL0PageTablePhysical = MemoryRegionPhysicalDramSdmmc1L0DevicePageTable.GetAddress();
+ constexpr dd::PhysicalAddress SdmmcL1PageTablePhysical = MemoryRegionPhysicalDramSdmmc1L1DevicePageTable.GetAddress();
+
+ /* Types. */
+ class EntryBase {
+ protected:
+ enum Bit : u32 {
+ Bit_Table = 28,
+ Bit_NonSecure = 29,
+ Bit_Writeable = 30,
+ Bit_Readable = 31,
+ };
+ private:
+ u32 m_value;
+ protected:
+ constexpr ALWAYS_INLINE u32 SelectBit(Bit n) const {
+ return (m_value & (1u << n));
+ }
+
+ constexpr ALWAYS_INLINE bool GetBit(Bit n) const {
+ return this->SelectBit(n) != 0;
+ }
+
+ static constexpr ALWAYS_INLINE u32 EncodeBit(Bit n, bool en) {
+ return en ? (1u << n) : 0;
+ }
+
+ static constexpr ALWAYS_INLINE u32 EncodeValue(bool r, bool w, bool ns, dd::PhysicalAddress addr, bool t) {
+ return EncodeBit(Bit_Readable, r) | EncodeBit(Bit_Writeable, w) | EncodeBit(Bit_NonSecure, ns) | EncodeBit(Bit_Table, t) | static_cast(addr >> DevicePageBits);
+ }
+
+ ALWAYS_INLINE void SetValue(u32 v) {
+ /* Prevent re-ordering around entry modifications. */
+ __asm__ __volatile__("" ::: "memory");
+ m_value = v;
+ __asm__ __volatile__("" ::: "memory");
+ }
+ public:
+ static constexpr ALWAYS_INLINE u32 EncodePtbDataValue(dd::PhysicalAddress addr) {
+ return EncodeValue(true, true, true, addr, false);
+ }
+ public:
+ constexpr ALWAYS_INLINE bool IsNonSecure() const { return this->GetBit(Bit_NonSecure); }
+ constexpr ALWAYS_INLINE bool IsWriteable() const { return this->GetBit(Bit_Writeable); }
+ constexpr ALWAYS_INLINE bool IsReadable() const { return this->GetBit(Bit_Readable); }
+ constexpr ALWAYS_INLINE bool IsValid() const { return this->IsWriteable() || this->IsReadable(); }
+
+ constexpr ALWAYS_INLINE u32 GetAttributes() const { return this->SelectBit(Bit_NonSecure) | this->SelectBit(Bit_Writeable) | this->SelectBit(Bit_Readable); }
+
+ constexpr ALWAYS_INLINE dd::PhysicalAddress GetPhysicalAddress() const { return (static_cast(m_value) << DevicePageBits) & PhysicalAddressMask; }
+
+ ALWAYS_INLINE void Invalidate() { this->SetValue(0); }
+ };
+
+ class PageDirectoryEntry : public EntryBase {
+ public:
+ constexpr ALWAYS_INLINE bool IsTable() const { return this->GetBit(Bit_Table); }
+
+ ALWAYS_INLINE void SetTable(bool r, bool w, bool ns, dd::PhysicalAddress addr) {
+ AMS_ASSERT(util::IsAligned(addr, DevicePageSize));
+ this->SetValue(EncodeValue(r, w, ns, addr, true));
+ }
+
+ ALWAYS_INLINE void SetLargePage(bool r, bool w, bool ns, dd::PhysicalAddress addr) {
+ AMS_ASSERT(util::IsAligned(addr, DeviceLargePageSize));
+ this->SetValue(EncodeValue(r, w, ns, addr, false));
+ }
+ };
+
+ class PageTableEntry : public EntryBase {
+ public:
+ ALWAYS_INLINE void SetPage(bool r, bool w, bool ns, dd::PhysicalAddress addr) {
+ AMS_ASSERT(util::IsAligned(addr, DevicePageSize));
+ this->SetValue(EncodeValue(r, w, ns, addr, true));
+ }
+ };
+
+ /* Memory controller access functionality. */
+ void WriteMcRegister(size_t offset, u32 value) {
+ reg::Write(MC + offset, value);
+ }
+
+ u32 ReadMcRegister(size_t offset) {
+ return reg::Read(MC + offset);
+ }
+
+ /* Memory controller utilities. */
+ void SmmuSynchronizationBarrier() {
+ ReadMcRegister(MC_SMMU_CONFIG);
+ }
+
+ void InvalidatePtc() {
+ WriteMcRegister(MC_SMMU_PTC_FLUSH_0, 0);
+ }
+
+ void InvalidatePtc(dd::PhysicalAddress address) {
+ WriteMcRegister(MC_SMMU_PTC_FLUSH_1, (static_cast(address) >> 32));
+ WriteMcRegister(MC_SMMU_PTC_FLUSH_0, (address & 0xFFFFFFF0u) | 1u);
+ }
+
+ enum TlbFlushVaMatch : u32 {
+ TlbFlushVaMatch_All = 0,
+ TlbFlushVaMatch_Section = 2,
+ TlbFlushVaMatch_Group = 3,
+ };
+
+ static constexpr ALWAYS_INLINE u32 EncodeTlbFlushValue(bool match_asid, u8 asid, dd::PhysicalAddress address, TlbFlushVaMatch match) {
+ return ((match_asid ? 1u : 0u) << 31) | ((asid & 0x7F) << 24) | (((address & 0xFFC00000u) >> DevicePageBits)) | (match);
+ }
+
+ void InvalidateTlb() {
+ return WriteMcRegister(MC_SMMU_TLB_FLUSH, EncodeTlbFlushValue(false, 0, 0, TlbFlushVaMatch_All));
+ }
+
+ void InvalidateTlb(u8 asid) {
+ return WriteMcRegister(MC_SMMU_TLB_FLUSH, EncodeTlbFlushValue(true, asid, 0, TlbFlushVaMatch_All));
+ }
+
+ void InvalidateTlbSection(u8 asid, dd::PhysicalAddress address) {
+ return WriteMcRegister(MC_SMMU_TLB_FLUSH, EncodeTlbFlushValue(true, asid, address, TlbFlushVaMatch_Section));
+ }
+
+ void SetTable(u8 asid, dd::PhysicalAddress address) {
+ /* Write the table address. */
+ {
+ WriteMcRegister(MC_SMMU_PTB_ASID, asid);
+ WriteMcRegister(MC_SMMU_PTB_DATA, EntryBase::EncodePtbDataValue(address));
+
+ SmmuSynchronizationBarrier();
+ }
+
+ /* Ensure consistency. */
+ InvalidatePtc();
+ InvalidateTlb(asid);
+ SmmuSynchronizationBarrier();
+ }
+
+ void MapImpl(dd::PhysicalAddress phys_addr, size_t size, DeviceVirtualAddress address, u8 asid, void *l0_table, dd::PhysicalAddress l0_phys, void *l1_table, dd::PhysicalAddress l1_phys) {
+ /* Validate L0. */
+ AMS_ABORT_UNLESS(l0_table != nullptr);
+ AMS_ABORT_UNLESS(l0_phys != 0);
+
+ /* Cache permissions. */
+ const bool read = true;
+ const bool write = true;
+
+ /* Walk the directory. */
+ u64 remaining = size;
+ while (remaining > 0) {
+ const size_t l1_index = (address % DeviceRegionSize) / DeviceLargePageSize;
+ const size_t l2_index = (address % DeviceLargePageSize) / DevicePageSize;
+
+ /* Get and validate l1. */
+ PageDirectoryEntry *l1 = static_cast(l0_table);
+ AMS_ASSERT(l1 != nullptr);
+
+ /* Setup an l1 table/entry, if needed. */
+ if (!l1[l1_index].IsTable()) {
+ /* Check that an entry doesn't already exist. */
+ AMS_ASSERT(!l1[l1_index].IsValid());
+
+ /* If we can make an l1 entry, do so. */
+ if (l2_index == 0 && util::IsAligned(phys_addr, DeviceLargePageSize) && remaining >= DeviceLargePageSize) {
+ /* Set the large page. */
+ l1[l1_index].SetLargePage(read, write, true, phys_addr);
+ hw::FlushDataCache(std::addressof(l1[l1_index]), sizeof(PageDirectoryEntry));
+
+ /* Synchronize. */
+ InvalidatePtc(l0_phys + l1_index * sizeof(PageDirectoryEntry));
+ InvalidateTlbSection(asid, address);
+ SmmuSynchronizationBarrier();
+
+ /* Advance. */
+ phys_addr += DeviceLargePageSize;
+ address += DeviceLargePageSize;
+ remaining -= DeviceLargePageSize;
+ continue;
+ } else {
+ /* Make an l1 table. */
+ AMS_ABORT_UNLESS(l1_table != nullptr);
+ AMS_ABORT_UNLESS(l1_phys != 0);
+
+ /* Clear the l1 table. */
+ std::memset(l1_table, 0, mmu::PageSize);
+ hw::FlushDataCache(l1_table, mmu::PageSize);
+
+ /* Set the l1 table. */
+ l1[l1_index].SetTable(true, true, true, l1_phys);
+ hw::FlushDataCache(std::addressof(l1[l1_index]), sizeof(PageDirectoryEntry));
+
+ /* Synchronize. */
+ InvalidatePtc(l0_phys + l1_index * sizeof(PageDirectoryEntry));
+ InvalidateTlbSection(asid, address);
+ SmmuSynchronizationBarrier();
+ }
+ }
+
+ /* If we get to this point, l1 must be a table. */
+ AMS_ASSERT(l1[l1_index].IsTable());
+ AMS_ABORT_UNLESS(l1_table != nullptr);
+ AMS_ABORT_UNLESS(l1_phys != 0);
+
+ /* Map l2 entries. */
+ {
+ PageTableEntry *l2 = static_cast(l1_table);
+
+ const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index;
+ const size_t map_count = std::min(remaining_in_entry, remaining / DevicePageSize);
+
+ /* Set the entries. */
+ for (size_t i = 0; i < map_count; ++i) {
+ AMS_ASSERT(!l2[l2_index + i].IsValid());
+ l2[l2_index + i].SetPage(read, write, true, phys_addr + DevicePageSize * i);
+ }
+ hw::FlushDataCache(std::addressof(l2[l2_index]), map_count * sizeof(PageTableEntry));
+
+ /* Invalidate the page table cache. */
+ for (size_t i = util::AlignDown(l2_index, 4); i <= util::AlignDown(l2_index + map_count - 1, 4); i += 4) {
+ InvalidatePtc(l1_phys + i * sizeof(PageTableEntry));
+ }
+
+ /* Synchronize. */
+ InvalidateTlbSection(asid, address);
+ SmmuSynchronizationBarrier();
+
+ /* Advance. */
+ phys_addr += map_count * DevicePageSize;
+ address += map_count * DevicePageSize;
+ remaining -= map_count * DevicePageSize;
+ }
+ }
+ }
+
+ }
+
+ void InitializeDevicePageTableForSdmmc1() {
+ /* Configure sdmmc to use our new page table. */
+ WriteMcRegister(MC_SMMU_SDMMC1A_ASID, SdmmcAsidRegisterValue);
+ SmmuSynchronizationBarrier();
+
+ /* Ensure consistency. */
+ InvalidatePtc();
+ InvalidateTlb();
+ SmmuSynchronizationBarrier();
+
+ /* Clear the L0 Page Table. */
+ std::memset(MemoryRegionVirtualDramSdmmc1L0DevicePageTable.GetPointer(), 0, mmu::PageSize);
+ hw::FlushDataCache(MemoryRegionVirtualDramSdmmc1L0DevicePageTable.GetPointer(), mmu::PageSize);
+
+ /* Set the page table for the sdmmc asid. */
+ SetTable(SdmmcAsid, SdmmcL0PageTablePhysical);
+
+ /* Map the appropriate region into the asid. */
+ MapImpl(MemoryRegionPhysicalDramSdmmcMappedData.GetAddress(), MemoryRegionPhysicalDramSdmmcMappedData.GetSize(), MemoryRegionVirtualDramSdmmcMappedData.GetAddress(),
+ SdmmcAsid,
+ MemoryRegionVirtualDramSdmmc1L0DevicePageTable.GetPointer(), SdmmcL0PageTablePhysical,
+ MemoryRegionVirtualDramSdmmc1L1DevicePageTable.GetPointer(), SdmmcL1PageTablePhysical);
+ }
+
+ void InitializeDevicePageTableForDc() {
+ /* Configure dc to use our new page table. */
+ WriteMcRegister(MC_SMMU_DC_ASID, DcAsidRegisterValue);
+ SmmuSynchronizationBarrier();
+
+ /* Ensure consistency. */
+ InvalidatePtc();
+ InvalidateTlb();
+ SmmuSynchronizationBarrier();
+
+ /* Clear the L0 Page Table. */
+ std::memset(MemoryRegionVirtualDramDcL0DevicePageTable.GetPointer(), 0, mmu::PageSize);
+ hw::FlushDataCache(MemoryRegionVirtualDramDcL0DevicePageTable.GetPointer(), mmu::PageSize);
+
+ /* Set the page table for the dc asid. */
+ SetTable(DcAsid, DcL0PageTablePhysical);
+
+ /* Map the appropriate region into the asid. */
+ static_assert(util::IsAligned(MemoryRegionDramDcFramebuffer.GetAddress(), DeviceLargePageSize));
+ static_assert(util::IsAligned(MemoryRegionDramDcFramebuffer.GetSize(), DeviceLargePageSize));
+
+ MapImpl(MemoryRegionDramDcFramebuffer.GetAddress(), MemoryRegionDramDcFramebuffer.GetSize(), MemoryRegionDramDcFramebuffer.GetAddress(),
+ DcAsid,
+ MemoryRegionVirtualDramDcL0DevicePageTable.GetPointer(), DcL0PageTablePhysical,
+ nullptr, 0);
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_device_page_table.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_device_page_table.hpp
new file mode 100644
index 00000000..141b1856
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_device_page_table.hpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+namespace ams::secmon::fatal {
+
+ void InitializeDevicePageTableForSdmmc1();
+ void InitializeDevicePageTableForDc();
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display.cpp
new file mode 100644
index 00000000..f315abe2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display.cpp
@@ -0,0 +1,602 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_device_page_table.hpp"
+#include "fatal_registers_di.hpp"
+#include "fatal_display.hpp"
+#include "fatal_print.hpp"
+
+namespace ams::secmon::fatal {
+
+ namespace {
+
+ #include "fatal_display_config.inc"
+
+ }
+
+ namespace {
+
+ /* Helpful defines. */
+ constexpr int DsiWaitForCommandMilliSecondsMax = 250;
+ constexpr int DsiWaitForCommandCompletionMilliSeconds = 5;
+ constexpr int DsiWaitForHostControlMilliSecondsMax = 150;
+
+ constexpr inline int I2cAddressMax77620Pmic = 0x3C;
+
+ constexpr size_t GPIO_PORT3_CNF_0 = 0x200;
+ constexpr size_t GPIO_PORT3_OE_0 = 0x210;
+ constexpr size_t GPIO_PORT3_OUT_0 = 0x220;
+
+ constexpr size_t GPIO_PORT6_CNF_1 = 0x504;
+ constexpr size_t GPIO_PORT6_OE_1 = 0x514;
+ constexpr size_t GPIO_PORT6_OUT_1 = 0x524;
+
+ /* Globals. */
+ constexpr inline const uintptr_t PMC = secmon::MemoryRegionVirtualDevicePmc .GetAddress();
+ constexpr inline const uintptr_t g_disp1_regs = secmon::MemoryRegionVirtualDeviceDisp1 .GetAddress();
+ constexpr inline const uintptr_t g_dsi_regs = secmon::MemoryRegionVirtualDeviceDsi .GetAddress();
+ constexpr inline const uintptr_t g_clk_rst_regs = secmon::MemoryRegionVirtualDeviceClkRst .GetAddress();
+ constexpr inline const uintptr_t g_gpio_regs = secmon::MemoryRegionVirtualDeviceGpio .GetAddress();
+ constexpr inline const uintptr_t g_apb_misc_regs = secmon::MemoryRegionVirtualDeviceApbMisc.GetAddress();
+ constexpr inline const uintptr_t g_mipi_cal_regs = secmon::MemoryRegionVirtualDeviceMipiCal.GetAddress();
+
+ constinit u32 *g_frame_buffer = nullptr;
+
+ inline void DoRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes, size_t num_writes) {
+ for (size_t i = 0; i < num_writes; i++) {
+ reg::Write(base_address + reg_writes[i].offset, reg_writes[i].value);
+ }
+ }
+
+ inline void DoSocDependentRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes_erista, size_t num_writes_erista, const RegisterWrite *reg_writes_mariko, size_t num_writes_mariko) {
+ switch (GetSocType()) {
+ case fuse::SocType_Erista: DoRegisterWrites(base_address, reg_writes_erista, num_writes_erista); break;
+ case fuse::SocType_Mariko: DoRegisterWrites(base_address, reg_writes_mariko, num_writes_mariko); break;
+ AMS_UNREACHABLE_DEFAULT_CASE();
+ }
+ }
+
+ inline void DoSleepOrRegisterWrites(uintptr_t base_address, const SleepOrRegisterWrite *reg_writes, size_t num_writes) {
+ for (size_t i = 0; i < num_writes; i++) {
+ switch (reg_writes[i].kind) {
+ case SleepOrRegisterWriteKind_Write:
+ reg::Write(base_address + sizeof(u32) * reg_writes[i].offset, reg_writes[i].value);
+ break;
+ case SleepOrRegisterWriteKind_Sleep:
+ util::WaitMicroSeconds(reg_writes[i].offset * UINT64_C(1000));
+ break;
+ AMS_UNREACHABLE_DEFAULT_CASE();
+ }
+ }
+ }
+
+ void WaitDsiTrigger() {
+ const u32 timeout = util::GetMicroSeconds() + (DsiWaitForCommandMilliSecondsMax * 1000u);
+
+ while (true) {
+ if (util::GetMicroSeconds() >= timeout) {
+ break;
+ }
+ if (reg::Read(g_dsi_regs + sizeof(u32) * DSI_TRIGGER) == 0) {
+ break;
+ }
+ }
+
+ util::WaitMicroSeconds(DsiWaitForCommandCompletionMilliSeconds * 1000u);
+ }
+
+ void WaitDsiHostControl() {
+ const u32 timeout = util::GetMicroSeconds() + (DsiWaitForHostControlMilliSecondsMax * 1000u);
+
+ while (true) {
+ if (util::GetMicroSeconds() >= timeout) {
+ break;
+ }
+ if ((reg::Read(g_dsi_regs + sizeof(u32) * DSI_HOST_CONTROL) & DSI_HOST_CONTROL_IMM_BTA) == 0) {
+ break;
+ }
+ }
+ }
+
+ void EnableBacklightForVendor2050ForAula(int brightness) {
+ /* Enable FRAME_END_INT */
+ reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_INT_ENABLE, 2);
+
+ /* Configure DSI_LINE_TYPE as FOUR */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 1);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 9);
+
+ /* Set and wait for FRAME_END_INT */
+ reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_INT_STATUS, 2);
+ while ((reg::Read(g_disp1_regs + sizeof(u32) * DC_CMD_INT_STATUS) & 2) != 0) { /* ... */ }
+
+ /* Configure display brightness. */
+ const u32 brightness_val = ((0x7FF * brightness) / 100);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x339);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, (brightness_val & 0x700) | ((brightness_val & 0xFF) << 16) | 0x51);
+
+ /* Set and wait for FRAME_END_INT */
+ reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_INT_STATUS, 2);
+ while ((reg::Read(g_disp1_regs + sizeof(u32) * DC_CMD_INT_STATUS) & 2) != 0) { /* ... */ }
+
+ /* Set client sync point block reset. */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_INCR_SYNCPT_CNTRL, 1);
+ util::WaitMicroSeconds(300'000ul);
+
+ /* Clear client sync point block resest. */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_INCR_SYNCPT_CNTRL, 0);
+ util::WaitMicroSeconds(300'000ul);
+
+ /* Clear DSI_LINE_TYPE config. */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 0);
+
+ /* Disable FRAME_END_INT */
+ reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_INT_ENABLE, 0);
+ reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_INT_STATUS, 2);
+ }
+
+ void EnableBacklightForGeneric(int brightness) {
+ AMS_UNUSED(brightness);
+
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x1);
+ }
+
+ #define DO_REGISTER_WRITES(base_address, writes) DoRegisterWrites(base_address, writes, util::size(writes))
+ #define DO_SOC_DEPENDENT_REGISTER_WRITES(base_address, writes) DoSocDependentRegisterWrites(base_address, writes##Erista, util::size(writes##Erista), writes##Mariko, util::size(writes##Mariko))
+ #define DO_SLEEP_OR_REGISTER_WRITES(base_address, writes) DoSleepOrRegisterWrites(base_address, writes, util::size(writes))
+
+ void InitializeFrameBuffer() {
+ if (g_frame_buffer != nullptr) {
+ std::memset(g_frame_buffer, 0, FrameBufferSize);
+ hw::FlushDataCache(g_frame_buffer, FrameBufferSize);
+ } else {
+ /* Clear the frame buffer. */
+ g_frame_buffer = secmon::MemoryRegionDramDcFramebuffer.GetPointer();
+ std::memset(g_frame_buffer, 0, FrameBufferSize);
+ hw::FlushDataCache(g_frame_buffer, FrameBufferSize);
+
+ /* Attach the frame buffer to DC. */
+ InitializeDevicePageTableForDc();
+ }
+ }
+
+ [[maybe_unused]] void FinalizeFrameBuffer() {
+ /* We don't actually support finalizing the framebuffer, so do nothing here. */
+ }
+
+ constexpr const char *GetErrorDescription(u32 error_desc) {
+ switch (error_desc) {
+ case 0x100:
+ return "Instruction Abort";
+ case 0x101:
+ return "Data Abort";
+ case 0x102:
+ return "PC Misalignment";
+ case 0x103:
+ return "SP Misalignment";
+ case 0x104:
+ return "Trap";
+ case 0x106:
+ return "SError";
+ case 0x301:
+ return "Bad SVC";
+ case 0xF00:
+ return "Kernel Panic";
+ case 0xFFD:
+ return "Stack overflow";
+ case 0xFFE:
+ return "std::abort() called";
+ default:
+ return "Unknown";
+ }
+ }
+
+ void PrintSuggestedErrorFix(const ams::impl::FatalErrorContext *f_ctx) {
+ /* Try to recognize certain errors automatically, and suggest fixes for them. */
+ const char *suggestion = nullptr;
+
+ constexpr u64 ProgramIdAmsMitm = UINT64_C(0x010041544D530000);
+ constexpr u64 ProgramIdBoot = UINT64_C(0x0100000000000005);
+ if (f_ctx->error_desc == 0xFFE) {
+ if (f_ctx->program_id == ProgramIdAmsMitm) {
+ /* When a user has archive bits set improperly, attempting to create an automatic backup will fail */
+ /* to create the file path with error 0x202 */
+ if (f_ctx->gprs[0] == fs::ResultPathNotFound().GetValue()) {
+ /* When the archive bit error is occurring, it manifests as failure to create automatic backup. */
+ /* Thus, we can search the stack for the automatic backups path. */
+ const char * const automatic_backups_prefix = "automatic_backups/X" /* ..... */;
+ const int prefix_len = std::strlen(automatic_backups_prefix);
+
+ for (size_t i = 0; i + prefix_len < f_ctx->stack_dump_size; ++i) {
+ if (std::memcmp(f_ctx->stack_dump + i, automatic_backups_prefix, prefix_len) == 0) {
+ suggestion = "The atmosphere directory may improperly have archive bits set.\n"
+ "Please try running an archive bit fixer tool (for example, the one in Hekate).\n";
+ break;
+ }
+ }
+ } else if (f_ctx->gprs[0] == fs::ResultExFatUnavailable().GetValue()) {
+ /* When a user installs non-exFAT firm but has an exFAT formatted SD card, this error will */
+ /* be returned on attempt to access the SD card. */
+ suggestion = "Your console has non-exFAT firmware installed, but your SD card\n"
+ "is formatted as exFAT. Format your SD card as FAT32, or manually\n"
+ "flash exFAT firmware to package2.\n";
+ }
+ } else if (f_ctx->program_id == ProgramIdBoot) {
+ /* 9.x -> 10.x updated the API for SvcQueryIoMapping. */
+ /* This can cause the kernel to reject incorrect-ABI calls by boot when a partial update is applied */
+ /* (older kernel in package2, for some reason). */
+ for (size_t i = 0; i < 8; ++i) {
+ if (f_ctx->gprs[i] == svc::ResultNotFound().GetValue()) {
+ suggestion = "A partial update may have been improperly performed.\n"
+ "To fix, try manually flashing latest package2 to MMC.\n"
+ "\n"
+ "For help doing this, seek support in the ReSwitched or\n"
+ "Nintendo Homebrew discord servers.\n";
+ break;
+ }
+ }
+ }
+ } else if (f_ctx->error_desc == 0xF00) { /* Kernel Panic */
+ suggestion = "Please contact SciresM#0524 on Discord, or create an issue on the Atmosphere\n"
+ "GitHub issue tracker. Thank you very much for helping to test mesosphere.\n";
+ }
+
+ /* If we found a suggestion, print it. */
+ if (suggestion != nullptr) {
+ Print("%s", suggestion);
+ }
+ }
+
+ void FinalizeDisplay() {
+ /* TODO: What other configuration is needed, if any? */
+
+ /* Configure LCD pinmux tristate + passthrough. */
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_NFC_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_NFC_INT, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_PWM, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_RST, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+
+ if (fuse::GetHardwareType() == fuse::HardwareType_Aula) {
+ /* Configure LCD backlight. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x4);
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OE_1, 0x4);
+ } else {
+ /* Configure LCD power, VDD. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_CNF_0, 0x3);
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_OE_0, 0x3);
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1);
+ util::WaitMicroSeconds(10'000ul);
+
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2);
+ util::WaitMicroSeconds(10'000ul);
+
+ /* Configure LCD backlight. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x7);
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OE_1, 0x7);
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x2);
+ }
+
+ /* Disable the LCD backlight. */
+ if (GetLcdVendor() == 0x2050) {
+ /* TODO: We're not sure display is alive. How to manage this? */
+ /* This is probably incorrect backlight disable for hw-type 5. */
+ reg::ClearBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x1);
+ } else {
+ reg::ClearBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x1);
+ }
+
+ /* Disable backlight RST/Voltage. */
+ reg::ClearBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4);
+ if (GetLcdVendor() == 0x2050) {
+ util::WaitMicroSeconds(30'000ul);
+ } else {
+ util::WaitMicroSeconds(10'000ul);
+ reg::ClearBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2);
+ util::WaitMicroSeconds(10'000ul);
+ reg::ClearBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1);
+ util::WaitMicroSeconds(10'000ul);
+ }
+
+ /* Cut clock to DSI. */
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_H_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_H_SET_SET_MIPI_CAL_RST, ENABLE),
+ CLK_RST_REG_BITS_ENUM(RST_DEV_H_SET_SET_DSI_RST, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_H_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLR_CLR_CLK_ENB_MIPI_CAL, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLR_CLR_CLK_ENB_DSI, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SET_SET_HOST1X_RST, ENABLE),
+ CLK_RST_REG_BITS_ENUM(RST_DEV_L_SET_SET_DISP1_RST, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLR_CLR_CLK_ENB_HOST1X, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLR_CLR_CLK_ENB_DISP1, ENABLE));
+
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_PAD_CONTROL_0, (DSI_PAD_CONTROL_VS1_PULLDN_CLK | DSI_PAD_CONTROL_VS1_PULLDN(0xF) | DSI_PAD_CONTROL_VS1_PDIO_CLK | DSI_PAD_CONTROL_VS1_PDIO(0xF)));
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_POWER_CONTROL, 0);
+ }
+
+ }
+
+ void InitializeDisplay() {
+ /* Ensure that the display is finalized. */
+ FinalizeDisplay();
+
+ /* Setup the framebuffer. */
+ InitializeFrameBuffer();
+
+ /* Get the hardware type. */
+ const auto hw_type = fuse::GetHardwareType();
+
+ /* Turn on DSI/voltage rail. */
+ {
+ if (GetSocType() == fuse::SocType_Mariko) {
+ i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, 0x18, 0x3A);
+ i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, 0x18, 0x3A);
+ }
+ i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, 0x23, 0xD0);
+ }
+
+ /* Enable MIPI CAL, DSI, DISP1, HOST1X, UART_FST_MIPI_CAL, DSIA LP clocks. */
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_H_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_H_CLR_CLR_MIPI_CAL_RST, ENABLE),
+ CLK_RST_REG_BITS_ENUM(RST_DEV_H_CLR_CLR_DSI_RST, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_H_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_SET_SET_CLK_ENB_MIPI_CAL, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_ENB_H_SET_SET_CLK_ENB_DSI, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_CLR_CLR_HOST1X_RST, ENABLE),
+ CLK_RST_REG_BITS_ENUM(RST_DEV_L_CLR_CLR_DISP1_RST, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_SET_SET_CLK_ENB_HOST1X, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_ENB_L_SET_SET_CLK_ENB_DISP1, ENABLE));
+
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_X_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_X_SET_SET_CLK_ENB_UART_FST_MIPI_CAL, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_UART_FST_MIPI_CAL, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_UART_FST_MIPI_CAL_UART_FST_MIPI_CAL_CLK_DIVISOR, 10),
+ CLK_RST_REG_BITS_ENUM (CLK_SOURCE_UART_FST_MIPI_CAL_UART_FST_MIPI_CAL_CLK_SRC, PLLP_OUT3));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_W_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_W_SET_SET_CLK_ENB_DSIA_LP, ENABLE));
+
+ reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_DSIA_LP_DSIA_LP_CLK_DIVISOR, 10),
+ CLK_RST_REG_BITS_ENUM (CLK_SOURCE_DSIA_LP_DSIA_LP_CLK_SRC, PLLP_OUT0));
+
+ /* Set IO_DPD_REQ to DPD_OFF. */
+ reg::ReadWrite(PMC + APBDEV_PMC_IO_DPD_REQ, PMC_REG_BITS_ENUM(IO_DPD_REQ_CODE, DPD_OFF));
+ reg::ReadWrite(PMC + APBDEV_PMC_IO_DPD2_REQ, PMC_REG_BITS_ENUM(IO_DPD2_REQ_CODE, DPD_OFF));
+
+ /* Configure LCD pinmux tristate + passthrough. */
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_NFC_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_NFC_INT, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_PWM, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+ reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_RST, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE)));
+
+ if (hw_type == fuse::HardwareType_Aula) {
+ /* Configure LCD backlight. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x4);
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OE_1, 0x4);
+ } else {
+ /* Configure LCD power, VDD. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_CNF_0, 0x3);
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_OE_0, 0x3);
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1);
+ util::WaitMicroSeconds(10'000ul);
+
+ reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2);
+ util::WaitMicroSeconds(10'000ul);
+
+ /* Configure LCD backlight. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x7);
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OE_1, 0x7);
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x2);
+ }
+
+ /* Configure display interface and display. */
+ reg::Write(g_mipi_cal_regs + MIPI_CAL_MIPI_BIAS_PAD_CFG2, 0);
+ if (GetSocType() == fuse::SocType_Mariko) {
+ reg::Write(g_mipi_cal_regs + MIPI_CAL_MIPI_BIAS_PAD_CFG0, 0);
+ reg::Write(g_apb_misc_regs + APB_MISC_GP_DSI_PAD_CONTROL, 0);
+ }
+
+ /* Execute configs. */
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld01);
+ DO_SLEEP_OR_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc01);
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init01);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init02);
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init03);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init04);
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init05);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init06);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init07);
+ util::WaitMicroSeconds(10'000ul);
+
+ /* Enable backlight reset. */
+ reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4);
+ util::WaitMicroSeconds(60'000ul);
+
+ if (hw_type == fuse::HardwareType_Aula) {
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_BTA_TIMING, 0x40103);
+ } else {
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_BTA_TIMING, 0x50204);
+ }
+
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x337);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ WaitDsiTrigger();
+
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x406);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ WaitDsiTrigger();
+
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_TX_TRIG_HOST | DSI_HOST_CONTROL_IMM_BTA | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC);
+ WaitDsiHostControl();
+ util::WaitMicroSeconds(5'000ul);
+
+ /* Parse LCD vendor. */
+ {
+ u32 host_response[3];
+ for (size_t i = 0; i < util::size(host_response); i++) {
+ host_response[i] = reg::Read(g_dsi_regs + sizeof(u32) * DSI_RD_DATA);
+ }
+
+ /* The last word from host response is:
+ Bits 0-7: FAB
+ Bits 8-15: REV
+ Bits 16-23: Minor REV
+ */
+ u32 lcd_vendor;
+ if ((host_response[2] & 0xFF) == 0x10) {
+ lcd_vendor = 0;
+ } else {
+ lcd_vendor = (host_response[2] >> 8) & 0xFF00;
+ }
+ lcd_vendor = (lcd_vendor & 0xFFFFFF00) | (host_response[2] & 0xFF);
+
+ AMS_ASSERT(lcd_vendor == GetLcdVendor());
+ }
+
+ /* LCD vendor specific configuration. */
+ switch (GetLcdVendor()) {
+ case 0x10: /* Japan Display Inc screens. */
+ DO_SLEEP_OR_REGISTER_WRITES(g_dsi_regs, DisplayConfigJdiSpecificInit01);
+ break;
+ case 0xF20: /* Innolux first revision screens. */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(180'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(5'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x739);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x751548B1);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x143209);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(5'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ break;
+ case 0xF30: /* AUO first revision screens. */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(180'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(5'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x739);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x711148B1);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x143209);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(5'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ break;
+ case 0x2050: /* Unknown (hardware type 5) screen. */
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(180'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xA015);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x205315);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x339);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x51);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(5'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ break;
+ case 0x1020: /* Innolux second revision screen. */
+ case 0x1030: /* AUO second revision screen. */
+ case 0x1040: /* Unknown second revision screen. */
+ default:
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ util::WaitMicroSeconds(120'000ul);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
+ reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
+ break;
+ }
+ util::WaitMicroSeconds(20'000ul);
+
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld02);
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init08);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
+ DO_SLEEP_OR_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init09);
+
+ reg::Write(g_disp1_regs + sizeof(u32) * DC_DISP_DISP_CLOCK_CONTROL, SHIFT_CLK_DIVIDER(4));
+ DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init10);
+ util::WaitMicroSeconds(10'000ul);
+
+ /* Configure MIPI CAL. */
+ DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal01);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal02);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal03);
+ DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal04);
+ if (GetSocType() == fuse::SocType_Mariko) {
+ /* On Mariko the above configurations are executed twice, for some reason. */
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal02);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11);
+ DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal03);
+ DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal04);
+ }
+ util::WaitMicroSeconds(10'000ul);
+
+ /* Write DISP1, FrameBuffer config. */
+ DO_SLEEP_OR_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc02);
+ DO_SLEEP_OR_REGISTER_WRITES(g_disp1_regs, DisplayConfigFrameBuffer);
+ if (GetLcdVendor() != 0x2050) {
+ util::WaitMicroSeconds(35'000ul);
+ }
+ }
+
+ void ShowDisplay(const ams::impl::FatalErrorContext *f_ctx, const Result save_result) {
+ /* Initialize the console. */
+ InitializeConsole(g_frame_buffer);
+ {
+ Print("%s\n", "A fatal error occurred when running Atmosph\xe8re.");
+ Print("Program ID: %016" PRIx64 "\n", f_ctx->program_id);
+ Print("Error Desc: %s (0x%x)\n", GetErrorDescription(f_ctx->error_desc), f_ctx->error_desc);
+ Print("\n");
+
+ if (R_SUCCEEDED(save_result)) {
+ Print("Report saved to /atmosphere/fatal_errors/report_%016" PRIx64 ".bin", f_ctx->report_identifier);
+ } else {
+ Print("Failed to save report to the SD card! (%08x)\n", save_result.GetValue());
+ }
+
+ PrintSuggestedErrorFix(f_ctx);
+
+ Print("\nPress POWER to reboot.\n");
+ }
+
+ /* Ensure the device will see consistent data. */
+ hw::FlushDataCache(g_frame_buffer, FrameBufferSize);
+
+ /* Enable backlight. */
+ constexpr auto DisplayBrightness = 100;
+ if (GetLcdVendor() == 0x2050) {
+ EnableBacklightForVendor2050ForAula(DisplayBrightness);
+ } else {
+ EnableBacklightForGeneric(DisplayBrightness);
+ }
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display.hpp
new file mode 100644
index 00000000..44d7c1f2
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display.hpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+namespace ams::secmon::fatal {
+
+ constexpr inline size_t FrameBufferHeight = 768;
+ constexpr inline size_t FrameBufferWidth = 1280;
+ constexpr inline size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32);
+
+ void InitializeDisplay();
+ void ShowDisplay();
+ void ShowDisplay(const ams::impl::FatalErrorContext *f_ctx, const Result save_result);
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display_config.inc b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display_config.inc
new file mode 100644
index 00000000..013b3525
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_display_config.inc
@@ -0,0 +1,682 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+
+struct RegisterWrite {
+ u32 offset;
+ u32 value;
+};
+
+enum SleepOrRegisterWriteKind : u16 {
+ SleepOrRegisterWriteKind_Write = 0,
+ SleepOrRegisterWriteKind_Sleep = 1,
+};
+
+struct SleepOrRegisterWrite {
+ SleepOrRegisterWriteKind kind;
+ u16 offset;
+ u32 value;
+};
+
+constexpr const RegisterWrite DisplayConfigPlld01Erista[] = {
+ {CLK_RST_CONTROLLER_CLK_SOURCE_DISP1, 0x40000000},
+ {CLK_RST_CONTROLLER_PLLD_BASE, 0x4830A001},
+ {CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000020},
+ {CLK_RST_CONTROLLER_PLLD_MISC, 0x002D0AAA},
+};
+
+constexpr const RegisterWrite DisplayConfigPlld01Mariko[] = {
+ {CLK_RST_CONTROLLER_CLK_SOURCE_DISP1, 0x40000000},
+ {CLK_RST_CONTROLLER_PLLD_BASE, 0x4830A001},
+ {CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000000},
+ {CLK_RST_CONTROLLER_PLLD_MISC, 0x002DFC00},
+};
+
+constexpr const SleepOrRegisterWrite DisplayConfigDc01[] = {
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_REG_ACT_CONTROL, 0x54},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DC_MCCIF_FIFOCTRL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_MEM_HIGH_PRIORITY, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_POWER_CONTROL, PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | PW4_ENABLE | PM0_ENABLE | PM1_ENABLE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL, SYNCPT_CNTRL_NO_STALL},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_CONT_SYNCPT_VSYNC, SYNCPT_VSYNC_ENABLE | 0x9}, // 9: SYNCPT
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE | WIN_B_UPDATE | WIN_C_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DV_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ /* Setup default YUV colorspace conversion coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_YOF, 0xF0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KYRGB, 0x12A},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUR, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVR, 0x198},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUG, 0x39B},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVG, 0x32F},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUB, 0x204},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVB, 0},
+ /* End of color coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DV_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ /* Setup default YUV colorspace conversion coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_YOF, 0xF0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KYRGB, 0x12A},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUR, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVR, 0x198},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUG, 0x39B},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVG, 0x32F},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUB, 0x204},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVB, 0},
+ /* End of color coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DV_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ /* Setup default YUV colorspace conversion coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_YOF, 0xF0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KYRGB, 0x12A},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUR, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVR, 0x198},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUG, 0x39B},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVG, 0x32F},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUB, 0x204},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVB, 0},
+ /* End of color coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_COLOR_CONTROL, BASE_COLOR_SIZE_888},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_INTERFACE_CONTROL, DISP_DATA_FORMAT_DF1P1C},
+ {SleepOrRegisterWriteKind_Write, DC_COM_PIN_OUTPUT_POLARITY(1), 0x1000000},
+ {SleepOrRegisterWriteKind_Write, DC_COM_PIN_OUTPUT_POLARITY(3), 0},
+ {SleepOrRegisterWriteKind_Write, 0x4E4, 0},
+ {SleepOrRegisterWriteKind_Write, DC_COM_CRC_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE | WIN_B_UPDATE | WIN_C_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, 0x716, 0x10000FF},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, 0x716, 0x10000FF},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, 0x716, 0x10000FF},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND_OPTION0, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE | WIN_B_UPDATE | WIN_C_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ}
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init01[] = {
+ {sizeof(u32) * DSI_WR_DATA, 0x0},
+ {sizeof(u32) * DSI_INT_ENABLE, 0x0},
+ {sizeof(u32) * DSI_INT_STATUS, 0x0},
+ {sizeof(u32) * DSI_INT_MASK, 0x0},
+ {sizeof(u32) * DSI_INIT_SEQ_DATA_0, 0x0},
+ {sizeof(u32) * DSI_INIT_SEQ_DATA_1, 0x0},
+ {sizeof(u32) * DSI_INIT_SEQ_DATA_2, 0x0},
+ {sizeof(u32) * DSI_INIT_SEQ_DATA_3, 0x0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init02Erista[] = {
+ {sizeof(u32) * DSI_INIT_SEQ_DATA_15, 0x0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init02Mariko[] = {
+ {sizeof(u32) * DSI_INIT_SEQ_DATA_15_MARIKO, 0x0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init03[] = {
+ {sizeof(u32) * DSI_DCS_CMDS, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_0_LO, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_1_LO, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_2_LO, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_3_LO, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_4_LO, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_5_LO, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_0_HI, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_1_HI, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_2_HI, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_3_HI, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_4_HI, 0},
+ {sizeof(u32) * DSI_PKT_SEQ_5_HI, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init04Erista[] = {
+ /* No register writes. */
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init04Mariko[] = {
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_2, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_3, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_4, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_5_MARIKO, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_6_MARIKO, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_7_MARIKO, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init05[] = {
+ {sizeof(u32) * DSI_PAD_CONTROL_CD, 0},
+ {sizeof(u32) * DSI_SOL_DELAY, 0x18},
+ {sizeof(u32) * DSI_MAX_THRESHOLD, 0x1E0},
+ {sizeof(u32) * DSI_TRIGGER, 0},
+ {sizeof(u32) * DSI_INIT_SEQ_CONTROL, 0},
+ {sizeof(u32) * DSI_PKT_LEN_0_1, 0},
+ {sizeof(u32) * DSI_PKT_LEN_2_3, 0},
+ {sizeof(u32) * DSI_PKT_LEN_4_5, 0},
+ {sizeof(u32) * DSI_PKT_LEN_6_7, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init06[] = {
+ {sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
+ {sizeof(u32) * DSI_PHY_TIMING_2, 0x30109},
+ {sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
+ {sizeof(u32) * DSI_TIMEOUT_0, DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(0xFFFF)},
+ {sizeof(u32) * DSI_TIMEOUT_1, DSI_TIMEOUT_PR(0x765) | DSI_TIMEOUT_TA(0x2000)},
+ {sizeof(u32) * DSI_TO_TALLY, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_0, DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0)}, // Enable
+ {sizeof(u32) * DSI_POWER_CONTROL, DSI_POWER_CONTROL_ENABLE},
+ {sizeof(u32) * DSI_POWER_CONTROL, DSI_POWER_CONTROL_ENABLE},
+ {sizeof(u32) * DSI_POWER_CONTROL, 0},
+ {sizeof(u32) * DSI_POWER_CONTROL, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init07[] = {
+ {sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
+ {sizeof(u32) * DSI_PHY_TIMING_2, 0x30118},
+ {sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
+ {sizeof(u32) * DSI_TIMEOUT_0, DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(0xFFFF)},
+ {sizeof(u32) * DSI_TIMEOUT_1, DSI_TIMEOUT_PR(0x1343) | DSI_TIMEOUT_TA(0x2000)},
+ {sizeof(u32) * DSI_TO_TALLY, 0},
+ {sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC},
+ {sizeof(u32) * DSI_CONTROL, DSI_CONTROL_LANES(3) | DSI_CONTROL_HOST_ENABLE},
+ {sizeof(u32) * DSI_POWER_CONTROL, DSI_POWER_CONTROL_ENABLE},
+ {sizeof(u32) * DSI_POWER_CONTROL, DSI_POWER_CONTROL_ENABLE},
+ {sizeof(u32) * DSI_MAX_THRESHOLD, 0x40},
+ {sizeof(u32) * DSI_TRIGGER, 0},
+ {sizeof(u32) * DSI_TX_CRC, 0},
+ {sizeof(u32) * DSI_INIT_SEQ_CONTROL, 0}
+};
+
+constexpr const RegisterWrite DisplayConfigDsiPhyTimingErista[] = {
+ {sizeof(u32) * DSI_PHY_TIMING_0, 0x6070601},
+};
+
+constexpr const RegisterWrite DisplayConfigDsiPhyTimingMariko[] = {
+ {sizeof(u32) * DSI_PHY_TIMING_0, 0x6070603},
+};
+
+constexpr const SleepOrRegisterWrite DisplayConfigJdiSpecificInit01[] = {
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x9483FFB9},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xBD15},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x1939},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAAAAAAD8},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAAAAAAEB},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAAEBAAAA},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAAAAAAAA},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAAAAAAEB},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAAEBAAAA},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xAA},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x1BD15},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2739},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFD8},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2BD15},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xF39},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFD8},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xFFFFFF},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xBD15},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x6D915},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xB9},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x1105},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Sleep, 180, 0},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2905},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+};
+
+constexpr const RegisterWrite DisplayConfigPlld02Erista[] = {
+ {CLK_RST_CONTROLLER_PLLD_BASE, 0x4810c001},
+ {CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000020},
+ {CLK_RST_CONTROLLER_PLLD_MISC, 0x002D0AAA},
+};
+
+constexpr const RegisterWrite DisplayConfigPlld02Mariko[] = {
+ {CLK_RST_CONTROLLER_PLLD_BASE, 0x4810c001},
+ {CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000000},
+ {CLK_RST_CONTROLLER_PLLD_MISC, 0x002DFC00},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init08[] = {
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+};
+
+constexpr const SleepOrRegisterWrite DisplayConfigDsi01Init09[] = {
+ {SleepOrRegisterWriteKind_Write, DSI_PHY_TIMING_1, 0x40A0E05},
+ {SleepOrRegisterWriteKind_Write, DSI_PHY_TIMING_2, 0x30172},
+ {SleepOrRegisterWriteKind_Write, DSI_BTA_TIMING, 0x190A14},
+ {SleepOrRegisterWriteKind_Write, DSI_TIMEOUT_0, DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(0xA40)},
+ {SleepOrRegisterWriteKind_Write, DSI_TIMEOUT_1, DSI_TIMEOUT_PR(0x5A2F) | DSI_TIMEOUT_TA(0x2000)},
+ {SleepOrRegisterWriteKind_Write, DSI_TO_TALLY, 0},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_0_LO, 0x40000208},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_2_LO, 0x40000308},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_4_LO, 0x40000308},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_1_LO, 0x40000308},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_3_LO, 0x3F3B2B08},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_3_HI, 0x2CC},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_5_LO, 0x3F3B2B08},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_SEQ_5_HI, 0x2CC},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_LEN_0_1, 0xCE0000},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_LEN_2_3, 0x87001A2},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_LEN_4_5, 0x190},
+ {SleepOrRegisterWriteKind_Write, DSI_PKT_LEN_6_7, 0x190},
+ {SleepOrRegisterWriteKind_Write, DSI_HOST_CONTROL, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init10[] = {
+ {sizeof(u32) * DSI_TRIGGER, 0},
+ {sizeof(u32) * DSI_CONTROL, 0},
+ {sizeof(u32) * DSI_SOL_DELAY, 6},
+ {sizeof(u32) * DSI_MAX_THRESHOLD, 0x1E0},
+ {sizeof(u32) * DSI_POWER_CONTROL, DSI_POWER_CONTROL_ENABLE},
+ {sizeof(u32) * DSI_CONTROL, DSI_CONTROL_HS_CLK_CTRL | DSI_CONTROL_FORMAT(3) | DSI_CONTROL_LANES(3) | DSI_CONTROL_VIDEO_ENABLE},
+ {sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_FIFO_SEL | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC},
+ {sizeof(u32) * DSI_CONTROL, DSI_CONTROL_HS_CLK_CTRL | DSI_CONTROL_FORMAT(3) | DSI_CONTROL_LANES(3) | DSI_CONTROL_VIDEO_ENABLE},
+ {sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC},
+ {sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC}
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init11Erista[] = {
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_2, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_3, DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3)},
+ {sizeof(u32) * DSI_PAD_CONTROL_4, 0}
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Init11Mariko[] = {
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_2, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_3, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_4, 0x77777},
+ {sizeof(u32) * DSI_PAD_CONTROL_5_MARIKO, 0x77777},
+ {sizeof(u32) * DSI_PAD_CONTROL_6_MARIKO, DSI_PAD_PREEMP_PD_CLK(0x1) | DSI_PAD_PREEMP_PU_CLK(0x1) | DSI_PAD_PREEMP_PD(0x01) | DSI_PAD_PREEMP_PU(0x1)},
+ {sizeof(u32) * DSI_PAD_CONTROL_7_MARIKO, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigMipiCal01[] = {
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG2, 0},
+ {MIPI_CAL_CIL_MIPI_CAL_STATUS, 0xF3F10000},
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG0, 1},
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG2, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigMipiCal02Erista[] = {
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG2, 0x10010},
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG1, 0x300},
+};
+
+constexpr const RegisterWrite DisplayConfigMipiCal02Mariko[] = {
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG2, 0x10010},
+ {MIPI_CAL_MIPI_BIAS_PAD_CFG1, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigMipiCal03Erista[] = {
+ {MIPI_CAL_DSIA_MIPI_CAL_CONFIG, 0x200200},
+ {MIPI_CAL_DSIB_MIPI_CAL_CONFIG, 0x200200},
+ {MIPI_CAL_DSIA_MIPI_CAL_CONFIG_2, 0x200002},
+ {MIPI_CAL_DSIB_MIPI_CAL_CONFIG_2, 0x200002},
+ {MIPI_CAL_CILA_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_CILB_MIPI_CAL_CONFIG, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigMipiCal03Mariko[] = {
+ {MIPI_CAL_DSIA_MIPI_CAL_CONFIG, 0x200006},
+ {MIPI_CAL_DSIB_MIPI_CAL_CONFIG, 0x200006},
+ {MIPI_CAL_DSIA_MIPI_CAL_CONFIG_2, 0x260000},
+ {MIPI_CAL_DSIB_MIPI_CAL_CONFIG_2, 0x260000},
+ {MIPI_CAL_CILA_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_CILB_MIPI_CAL_CONFIG, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigMipiCal04[] = {
+ {MIPI_CAL_CILC_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_CILD_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_CILE_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_CILF_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_DSIC_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_DSID_MIPI_CAL_CONFIG, 0},
+ {MIPI_CAL_DSIB_MIPI_CAL_CONFIG_2, 0},
+ {MIPI_CAL_DSIC_MIPI_CAL_CONFIG_2, 0},
+ {MIPI_CAL_DSID_MIPI_CAL_CONFIG_2, 0},
+ {MIPI_CAL_MIPI_CAL_CTRL, 0x2A000001},
+};
+
+constexpr const SleepOrRegisterWrite DisplayConfigDc02[] = {
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DV_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ /* Setup default YUV colorspace conversion coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_YOF, 0xF0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KYRGB, 0x12A},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUR, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVR, 0x198},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUG, 0x39B},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVG, 0x32F},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUB, 0x204},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVB, 0},
+ /* End of color coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DV_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ /* Setup default YUV colorspace conversion coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_YOF, 0xF0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KYRGB, 0x12A},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUR, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVR, 0x198},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUG, 0x39B},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVG, 0x32F},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUB, 0x204},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVB, 0},
+ /* End of color coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DV_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ /* Setup default YUV colorspace conversion coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_YOF, 0xF0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KYRGB, 0x12A},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUR, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVR, 0x198},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUG, 0x39B},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVG, 0x32F},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KUB, 0x204},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_CSC_KVB, 0},
+ /* End of color coefficients */
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_COLOR_CONTROL, BASE_COLOR_SIZE_888},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_INTERFACE_CONTROL, DISP_DATA_FORMAT_DF1P1C},
+ {SleepOrRegisterWriteKind_Write, DC_COM_PIN_OUTPUT_POLARITY(1), 0x1000000},
+ {SleepOrRegisterWriteKind_Write, DC_COM_PIN_OUTPUT_POLARITY(3), 0},
+ {SleepOrRegisterWriteKind_Write, 0x4E4, 0},
+ {SleepOrRegisterWriteKind_Write, DC_COM_CRC_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE | WIN_B_UPDATE | WIN_C_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, 0x716, 0x10000FF},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, 0x716, 0x10000FF},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, 0x716, 0x10000FF},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND_OPTION0, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE | WIN_B_UPDATE | WIN_C_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, 0},
+ /* Set Display timings */
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_TIMING_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_REF_TO_SYNC, (1 << 16)}, // h_ref_to_sync = 0, v_ref_to_sync = 1.
+ {SleepOrRegisterWriteKind_Write, DC_DISP_SYNC_WIDTH, 0x10048},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_BACK_PORCH, 0x90048},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_ACTIVE, 0x50002D0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_FRONT_PORCH, 0xA0088}, // Sources say that this should be above the DC_DISP_ACTIVE cmd.
+ /* End of Display timings */
+ {SleepOrRegisterWriteKind_Write, DC_DISP_SHIFT_CLOCK_OPTIONS, SC1_H_QUALIFIER_NONE | SC0_H_QUALIFIER_NONE},
+ {SleepOrRegisterWriteKind_Write, DC_COM_PIN_OUTPUT_ENABLE(1), 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DATA_ENABLE_OPTIONS, DE_SELECT_ACTIVE | DE_CONTROL_NORMAL},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_INTERFACE_CONTROL, DISP_DATA_FORMAT_DF1P1C},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_CLOCK_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND_OPTION0, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND, DISP_CTRL_MODE_C_DISPLAY},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, READ_MUX | WRITE_MUX},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_FRONT_PORCH, 0xA0088},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_GENERAL_INCR_SYNCPT, 0x301},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_GENERAL_INCR_SYNCPT, 0x301},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_ACCESS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_CLOCK_CONTROL, PIXEL_CLK_DIVIDER_PCD1 | SHIFT_CLK_DIVIDER(4)},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_COLOR_CONTROL, BASE_COLOR_SIZE_888},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND_OPTION0, 0}
+};
+
+constexpr u32 DisplayConfigFrameBufferAddress = 0xC0000000;
+
+constexpr const SleepOrRegisterWrite DisplayConfigFrameBuffer[] = {
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT}, //Enable window C.
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT}, //Enable window B.
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT}, //Enable window A.
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, DSI_ENABLE}, //DSI_ENABLE
+ {SleepOrRegisterWriteKind_Write, DC_WIN_COLOR_DEPTH, WIN_COLOR_DEPTH_B8G8R8A8}, //T_A8R8G8B8 //NX Default: T_A8B8G8R8, WIN_COLOR_DEPTH_R8G8B8A8
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_POSITION, 0}, //(0,0)
+ {SleepOrRegisterWriteKind_Write, DC_WIN_H_INITIAL_DDA, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_V_INITIAL_DDA, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_PRESCALED_SIZE, V_PRESCALED_SIZE(1280) | H_PRESCALED_SIZE(2880)}, //Pre-scaled size: 1280x2880 bytes.
+ {SleepOrRegisterWriteKind_Write, DC_WIN_DDA_INC, V_DDA_INC(0x1000) | H_DDA_INC(0x1000)},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_SIZE, V_SIZE(1280) | H_SIZE(720)}, //Window size: 1280 vertical lines x 720 horizontal pixels.
+ {SleepOrRegisterWriteKind_Write, DC_WIN_LINE_STRIDE, 0x6000C00}, //768*2x768*4 (= 0x600 x 0xC00) bytes, see TRM for alignment requirements.
+ {SleepOrRegisterWriteKind_Write, DC_WIN_BUFFER_CONTROL, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WINBUF_SURFACE_KIND, 0}, //Regular surface.
+ {SleepOrRegisterWriteKind_Write, DC_WINBUF_START_ADDR, DisplayConfigFrameBufferAddress}, //Framebuffer address.
+ {SleepOrRegisterWriteKind_Write, DC_WINBUF_ADDR_H_OFFSET, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WINBUF_ADDR_V_OFFSET, 0},
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, DSI_ENABLE}, //DSI_ENABLE
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, DSI_ENABLE}, //DSI_ENABLE
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, 0},
+ {SleepOrRegisterWriteKind_Write, DC_DISP_DISP_WIN_OPTIONS, DSI_ENABLE}, //DSI_ENABLE
+ {SleepOrRegisterWriteKind_Write, DC_WIN_WIN_OPTIONS, WIN_ENABLE}, //Enable window AD.
+ {SleepOrRegisterWriteKind_Write, DC_CMD_DISPLAY_COMMAND, DISP_CTRL_MODE_C_DISPLAY}, //DISPLAY_CTRL_MODE: continuous display.
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_UPDATE | WIN_A_UPDATE}, //General update; window A update.
+ {SleepOrRegisterWriteKind_Write, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ} //General activation request; window A activation request.
+};
+
+constexpr const RegisterWrite DisplayConfigDc01Fini01[] = {
+ {sizeof(u32) * DC_DISP_FRONT_PORCH, 0xA0088},
+ {sizeof(u32) * DC_CMD_INT_MASK, 0},
+ {sizeof(u32) * DC_CMD_STATE_ACCESS, 0},
+ {sizeof(u32) * DC_CMD_INT_ENABLE, 0},
+ {sizeof(u32) * DC_CMD_CONT_SYNCPT_VSYNC, 0},
+ {sizeof(u32) * DC_CMD_DISPLAY_COMMAND, DISP_CTRL_MODE_STOP},
+ {sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+ {sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {sizeof(u32) * DC_CMD_GENERAL_INCR_SYNCPT, 0x301},
+ {sizeof(u32) * DC_CMD_GENERAL_INCR_SYNCPT, 0x301},
+ {sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
+ {sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Fini01[] = {
+ {sizeof(u32) * DSI_POWER_CONTROL, 0},
+ {sizeof(u32) * DSI_PAD_CONTROL_1, 0},
+};
+
+constexpr const RegisterWrite DisplayConfigDsi01Fini02[] = {
+ {sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
+ {sizeof(u32) * DSI_PHY_TIMING_2, 0x30118},
+ {sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
+ {sizeof(u32) * DSI_TIMEOUT_0, DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(0xFFFF) },
+ {sizeof(u32) * DSI_TIMEOUT_1, DSI_TIMEOUT_PR(0x1343) | DSI_TIMEOUT_TA(0x2000)},
+ {sizeof(u32) * DSI_TO_TALLY, 0},
+ {sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC},
+ {sizeof(u32) * DSI_CONTROL, DSI_CONTROL_LANES(3) | DSI_CONTROL_HOST_ENABLE},
+ {sizeof(u32) * DSI_POWER_CONTROL, DSI_POWER_CONTROL_ENABLE},
+ {sizeof(u32) * DSI_MAX_THRESHOLD, 0x40},
+ {sizeof(u32) * DSI_TRIGGER, 0},
+ {sizeof(u32) * DSI_TX_CRC, 0},
+ {sizeof(u32) * DSI_INIT_SEQ_CONTROL, 0}
+};
+
+constexpr const SleepOrRegisterWrite DisplayConfigJdiSpecificFini01[] = {
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x9483FFB9},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2139},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x191919D5},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xB39},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x4F0F41B1},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xF179A433},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2D81},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xB9},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+};
+
+constexpr const SleepOrRegisterWrite DisplayConfigAuoRev1SpecificFini01[] = {
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x9483FFB9},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2C39},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x191919D5},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x2C39},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x191919D6},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x19191919},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xB39},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x711148B1},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x71143209},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x114D31},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
+ {SleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0xB9},
+ {SleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
+ {SleepOrRegisterWriteKind_Sleep, 5, 0},
+};
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_font.inc b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_font.inc
new file mode 100644
index 00000000..37e1453f
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_font.inc
@@ -0,0 +1,4655 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+/*
+ * (C) Copyright 2000
+ * Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This file contains an 8x16 bitmap font for code page 437.
+ */
+
+constexpr inline const size_t FontWidth = 8;
+constexpr inline const size_t FontHeight = 16;
+
+constexpr inline const u8 FontData[] = {
+ /* 0 0x00 '^@' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 1 0x01 '^A' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x81, /* 10000001 */
+ 0xa5, /* 10100101 */
+ 0x81, /* 10000001 */
+ 0x81, /* 10000001 */
+ 0xbd, /* 10111101 */
+ 0x99, /* 10011001 */
+ 0x81, /* 10000001 */
+ 0x81, /* 10000001 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 2 0x02 '^B' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0xff, /* 11111111 */
+ 0xdb, /* 11011011 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xc3, /* 11000011 */
+ 0xe7, /* 11100111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 3 0x03 '^C' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 4 0x04 '^D' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x7c, /* 01111100 */
+ 0xfe, /* 11111110 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 5 0x05 '^E' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0xe7, /* 11100111 */
+ 0xe7, /* 11100111 */
+ 0xe7, /* 11100111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 6 0x06 '^F' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 7 0x07 '^G' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 8 0x08 '^H' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xe7, /* 11100111 */
+ 0xc3, /* 11000011 */
+ 0xc3, /* 11000011 */
+ 0xe7, /* 11100111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 9 0x09 '^I' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x42, /* 01000010 */
+ 0x42, /* 01000010 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 10 0x0a '^J' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xc3, /* 11000011 */
+ 0x99, /* 10011001 */
+ 0xbd, /* 10111101 */
+ 0xbd, /* 10111101 */
+ 0x99, /* 10011001 */
+ 0xc3, /* 11000011 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 11 0x0b '^K' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1e, /* 00011110 */
+ 0x0e, /* 00001110 */
+ 0x1a, /* 00011010 */
+ 0x32, /* 00110010 */
+ 0x78, /* 01111000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 12 0x0c '^L' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 13 0x0d '^M' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3f, /* 00111111 */
+ 0x33, /* 00110011 */
+ 0x3f, /* 00111111 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x70, /* 01110000 */
+ 0xf0, /* 11110000 */
+ 0xe0, /* 11100000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 14 0x0e '^N' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7f, /* 01111111 */
+ 0x63, /* 01100011 */
+ 0x7f, /* 01111111 */
+ 0x63, /* 01100011 */
+ 0x63, /* 01100011 */
+ 0x63, /* 01100011 */
+ 0x63, /* 01100011 */
+ 0x67, /* 01100111 */
+ 0xe7, /* 11100111 */
+ 0xe6, /* 11100110 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 15 0x0f '^O' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xdb, /* 11011011 */
+ 0x3c, /* 00111100 */
+ 0xe7, /* 11100111 */
+ 0x3c, /* 00111100 */
+ 0xdb, /* 11011011 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 16 0x10 '^P' */
+ 0x00, /* 00000000 */
+ 0x80, /* 10000000 */
+ 0xc0, /* 11000000 */
+ 0xe0, /* 11100000 */
+ 0xf0, /* 11110000 */
+ 0xf8, /* 11111000 */
+ 0xfe, /* 11111110 */
+ 0xf8, /* 11111000 */
+ 0xf0, /* 11110000 */
+ 0xe0, /* 11100000 */
+ 0xc0, /* 11000000 */
+ 0x80, /* 10000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 17 0x11 '^Q' */
+ 0x00, /* 00000000 */
+ 0x02, /* 00000010 */
+ 0x06, /* 00000110 */
+ 0x0e, /* 00001110 */
+ 0x1e, /* 00011110 */
+ 0x3e, /* 00111110 */
+ 0xfe, /* 11111110 */
+ 0x3e, /* 00111110 */
+ 0x1e, /* 00011110 */
+ 0x0e, /* 00001110 */
+ 0x06, /* 00000110 */
+ 0x02, /* 00000010 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 18 0x12 '^R' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 19 0x13 '^S' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 20 0x14 '^T' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7f, /* 01111111 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0x7b, /* 01111011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 21 0x15 '^U' */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x0c, /* 00001100 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 22 0x16 '^V' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 23 0x17 '^W' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 24 0x18 '^X' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 25 0x19 '^Y' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 26 0x1a '^Z' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0xfe, /* 11111110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 27 0x1b '^[' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xfe, /* 11111110 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 28 0x1c '^\' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 29 0x1d '^]' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x28, /* 00101000 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x28, /* 00101000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 30 0x1e '^^' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x7c, /* 01111100 */
+ 0x7c, /* 01111100 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 31 0x1f '^_' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x7c, /* 01111100 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 32 0x20 ' ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 33 0x21 '!' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 34 0x22 '"' */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x24, /* 00100100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 35 0x23 '#' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 36 0x24 '$' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0x7c, /* 01111100 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x86, /* 10000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 37 0x25 '%' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc2, /* 11000010 */
+ 0xc6, /* 11000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc6, /* 11000110 */
+ 0x86, /* 10000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 38 0x26 '&' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 39 0x27 ''' */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 40 0x28 '(' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 41 0x29 ')' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 42 0x2a '*' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0xff, /* 11111111 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 43 0x2b '+' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 44 0x2c ',' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 45 0x2d '-' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 46 0x2e '.' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 47 0x2f '/' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x02, /* 00000010 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0x80, /* 10000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 48 0x30 '0' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 49 0x31 '1' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x38, /* 00111000 */
+ 0x78, /* 01111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 50 0x32 '2' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 51 0x33 '3' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x3c, /* 00111100 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 52 0x34 '4' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x1c, /* 00011100 */
+ 0x3c, /* 00111100 */
+ 0x6c, /* 01101100 */
+ 0xcc, /* 11001100 */
+ 0xfe, /* 11111110 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x1e, /* 00011110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 53 0x35 '5' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xfc, /* 11111100 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 54 0x36 '6' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xfc, /* 11111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 55 0x37 '7' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 56 0x38 '8' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 57 0x39 '9' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7e, /* 01111110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 58 0x3a ':' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 59 0x3b ';' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 60 0x3c '<' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 61 0x3d '=' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 62 0x3e '>' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 63 0x3f '?' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 64 0x40 '@' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xde, /* 11011110 */
+ 0xde, /* 11011110 */
+ 0xde, /* 11011110 */
+ 0xdc, /* 11011100 */
+ 0xc0, /* 11000000 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 65 0x41 'A' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 66 0x42 'B' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfc, /* 11111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xfc, /* 11111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 67 0x43 'C' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc2, /* 11000010 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 68 0x44 'D' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 69 0x45 'E' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x66, /* 01100110 */
+ 0x62, /* 01100010 */
+ 0x68, /* 01101000 */
+ 0x78, /* 01111000 */
+ 0x68, /* 01101000 */
+ 0x60, /* 01100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 70 0x46 'F' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x66, /* 01100110 */
+ 0x62, /* 01100010 */
+ 0x68, /* 01101000 */
+ 0x78, /* 01111000 */
+ 0x68, /* 01101000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 71 0x47 'G' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xde, /* 11011110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x66, /* 01100110 */
+ 0x3a, /* 00111010 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 72 0x48 'H' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 73 0x49 'I' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 74 0x4a 'J' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1e, /* 00011110 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 75 0x4b 'K' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe6, /* 11100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x78, /* 01111000 */
+ 0x78, /* 01111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 76 0x4c 'L' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf0, /* 11110000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 77 0x4d 'M' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xee, /* 11101110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xd6, /* 11010110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 78 0x4e 'N' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xe6, /* 11100110 */
+ 0xf6, /* 11110110 */
+ 0xfe, /* 11111110 */
+ 0xde, /* 11011110 */
+ 0xce, /* 11001110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 79 0x4f 'O' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 80 0x50 'P' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfc, /* 11111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 81 0x51 'Q' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xde, /* 11011110 */
+ 0x7c, /* 01111100 */
+ 0x0c, /* 00001100 */
+ 0x0e, /* 00001110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 82 0x52 'R' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfc, /* 11111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 83 0x53 'S' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x38, /* 00111000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 84 0x54 'T' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x5a, /* 01011010 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 85 0x55 'U' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 86 0x56 'V' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 87 0x57 'W' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xfe, /* 11111110 */
+ 0xee, /* 11101110 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 88 0x58 'X' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x7c, /* 01111100 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 89 0x59 'Y' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 90 0x5a 'Z' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0x86, /* 10000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc2, /* 11000010 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 91 0x5b '[' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 92 0x5c '\' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x80, /* 10000000 */
+ 0xc0, /* 11000000 */
+ 0xe0, /* 11100000 */
+ 0x70, /* 01110000 */
+ 0x38, /* 00111000 */
+ 0x1c, /* 00011100 */
+ 0x0e, /* 00001110 */
+ 0x06, /* 00000110 */
+ 0x02, /* 00000010 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 93 0x5d ']' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 94 0x5e '^' */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 95 0x5f '_' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 96 0x60 '`' */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 97 0x61 'a' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 98 0x62 'b' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe0, /* 11100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x78, /* 01111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 99 0x63 'c' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 100 0x64 'd' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1c, /* 00011100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x3c, /* 00111100 */
+ 0x6c, /* 01101100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 101 0x65 'e' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 102 0x66 'f' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1c, /* 00011100 */
+ 0x36, /* 00110110 */
+ 0x32, /* 00110010 */
+ 0x30, /* 00110000 */
+ 0x78, /* 01111000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 103 0x67 'g' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x7c, /* 01111100 */
+ 0x0c, /* 00001100 */
+ 0xcc, /* 11001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+
+ /* 104 0x68 'h' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe0, /* 11100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x6c, /* 01101100 */
+ 0x76, /* 01110110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 105 0x69 'i' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 106 0x6a 'j' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+
+ /* 107 0x6b 'k' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe0, /* 11100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x78, /* 01111000 */
+ 0x78, /* 01111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 108 0x6c 'l' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 109 0x6d 'm' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xec, /* 11101100 */
+ 0xfe, /* 11111110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 110 0x6e 'n' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 111 0x6f 'o' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 112 0x70 'p' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+
+ /* 113 0x71 'q' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x7c, /* 01111100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x1e, /* 00011110 */
+ 0x00, /* 00000000 */
+
+ /* 114 0x72 'r' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x76, /* 01110110 */
+ 0x66, /* 01100110 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 115 0x73 's' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x38, /* 00111000 */
+ 0x0c, /* 00001100 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 116 0x74 't' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0xfc, /* 11111100 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x36, /* 00110110 */
+ 0x1c, /* 00011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 117 0x75 'u' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 118 0x76 'v' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 119 0x77 'w' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 120 0x78 'x' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 121 0x79 'y' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7e, /* 01111110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+
+ /* 122 0x7a 'z' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xcc, /* 11001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 123 0x7b '{' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x0e, /* 00001110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 124 0x7c '|' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 125 0x7d '}' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x70, /* 01110000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x0e, /* 00001110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 126 0x7e '~' */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 127 0x7f '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 128 0x80 '€' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc2, /* 11000010 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 129 0x81 '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 130 0x82 '‚' */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 131 0x83 'ƒ' */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 132 0x84 '„' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 133 0x85 '…' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 134 0x86 '†' */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 135 0x87 '‡' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 136 0x88 'ˆ' */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 137 0x89 '‰' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 138 0x8a 'Š' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 139 0x8b '‹' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 140 0x8c 'Œ' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 141 0x8d '' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 142 0x8e 'Ž' */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 143 0x8f '' */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 144 0x90 '' */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x66, /* 01100110 */
+ 0x62, /* 01100010 */
+ 0x68, /* 01101000 */
+ 0x78, /* 01111000 */
+ 0x68, /* 01101000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 145 0x91 '‘' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xec, /* 11101100 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x7e, /* 01111110 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0x6e, /* 01101110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 146 0x92 '’' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3e, /* 00111110 */
+ 0x6c, /* 01101100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xfe, /* 11111110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xce, /* 11001110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 147 0x93 '“' */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 148 0x94 '”' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 149 0x95 '•' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 150 0x96 '–' */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x78, /* 01111000 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 151 0x97 '—' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 152 0x98 '˜' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7e, /* 01111110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+
+ /* 153 0x99 '™' */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 154 0x9a 'š' */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 155 0x9b '›' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 156 0x9c 'œ' */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x64, /* 01100100 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xe6, /* 11100110 */
+ 0xfc, /* 11111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 157 0x9d '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 158 0x9e 'ž' */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xf8, /* 11111000 */
+ 0xc4, /* 11000100 */
+ 0xcc, /* 11001100 */
+ 0xde, /* 11011110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 159 0x9f 'Ÿ' */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x1b, /* 00011011 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xd8, /* 11011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 160 0xa0 ' ' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 161 0xa1 '¡' */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 162 0xa2 '¢' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 163 0xa3 '£' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 164 0xa4 '¤' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 165 0xa5 '¥' */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xe6, /* 11100110 */
+ 0xf6, /* 11110110 */
+ 0xfe, /* 11111110 */
+ 0xde, /* 11011110 */
+ 0xce, /* 11001110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 166 0xa6 '¦' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x3e, /* 00111110 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 167 0xa7 '§' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 168 0xa8 '¨' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 169 0xa9 '©' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 170 0xaa 'ª' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 171 0xab '«' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0xe0, /* 11100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xdc, /* 11011100 */
+ 0x86, /* 10000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x3e, /* 00111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 172 0xac '¬' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0xe0, /* 11100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x66, /* 01100110 */
+ 0xce, /* 11001110 */
+ 0x9a, /* 10011010 */
+ 0x3f, /* 00111111 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 173 0xad '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 174 0xae '®' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x36, /* 00110110 */
+ 0x6c, /* 01101100 */
+ 0xd8, /* 11011000 */
+ 0x6c, /* 01101100 */
+ 0x36, /* 00110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 175 0xaf '¯' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xd8, /* 11011000 */
+ 0x6c, /* 01101100 */
+ 0x36, /* 00110110 */
+ 0x6c, /* 01101100 */
+ 0xd8, /* 11011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 176 0xb0 '°' */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+
+ /* 177 0xb1 '±' */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+
+ /* 178 0xb2 '²' */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+
+ /* 179 0xb3 '³' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 180 0xb4 '´' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 181 0xb5 'µ' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 182 0xb6 '¶' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf6, /* 11110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 183 0xb7 '·' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 184 0xb8 '¸' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 185 0xb9 '¹' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf6, /* 11110110 */
+ 0x06, /* 00000110 */
+ 0xf6, /* 11110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 186 0xba 'º' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 187 0xbb '»' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x06, /* 00000110 */
+ 0xf6, /* 11110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 188 0xbc '¼' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf6, /* 11110110 */
+ 0x06, /* 00000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 189 0xbd '½' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 190 0xbe '¾' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 191 0xbf '¿' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 192 0xc0 'À' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 193 0xc1 'Á' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 194 0xc2 'Â' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 195 0xc3 'Ã' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 196 0xc4 'Ä' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 197 0xc5 'Å' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 198 0xc6 'Æ' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 199 0xc7 'Ç' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x37, /* 00110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 200 0xc8 'È' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x37, /* 00110111 */
+ 0x30, /* 00110000 */
+ 0x3f, /* 00111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 201 0xc9 'É' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3f, /* 00111111 */
+ 0x30, /* 00110000 */
+ 0x37, /* 00110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 202 0xca 'Ê' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf7, /* 11110111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 203 0xcb 'Ë' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xf7, /* 11110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 204 0xcc 'Ì' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x37, /* 00110111 */
+ 0x30, /* 00110000 */
+ 0x37, /* 00110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 205 0xcd 'Í' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 206 0xce 'Î' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf7, /* 11110111 */
+ 0x00, /* 00000000 */
+ 0xf7, /* 11110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 207 0xcf 'Ï' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 208 0xd0 'Ð' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 209 0xd1 'Ñ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 210 0xd2 'Ò' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 211 0xd3 'Ó' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x3f, /* 00111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 212 0xd4 'Ô' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 213 0xd5 'Õ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 214 0xd6 'Ö' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3f, /* 00111111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 215 0xd7 '×' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xff, /* 11111111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 216 0xd8 'Ø' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 217 0xd9 'Ù' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 218 0xda 'Ú' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 219 0xdb 'Û' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 220 0xdc 'Ü' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 221 0xdd 'Ý' */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+
+ /* 222 0xde 'Þ' */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+
+ /* 223 0xdf 'ß' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 224 0xe0 'à' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xdc, /* 11011100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 225 0xe1 'á' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xd8, /* 11011000 */
+ 0xcc, /* 11001100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 226 0xe2 'â' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 227 0xe3 'ã' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 228 0xe4 'ä' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 229 0xe5 'å' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 230 0xe6 'æ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+
+ /* 231 0xe7 'ç' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 232 0xe8 'è' */
+ 0x00, /* 00000000 */
+ 0x40, /* 01000000*/
+ 0xe0, /* 01110000 */
+ 0x1c, /* 00011100 */
+ 0x06, /* 00000110 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 233 0xe9 'é' */
+ 0x00, /* 00000000 */
+ 0x02, /* 00000010*/
+ 0x0e, /* 00001110 */
+ 0x78, /* 00111000 */
+ 0xc0, /* 01100000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 234 0xea 'ê' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0xee, /* 11101110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 235 0xeb 'ë' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1e, /* 00011110 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x3e, /* 00111110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 236 0xec 'ì' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 237 0xed 'í' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x03, /* 00000011 */
+ 0x06, /* 00000110 */
+ 0x7e, /* 01111110 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0xf3, /* 11110011 */
+ 0x7e, /* 01111110 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 238 0xee 'î' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1c, /* 00011100 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x1c, /* 00011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 239 0xef 'ï' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 240 0xf0 'ð' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 241 0xf1 'ñ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 242 0xf2 'ò' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 243 0xf3 'ó' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 244 0xf4 'ô' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 245 0xf5 'õ' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 246 0xf6 'ö' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 247 0xf7 '÷' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 248 0xf8 'ø' */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 249 0xf9 'ù' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 250 0xfa 'ú' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 251 0xfb 'û' */
+ 0x00, /* 00000000 */
+ 0x0f, /* 00001111 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0xec, /* 11101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x3c, /* 00111100 */
+ 0x1c, /* 00011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 252 0xfc 'ü' */
+ 0x00, /* 00000000 */
+ 0x6c, /* 01101100 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 253 0xfd 'ý' */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x32, /* 00110010 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 254 0xfe 'þ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 255 0xff 'ÿ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+};
+
+constexpr inline u32 FontDrawTable[16][4] = {
+ { 0x00000000, 0x00000000, 0x00000000, 0x00000000 },
+ { 0x00000000, 0x00000000, 0x00000000, 0x00ffffff },
+ { 0x00000000, 0x00000000, 0x00ffffff, 0x00000000 },
+ { 0x00000000, 0x00000000, 0x00ffffff, 0x00ffffff },
+ { 0x00000000, 0x00ffffff, 0x00000000, 0x00000000 },
+ { 0x00000000, 0x00ffffff, 0x00000000, 0x00ffffff },
+ { 0x00000000, 0x00ffffff, 0x00ffffff, 0x00000000 },
+ { 0x00000000, 0x00ffffff, 0x00ffffff, 0x00ffffff },
+ { 0x00ffffff, 0x00000000, 0x00000000, 0x00000000 },
+ { 0x00ffffff, 0x00000000, 0x00000000, 0x00ffffff },
+ { 0x00ffffff, 0x00000000, 0x00ffffff, 0x00000000 },
+ { 0x00ffffff, 0x00000000, 0x00ffffff, 0x00ffffff },
+ { 0x00ffffff, 0x00ffffff, 0x00000000, 0x00000000 },
+ { 0x00ffffff, 0x00ffffff, 0x00000000, 0x00ffffff },
+ { 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00000000 },
+ { 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff }
+};
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_main.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_main.cpp
new file mode 100644
index 00000000..810566c9
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_main.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_sdmmc.hpp"
+#include "fatal_save_context.hpp"
+#include "fatal_sound.hpp"
+#include "fatal_display.hpp"
+
+namespace ams::secmon::fatal {
+
+ namespace {
+
+ constexpr inline int I2cAddressMax77620Pmic = 0x3C;
+
+ ALWAYS_INLINE const ams::impl::FatalErrorContext *GetFatalErrorContext() {
+ return MemoryRegionVirtualTzramMarikoProgramFatalErrorContext.GetPointer();
+ }
+
+ }
+
+ void Main() {
+ /* Set library register addresses. */
+ actmon::SetRegisterAddress(MemoryRegionVirtualDeviceActivityMonitor.GetAddress());
+ clkrst::SetRegisterAddress(MemoryRegionVirtualDeviceClkRst.GetAddress());
+ flow::SetRegisterAddress(MemoryRegionVirtualDeviceFlowController.GetAddress());
+ fuse::SetRegisterAddress(MemoryRegionVirtualDeviceFuses.GetAddress());
+ gic::SetRegisterAddress(MemoryRegionVirtualDeviceGicDistributor.GetAddress(), MemoryRegionVirtualDeviceGicCpuInterface.GetAddress());
+ i2c::SetRegisterAddress(i2c::Port_1, MemoryRegionVirtualDeviceI2c1.GetAddress());
+ i2c::SetRegisterAddress(i2c::Port_5, MemoryRegionVirtualDeviceI2c5.GetAddress());
+ pinmux::SetRegisterAddress(MemoryRegionVirtualDeviceApbMisc.GetAddress(), MemoryRegionVirtualDeviceGpio.GetAddress());
+ pmc::SetRegisterAddress(MemoryRegionVirtualDevicePmc.GetAddress());
+ se::SetRegisterAddress(MemoryRegionVirtualDeviceSecurityEngine.GetAddress(), MemoryRegionVirtualDeviceSecurityEngine2.GetAddress());
+ uart::SetRegisterAddress(MemoryRegionVirtualDeviceUart.GetAddress());
+ wdt::SetRegisterAddress(MemoryRegionVirtualDeviceTimer.GetAddress());
+ util::SetRegisterAddress(MemoryRegionVirtualDeviceTimer.GetAddress());
+
+ /* Ensure that the log library is initialized. */
+ log::Initialize();
+
+ AMS_SECMON_LOG("%s\n", "Fatal start.");
+
+ /* Save the fatal error context. */
+ const auto *f_ctx = GetFatalErrorContext();
+ Result result = SaveFatalErrorContext(f_ctx);
+ if (R_SUCCEEDED(result)) {
+ AMS_SECMON_LOG("Saved fatal error context to /atmosphere/fatal_reports/report_%016" PRIx64 ".bin!\n", f_ctx->report_identifier);
+ } else {
+ AMS_SECMON_LOG("Failed to save fatal error context: %08x\n", result.GetValue());
+ }
+
+ /* Ensure that i2c-1/i2c-5 are usable for communicating with the audio device/pmic. */
+ clkrst::EnableI2c1Clock();
+ clkrst::EnableI2c5Clock();
+ i2c::Initialize(i2c::Port_1);
+ i2c::Initialize(i2c::Port_5);
+
+ /* Shut down audio. */
+ {
+ StopSound();
+ }
+
+ /* Display the fatal error. */
+ {
+ AMS_SECMON_LOG("Showing Display, LCD Vendor = %04x\n", GetLcdVendor());
+ InitializeDisplay();
+ ShowDisplay(f_ctx, result);
+ }
+
+ /* Ensure we have nothing waiting to be logged. */
+ AMS_LOG_FLUSH();
+
+ /* Wait for power button to be pressed. */
+ while (!pmic::IsPowerButtonPressed()) {
+ util::WaitMicroSeconds(100);
+ }
+
+ /* Reboot. */
+ pmic::ShutdownSystem(true);
+
+ /* Wait for our reboot to complete. */
+ AMS_INFINITE_LOOP();
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_print.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_print.cpp
new file mode 100644
index 00000000..b9572975
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_print.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_display.hpp"
+#include "fatal_print.hpp"
+
+namespace ams::secmon::fatal {
+
+ namespace {
+
+ #include "fatal_font.inc"
+
+ constexpr inline const u32 TextColor = 0xFFA0A0A0;
+
+ constexpr inline const size_t ConsoleWidth = FrameBufferWidth / FontWidth;
+ constexpr inline const size_t ConsoleHeight = FrameBufferHeight / FontHeight;
+
+ constinit u32 *g_frame_buffer = nullptr;
+ constinit size_t g_col = 1;
+ constinit size_t g_row = 0;
+
+ void SetPixel(size_t x, size_t y, u32 color) {
+ g_frame_buffer[(FrameBufferWidth - x) * FrameBufferHeight + y] = color;
+ }
+
+ void PutCarriageReturn() {
+ g_col = 1;
+ }
+
+ void PutNewLine() {
+ g_col = 1;
+ ++g_row;
+
+ /* TODO: Support scrolling? */
+ }
+
+ void PutCharImpl(const char c) {
+ /* Get the character data for the font. */
+ const u8 * cdata = FontData + c * (FontHeight * util::DivideUp(FontWidth, BITSIZEOF(u8)));
+
+ /* Determine where to start drawing. */
+ const size_t x = g_col * FontWidth;
+ const size_t y = g_row * FontHeight;
+
+ for (size_t cur_y = 0; cur_y < FontHeight; ++cur_y) {
+ size_t cur_x = 0;
+ int wbits = FontWidth;
+ while (wbits > 0) {
+ const auto bits = *(cdata++);
+
+ SetPixel(x + cur_x + 0, y + cur_y, FontDrawTable[(bits >> 4) & 0xF][0] & TextColor);
+ SetPixel(x + cur_x + 1, y + cur_y, FontDrawTable[(bits >> 4) & 0xF][1] & TextColor);
+ SetPixel(x + cur_x + 2, y + cur_y, FontDrawTable[(bits >> 4) & 0xF][2] & TextColor);
+ SetPixel(x + cur_x + 3, y + cur_y, FontDrawTable[(bits >> 4) & 0xF][3] & TextColor);
+ SetPixel(x + cur_x + 4, y + cur_y, FontDrawTable[(bits >> 0) & 0xF][0] & TextColor);
+ SetPixel(x + cur_x + 5, y + cur_y, FontDrawTable[(bits >> 0) & 0xF][1] & TextColor);
+ SetPixel(x + cur_x + 6, y + cur_y, FontDrawTable[(bits >> 0) & 0xF][2] & TextColor);
+ SetPixel(x + cur_x + 7, y + cur_y, FontDrawTable[(bits >> 0) & 0xF][3] & TextColor);
+
+ cur_x += BITSIZEOF(u8);
+ wbits -= BITSIZEOF(u8);
+ }
+ }
+ }
+
+ void PutChar(const char c) {
+ switch (c) {
+ case '\r':
+ PutCarriageReturn();
+ break;
+ case '\n':
+ PutNewLine();
+ break;
+ default:
+ PutCharImpl(c);
+ if ((++g_col) >= ConsoleWidth) {
+ PutNewLine();
+ }
+ }
+ }
+
+ }
+
+ void InitializeConsole(u32 *frame_buffer) {
+ /* Setup the console variables. */
+ g_frame_buffer = frame_buffer;
+ g_col = 1;
+ g_row = 0;
+
+ /* Clear the console. */
+ std::memset(g_frame_buffer, 0, FrameBufferSize);
+ }
+
+ void Print(const char *fmt, ...) {
+ /* Generate the string. */
+ char log_str[1_KB];
+ {
+ std::va_list vl;
+ va_start(vl, fmt);
+ util::TVSNPrintf(log_str, sizeof(log_str), fmt, vl);
+ va_end(vl);
+ }
+
+ /* Print each character. */
+ const size_t len = std::strlen(log_str);
+ for (size_t i = 0; i < len; ++i) {
+ PutChar(log_str[i]);
+ }
+
+ /* Flush the console. */
+ hw::FlushDataCache(g_frame_buffer, FrameBufferSize);
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_print.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_print.hpp
new file mode 100644
index 00000000..2d283fe4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_print.hpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+namespace ams::secmon::fatal {
+
+ void InitializeConsole(u32 *frame_buffer);
+ void Print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_registers_di.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_registers_di.hpp
new file mode 100644
index 00000000..bf042d1b
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_registers_di.hpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (C) 2018 CTCaer
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+#define DC_CMD_GENERAL_INCR_SYNCPT 0x00
+
+#define DC_CMD_GENERAL_INCR_SYNCPT_CNTRL 0x01
+#define SYNCPT_CNTRL_NO_STALL (1 << 8)
+#define SYNCPT_CNTRL_SOFT_RESET (1 << 0)
+
+#define DC_CMD_CONT_SYNCPT_VSYNC 0x28
+#define SYNCPT_VSYNC_ENABLE (1 << 8)
+
+#define DC_CMD_DISPLAY_COMMAND_OPTION0 0x031
+
+#define DC_CMD_DISPLAY_COMMAND 0x32
+#define DISP_CTRL_MODE_STOP (0 << 5)
+#define DISP_CTRL_MODE_C_DISPLAY (1 << 5)
+#define DISP_CTRL_MODE_NC_DISPLAY (2 << 5)
+#define DISP_CTRL_MODE_MASK (3 << 5)
+
+#define DC_CMD_DISPLAY_POWER_CONTROL 0x36
+#define PW0_ENABLE (1 << 0)
+#define PW1_ENABLE (1 << 2)
+#define PW2_ENABLE (1 << 4)
+#define PW3_ENABLE (1 << 6)
+#define PW4_ENABLE (1 << 8)
+#define PM0_ENABLE (1 << 16)
+#define PM1_ENABLE (1 << 18)
+
+#define DC_CMD_INT_STATUS 0x37
+#define DC_CMD_INT_MASK 0x38
+#define DC_CMD_INT_ENABLE 0x39
+
+#define DC_CMD_STATE_ACCESS 0x40
+#define READ_MUX (1 << 0)
+#define WRITE_MUX (1 << 2)
+
+#define DC_CMD_STATE_CONTROL 0x41
+#define GENERAL_ACT_REQ (1 << 0)
+#define WIN_A_ACT_REQ (1 << 1)
+#define WIN_B_ACT_REQ (1 << 2)
+#define WIN_C_ACT_REQ (1 << 3)
+#define CURSOR_ACT_REQ (1 << 7)
+#define GENERAL_UPDATE (1 << 8)
+#define WIN_A_UPDATE (1 << 9)
+#define WIN_B_UPDATE (1 << 10)
+#define WIN_C_UPDATE (1 << 11)
+#define CURSOR_UPDATE (1 << 15)
+#define NC_HOST_TRIG (1 << 24)
+
+#define DC_CMD_DISPLAY_WINDOW_HEADER 0x42
+#define WINDOW_A_SELECT (1 << 4)
+#define WINDOW_B_SELECT (1 << 5)
+#define WINDOW_C_SELECT (1 << 6)
+
+#define DC_CMD_REG_ACT_CONTROL 0x043
+
+#define DC_COM_CRC_CONTROL 0x300
+#define DC_COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x))
+#define DC_COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x))
+
+#define DC_COM_DSC_TOP_CTL 0x33E
+
+#define DC_DISP_DISP_WIN_OPTIONS 0x402
+#define HDMI_ENABLE (1 << 30)
+#define DSI_ENABLE (1 << 29)
+#define SOR1_TIMING_CYA (1 << 27)
+#define SOR1_ENABLE (1 << 26)
+#define SOR_ENABLE (1 << 25)
+#define CURSOR_ENABLE (1 << 16)
+
+#define DC_DISP_DISP_MEM_HIGH_PRIORITY 0x403
+#define DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER 0x404
+#define DC_DISP_DISP_TIMING_OPTIONS 0x405
+#define DC_DISP_REF_TO_SYNC 0x406
+#define DC_DISP_SYNC_WIDTH 0x407
+#define DC_DISP_BACK_PORCH 0x408
+#define DC_DISP_ACTIVE 0x409
+#define DC_DISP_FRONT_PORCH 0x40A
+
+#define DC_DISP_DISP_CLOCK_CONTROL 0x42E
+#define PIXEL_CLK_DIVIDER_PCD1 (0 << 8)
+#define PIXEL_CLK_DIVIDER_PCD1H (1 << 8)
+#define PIXEL_CLK_DIVIDER_PCD2 (2 << 8)
+#define PIXEL_CLK_DIVIDER_PCD3 (3 << 8)
+#define PIXEL_CLK_DIVIDER_PCD4 (4 << 8)
+#define PIXEL_CLK_DIVIDER_PCD6 (5 << 8)
+#define PIXEL_CLK_DIVIDER_PCD8 (6 << 8)
+#define PIXEL_CLK_DIVIDER_PCD9 (7 << 8)
+#define PIXEL_CLK_DIVIDER_PCD12 (8 << 8)
+#define PIXEL_CLK_DIVIDER_PCD16 (9 << 8)
+#define PIXEL_CLK_DIVIDER_PCD18 (10 << 8)
+#define PIXEL_CLK_DIVIDER_PCD24 (11 << 8)
+#define PIXEL_CLK_DIVIDER_PCD13 (12 << 8)
+#define SHIFT_CLK_DIVIDER(x) ((x) & 0xff)
+
+#define DC_DISP_DISP_INTERFACE_CONTROL 0x42F
+#define DISP_DATA_FORMAT_DF1P1C (0 << 0)
+#define DISP_DATA_FORMAT_DF1P2C24B (1 << 0)
+#define DISP_DATA_FORMAT_DF1P2C18B (2 << 0)
+#define DISP_DATA_FORMAT_DF1P2C16B (3 << 0)
+#define DISP_DATA_FORMAT_DF2S (4 << 0)
+#define DISP_DATA_FORMAT_DF3S (5 << 0)
+#define DISP_DATA_FORMAT_DFSPI (6 << 0)
+#define DISP_DATA_FORMAT_DF1P3C24B (7 << 0)
+#define DISP_DATA_FORMAT_DF1P3C18B (8 << 0)
+#define DISP_ALIGNMENT_MSB (0 << 8)
+#define DISP_ALIGNMENT_LSB (1 << 8)
+#define DISP_ORDER_RED_BLUE (0 << 9)
+#define DISP_ORDER_BLUE_RED (1 << 9)
+
+#define DC_DISP_DISP_COLOR_CONTROL 0x430
+#define DITHER_CONTROL_MASK (3 << 8)
+#define DITHER_CONTROL_DISABLE (0 << 8)
+#define DITHER_CONTROL_ORDERED (2 << 8)
+#define DITHER_CONTROL_ERRDIFF (3 << 8)
+#define BASE_COLOR_SIZE_MASK (0xf << 0)
+#define BASE_COLOR_SIZE_666 (0 << 0)
+#define BASE_COLOR_SIZE_111 (1 << 0)
+#define BASE_COLOR_SIZE_222 (2 << 0)
+#define BASE_COLOR_SIZE_333 (3 << 0)
+#define BASE_COLOR_SIZE_444 (4 << 0)
+#define BASE_COLOR_SIZE_555 (5 << 0)
+#define BASE_COLOR_SIZE_565 (6 << 0)
+#define BASE_COLOR_SIZE_332 (7 << 0)
+#define BASE_COLOR_SIZE_888 (8 << 0)
+
+#define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431
+#define SC1_H_QUALIFIER_NONE (1 << 16)
+#define SC0_H_QUALIFIER_NONE (1 << 0)
+
+#define DC_DISP_DATA_ENABLE_OPTIONS 0x432
+#define DE_SELECT_ACTIVE_BLANK (0 << 0)
+#define DE_SELECT_ACTIVE (1 << 0)
+#define DE_SELECT_ACTIVE_IS (2 << 0)
+#define DE_CONTROL_ONECLK (0 << 2)
+#define DE_CONTROL_NORMAL (1 << 2)
+#define DE_CONTROL_EARLY_EXT (2 << 2)
+#define DE_CONTROL_EARLY (3 << 2)
+#define DE_CONTROL_ACTIVE_BLANK (4 << 2)
+
+#define DC_DISP_DC_MCCIF_FIFOCTRL 0x480
+#define DC_DISP_SD_BL_PARAMETERS 0x4D7
+#define DC_DISP_SD_BL_CONTROL 0x4DC
+#define DC_DISP_BLEND_BACKGROUND_COLOR 0x4E4
+
+#define DC_WIN_CSC_YOF 0x611
+#define DC_WIN_CSC_KYRGB 0x612
+#define DC_WIN_CSC_KUR 0x613
+#define DC_WIN_CSC_KVR 0x614
+#define DC_WIN_CSC_KUG 0x615
+#define DC_WIN_CSC_KVG 0x616
+#define DC_WIN_CSC_KUB 0x617
+#define DC_WIN_CSC_KVB 0x618
+#define DC_WIN_AD_WIN_OPTIONS 0xB80
+#define DC_WIN_BD_WIN_OPTIONS 0xD80
+#define DC_WIN_CD_WIN_OPTIONS 0xF80
+
+// The following registers are A/B/C shadows of the 0xB80/0xD80/0xF80 registers (see DISPLAY_WINDOW_HEADER).
+#define DC_WIN_WIN_OPTIONS 0x700
+#define H_DIRECTION (1 << 0)
+#define V_DIRECTION (1 << 2)
+#define SCAN_COLUMN (1 << 4)
+#define COLOR_EXPAND (1 << 6)
+#define CSC_ENABLE (1 << 18)
+#define WIN_ENABLE (1 << 30)
+
+#define DC_WIN_COLOR_DEPTH 0x703
+#define WIN_COLOR_DEPTH_P1 0x0
+#define WIN_COLOR_DEPTH_P2 0x1
+#define WIN_COLOR_DEPTH_P4 0x2
+#define WIN_COLOR_DEPTH_P8 0x3
+#define WIN_COLOR_DEPTH_B4G4R4A4 0x4
+#define WIN_COLOR_DEPTH_B5G5R5A 0x5
+#define WIN_COLOR_DEPTH_B5G6R5 0x6
+#define WIN_COLOR_DEPTH_AB5G5R5 0x7
+#define WIN_COLOR_DEPTH_B8G8R8A8 0xC
+#define WIN_COLOR_DEPTH_R8G8B8A8 0xD
+#define WIN_COLOR_DEPTH_B6x2G6x2R6x2A8 0xE
+#define WIN_COLOR_DEPTH_R6x2G6x2B6x2A8 0xF
+#define WIN_COLOR_DEPTH_YCbCr422 0x10
+#define WIN_COLOR_DEPTH_YUV422 0x11
+#define WIN_COLOR_DEPTH_YCbCr420P 0x12
+#define WIN_COLOR_DEPTH_YUV420P 0x13
+#define WIN_COLOR_DEPTH_YCbCr422P 0x14
+#define WIN_COLOR_DEPTH_YUV422P 0x15
+#define WIN_COLOR_DEPTH_YCbCr422R 0x16
+#define WIN_COLOR_DEPTH_YUV422R 0x17
+#define WIN_COLOR_DEPTH_YCbCr422RA 0x18
+#define WIN_COLOR_DEPTH_YUV422RA 0x19
+
+#define DC_WIN_BUFFER_CONTROL 0x702
+#define DC_WIN_POSITION 0x704
+
+#define DC_WIN_SIZE 0x705
+#define H_SIZE(x) (((x) & 0x1fff) << 0)
+#define V_SIZE(x) (((x) & 0x1fff) << 16)
+
+#define DC_WIN_PRESCALED_SIZE 0x706
+#define H_PRESCALED_SIZE(x) (((x) & 0x7fff) << 0)
+#define V_PRESCALED_SIZE(x) (((x) & 0x1fff) << 16)
+
+#define DC_WIN_H_INITIAL_DDA 0x707
+#define DC_WIN_V_INITIAL_DDA 0x708
+
+#define DC_WIN_DDA_INC 0x709
+#define H_DDA_INC(x) (((x) & 0xffff) << 0)
+#define V_DDA_INC(x) (((x) & 0xffff) << 16)
+
+#define DC_WIN_LINE_STRIDE 0x70A
+#define LINE_STRIDE(x) (x)
+#define UV_LINE_STRIDE(x) (((x) & 0xffff) << 16)
+#define DC_WIN_DV_CONTROL 0x70E
+
+// The following registers are A/B/C shadows of the 0xBC0/0xDC0/0xFC0 registers (see DISPLAY_WINDOW_HEADER).
+#define DC_WINBUF_START_ADDR 0x800
+#define DC_WINBUF_ADDR_H_OFFSET 0x806
+#define DC_WINBUF_ADDR_V_OFFSET 0x808
+#define DC_WINBUF_SURFACE_KIND 0x80B
+#define PITCH (0 << 0)
+#define TILED (1 << 0)
+#define BLOCK (2 << 0)
+#define BLOCK_HEIGHT(x) (((x) & 0x7) << 4)
+
+/*! Display serial interface registers. */
+#define _DSIREG(reg) ((reg) * 4)
+
+#define DSI_INCR_SYNCPT_CNTRL 0x1
+
+#define DSI_RD_DATA 0x9
+#define DSI_WR_DATA 0xA
+
+#define DSI_POWER_CONTROL 0xB
+#define DSI_POWER_CONTROL_ENABLE 1
+
+#define DSI_INT_ENABLE 0xC
+#define DSI_INT_STATUS 0xD
+#define DSI_INT_MASK 0xE
+
+#define DSI_HOST_CONTROL 0xF
+#define DSI_HOST_CONTROL_FIFO_RESET (1 << 21)
+#define DSI_HOST_CONTROL_CRC_RESET (1 << 20)
+#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12)
+#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12)
+#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12)
+#define DSI_HOST_CONTROL_RAW (1 << 6)
+#define DSI_HOST_CONTROL_HS (1 << 5)
+#define DSI_HOST_CONTROL_FIFO_SEL (1 << 4)
+#define DSI_HOST_CONTROL_IMM_BTA (1 << 3)
+#define DSI_HOST_CONTROL_PKT_BTA (1 << 2)
+#define DSI_HOST_CONTROL_CS (1 << 1)
+#define DSI_HOST_CONTROL_ECC (1 << 0)
+
+#define DSI_CONTROL 0x10
+#define DSI_CONTROL_HS_CLK_CTRL (1 << 20)
+#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16)
+#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12)
+#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8)
+#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4)
+#define DSI_CONTROL_DCS_ENABLE (1 << 3)
+#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2)
+#define DSI_CONTROL_VIDEO_ENABLE (1 << 1)
+#define DSI_CONTROL_HOST_ENABLE (1 << 0)
+
+#define DSI_SOL_DELAY 0x11
+#define DSI_MAX_THRESHOLD 0x12
+
+#define DSI_TRIGGER 0x13
+#define DSI_TRIGGER_HOST (1 << 1)
+#define DSI_TRIGGER_VIDEO (1 << 0)
+
+#define DSI_TX_CRC 0x14
+#define DSI_STATUS 0x15
+#define DSI_INIT_SEQ_CONTROL 0x1A
+#define DSI_INIT_SEQ_DATA_0 0x1B
+#define DSI_INIT_SEQ_DATA_1 0x1C
+#define DSI_INIT_SEQ_DATA_2 0x1D
+#define DSI_INIT_SEQ_DATA_3 0x1E
+#define DSI_PKT_SEQ_0_LO 0x23
+#define DSI_PKT_SEQ_0_HI 0x24
+#define DSI_PKT_SEQ_1_LO 0x25
+#define DSI_PKT_SEQ_1_HI 0x26
+#define DSI_PKT_SEQ_2_LO 0x27
+#define DSI_PKT_SEQ_2_HI 0x28
+#define DSI_PKT_SEQ_3_LO 0x29
+#define DSI_PKT_SEQ_3_HI 0x2A
+#define DSI_PKT_SEQ_4_LO 0x2B
+#define DSI_PKT_SEQ_4_HI 0x2C
+#define DSI_PKT_SEQ_5_LO 0x2D
+#define DSI_PKT_SEQ_5_HI 0x2E
+#define DSI_DCS_CMDS 0x33
+#define DSI_PKT_LEN_0_1 0x34
+#define DSI_PKT_LEN_2_3 0x35
+#define DSI_PKT_LEN_4_5 0x36
+#define DSI_PKT_LEN_6_7 0x37
+#define DSI_PHY_TIMING_0 0x3C
+#define DSI_PHY_TIMING_1 0x3D
+#define DSI_PHY_TIMING_2 0x3E
+#define DSI_BTA_TIMING 0x3F
+
+#define DSI_TIMEOUT_0 0x44
+#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16)
+#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0)
+
+#define DSI_TIMEOUT_1 0x45
+#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16)
+#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0)
+
+#define DSI_TO_TALLY 0x46
+
+#define DSI_PAD_CONTROL_0 0x4B
+#define DSI_PAD_CONTROL_VS1_PULLDN_CLK (1 << 24)
+#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16)
+#define DSI_PAD_CONTROL_VS1_PDIO_CLK (1 << 8)
+#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0)
+
+#define DSI_PAD_CONTROL_CD 0x4c
+#define DSI_VIDEO_MODE_CONTROL 0x4E
+
+#define DSI_PAD_CONTROL_1 0x4F
+#define DSI_PAD_CONTROL_2 0x50
+
+#define DSI_PAD_CONTROL_3 0x51
+#define DSI_PAD_PREEMP_PD_CLK(x) (((x) & 0x3) << 12)
+#define DSI_PAD_PREEMP_PU_CLK(x) (((x) & 0x3) << 8)
+#define DSI_PAD_PREEMP_PD(x) (((x) & 0x3) << 4)
+#define DSI_PAD_PREEMP_PU(x) (((x) & 0x3) << 0)
+
+#define DSI_PAD_CONTROL_4 0x52
+#define DSI_PAD_CONTROL_5_MARIKO 0x53
+#define DSI_PAD_CONTROL_6_MARIKO 0x54
+#define DSI_PAD_CONTROL_7_MARIKO 0x55
+#define DSI_INIT_SEQ_DATA_15 0x5F
+
+#define DSI_INIT_SEQ_DATA_15_MARIKO 0x62
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_save_context.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_save_context.cpp
new file mode 100644
index 00000000..7c2eed14
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_save_context.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_save_context.hpp"
+#include "fatal_sdmmc.hpp"
+#include "fs/fatal_fs_api.hpp"
+
+namespace ams::secmon::fatal {
+
+ Result SaveFatalErrorContext(const ams::impl::FatalErrorContext *ctx) {
+ /* Initialize the sdmmc driver. */
+ R_TRY(InitializeSdCard());
+
+ /* Get the connection status. */
+ #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)
+ {
+ sdmmc::SpeedMode speed_mode;
+ sdmmc::BusWidth bus_width;
+ R_TRY(CheckSdCardConnection(std::addressof(speed_mode), std::addressof(bus_width)));
+ AMS_SECMON_LOG("Sd Card Connection:\n");
+ AMS_SECMON_LOG(" Speed Mode: %u\n", static_cast(speed_mode));
+ AMS_SECMON_LOG(" Bus Width: %u\n", static_cast(bus_width));
+ }
+ #endif
+
+ /* Mount the SD card. */
+ R_UNLESS(fs::MountSdCard(), fs::ResultPartitionNotFound());
+
+ /* Unmount the SD card once we're done. */
+ ON_SCOPE_EXIT { fs::UnmountSdCard(); };
+
+ /* Create and open the file. */
+ fs::FileHandle file;
+ {
+ /* Generate the file path. */
+ char path[0x40];
+ util::TSNPrintf(path, sizeof(path), "/atmosphere/fatal_errors/report_%016" PRIx64 ".bin", ctx->report_identifier);
+
+ /* Create the file. */
+ R_TRY(fs::CreateFile(path, sizeof(*ctx)));
+
+ /* Open the file. */
+ R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_ReadWrite));
+ }
+
+ /* Ensure we close the file when done with it. */
+ ON_SCOPE_EXIT { fs::CloseFile(file); };
+
+ /* Write the context to the file. */
+ R_TRY(fs::WriteFile(file, 0, ctx, sizeof(*ctx), fs::WriteOption::Flush));
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_save_context.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_save_context.hpp
new file mode 100644
index 00000000..97f04b71
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_save_context.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+namespace ams::secmon::fatal {
+
+ Result SaveFatalErrorContext(const ams::impl::FatalErrorContext *ctx);
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc.cpp
new file mode 100644
index 00000000..668abbe4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_device_page_table.hpp"
+
+namespace ams::secmon::fatal {
+
+ namespace {
+
+ constexpr inline auto Port = sdmmc::Port_SdCard0;
+
+ ALWAYS_INLINE u8 *GetSdCardWorkBuffer() {
+ return MemoryRegionVirtualDramSdmmcMappedData.GetPointer() + MemoryRegionVirtualDramSdmmcMappedData.GetSize() - mmu::PageSize;
+ }
+
+ ALWAYS_INLINE u8 *GetSdCardDmaBuffer() {
+ return MemoryRegionVirtualDramSdmmcMappedData.GetPointer();
+ }
+
+ constexpr inline size_t SdCardDmaBufferSize = MemoryRegionVirtualDramSdmmcMappedData.GetSize() - mmu::PageSize;
+ constexpr inline size_t SdCardDmaBufferSectors = SdCardDmaBufferSize / sdmmc::SectorSize;
+ static_assert(util::IsAligned(SdCardDmaBufferSize, sdmmc::SectorSize));
+
+ }
+
+ Result InitializeSdCard() {
+ /* Map main memory for the sdmmc device. */
+ InitializeDevicePageTableForSdmmc1();
+
+ /* Initialize sdmmc library. */
+ sdmmc::Initialize(Port);
+
+ sdmmc::SetSdCardWorkBuffer(Port, GetSdCardWorkBuffer(), sdmmc::SdCardWorkBufferSize);
+
+ //sdmmc::Deactivate(Port);
+ R_TRY(sdmmc::Activate(Port));
+
+ R_SUCCEED();
+ }
+
+ Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw) {
+ R_RETURN(sdmmc::CheckSdCardConnection(out_sm, out_bw, Port));
+ }
+
+ Result ReadSdCard(void *dst, size_t size, size_t sector_index, size_t sector_count) {
+ /* Validate that our buffer is valid. */
+ AMS_ASSERT(size >= sector_count * sdmmc::SectorSize);
+ AMS_UNUSED(size);
+
+ /* Repeatedly read sectors. */
+ u8 *dst_u8 = static_cast(dst);
+ void * const dma_buffer = GetSdCardDmaBuffer();
+ while (sector_count > 0) {
+ /* Read sectors into the DMA buffer. */
+ const size_t cur_sectors = std::min(sector_count, SdCardDmaBufferSectors);
+ const size_t cur_size = cur_sectors * sdmmc::SectorSize;
+ R_TRY(sdmmc::Read(dma_buffer, cur_size, Port, sector_index, cur_sectors));
+
+ /* Copy data from the DMA buffer to the output. */
+ std::memcpy(dst_u8, dma_buffer, cur_size);
+
+ /* Advance. */
+ dst_u8 += cur_size;
+ sector_index += cur_sectors;
+ sector_count -= cur_sectors;
+ }
+
+ R_SUCCEED();
+ }
+
+ Result WriteSdCard(size_t sector_index, size_t sector_count, const void *src, size_t size) {
+ /* Validate that our buffer is valid. */
+ AMS_ASSERT(size >= sector_count * sdmmc::SectorSize);
+ AMS_UNUSED(size);
+
+ /* Repeatedly read sectors. */
+ const u8 *src_u8 = static_cast(src);
+ void * const dma_buffer = GetSdCardDmaBuffer();
+ while (sector_count > 0) {
+ /* Copy sectors into the DMA buffer. */
+ const size_t cur_sectors = std::min(sector_count, SdCardDmaBufferSectors);
+ const size_t cur_size = cur_sectors * sdmmc::SectorSize;
+ std::memcpy(dma_buffer, src_u8, cur_size);
+
+ /* Write sectors to the sd card. */
+ R_TRY(sdmmc::Write(Port, sector_index, cur_sectors, dma_buffer, cur_size));
+
+ /* Advance. */
+ src_u8 += cur_size;
+ sector_index += cur_sectors;
+ sector_count -= cur_sectors;
+ }
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc.hpp
new file mode 100644
index 00000000..38a3e1b4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc.hpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+namespace ams::secmon::fatal {
+
+ Result InitializeSdCard();
+ Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw);
+ Result ReadSdCard(void *dst, size_t size, size_t sector_index, size_t sector_count);
+ Result WriteSdCard(size_t sector_index, size_t sector_count, const void *src, size_t size);
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc_c.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc_c.cpp
new file mode 100644
index 00000000..2f2dda35
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc_c.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_sdmmc_c.h"
+#include "fatal_sdmmc.hpp"
+
+bool sdmmc_read_sd_card(void *dst, size_t size, size_t sector_index, size_t sector_count) {
+ return R_SUCCEEDED(ams::secmon::fatal::ReadSdCard(dst, size, sector_index, sector_count));
+}
+
+bool sdmmc_write_sd_card(size_t sector_index, size_t sector_count, const void *src, size_t size) {
+ return R_SUCCEEDED(ams::secmon::fatal::WriteSdCard(sector_index, sector_count, src, size));
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc_c.h b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc_c.h
new file mode 100644
index 00000000..4075f197
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sdmmc_c.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool sdmmc_read_sd_card(void *dst, size_t size, size_t sector_index, size_t sector_count);
+bool sdmmc_write_sd_card(size_t sector_index, size_t sector_count, const void *src, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sound.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sound.cpp
new file mode 100644
index 00000000..cc06e277
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sound.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#include
+#include "fatal_sound.hpp"
+
+namespace ams::secmon::fatal {
+
+ namespace {
+
+ constexpr inline int I2cAddressMaxAlc5639 = 0x1C;
+
+ constexpr inline uintptr_t GPIO = secmon::MemoryRegionVirtualDeviceGpio.GetAddress();
+
+ constexpr size_t GPIO_PORT7_CNF_1 = 0x604;
+ constexpr size_t GPIO_PORT7_OE_1 = 0x614;
+ constexpr size_t GPIO_PORT7_OUT_1 = 0x624;
+
+ void WriteAlc5639Register(int r, u16 val) {
+ i2c::Send(i2c::Port_1, I2cAddressMaxAlc5639, r, std::addressof(val), sizeof(val));
+ }
+
+ }
+
+ void StopSound() {
+ /* Mute output to the speaker, setting left/right volume to 0 DB. */
+ WriteAlc5639Register(0x01, 0xC8C8);
+
+ /* Mute output to headphones, setting left/right volume to 0 DB. */
+ WriteAlc5639Register(0x02, 0xC8C8);
+
+ /* Clear all Power Management Control registers by writing 0x0000 to them. */
+ for (int r = 0x61; r <= 0x66; ++r) {
+ WriteAlc5639Register(r, 0x0000);
+ }
+
+ /* Configure CodecLdoEn as GPIO. */
+ reg::SetBits(GPIO + GPIO_PORT7_CNF_1, (1u << 4));
+
+ /* Configure CodecLdoEn as Output. */
+ reg::SetBits(GPIO + GPIO_PORT7_OE_1, (1u << 4));
+
+ /* Wait 200 milliseconds for config to take effect. */
+ util::WaitMicroSeconds(200'000ul);
+
+ /* Pull CodecLdoEn low. */
+ reg::ClearBits(GPIO + GPIO_PORT7_OUT_1, (1u << 4));
+ }
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sound.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sound.hpp
new file mode 100644
index 00000000..5aef9f3c
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatal_sound.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see .
+ */
+#pragma once
+#include
+
+namespace ams::secmon::fatal {
+
+ void StopSound();
+
+}
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/00history.txt b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/00history.txt
new file mode 100644
index 00000000..cb8753b4
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/00history.txt
@@ -0,0 +1,340 @@
+----------------------------------------------------------------------------
+ Revision history of FatFs module
+----------------------------------------------------------------------------
+
+R0.00 (February 26, 2006)
+
+ Prototype.
+
+
+
+R0.01 (April 29, 2006)
+
+ The first release.
+
+
+
+R0.02 (June 01, 2006)
+
+ Added FAT12 support.
+ Removed unbuffered mode.
+ Fixed a problem on small (<32M) partition.
+
+
+
+R0.02a (June 10, 2006)
+
+ Added a configuration option (_FS_MINIMUM).
+
+
+
+R0.03 (September 22, 2006)
+
+ Added f_rename().
+ Changed option _FS_MINIMUM to _FS_MINIMIZE.
+
+
+
+R0.03a (December 11, 2006)
+
+ Improved cluster scan algorithm to write files fast.
+ Fixed f_mkdir() creates incorrect directory on FAT32.
+
+
+
+R0.04 (February 04, 2007)
+
+ Added f_mkfs().
+ Supported multiple drive system.
+ Changed some interfaces for multiple drive system.
+ Changed f_mountdrv() to f_mount().
+
+
+
+R0.04a (April 01, 2007)
+
+ Supported multiple partitions on a physical drive.
+ Added a capability of extending file size to f_lseek().
+ Added minimization level 3.
+ Fixed an endian sensitive code in f_mkfs().
+
+
+
+R0.04b (May 05, 2007)
+
+ Added a configuration option _USE_NTFLAG.
+ Added FSINFO support.
+ Fixed DBCS name can result FR_INVALID_NAME.
+ Fixed short seek (<= csize) collapses the file object.
+
+
+
+R0.05 (August 25, 2007)
+
+ Changed arguments of f_read(), f_write() and f_mkfs().
+ Fixed f_mkfs() on FAT32 creates incorrect FSINFO.
+ Fixed f_mkdir() on FAT32 creates incorrect directory.
+
+
+
+R0.05a (February 03, 2008)
+
+ Added f_truncate() and f_utime().
+ Fixed off by one error at FAT sub-type determination.
+ Fixed btr in f_read() can be mistruncated.
+ Fixed cached sector is not flushed when create and close without write.
+
+
+
+R0.06 (April 01, 2008)
+
+ Added fputc(), fputs(), fprintf() and fgets().
+ Improved performance of f_lseek() on moving to the same or following cluster.
+
+
+
+R0.07 (April 01, 2009)
+
+ Merged Tiny-FatFs as a configuration option. (_FS_TINY)
+ Added long file name feature. (_USE_LFN)
+ Added multiple code page feature. (_CODE_PAGE)
+ Added re-entrancy for multitask operation. (_FS_REENTRANT)
+ Added auto cluster size selection to f_mkfs().
+ Added rewind option to f_readdir().
+ Changed result code of critical errors.
+ Renamed string functions to avoid name collision.
+
+
+
+R0.07a (April 14, 2009)
+
+ Septemberarated out OS dependent code on reentrant cfg.
+ Added multiple sector size feature.
+
+
+
+R0.07c (June 21, 2009)
+
+ Fixed f_unlink() can return FR_OK on error.
+ Fixed wrong cache control in f_lseek().
+ Added relative path feature.
+ Added f_chdir() and f_chdrive().
+ Added proper case conversion to extended character.
+
+
+
+R0.07e (November 03, 2009)
+
+ Septemberarated out configuration options from ff.h to ffconf.h.
+ Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH.
+ Fixed name matching error on the 13 character boundary.
+ Added a configuration option, _LFN_UNICODE.
+ Changed f_readdir() to return the SFN with always upper case on non-LFN cfg.
+
+
+
+R0.08 (May 15, 2010)
+
+ Added a memory configuration option. (_USE_LFN = 3)
+ Added file lock feature. (_FS_SHARE)
+ Added fast seek feature. (_USE_FASTSEEK)
+ Changed some types on the API, XCHAR->TCHAR.
+ Changed .fname in the FILINFO structure on Unicode cfg.
+ String functions support UTF-8 encoding files on Unicode cfg.
+
+
+
+R0.08a (August 16, 2010)
+
+ Added f_getcwd(). (_FS_RPATH = 2)
+ Added sector erase feature. (_USE_ERASE)
+ Moved file lock semaphore table from fs object to the bss.
+ Fixed f_mkfs() creates wrong FAT32 volume.
+
+
+
+R0.08b (January 15, 2011)
+
+ Fast seek feature is also applied to f_read() and f_write().
+ f_lseek() reports required table size on creating CLMP.
+ Extended format syntax of f_printf().
+ Ignores duplicated directory separators in given path name.
+
+
+
+R0.09 (September 06, 2011)
+
+ f_mkfs() supports multiple partition to complete the multiple partition feature.
+ Added f_fdisk().
+
+
+
+R0.09a (August 27, 2012)
+
+ Changed f_open() and f_opendir() reject null object pointer to avoid crash.
+ Changed option name _FS_SHARE to _FS_LOCK.
+ Fixed assertion failure due to OS/2 EA on FAT12/16 volume.
+
+
+
+R0.09b (January 24, 2013)
+
+ Added f_setlabel() and f_getlabel().
+
+
+
+R0.10 (October 02, 2013)
+
+ Added selection of character encoding on the file. (_STRF_ENCODE)
+ Added f_closedir().
+ Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO)
+ Added forced mount feature with changes of f_mount().
+ Improved behavior of volume auto detection.
+ Improved write throughput of f_puts() and f_printf().
+ Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write().
+ Fixed f_write() can be truncated when the file size is close to 4GB.
+ Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error.
+
+
+
+R0.10a (January 15, 2014)
+
+ Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID)
+ Added a configuration option of minimum sector size. (_MIN_SS)
+ 2nd argument of f_rename() can have a drive number and it will be ignored.
+ Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10)
+ Fixed f_close() invalidates the file object without volume lock.
+ Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10)
+ Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07)
+
+
+
+R0.10b (May 19, 2014)
+
+ Fixed a hard error in the disk I/O layer can collapse the directory entry.
+ Fixed LFN entry is not deleted when delete/rename an object with lossy converted SFN. (appeared at R0.07)
+
+
+
+R0.10c (November 09, 2014)
+
+ Added a configuration option for the platforms without RTC. (_FS_NORTC)
+ Changed option name _USE_ERASE to _USE_TRIM.
+ Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b)
+ Fixed a potential problem of FAT access that can appear on disk error.
+ Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08)
+
+
+
+R0.11 (February 09, 2015)
+
+ Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND)
+ Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c)
+ Fixed _FS_NORTC option does not work properly. (appeared at R0.10c)
+
+
+
+R0.11a (September 05, 2015)
+
+ Fixed wrong media change can lead a deadlock at thread-safe configuration.
+ Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE)
+ Removed some code pages actually not exist on the standard systems. (_CODE_PAGE)
+ Fixed errors in the case conversion teble of code page 437 and 850 (ff.c).
+ Fixed errors in the case conversion teble of Unicode (cc*.c).
+
+
+
+R0.12 (April 12, 2016)
+
+ Added support for exFAT file system. (_FS_EXFAT)
+ Added f_expand(). (_USE_EXPAND)
+ Changed some members in FINFO structure and behavior of f_readdir().
+ Added an option _USE_CHMOD.
+ Removed an option _WORD_ACCESS.
+ Fixed errors in the case conversion table of Unicode (cc*.c).
+
+
+
+R0.12a (July 10, 2016)
+
+ Added support for creating exFAT volume with some changes of f_mkfs().
+ Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed.
+ f_forward() is available regardless of _FS_TINY.
+ Fixed f_mkfs() creates wrong volume. (appeared at R0.12)
+ Fixed wrong memory read in create_name(). (appeared at R0.12)
+ Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD.
+
+
+
+R0.12b (September 04, 2016)
+
+ Made f_rename() be able to rename objects with the same name but case.
+ Fixed an error in the case conversion teble of code page 866. (ff.c)
+ Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12)
+ Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12)
+ Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12)
+ Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12)
+ Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12)
+ Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12)
+
+
+
+R0.12c (March 04, 2017)
+
+ Improved write throughput at the fragmented file on the exFAT volume.
+ Made memory usage for exFAT be able to be reduced as decreasing _MAX_LFN.
+ Fixed successive f_getfree() can return wrong count on the FAT12/16 volume. (appeared at R0.12)
+ Fixed configuration option _VOLUMES cannot be set 10. (appeared at R0.10c)
+
+
+
+R0.13 (May 21, 2017)
+
+ Changed heading character of configuration keywords "_" to "FF_".
+ Removed ASCII-only configuration, FF_CODE_PAGE = 1. Use FF_CODE_PAGE = 437 instead.
+ Added f_setcp(), run-time code page configuration. (FF_CODE_PAGE = 0)
+ Improved cluster allocation time on stretch a deep buried cluster chain.
+ Improved processing time of f_mkdir() with large cluster size by using FF_USE_LFN = 3.
+ Improved NoFatChain flag of the fragmented file to be set after it is truncated and got contiguous.
+ Fixed archive attribute is left not set when a file on the exFAT volume is renamed. (appeared at R0.12)
+ Fixed exFAT FAT entry can be collapsed when write or lseek operation to the existing file is done. (appeared at R0.12c)
+ Fixed creating a file can fail when a new cluster allocation to the exFAT directory occures. (appeared at R0.12c)
+
+
+
+R0.13a (October 14, 2017)
+
+ Added support for UTF-8 encoding on the API. (FF_LFN_UNICODE = 2)
+ Added options for file name output buffer. (FF_LFN_BUF, FF_SFN_BUF).
+ Added dynamic memory allocation option for working buffer of f_mkfs() and f_fdisk().
+ Fixed f_fdisk() and f_mkfs() create the partition table with wrong CHS parameters. (appeared at R0.09)
+ Fixed f_unlink() can cause lost clusters at fragmented file on the exFAT volume. (appeared at R0.12c)
+ Fixed f_setlabel() rejects some valid characters for exFAT volume. (appeared at R0.12)
+
+
+
+R0.13b (April 07, 2018)
+
+ Added support for UTF-32 encoding on the API. (FF_LFN_UNICODE = 3)
+ Added support for Unix style volume ID. (FF_STR_VOLUME_ID = 2)
+ Fixed accesing any object on the exFAT root directory beyond the cluster boundary can fail. (appeared at R0.12c)
+ Fixed f_setlabel() does not reject some invalid characters. (appeared at R0.09b)
+
+
+
+R0.13c (October 14, 2018)
+ Supported stdint.h for C99 and later. (integer.h was included in ff.h)
+ Fixed reading a directory gets infinite loop when the last directory entry is not empty. (appeared at R0.12)
+ Fixed creating a sub-directory in the fragmented sub-directory on the exFAT volume collapses FAT chain of the parent directory. (appeared at R0.12)
+ Fixed f_getcwd() cause output buffer overrun when the buffer has a valid drive number. (appeared at R0.13b)
+
+
+
+R0.14 (October 14, 2019)
+ Added support for 64-bit LBA and GUID partition table (FF_LBA64 = 1)
+ Changed some API functions, f_mkfs() and f_fdisk().
+ Fixed f_open() function cannot find the file with file name in length of FF_MAX_LFN characters.
+ Fixed f_readdir() function cannot retrieve long file names in length of FF_MAX_LFN - 1 characters.
+ Fixed f_readdir() function returns file names with wrong case conversion. (appeared at R0.12)
+ Fixed f_mkfs() function can fail to create exFAT volume in the second partition. (appeared at R0.12)
+
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/00readme.txt b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/00readme.txt
new file mode 100644
index 00000000..234c675a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/00readme.txt
@@ -0,0 +1,21 @@
+FatFs Module Source Files R0.14
+
+
+FILES
+
+ 00readme.txt This file.
+ 00history.txt Revision history.
+ ff.c FatFs module.
+ ffconf.h Configuration file of FatFs module.
+ ff.h Common include file for FatFs and application module.
+ diskio.h Common include file for FatFs and disk I/O module.
+ diskio.c An example of glue function to attach existing disk I/O module to FatFs.
+ ffunicode.c Optional Unicode utility functions.
+ ffsystem.c An example of optional O/S related functions.
+
+
+ Low level disk I/O module is not included in this archive because the FatFs
+ module is only a generic file system layer and it does not depend on any specific
+ storage device. You need to provide a low level disk I/O module written to
+ control the storage device that attached to the target system.
+
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/diskio.c b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/diskio.c
new file mode 100644
index 00000000..43fa0d37
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/diskio.c
@@ -0,0 +1,102 @@
+/*-----------------------------------------------------------------------*/
+/* 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
+#include
+#include "ff.h" /* Obtains integer types */
+#include "diskio.h" /* Declarations of disk functions */
+#include "ffconf.h"
+
+#include "../fatal_sdmmc_c.h"
+
+/*-----------------------------------------------------------------------*/
+/* Get Drive Status */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_status (
+ BYTE pdrv /* Physical drive nmuber to identify the drive */
+)
+{
+ return RES_OK;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Inidialize a Drive */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_initialize (
+ BYTE pdrv /* Physical drive nmuber to identify the drive */
+)
+{
+ return RES_OK;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read Sector(s) */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_read (
+ BYTE pdrv, /* Physical drive nmuber 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 */
+)
+{
+ switch (pdrv) {
+ case 0:
+ return sdmmc_read_sd_card(buff, count * 512, sector, count) ? RES_OK : RES_ERROR;
+ default:
+ return RES_PARERR;
+ }
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Write Sector(s) */
+/*-----------------------------------------------------------------------*/
+
+#if FF_FS_READONLY == 0
+
+DRESULT disk_write (
+ BYTE pdrv, /* Physical drive nmuber 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 */
+)
+{
+ switch (pdrv) {
+ case 0:
+ return sdmmc_write_sd_card(sector, count, buff, count * 512) ? RES_OK : RES_ERROR;
+ default:
+ return RES_PARERR;
+ }
+}
+
+#endif
+
+
+/*-----------------------------------------------------------------------*/
+/* Miscellaneous Functions */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_ioctl (
+ BYTE pdrv, /* Physical drive nmuber (0..) */
+ BYTE cmd, /* Control code */
+ void *buff /* Buffer to send/receive control data */
+)
+{
+
+ return RES_OK;
+}
+
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/diskio.h b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/diskio.h
new file mode 100644
index 00000000..511269ce
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/diskio.h
@@ -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
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ff.c b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ff.c
new file mode 100644
index 00000000..3e8d435a
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ff.c
@@ -0,0 +1,6864 @@
+/*----------------------------------------------------------------------------/
+/ 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.
+/
+/----------------------------------------------------------------------------*/
+#pragma GCC diagnostic ignored "-Wunused-function"
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
+
+#include "ff.h" /* Declarations of FatFs API */
+#include "diskio.h" /* Declarations of device I/O functions */
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Definitions
+
+---------------------------------------------------------------------------*/
+
+#if FF_DEFINED != 86606 /* Revision ID */
+#error Wrong include file (ff.h).
+#endif
+
+
+/* Limits and boundaries */
+#define MAX_DIR 0x200000 /* Max size of FAT directory */
+#define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */
+#define MAX_FAT12 0xFF5 /* Max FAT12 clusters (differs from specs, but right for real DOS/Windows behavior) */
+#define MAX_FAT16 0xFFF5 /* Max FAT16 clusters (differs from specs, but right for real DOS/Windows behavior) */
+#define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */
+#define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */
+
+
+/* Character code support macros */
+#define IsUpper(c) ((c) >= 'A' && (c) <= 'Z')
+#define IsLower(c) ((c) >= 'a' && (c) <= 'z')
+#define IsDigit(c) ((c) >= '0' && (c) <= '9')
+#define IsSurrogate(c) ((c) >= 0xD800 && (c) <= 0xDFFF)
+#define IsSurrogateH(c) ((c) >= 0xD800 && (c) <= 0xDBFF)
+#define IsSurrogateL(c) ((c) >= 0xDC00 && (c) <= 0xDFFF)
+
+
+/* Additional file access control and file status flags for internal use */
+#define FA_SEEKEND 0x20 /* Seek to end of the file on file open */
+#define FA_MODIFIED 0x40 /* File has been modified */
+#define FA_DIRTY 0x80 /* FIL.buf[] needs to be written-back */
+
+
+/* Additional file attribute bits for internal use */
+#define AM_VOL 0x08 /* Volume label */
+#define AM_LFN 0x0F /* LFN entry */
+#define AM_MASK 0x3F /* Mask of defined bits */
+
+
+/* Name status flags in fn[11] */
+#define NSFLAG 11 /* Index of the name status byte */
+#define NS_LOSS 0x01 /* Out of 8.3 format */
+#define NS_LFN 0x02 /* Force to create LFN entry */
+#define NS_LAST 0x04 /* Last segment */
+#define NS_BODY 0x08 /* Lower case flag (body) */
+#define NS_EXT 0x10 /* Lower case flag (ext) */
+#define NS_DOT 0x20 /* Dot entry */
+#define NS_NOLFN 0x40 /* Do not find LFN */
+#define NS_NONAME 0x80 /* Not followed */
+
+
+/* exFAT directory entry types */
+#define ET_BITMAP 0x81 /* Allocation bitmap */
+#define ET_UPCASE 0x82 /* Up-case table */
+#define ET_VLABEL 0x83 /* Volume label */
+#define ET_FILEDIR 0x85 /* File and directory */
+#define ET_STREAM 0xC0 /* Stream extension */
+#define ET_FILENAME 0xC1 /* Name extension */
+
+
+/* FatFs refers the FAT structure as simple byte array instead of structure member
+/ because the C structure is not binary compatible between different platforms */
+
+#define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */
+#define BS_OEMName 3 /* OEM name (8-byte) */
+#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */
+#define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */
+#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */
+#define BPB_NumFATs 16 /* Number of FATs (BYTE) */
+#define BPB_RootEntCnt 17 /* Size of root directory area for FAT [entry] (WORD) */
+#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */
+#define BPB_Media 21 /* Media descriptor byte (BYTE) */
+#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */
+#define BPB_SecPerTrk 24 /* Number of sectors per track for int13h [sector] (WORD) */
+#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */
+#define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */
+#define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */
+#define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */
+#define BS_NTres 37 /* WindowsNT error flag (BYTE) */
+#define BS_BootSig 38 /* Extended boot signature (BYTE) */
+#define BS_VolID 39 /* Volume serial number (DWORD) */
+#define BS_VolLab 43 /* Volume label string (8-byte) */
+#define BS_FilSysType 54 /* Filesystem type string (8-byte) */
+#define BS_BootCode 62 /* Boot code (448-byte) */
+#define BS_55AA 510 /* Signature word (WORD) */
+
+#define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */
+#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */
+#define BPB_FSVer32 42 /* FAT32: Filesystem version (WORD) */
+#define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */
+#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */
+#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */
+#define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */
+#define BS_NTres32 65 /* FAT32: Error flag (BYTE) */
+#define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */
+#define BS_VolID32 67 /* FAT32: Volume serial number (DWORD) */
+#define BS_VolLab32 71 /* FAT32: Volume label string (8-byte) */
+#define BS_FilSysType32 82 /* FAT32: Filesystem type string (8-byte) */
+#define BS_BootCode32 90 /* FAT32: Boot code (420-byte) */
+
+#define BPB_ZeroedEx 11 /* exFAT: MBZ field (53-byte) */
+#define BPB_VolOfsEx 64 /* exFAT: Volume offset from top of the drive [sector] (QWORD) */
+#define BPB_TotSecEx 72 /* exFAT: Volume size [sector] (QWORD) */
+#define BPB_FatOfsEx 80 /* exFAT: FAT offset from top of the volume [sector] (DWORD) */
+#define BPB_FatSzEx 84 /* exFAT: FAT size [sector] (DWORD) */
+#define BPB_DataOfsEx 88 /* exFAT: Data offset from top of the volume [sector] (DWORD) */
+#define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */
+#define BPB_RootClusEx 96 /* exFAT: Root directory start cluster (DWORD) */
+#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */
+#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD) */
+#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD) */
+#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in unit of byte (BYTE) */
+#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in unit of sector (BYTE) */
+#define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */
+#define BPB_DrvNumEx 111 /* exFAT: Physical drive number for int13h (BYTE) */
+#define BPB_PercInUseEx 112 /* exFAT: Percent in use (BYTE) */
+#define BPB_RsvdEx 113 /* exFAT: Reserved (7-byte) */
+#define BS_BootCodeEx 120 /* exFAT: Boot code (390-byte) */
+
+#define DIR_Name 0 /* Short file name (11-byte) */
+#define DIR_Attr 11 /* Attribute (BYTE) */
+#define DIR_NTres 12 /* Lower case flag (BYTE) */
+#define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */
+#define DIR_CrtTime 14 /* Created time (DWORD) */
+#define DIR_LstAccDate 18 /* Last accessed date (WORD) */
+#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */
+#define DIR_ModTime 22 /* Modified time (DWORD) */
+#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */
+#define DIR_FileSize 28 /* File size (DWORD) */
+#define LDIR_Ord 0 /* LFN: LFN order and LLE flag (BYTE) */
+#define LDIR_Attr 11 /* LFN: LFN attribute (BYTE) */
+#define LDIR_Type 12 /* LFN: Entry type (BYTE) */
+#define LDIR_Chksum 13 /* LFN: Checksum of the SFN (BYTE) */
+#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD) */
+#define XDIR_Type 0 /* exFAT: Type of exFAT directory entry (BYTE) */
+#define XDIR_NumLabel 1 /* exFAT: Number of volume label characters (BYTE) */
+#define XDIR_Label 2 /* exFAT: Volume label (11-WORD) */
+#define XDIR_CaseSum 4 /* exFAT: Sum of case conversion table (DWORD) */
+#define XDIR_NumSec 1 /* exFAT: Number of secondary entries (BYTE) */
+#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD) */
+#define XDIR_Attr 4 /* exFAT: File attribute (WORD) */
+#define XDIR_CrtTime 8 /* exFAT: Created time (DWORD) */
+#define XDIR_ModTime 12 /* exFAT: Modified time (DWORD) */
+#define XDIR_AccTime 16 /* exFAT: Last accessed time (DWORD) */
+#define XDIR_CrtTime10 20 /* exFAT: Created time subsecond (BYTE) */
+#define XDIR_ModTime10 21 /* exFAT: Modified time subsecond (BYTE) */
+#define XDIR_CrtTZ 22 /* exFAT: Created timezone (BYTE) */
+#define XDIR_ModTZ 23 /* exFAT: Modified timezone (BYTE) */
+#define XDIR_AccTZ 24 /* exFAT: Last accessed timezone (BYTE) */
+#define XDIR_GenFlags 33 /* exFAT: General secondary flags (BYTE) */
+#define XDIR_NumName 35 /* exFAT: Number of file name characters (BYTE) */
+#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD) */
+#define XDIR_ValidFileSize 40 /* exFAT: Valid file size (QWORD) */
+#define XDIR_FstClus 52 /* exFAT: First cluster of the file data (DWORD) */
+#define XDIR_FileSize 56 /* exFAT: File/Directory size (QWORD) */
+
+#define SZDIRE 32 /* Size of a directory entry */
+#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */
+#define RDDEM 0x05 /* Replacement of the character collides with DDEM */
+#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */
+
+#define FSI_LeadSig 0 /* FAT32 FSI: Leading signature (DWORD) */
+#define FSI_StrucSig 484 /* FAT32 FSI: Structure signature (DWORD) */
+#define FSI_Free_Count 488 /* FAT32 FSI: Number of free clusters (DWORD) */
+#define FSI_Nxt_Free 492 /* FAT32 FSI: Last allocated cluster (DWORD) */
+
+#define MBR_Table 446 /* MBR: Offset of partition table in the MBR */
+#define SZ_PTE 16 /* MBR: Size of a partition table entry */
+#define PTE_Boot 0 /* MBR PTE: Boot indicator */
+#define PTE_StHead 1 /* MBR PTE: Start head */
+#define PTE_StSec 2 /* MBR PTE: Start sector */
+#define PTE_StCyl 3 /* MBR PTE: Start cylinder */
+#define PTE_System 4 /* MBR PTE: System ID */
+#define PTE_EdHead 5 /* MBR PTE: End head */
+#define PTE_EdSec 6 /* MBR PTE: End sector */
+#define PTE_EdCyl 7 /* MBR PTE: End cylinder */
+#define PTE_StLba 8 /* MBR PTE: Start in LBA */
+#define PTE_SizLba 12 /* MBR PTE: Size in LBA */
+
+#define GPTH_Sign 0 /* GPT: Header signature (8-byte) */
+#define GPTH_Rev 8 /* GPT: Revision (DWORD) */
+#define GPTH_Size 12 /* GPT: Header size (DWORD) */
+#define GPTH_Bcc 16 /* GPT: Header BCC (DWORD) */
+#define GPTH_CurLba 24 /* GPT: Main header LBA (QWORD) */
+#define GPTH_BakLba 32 /* GPT: Backup header LBA (QWORD) */
+#define GPTH_FstLba 40 /* GPT: First LBA for partitions (QWORD) */
+#define GPTH_LstLba 48 /* GPT: Last LBA for partitions (QWORD) */
+#define GPTH_DskGuid 56 /* GPT: Disk GUID (16-byte) */
+#define GPTH_PtOfs 72 /* GPT: Partation table LBA (QWORD) */
+#define GPTH_PtNum 80 /* GPT: Number of table entries (DWORD) */
+#define GPTH_PteSize 84 /* GPT: Size of table entry (DWORD) */
+#define GPTH_PtBcc 88 /* GPT: Partation table BCC (DWORD) */
+#define SZ_GPTE 128 /* GPT: Size of partition table entry */
+#define GPTE_PtGuid 0 /* GPT PTE: Partition type GUID (16-byte) */
+#define GPTE_UpGuid 16 /* GPT PTE: Partition unique GUID (16-byte) */
+#define GPTE_FstLba 32 /* GPT PTE: First LBA (QWORD) */
+#define GPTE_LstLba 40 /* GPT PTE: Last LBA inclusive (QWORD) */
+#define GPTE_Flags 48 /* GPT PTE: Flags (QWORD) */
+#define GPTE_Name 56 /* GPT PTE: Name */
+
+
+/* Post process on fatal error in the file operations */
+#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); }
+
+
+/* Re-entrancy related */
+#if FF_FS_REENTRANT
+#if FF_USE_LFN == 1
+#error Static LFN work area cannot be used at thread-safe configuration
+#endif
+#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; }
+#else
+#define LEAVE_FF(fs, res) return res
+#endif
+
+
+/* Definitions of logical drive - physical location conversion */
+#if FF_MULTI_PARTITION
+#define LD2PD(vol) VolToPart[vol].pd /* Get physical drive number */
+#define LD2PT(vol) VolToPart[vol].pt /* Get partition index */
+#else
+#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is associated with the same physical drive number */
+#define LD2PT(vol) 0 /* Find first valid partition or in SFD */
+#endif
+
+
+/* Definitions of sector size */
+#if (FF_MAX_SS < FF_MIN_SS) || (FF_MAX_SS != 512 && FF_MAX_SS != 1024 && FF_MAX_SS != 2048 && FF_MAX_SS != 4096) || (FF_MIN_SS != 512 && FF_MIN_SS != 1024 && FF_MIN_SS != 2048 && FF_MIN_SS != 4096)
+#error Wrong sector size configuration
+#endif
+#if FF_MAX_SS == FF_MIN_SS
+#define SS(fs) ((UINT)FF_MAX_SS) /* Fixed sector size */
+#else
+#define SS(fs) ((fs)->ssize) /* Variable sector size */
+#endif
+
+
+/* Timestamp */
+#if FF_FS_NORTC == 1
+#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31
+#error Invalid FF_FS_NORTC settings
+#endif
+#define GET_FATTIME() ((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16)
+#else
+#define GET_FATTIME() get_fattime()
+#endif
+
+
+/* File lock controls */
+#if FF_FS_LOCK != 0
+#if FF_FS_READONLY
+#error FF_FS_LOCK must be 0 at read-only configuration
+#endif
+typedef struct {
+ FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */
+ DWORD clu; /* Object ID 2, containing directory (0:root) */
+ DWORD ofs; /* Object ID 3, offset in the directory */
+ WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */
+} FILESEM;
+#endif
+
+
+/* SBCS up-case tables (\x80-\xFF) */
+#define TBL_CT437 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT720 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT737 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \
+ 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT771 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDC,0xDE,0xDE, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF}
+#define TBL_CT775 {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
+ 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT850 {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \
+ 0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \
+ 0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT852 {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \
+ 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF}
+#define TBL_CT855 {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \
+ 0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \
+ 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \
+ 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \
+ 0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT857 {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \
+ 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT860 {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x86,0x8B,0x9F,0x96,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT861 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
+ 0xA4,0xA5,0xA6,0xA7,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT862 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT863 {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \
+ 0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \
+ 0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT864 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT865 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT866 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+#define TBL_CT869 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x86,0x9C,0x8D,0x8F,0x90, \
+ 0x91,0x90,0x92,0x95,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \
+ 0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \
+ 0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF}
+
+
+/* DBCS code range |----- 1st byte -----| |----------- 2nd byte -----------| */
+/* <------> <------> <------> <------> <------> */
+#define TBL_DC932 {0x81, 0x9F, 0xE0, 0xFC, 0x40, 0x7E, 0x80, 0xFC, 0x00, 0x00}
+#define TBL_DC936 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0x80, 0xFE, 0x00, 0x00}
+#define TBL_DC949 {0x81, 0xFE, 0x00, 0x00, 0x41, 0x5A, 0x61, 0x7A, 0x81, 0xFE}
+#define TBL_DC950 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0xA1, 0xFE, 0x00, 0x00}
+
+
+/* Macros for table definitions */
+#define MERGE_2STR(a, b) a ## b
+#define MKCVTBL(hd, cp) MERGE_2STR(hd, cp)
+
+
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Work Area
+
+---------------------------------------------------------------------------*/
+/* Remark: Variables defined here without initial value shall be guaranteed
+/ zero/null at start-up. If not, the linker option or start-up routine is
+/ not compliance with C standard. */
+
+/*--------------------------------*/
+/* File/Volume controls */
+/*--------------------------------*/
+
+#if FF_VOLUMES < 1 || FF_VOLUMES > 10
+#error Wrong FF_VOLUMES setting
+#endif
+static FATFS* FatFs[FF_VOLUMES]; /* Pointer to the filesystem objects (logical drives) */
+static WORD Fsid; /* Filesystem mount ID */
+
+#if FF_FS_RPATH != 0
+static BYTE CurrVol; /* Current drive */
+#endif
+
+#if FF_FS_LOCK != 0
+static FILESEM Files[FF_FS_LOCK]; /* Open object lock semaphores */
+#endif
+
+#if FF_STR_VOLUME_ID
+#ifdef FF_VOLUME_STRS
+static const char* const VolumeStr[FF_VOLUMES] = {FF_VOLUME_STRS}; /* Pre-defined volume ID */
+#endif
+#endif
+
+#if FF_LBA64
+#if FF_MIN_GPT > 0x100000000
+#error Wrong FF_MIN_GPT setting
+#endif
+static const BYTE GUID_MS_Basic[16] = {0xA2,0xA0,0xD0,0xEB,0xE5,0xB9,0x33,0x44,0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7};
+#endif
+
+
+
+/*--------------------------------*/
+/* LFN/Directory working buffer */
+/*--------------------------------*/
+
+#if FF_USE_LFN == 0 /* Non-LFN configuration */
+#if FF_FS_EXFAT
+#error LFN must be enabled when enable exFAT
+#endif
+#define DEF_NAMBUF
+#define INIT_NAMBUF(fs)
+#define FREE_NAMBUF()
+#define LEAVE_MKFS(res) return res
+
+#else /* LFN configurations */
+#if FF_MAX_LFN < 12 || FF_MAX_LFN > 255
+#error Wrong setting of FF_MAX_LFN
+#endif
+#if FF_LFN_BUF < FF_SFN_BUF || FF_SFN_BUF < 12
+#error Wrong setting of FF_LFN_BUF or FF_SFN_BUF
+#endif
+#if FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3
+#error Wrong setting of FF_LFN_UNICODE
+#endif
+static const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* FAT: Offset of LFN characters in the directory entry */
+#define MAXDIRB(nc) ((nc + 44U) / 15 * SZDIRE) /* exFAT: Size of directory entry block scratchpad buffer needed for the name length */
+
+#if FF_USE_LFN == 1 /* LFN enabled with static working buffer */
+#if FF_FS_EXFAT
+static BYTE DirBuf[MAXDIRB(FF_MAX_LFN)]; /* Directory entry block scratchpad buffer */
+#endif
+static WCHAR LfnBuf[FF_MAX_LFN + 1]; /* LFN working buffer */
+#define DEF_NAMBUF
+#define INIT_NAMBUF(fs)
+#define FREE_NAMBUF()
+#define LEAVE_MKFS(res) return res
+
+#elif FF_USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */
+#if FF_FS_EXFAT
+#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; BYTE dbuf[MAXDIRB(FF_MAX_LFN)]; /* LFN working buffer and directory entry block scratchpad buffer */
+#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; }
+#define FREE_NAMBUF()
+#else
+#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; /* LFN working buffer */
+#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; }
+#define FREE_NAMBUF()
+#endif
+#define LEAVE_MKFS(res) return res
+
+#elif FF_USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */
+#if FF_FS_EXFAT
+#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer and directory entry block scratchpad buffer */
+#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2 + MAXDIRB(FF_MAX_LFN)); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+FF_MAX_LFN+1); }
+#define FREE_NAMBUF() ff_memfree(lfn)
+#else
+#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer */
+#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; }
+#define FREE_NAMBUF() ff_memfree(lfn)
+#endif
+#define LEAVE_MKFS(res) { if (!work) ff_memfree(buf); return res; }
+#define MAX_MALLOC 0x8000 /* Must be >=FF_MAX_SS */
+
+#else
+#error Wrong setting of FF_USE_LFN
+
+#endif /* FF_USE_LFN == 1 */
+#endif /* FF_USE_LFN == 0 */
+
+
+
+/*--------------------------------*/
+/* Code conversion tables */
+/*--------------------------------*/
+
+#if FF_CODE_PAGE == 0 /* Run-time code page configuration */
+#define CODEPAGE CodePage
+static WORD CodePage; /* Current code page */
+static const BYTE *ExCvt, *DbcTbl; /* Pointer to current SBCS up-case table and DBCS code range table below */
+
+static const BYTE Ct437[] = TBL_CT437;
+static const BYTE Ct720[] = TBL_CT720;
+static const BYTE Ct737[] = TBL_CT737;
+static const BYTE Ct771[] = TBL_CT771;
+static const BYTE Ct775[] = TBL_CT775;
+static const BYTE Ct850[] = TBL_CT850;
+static const BYTE Ct852[] = TBL_CT852;
+static const BYTE Ct855[] = TBL_CT855;
+static const BYTE Ct857[] = TBL_CT857;
+static const BYTE Ct860[] = TBL_CT860;
+static const BYTE Ct861[] = TBL_CT861;
+static const BYTE Ct862[] = TBL_CT862;
+static const BYTE Ct863[] = TBL_CT863;
+static const BYTE Ct864[] = TBL_CT864;
+static const BYTE Ct865[] = TBL_CT865;
+static const BYTE Ct866[] = TBL_CT866;
+static const BYTE Ct869[] = TBL_CT869;
+static const BYTE Dc932[] = TBL_DC932;
+static const BYTE Dc936[] = TBL_DC936;
+static const BYTE Dc949[] = TBL_DC949;
+static const BYTE Dc950[] = TBL_DC950;
+
+#elif FF_CODE_PAGE < 900 /* Static code page configuration (SBCS) */
+#define CODEPAGE FF_CODE_PAGE
+static const BYTE ExCvt[] = MKCVTBL(TBL_CT, FF_CODE_PAGE);
+
+#else /* Static code page configuration (DBCS) */
+#define CODEPAGE FF_CODE_PAGE
+static const BYTE DbcTbl[] = MKCVTBL(TBL_DC, FF_CODE_PAGE);
+
+#endif
+
+
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Functions
+
+---------------------------------------------------------------------------*/
+
+
+/*-----------------------------------------------------------------------*/
+/* Load/Store multi-byte word in the FAT structure */
+/*-----------------------------------------------------------------------*/
+
+static WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */
+{
+ WORD rv;
+
+ rv = ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+
+static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */
+{
+ DWORD rv;
+
+ rv = ptr[3];
+ rv = rv << 8 | ptr[2];
+ rv = rv << 8 | ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+
+#if FF_FS_EXFAT
+static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */
+{
+ QWORD rv;
+
+ rv = ptr[7];
+ rv = rv << 8 | ptr[6];
+ rv = rv << 8 | ptr[5];
+ rv = rv << 8 | ptr[4];
+ rv = rv << 8 | ptr[3];
+ rv = rv << 8 | ptr[2];
+ rv = rv << 8 | ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+#endif
+
+#if !FF_FS_READONLY
+static void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+
+static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+
+#if FF_FS_EXFAT
+static void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+#endif
+#endif /* !FF_FS_READONLY */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* String functions */
+/*-----------------------------------------------------------------------*/
+
+/* Copy memory to memory */
+static void mem_cpy (void* dst, const void* src, UINT cnt)
+{
+ BYTE *d = (BYTE*)dst;
+ const BYTE *s = (const BYTE*)src;
+
+ if (cnt != 0) {
+ do {
+ *d++ = *s++;
+ } while (--cnt);
+ }
+}
+
+
+/* Fill memory block */
+static void mem_set (void* dst, int val, UINT cnt)
+{
+ BYTE *d = (BYTE*)dst;
+
+ do {
+ *d++ = (BYTE)val;
+ } while (--cnt);
+}
+
+
+/* Compare memory block */
+static int mem_cmp (const void* dst, const void* src, UINT cnt) /* ZR:same, NZ:different */
+{
+ const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src;
+ int r = 0;
+
+ do {
+ r = *d++ - *s++;
+ } while (--cnt && r == 0);
+
+ return r;
+}
+
+
+/* Check if chr is contained in the string */
+static int chk_chr (const char* str, int chr) /* NZ:contained, ZR:not contained */
+{
+ while (*str && *str != chr) str++;
+ return *str;
+}
+
+
+/* Test if the byte is DBC 1st byte */
+static int dbc_1st (BYTE c)
+{
+#if FF_CODE_PAGE == 0 /* Variable code page */
+ if (DbcTbl && c >= DbcTbl[0]) {
+ if (c <= DbcTbl[1]) return 1; /* 1st byte range 1 */
+ if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; /* 1st byte range 2 */
+ }
+#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */
+ if (c >= DbcTbl[0]) {
+ if (c <= DbcTbl[1]) return 1;
+ if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1;
+ }
+#else /* SBCS fixed code page */
+ if (c != 0) return 0; /* Always false */
+#endif
+ return 0;
+}
+
+
+/* Test if the byte is DBC 2nd byte */
+static int dbc_2nd (BYTE c)
+{
+#if FF_CODE_PAGE == 0 /* Variable code page */
+ if (DbcTbl && c >= DbcTbl[4]) {
+ if (c <= DbcTbl[5]) return 1; /* 2nd byte range 1 */
+ if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; /* 2nd byte range 2 */
+ if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; /* 2nd byte range 3 */
+ }
+#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */
+ if (c >= DbcTbl[4]) {
+ if (c <= DbcTbl[5]) return 1;
+ if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1;
+ if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1;
+ }
+#else /* SBCS fixed code page */
+ if (c != 0) return 0; /* Always false */
+#endif
+ return 0;
+}
+
+
+#if FF_USE_LFN
+
+/* Get a Unicode code point from the TCHAR string in defined API encodeing */
+static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on surrogate pair, 0xFFFFFFFF on decode error) */
+ const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */
+)
+{
+ DWORD uc;
+ const TCHAR *p = *str;
+
+#if FF_LFN_UNICODE == 1 /* UTF-16 input */
+ WCHAR wc;
+
+ uc = *p++; /* Get a unit */
+ if (IsSurrogate(uc)) { /* Surrogate? */
+ wc = *p++; /* Get low surrogate */
+ if (!IsSurrogateH(uc) || !IsSurrogateL(wc)) return 0xFFFFFFFF; /* Wrong surrogate? */
+ uc = uc << 16 | wc;
+ }
+
+#elif FF_LFN_UNICODE == 2 /* UTF-8 input */
+ BYTE b;
+ int nf;
+
+ uc = (BYTE)*p++; /* Get an encoding unit */
+ if (uc & 0x80) { /* Multiple byte code? */
+ if ((uc & 0xE0) == 0xC0) { /* 2-byte sequence? */
+ uc &= 0x1F; nf = 1;
+ } else {
+ if ((uc & 0xF0) == 0xE0) { /* 3-byte sequence? */
+ uc &= 0x0F; nf = 2;
+ } else {
+ if ((uc & 0xF8) == 0xF0) { /* 4-byte sequence? */
+ uc &= 0x07; nf = 3;
+ } else { /* Wrong sequence */
+ return 0xFFFFFFFF;
+ }
+ }
+ }
+ do { /* Get trailing bytes */
+ b = (BYTE)*p++;
+ if ((b & 0xC0) != 0x80) return 0xFFFFFFFF; /* Wrong sequence? */
+ uc = uc << 6 | (b & 0x3F);
+ } while (--nf != 0);
+ if (uc < 0x80 || IsSurrogate(uc) || uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */
+ if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */
+ }
+
+#elif FF_LFN_UNICODE == 3 /* UTF-32 input */
+ uc = (TCHAR)*p++; /* Get a unit */
+ if (uc >= 0x110000 || IsSurrogate(uc)) return 0xFFFFFFFF; /* Wrong code? */
+ if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */
+
+#else /* ANSI/OEM input */
+ BYTE b;
+ WCHAR wc;
+
+ wc = (BYTE)*p++; /* Get a byte */
+ if (dbc_1st((BYTE)wc)) { /* Is it a DBC 1st byte? */
+ b = (BYTE)*p++; /* Get 2nd byte */
+ if (!dbc_2nd(b)) return 0xFFFFFFFF; /* Invalid code? */
+ wc = (wc << 8) + b; /* Make a DBC */
+ }
+ if (wc != 0) {
+ wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM ==> Unicode */
+ if (wc == 0) return 0xFFFFFFFF; /* Invalid code? */
+ }
+ uc = wc;
+
+#endif
+ *str = p; /* Next read pointer */
+ return uc;
+}
+
+
+/* Output a TCHAR string in defined API encoding */
+static BYTE put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */
+ DWORD chr, /* UTF-16 encoded character (Surrogate pair if >=0x10000) */
+ TCHAR* buf, /* Output buffer */
+ UINT szb /* Size of the buffer */
+)
+{
+#if FF_LFN_UNICODE == 1 /* UTF-16 output */
+ WCHAR hs, wc;
+
+ hs = (WCHAR)(chr >> 16);
+ wc = (WCHAR)chr;
+ if (hs == 0) { /* Single encoding unit? */
+ if (szb < 1 || IsSurrogate(wc)) return 0; /* Buffer overflow or wrong code? */
+ *buf = wc;
+ return 1;
+ }
+ if (szb < 2 || !IsSurrogateH(hs) || !IsSurrogateL(wc)) return 0; /* Buffer overflow or wrong surrogate? */
+ *buf++ = hs;
+ *buf++ = wc;
+ return 2;
+
+#elif FF_LFN_UNICODE == 2 /* UTF-8 output */
+ DWORD hc;
+
+ if (chr < 0x80) { /* Single byte code? */
+ if (szb < 1) return 0; /* Buffer overflow? */
+ *buf = (TCHAR)chr;
+ return 1;
+ }
+ if (chr < 0x800) { /* 2-byte sequence? */
+ if (szb < 2) return 0; /* Buffer overflow? */
+ *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F));
+ return 2;
+ }
+ if (chr < 0x10000) { /* 3-byte sequence? */
+ if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */
+ *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F));
+ return 3;
+ }
+ /* 4-byte sequence */
+ if (szb < 4) return 0; /* Buffer overflow? */
+ hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */
+ chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */
+ if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */
+ chr = (hc | chr) + 0x10000;
+ *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07));
+ *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F));
+ *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F));
+ return 4;
+
+#elif FF_LFN_UNICODE == 3 /* UTF-32 output */
+ DWORD hc;
+
+ if (szb < 1) return 0; /* Buffer overflow? */
+ if (chr >= 0x10000) { /* Out of BMP? */
+ hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */
+ chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */
+ if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */
+ chr = (hc | chr) + 0x10000;
+ }
+ *buf++ = (TCHAR)chr;
+ return 1;
+
+#else /* ANSI/OEM output */
+ WCHAR wc;
+
+ wc = ff_uni2oem(chr, CODEPAGE);
+ if (wc >= 0x100) { /* Is this a DBC? */
+ if (szb < 2) return 0;
+ *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */
+ *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */
+ return 2;
+ }
+ if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */
+ *buf++ = (TCHAR)wc; /* Store the character */
+ return 1;
+#endif
+}
+#endif /* FF_USE_LFN */
+
+
+#if FF_FS_REENTRANT
+/*-----------------------------------------------------------------------*/
+/* Request/Release grant to access the volume */
+/*-----------------------------------------------------------------------*/
+static int lock_fs ( /* 1:Ok, 0:timeout */
+ FATFS* fs /* Filesystem object */
+)
+{
+ return ff_req_grant(fs->sobj);
+}
+
+
+static void unlock_fs (
+ FATFS* fs, /* Filesystem object */
+ FRESULT res /* Result code to be returned */
+)
+{
+ if (fs && res != FR_NOT_ENABLED && res != FR_INVALID_DRIVE && res != FR_TIMEOUT) {
+ ff_rel_grant(fs->sobj);
+ }
+}
+
+#endif
+
+
+
+#if FF_FS_LOCK != 0
+/*-----------------------------------------------------------------------*/
+/* File lock control functions */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT chk_lock ( /* Check if the file can be accessed */
+ DIR* dp, /* Directory object pointing the file to be checked */
+ int acc /* Desired access type (0:Read mode open, 1:Write mode open, 2:Delete or rename) */
+)
+{
+ UINT i, be;
+
+ /* Search open object table for the object */
+ be = 0;
+ for (i = 0; i < FF_FS_LOCK; i++) {
+ if (Files[i].fs) { /* Existing entry */
+ if (Files[i].fs == dp->obj.fs && /* Check if the object matches with an open object */
+ Files[i].clu == dp->obj.sclust &&
+ Files[i].ofs == dp->dptr) break;
+ } else { /* Blank entry */
+ be = 1;
+ }
+ }
+ if (i == FF_FS_LOCK) { /* The object has not been opened */
+ return (!be && acc != 2) ? FR_TOO_MANY_OPEN_FILES : FR_OK; /* Is there a blank entry for new object? */
+ }
+
+ /* The object was opened. Reject any open against writing file and all write mode open */
+ return (acc != 0 || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK;
+}
+
+
+static int enq_lock (void) /* Check if an entry is available for a new object */
+{
+ UINT i;
+
+ for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ;
+ return (i == FF_FS_LOCK) ? 0 : 1;
+}
+
+
+static UINT inc_lock ( /* Increment object open counter and returns its index (0:Internal error) */
+ DIR* dp, /* Directory object pointing the file to register or increment */
+ int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */
+)
+{
+ UINT i;
+
+
+ for (i = 0; i < FF_FS_LOCK; i++) { /* Find the object */
+ if (Files[i].fs == dp->obj.fs
+ && Files[i].clu == dp->obj.sclust
+ && Files[i].ofs == dp->dptr) break;
+ }
+
+ if (i == FF_FS_LOCK) { /* Not opened. Register it as new. */
+ for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ;
+ if (i == FF_FS_LOCK) return 0; /* No free entry to register (int err) */
+ Files[i].fs = dp->obj.fs;
+ Files[i].clu = dp->obj.sclust;
+ Files[i].ofs = dp->dptr;
+ Files[i].ctr = 0;
+ }
+
+ if (acc >= 1 && Files[i].ctr) return 0; /* Access violation (int err) */
+
+ Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */
+
+ return i + 1; /* Index number origin from 1 */
+}
+
+
+static FRESULT dec_lock ( /* Decrement object open counter */
+ UINT i /* Semaphore index (1..) */
+)
+{
+ WORD n;
+ FRESULT res;
+
+
+ if (--i < FF_FS_LOCK) { /* Index number origin from 0 */
+ n = Files[i].ctr;
+ if (n == 0x100) n = 0; /* If write mode open, delete the entry */
+ if (n > 0) n--; /* Decrement read mode open count */
+ Files[i].ctr = n;
+ if (n == 0) Files[i].fs = 0; /* Delete the entry if open count gets zero */
+ res = FR_OK;
+ } else {
+ res = FR_INT_ERR; /* Invalid index nunber */
+ }
+ return res;
+}
+
+
+static void clear_lock ( /* Clear lock entries of the volume */
+ FATFS *fs
+)
+{
+ UINT i;
+
+ for (i = 0; i < FF_FS_LOCK; i++) {
+ if (Files[i].fs == fs) Files[i].fs = 0;
+ }
+}
+
+#endif /* FF_FS_LOCK != 0 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Move/Flush disk access window in the filesystem object */
+/*-----------------------------------------------------------------------*/
+#if !FF_FS_READONLY
+static FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS* fs /* Filesystem object */
+)
+{
+ FRESULT res = FR_OK;
+
+
+ if (fs->wflag) { /* Is the disk access window dirty? */
+ if (disk_write(fs->pdrv, fs->win, fs->winsect, 1) == RES_OK) { /* Write it back into the volume */
+ fs->wflag = 0; /* Clear window dirty flag */
+ if (fs->winsect - fs->fatbase < fs->fsize) { /* Is it in the 1st FAT? */
+ if (fs->n_fats == 2) disk_write(fs->pdrv, fs->win, fs->winsect + fs->fsize, 1); /* Reflect it to 2nd FAT if needed */
+ }
+ } else {
+ res = FR_DISK_ERR;
+ }
+ }
+ return res;
+}
+#endif
+
+
+static FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS* fs, /* Filesystem object */
+ LBA_t sect /* Sector LBA to make appearance in the fs->win[] */
+)
+{
+ FRESULT res = FR_OK;
+
+
+ if (sect != fs->winsect) { /* Window offset changed? */
+#if !FF_FS_READONLY
+ res = sync_window(fs); /* Flush the window */
+#endif
+ if (res == FR_OK) { /* Fill sector window with new data */
+ if (disk_read(fs->pdrv, fs->win, sect, 1) != RES_OK) {
+ sect = (LBA_t)0 - 1; /* Invalidate window if read data is not valid */
+ res = FR_DISK_ERR;
+ }
+ fs->winsect = sect;
+ }
+ }
+ return res;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Synchronize filesystem and data on the storage */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS* fs /* Filesystem object */
+)
+{
+ FRESULT res;
+
+
+ res = sync_window(fs);
+ if (res == FR_OK) {
+ if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */
+ /* Create FSInfo structure */
+ mem_set(fs->win, 0, sizeof fs->win);
+ st_word(fs->win + BS_55AA, 0xAA55);
+ st_dword(fs->win + FSI_LeadSig, 0x41615252);
+ st_dword(fs->win + FSI_StrucSig, 0x61417272);
+ st_dword(fs->win + FSI_Free_Count, fs->free_clst);
+ st_dword(fs->win + FSI_Nxt_Free, fs->last_clst);
+ /* Write it into the FSInfo sector */
+ fs->winsect = fs->volbase + 1;
+ disk_write(fs->pdrv, fs->win, fs->winsect, 1);
+ fs->fsi_flag = 0;
+ }
+ /* Make sure that no pending write process in the lower layer */
+ if (disk_ioctl(fs->pdrv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR;
+ }
+
+ return res;
+}
+
+#endif
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Get physical sector number from cluster number */
+/*-----------------------------------------------------------------------*/
+
+static LBA_t clst2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */
+ FATFS* fs, /* Filesystem object */
+ DWORD clst /* Cluster# to be converted */
+)
+{
+ clst -= 2; /* Cluster number is origin from 2 */
+ if (clst >= fs->n_fatent - 2) return 0; /* Is it invalid cluster number? */
+ return fs->database + (LBA_t)fs->csize * clst; /* Start sector number of the cluster */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT access - Read value of a FAT entry */
+/*-----------------------------------------------------------------------*/
+
+static DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */
+ FFOBJID* obj, /* Corresponding object */
+ DWORD clst /* Cluster number to get the value */
+)
+{
+ UINT wc, bc;
+ DWORD val;
+ FATFS *fs = obj->fs;
+
+
+ if (clst < 2 || clst >= fs->n_fatent) { /* Check if in valid range */
+ val = 1; /* Internal error */
+
+ } else {
+ val = 0xFFFFFFFF; /* Default value falls on disk error */
+
+ switch (fs->fs_type) {
+ case FS_FAT12 :
+ bc = (UINT)clst; bc += bc / 2;
+ if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break;
+ wc = fs->win[bc++ % SS(fs)]; /* Get 1st byte of the entry */
+ if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break;
+ wc |= fs->win[bc % SS(fs)] << 8; /* Merge 2nd byte of the entry */
+ val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF); /* Adjust bit position */
+ break;
+
+ case FS_FAT16 :
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break;
+ val = ld_word(fs->win + clst * 2 % SS(fs)); /* Simple WORD array */
+ break;
+
+ case FS_FAT32 :
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break;
+ val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x0FFFFFFF; /* Simple DWORD array but mask out upper 4 bits */
+ break;
+#if FF_FS_EXFAT
+ case FS_EXFAT :
+ if ((obj->objsize != 0 && obj->sclust != 0) || obj->stat == 0) { /* Object except root dir must have valid data length */
+ DWORD cofs = clst - obj->sclust; /* Offset from start cluster */
+ DWORD clen = (DWORD)((LBA_t)((obj->objsize - 1) / SS(fs)) / fs->csize); /* Number of clusters - 1 */
+
+ if (obj->stat == 2 && cofs <= clen) { /* Is it a contiguous chain? */
+ val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* No data on the FAT, generate the value */
+ break;
+ }
+ if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the 1st fragment? */
+ val = clst + 1; /* Generate the value */
+ break;
+ }
+ if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */
+ if (obj->n_frag != 0) { /* Is it on the growing edge? */
+ val = 0x7FFFFFFF; /* Generate EOC */
+ } else {
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break;
+ val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x7FFFFFFF;
+ }
+ break;
+ }
+ }
+ /* go to default */
+#endif
+ default:
+ val = 1; /* Internal error */
+ }
+ }
+
+ return val;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT access - Change value of a FAT entry */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */
+ FATFS* fs, /* Corresponding filesystem object */
+ DWORD clst, /* FAT index number (cluster number) to be changed */
+ DWORD val /* New value to be set to the entry */
+)
+{
+ UINT bc;
+ BYTE *p;
+ FRESULT res = FR_INT_ERR;
+
+
+ if (clst >= 2 && clst < fs->n_fatent) { /* Check if in valid range */
+ switch (fs->fs_type) {
+ case FS_FAT12 :
+ bc = (UINT)clst; bc += bc / 2; /* bc: byte offset of the entry */
+ res = move_window(fs, fs->fatbase + (bc / SS(fs)));
+ if (res != FR_OK) break;
+ p = fs->win + bc++ % SS(fs);
+ *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; /* Update 1st byte */
+ fs->wflag = 1;
+ res = move_window(fs, fs->fatbase + (bc / SS(fs)));
+ if (res != FR_OK) break;
+ p = fs->win + bc % SS(fs);
+ *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); /* Update 2nd byte */
+ fs->wflag = 1;
+ break;
+
+ case FS_FAT16 :
+ res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2)));
+ if (res != FR_OK) break;
+ st_word(fs->win + clst * 2 % SS(fs), (WORD)val); /* Simple WORD array */
+ fs->wflag = 1;
+ break;
+
+ case FS_FAT32 :
+#if FF_FS_EXFAT
+ case FS_EXFAT :
+#endif
+ res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4)));
+ if (res != FR_OK) break;
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ val = (val & 0x0FFFFFFF) | (ld_dword(fs->win + clst * 4 % SS(fs)) & 0xF0000000);
+ }
+ st_dword(fs->win + clst * 4 % SS(fs), val);
+ fs->wflag = 1;
+ break;
+ }
+ }
+ return res;
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+#if FF_FS_EXFAT && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* exFAT: Accessing FAT and Allocation Bitmap */
+/*-----------------------------------------------------------------------*/
+
+/*--------------------------------------*/
+/* Find a contiguous free cluster block */
+/*--------------------------------------*/
+
+static DWORD find_bitmap ( /* 0:Not found, 2..:Cluster block found, 0xFFFFFFFF:Disk error */
+ FATFS* fs, /* Filesystem object */
+ DWORD clst, /* Cluster number to scan from */
+ DWORD ncl /* Number of contiguous clusters to find (1..) */
+)
+{
+ BYTE bm, bv;
+ UINT i;
+ DWORD val, scl, ctr;
+
+
+ clst -= 2; /* The first bit in the bitmap corresponds to cluster #2 */
+ if (clst >= fs->n_fatent - 2) clst = 0;
+ scl = val = clst; ctr = 0;
+ for (;;) {
+ if (move_window(fs, fs->bitbase + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF;
+ i = val / 8 % SS(fs); bm = 1 << (val % 8);
+ do {
+ do {
+ bv = fs->win[i] & bm; bm <<= 1; /* Get bit value */
+ if (++val >= fs->n_fatent - 2) { /* Next cluster (with wrap-around) */
+ val = 0; bm = 0; i = SS(fs);
+ }
+ if (bv == 0) { /* Is it a free cluster? */
+ if (++ctr == ncl) return scl + 2; /* Check if run length is sufficient for required */
+ } else {
+ scl = val; ctr = 0; /* Encountered a cluster in-use, restart to scan */
+ }
+ if (val == clst) return 0; /* All cluster scanned? */
+ } while (bm != 0);
+ bm = 1;
+ } while (++i < SS(fs));
+ }
+}
+
+
+/*----------------------------------------*/
+/* Set/Clear a block of allocation bitmap */
+/*----------------------------------------*/
+
+static FRESULT change_bitmap (
+ FATFS* fs, /* Filesystem object */
+ DWORD clst, /* Cluster number to change from */
+ DWORD ncl, /* Number of clusters to be changed */
+ int bv /* bit value to be set (0 or 1) */
+)
+{
+ BYTE bm;
+ UINT i;
+ LBA_t sect;
+
+
+ clst -= 2; /* The first bit corresponds to cluster #2 */
+ sect = fs->bitbase + clst / 8 / SS(fs); /* Sector address */
+ i = clst / 8 % SS(fs); /* Byte offset in the sector */
+ bm = 1 << (clst % 8); /* Bit mask in the byte */
+ for (;;) {
+ if (move_window(fs, sect++) != FR_OK) return FR_DISK_ERR;
+ do {
+ do {
+ if (bv == (int)((fs->win[i] & bm) != 0)) return FR_INT_ERR; /* Is the bit expected value? */
+ fs->win[i] ^= bm; /* Flip the bit */
+ fs->wflag = 1;
+ if (--ncl == 0) return FR_OK; /* All bits processed? */
+ } while (bm <<= 1); /* Next bit */
+ bm = 1;
+ } while (++i < SS(fs)); /* Next byte */
+ i = 0;
+ }
+}
+
+
+/*---------------------------------------------*/
+/* Fill the first fragment of the FAT chain */
+/*---------------------------------------------*/
+
+static FRESULT fill_first_frag (
+ FFOBJID* obj /* Pointer to the corresponding object */
+)
+{
+ FRESULT res;
+ DWORD cl, n;
+
+
+ if (obj->stat == 3) { /* Has the object been changed 'fragmented' in this session? */
+ for (cl = obj->sclust, n = obj->n_cont; n; cl++, n--) { /* Create cluster chain on the FAT */
+ res = put_fat(obj->fs, cl, cl + 1);
+ if (res != FR_OK) return res;
+ }
+ obj->stat = 0; /* Change status 'FAT chain is valid' */
+ }
+ return FR_OK;
+}
+
+
+/*---------------------------------------------*/
+/* Fill the last fragment of the FAT chain */
+/*---------------------------------------------*/
+
+static FRESULT fill_last_frag (
+ FFOBJID* obj, /* Pointer to the corresponding object */
+ DWORD lcl, /* Last cluster of the fragment */
+ DWORD term /* Value to set the last FAT entry */
+)
+{
+ FRESULT res;
+
+
+ while (obj->n_frag > 0) { /* Create the chain of last fragment */
+ res = put_fat(obj->fs, lcl - obj->n_frag + 1, (obj->n_frag > 1) ? lcl - obj->n_frag + 2 : term);
+ if (res != FR_OK) return res;
+ obj->n_frag--;
+ }
+ return FR_OK;
+}
+
+#endif /* FF_FS_EXFAT && !FF_FS_READONLY */
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Remove a cluster chain */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */
+ FFOBJID* obj, /* Corresponding object */
+ DWORD clst, /* Cluster to remove a chain from */
+ DWORD pclst /* Previous cluster of clst (0 if entire chain) */
+)
+{
+ FRESULT res = FR_OK;
+ DWORD nxt;
+ FATFS *fs = obj->fs;
+#if FF_FS_EXFAT || FF_USE_TRIM
+ DWORD scl = clst, ecl = clst;
+#endif
+#if FF_USE_TRIM
+ LBA_t rt[2];
+#endif
+
+ if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Check if in valid range */
+
+ /* Mark the previous cluster 'EOC' on the FAT if it exists */
+ if (pclst != 0 && (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT || obj->stat != 2)) {
+ res = put_fat(fs, pclst, 0xFFFFFFFF);
+ if (res != FR_OK) return res;
+ }
+
+ /* Remove the chain */
+ do {
+ nxt = get_fat(obj, clst); /* Get cluster status */
+ if (nxt == 0) break; /* Empty cluster? */
+ if (nxt == 1) return FR_INT_ERR; /* Internal error? */
+ if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error? */
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ res = put_fat(fs, clst, 0); /* Mark the cluster 'free' on the FAT */
+ if (res != FR_OK) return res;
+ }
+ if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */
+ fs->free_clst++;
+ fs->fsi_flag |= 1;
+ }
+#if FF_FS_EXFAT || FF_USE_TRIM
+ if (ecl + 1 == nxt) { /* Is next cluster contiguous? */
+ ecl = nxt;
+ } else { /* End of contiguous cluster block */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */
+ if (res != FR_OK) return res;
+ }
+#endif
+#if FF_USE_TRIM
+ rt[0] = clst2sect(fs, scl); /* Start of data area to be freed */
+ rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area to be freed */
+ disk_ioctl(fs->pdrv, CTRL_TRIM, rt); /* Inform storage device that the data in the block may be erased */
+#endif
+ scl = ecl = nxt;
+ }
+#endif
+ clst = nxt; /* Next cluster */
+ } while (clst < fs->n_fatent); /* Repeat while not the last link */
+
+#if FF_FS_EXFAT
+ /* Some post processes for chain status */
+ if (fs->fs_type == FS_EXFAT) {
+ if (pclst == 0) { /* Has the entire chain been removed? */
+ obj->stat = 0; /* Change the chain status 'initial' */
+ } else {
+ if (obj->stat == 0) { /* Is it a fragmented chain from the beginning of this session? */
+ clst = obj->sclust; /* Follow the chain to check if it gets contiguous */
+ while (clst != pclst) {
+ nxt = get_fat(obj, clst);
+ if (nxt < 2) return FR_INT_ERR;
+ if (nxt == 0xFFFFFFFF) return FR_DISK_ERR;
+ if (nxt != clst + 1) break; /* Not contiguous? */
+ clst++;
+ }
+ if (clst == pclst) { /* Has the chain got contiguous again? */
+ obj->stat = 2; /* Change the chain status 'contiguous' */
+ }
+ } else {
+ if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Was the chain fragmented in this session and got contiguous again? */
+ obj->stat = 2; /* Change the chain status 'contiguous' */
+ }
+ }
+ }
+ }
+#endif
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Stretch a chain or Create a new chain */
+/*-----------------------------------------------------------------------*/
+
+static DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */
+ FFOBJID* obj, /* Corresponding object */
+ DWORD clst /* Cluster# to stretch, 0:Create a new chain */
+)
+{
+ DWORD cs, ncl, scl;
+ FRESULT res;
+ FATFS *fs = obj->fs;
+
+
+ if (clst == 0) { /* Create a new chain */
+ scl = fs->last_clst; /* Suggested cluster to start to find */
+ if (scl == 0 || scl >= fs->n_fatent) scl = 1;
+ }
+ else { /* Stretch a chain */
+ cs = get_fat(obj, clst); /* Check the cluster status */
+ if (cs < 2) return 1; /* Test for insanity */
+ if (cs == 0xFFFFFFFF) return cs; /* Test for disk error */
+ if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */
+ scl = clst; /* Cluster to start to find */
+ }
+ if (fs->free_clst == 0) return 0; /* No free cluster */
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ ncl = find_bitmap(fs, scl, 1); /* Find a free cluster */
+ if (ncl == 0 || ncl == 0xFFFFFFFF) return ncl; /* No free cluster or hard error? */
+ res = change_bitmap(fs, ncl, 1, 1); /* Mark the cluster 'in use' */
+ if (res == FR_INT_ERR) return 1;
+ if (res == FR_DISK_ERR) return 0xFFFFFFFF;
+ if (clst == 0) { /* Is it a new chain? */
+ obj->stat = 2; /* Set status 'contiguous' */
+ } else { /* It is a stretched chain */
+ if (obj->stat == 2 && ncl != scl + 1) { /* Is the chain got fragmented? */
+ obj->n_cont = scl - obj->sclust; /* Set size of the contiguous part */
+ obj->stat = 3; /* Change status 'just fragmented' */
+ }
+ }
+ if (obj->stat != 2) { /* Is the file non-contiguous? */
+ if (ncl == clst + 1) { /* Is the cluster next to previous one? */
+ obj->n_frag = obj->n_frag ? obj->n_frag + 1 : 2; /* Increment size of last framgent */
+ } else { /* New fragment */
+ if (obj->n_frag == 0) obj->n_frag = 1;
+ res = fill_last_frag(obj, clst, ncl); /* Fill last fragment on the FAT and link it to new one */
+ if (res == FR_OK) obj->n_frag = 1;
+ }
+ }
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ ncl = 0;
+ if (scl == clst) { /* Stretching an existing chain? */
+ ncl = scl + 1; /* Test if next cluster is free */
+ if (ncl >= fs->n_fatent) ncl = 2;
+ cs = get_fat(obj, ncl); /* Get next cluster status */
+ if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */
+ if (cs != 0) { /* Not free? */
+ cs = fs->last_clst; /* Start at suggested cluster if it is valid */
+ if (cs >= 2 && cs < fs->n_fatent) scl = cs;
+ ncl = 0;
+ }
+ }
+ if (ncl == 0) { /* The new cluster cannot be contiguous and find another fragment */
+ ncl = scl; /* Start cluster */
+ for (;;) {
+ ncl++; /* Next cluster */
+ if (ncl >= fs->n_fatent) { /* Check wrap-around */
+ ncl = 2;
+ if (ncl > scl) return 0; /* No free cluster found? */
+ }
+ cs = get_fat(obj, ncl); /* Get the cluster status */
+ if (cs == 0) break; /* Found a free cluster? */
+ if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */
+ if (ncl == scl) return 0; /* No free cluster found? */
+ }
+ }
+ res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */
+ if (res == FR_OK && clst != 0) {
+ res = put_fat(fs, clst, ncl); /* Link it from the previous one if needed */
+ }
+ }
+
+ if (res == FR_OK) { /* Update FSINFO if function succeeded. */
+ fs->last_clst = ncl;
+ if (fs->free_clst <= fs->n_fatent - 2) fs->free_clst--;
+ fs->fsi_flag |= 1;
+ } else {
+ ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; /* Failed. Generate error status */
+ }
+
+ return ncl; /* Return new cluster number or error status */
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+#if FF_USE_FASTSEEK
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Convert offset into cluster with link map table */
+/*-----------------------------------------------------------------------*/
+
+static DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t ofs /* File offset to be converted to cluster# */
+)
+{
+ DWORD cl, ncl, *tbl;
+ FATFS *fs = fp->obj.fs;
+
+
+ tbl = fp->cltbl + 1; /* Top of CLMT */
+ cl = (DWORD)(ofs / SS(fs) / fs->csize); /* Cluster order from top of the file */
+ for (;;) {
+ ncl = *tbl++; /* Number of cluters in the fragment */
+ if (ncl == 0) return 0; /* End of table? (error) */
+ if (cl < ncl) break; /* In this fragment? */
+ cl -= ncl; tbl++; /* Next fragment */
+ }
+ return cl + *tbl; /* Return the cluster number */
+}
+
+#endif /* FF_USE_FASTSEEK */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Fill a cluster with zeros */
+/*-----------------------------------------------------------------------*/
+
+#if !FF_FS_READONLY
+static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */
+ FATFS *fs, /* Filesystem object */
+ DWORD clst /* Directory table to clear */
+)
+{
+ LBA_t sect;
+ UINT n, szb;
+ BYTE *ibuf;
+
+
+ if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */
+ sect = clst2sect(fs, clst); /* Top of the cluster */
+ fs->winsect = sect; /* Set window to top of the cluster */
+ mem_set(fs->win, 0, sizeof fs->win); /* Clear window buffer */
+#if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */
+ /* Allocate a temporary buffer */
+ for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ;
+ if (szb > SS(fs)) { /* Buffer allocated? */
+ mem_set(ibuf, 0, szb);
+ szb /= SS(fs); /* Bytes -> Sectors */
+ for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */
+ ff_memfree(ibuf);
+ } else
+#endif
+ {
+ ibuf = fs->win; szb = 1; /* Use window buffer (many single-sector writes may take a time) */
+ for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */
+ }
+ return (n == fs->csize) ? FR_OK : FR_DISK_ERR;
+}
+#endif /* !FF_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Set directory index */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp, /* Pointer to directory object */
+ DWORD ofs /* Offset of directory table */
+)
+{
+ DWORD csz, clst;
+ FATFS *fs = dp->obj.fs;
+
+
+ if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR) || ofs % SZDIRE) { /* Check range of offset and alignment */
+ return FR_INT_ERR;
+ }
+ dp->dptr = ofs; /* Set current offset */
+ clst = dp->obj.sclust; /* Table start cluster (0:root) */
+ if (clst == 0 && fs->fs_type >= FS_FAT32) { /* Replace cluster# 0 with root cluster# */
+ clst = (DWORD)fs->dirbase;
+ if (FF_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */
+ }
+
+ if (clst == 0) { /* Static table (root-directory on the FAT volume) */
+ if (ofs / SZDIRE >= fs->n_rootdir) return FR_INT_ERR; /* Is index out of range? */
+ dp->sect = fs->dirbase;
+
+ } else { /* Dynamic table (sub-directory or root-directory on the FAT32/exFAT volume) */
+ csz = (DWORD)fs->csize * SS(fs); /* Bytes per cluster */
+ while (ofs >= csz) { /* Follow cluster chain */
+ clst = get_fat(&dp->obj, clst); /* Get next cluster */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Reached to end of table or internal error */
+ ofs -= csz;
+ }
+ dp->sect = clst2sect(fs, clst);
+ }
+ dp->clust = clst; /* Current cluster# */
+ if (dp->sect == 0) return FR_INT_ERR;
+ dp->sect += ofs / SS(fs); /* Sector# of the directory entry */
+ dp->dir = fs->win + (ofs % SS(fs)); /* Pointer to the entry in the win[] */
+
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Move directory table index next */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */
+ DIR* dp, /* Pointer to the directory object */
+ int stretch /* 0: Do not stretch table, 1: Stretch table if needed */
+)
+{
+ DWORD ofs, clst;
+ FATFS *fs = dp->obj.fs;
+
+
+ ofs = dp->dptr + SZDIRE; /* Next entry */
+ if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) dp->sect = 0; /* Disable it if the offset reached the max value */
+ if (dp->sect == 0) return FR_NO_FILE; /* Report EOT if it has been disabled */
+
+ if (ofs % SS(fs) == 0) { /* Sector changed? */
+ dp->sect++; /* Next sector */
+
+ if (dp->clust == 0) { /* Static table */
+ if (ofs / SZDIRE >= fs->n_rootdir) { /* Report EOT if it reached end of static table */
+ dp->sect = 0; return FR_NO_FILE;
+ }
+ }
+ else { /* Dynamic table */
+ if ((ofs / SS(fs) & (fs->csize - 1)) == 0) { /* Cluster changed? */
+ clst = get_fat(&dp->obj, dp->clust); /* Get next cluster */
+ if (clst <= 1) return FR_INT_ERR; /* Internal error */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (clst >= fs->n_fatent) { /* It reached end of dynamic table */
+#if !FF_FS_READONLY
+ if (!stretch) { /* If no stretch, report EOT */
+ dp->sect = 0; return FR_NO_FILE;
+ }
+ clst = create_chain(&dp->obj, dp->clust); /* Allocate a cluster */
+ if (clst == 0) return FR_DENIED; /* No free cluster */
+ if (clst == 1) return FR_INT_ERR; /* Internal error */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (dir_clear(fs, clst) != FR_OK) return FR_DISK_ERR; /* Clean up the stretched table */
+ if (FF_FS_EXFAT) dp->obj.stat |= 4; /* exFAT: The directory has been stretched */
+#else
+ if (!stretch) dp->sect = 0; /* (this line is to suppress compiler warning) */
+ dp->sect = 0; return FR_NO_FILE; /* Report EOT */
+#endif
+ }
+ dp->clust = clst; /* Initialize data for new cluster */
+ dp->sect = clst2sect(fs, clst);
+ }
+ }
+ }
+ dp->dptr = ofs; /* Current entry */
+ dp->dir = fs->win + ofs % SS(fs); /* Pointer to the entry in the win[] */
+
+ return FR_OK;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Reserve a block of directory entries */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp, /* Pointer to the directory object */
+ UINT nent /* Number of contiguous entries to allocate */
+)
+{
+ FRESULT res;
+ UINT n;
+ FATFS *fs = dp->obj.fs;
+
+
+ res = dir_sdi(dp, 0);
+ if (res == FR_OK) {
+ n = 0;
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+#if FF_FS_EXFAT
+ if ((fs->fs_type == FS_EXFAT) ? (int)((dp->dir[XDIR_Type] & 0x80) == 0) : (int)(dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0)) {
+#else
+ if (dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0) {
+#endif
+ if (++n == nent) break; /* A block of contiguous free entries is found */
+ } else {
+ n = 0; /* Not a blank entry. Restart to search */
+ }
+ res = dir_next(dp, 1);
+ } while (res == FR_OK); /* Next entry with table stretch enabled */
+ }
+
+ if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */
+ return res;
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT: Directory handling - Load/Store start cluster number */
+/*-----------------------------------------------------------------------*/
+
+static DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */
+ FATFS* fs, /* Pointer to the fs object */
+ const BYTE* dir /* Pointer to the key entry */
+)
+{
+ DWORD cl;
+
+ cl = ld_word(dir + DIR_FstClusLO);
+ if (fs->fs_type == FS_FAT32) {
+ cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16;
+ }
+
+ return cl;
+}
+
+
+#if !FF_FS_READONLY
+static void st_clust (
+ FATFS* fs, /* Pointer to the fs object */
+ BYTE* dir, /* Pointer to the key entry */
+ DWORD cl /* Value to be set */
+)
+{
+ st_word(dir + DIR_FstClusLO, (WORD)cl);
+ if (fs->fs_type == FS_FAT32) {
+ st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16));
+ }
+}
+#endif
+
+
+
+#if FF_USE_LFN
+/*--------------------------------------------------------*/
+/* FAT-LFN: Compare a part of file name with an LFN entry */
+/*--------------------------------------------------------*/
+
+static int cmp_lfn ( /* 1:matched, 0:not matched */
+ const WCHAR* lfnbuf, /* Pointer to the LFN working buffer to be compared */
+ BYTE* dir /* Pointer to the directory entry containing the part of LFN */
+)
+{
+ UINT i, s;
+ WCHAR wc, uc;
+
+
+ if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */
+
+ i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */
+
+ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */
+ uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */
+ if (wc != 0) {
+ if (i >= FF_MAX_LFN + 1 || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */
+ return 0; /* Not matched */
+ }
+ wc = uc;
+ } else {
+ if (uc != 0xFFFF) return 0; /* Check filler */
+ }
+ }
+
+ if ((dir[LDIR_Ord] & LLEF) && wc && lfnbuf[i]) return 0; /* Last segment matched but different length */
+
+ return 1; /* The part of LFN matched */
+}
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT
+/*-----------------------------------------------------*/
+/* FAT-LFN: Pick a part of file name from an LFN entry */
+/*-----------------------------------------------------*/
+
+static int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */
+ WCHAR* lfnbuf, /* Pointer to the LFN working buffer */
+ BYTE* dir /* Pointer to the LFN entry */
+)
+{
+ UINT i, s;
+ WCHAR wc, uc;
+
+
+ if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO is 0 */
+
+ i = ((dir[LDIR_Ord] & ~LLEF) - 1) * 13; /* Offset in the LFN buffer */
+
+ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */
+ uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */
+ if (wc != 0) {
+ if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */
+ lfnbuf[i++] = wc = uc; /* Store it */
+ } else {
+ if (uc != 0xFFFF) return 0; /* Check filler */
+ }
+ }
+
+ if (dir[LDIR_Ord] & LLEF && wc != 0) { /* Put terminator if it is the last LFN part and not terminated */
+ if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */
+ lfnbuf[i] = 0;
+ }
+
+ return 1; /* The part of LFN is valid */
+}
+#endif
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------*/
+/* FAT-LFN: Create an entry of LFN entries */
+/*-----------------------------------------*/
+
+static void put_lfn (
+ const WCHAR* lfn, /* Pointer to the LFN */
+ BYTE* dir, /* Pointer to the LFN entry to be created */
+ BYTE ord, /* LFN order (1-20) */
+ BYTE sum /* Checksum of the corresponding SFN */
+)
+{
+ UINT i, s;
+ WCHAR wc;
+
+
+ dir[LDIR_Chksum] = sum; /* Set checksum */
+ dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */
+ dir[LDIR_Type] = 0;
+ st_word(dir + LDIR_FstClusLO, 0);
+
+ i = (ord - 1) * 13; /* Get offset in the LFN working buffer */
+ s = wc = 0;
+ do {
+ if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */
+ st_word(dir + LfnOfs[s], wc); /* Put it */
+ if (wc == 0) wc = 0xFFFF; /* Padding characters for following items */
+ } while (++s < 13);
+ if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */
+ dir[LDIR_Ord] = ord; /* Set the LFN order */
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_USE_LFN */
+
+
+
+#if FF_USE_LFN && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT-LFN: Create a Numbered SFN */
+/*-----------------------------------------------------------------------*/
+
+static void gen_numname (
+ BYTE* dst, /* Pointer to the buffer to store numbered SFN */
+ const BYTE* src, /* Pointer to SFN */
+ const WCHAR* lfn, /* Pointer to LFN */
+ UINT seq /* Sequence number */
+)
+{
+ BYTE ns[8], c;
+ UINT i, j;
+ WCHAR wc;
+ DWORD sreg;
+
+
+ mem_cpy(dst, src, 11);
+
+ if (seq > 5) { /* In case of many collisions, generate a hash number instead of sequential number */
+ sreg = seq;
+ while (*lfn) { /* Create a CRC as hash value */
+ wc = *lfn++;
+ for (i = 0; i < 16; i++) {
+ sreg = (sreg << 1) + (wc & 1);
+ wc >>= 1;
+ if (sreg & 0x10000) sreg ^= 0x11021;
+ }
+ }
+ seq = (UINT)sreg;
+ }
+
+ /* itoa (hexdecimal) */
+ i = 7;
+ do {
+ c = (BYTE)((seq % 16) + '0');
+ if (c > '9') c += 7;
+ ns[i--] = c;
+ seq /= 16;
+ } while (seq);
+ ns[i] = '~';
+
+ /* Append the number to the SFN body */
+ for (j = 0; j < i && dst[j] != ' '; j++) {
+ if (dbc_1st(dst[j])) {
+ if (j == i - 1) break;
+ j++;
+ }
+ }
+ do {
+ dst[j++] = (i < 8) ? ns[i++] : ' ';
+ } while (j < 8);
+}
+#endif /* FF_USE_LFN && !FF_FS_READONLY */
+
+
+
+#if FF_USE_LFN
+/*-----------------------------------------------------------------------*/
+/* FAT-LFN: Calculate checksum of an SFN entry */
+/*-----------------------------------------------------------------------*/
+
+static BYTE sum_sfn (
+ const BYTE* dir /* Pointer to the SFN entry */
+)
+{
+ BYTE sum = 0;
+ UINT n = 11;
+
+ do {
+ sum = (sum >> 1) + (sum << 7) + *dir++;
+ } while (--n);
+ return sum;
+}
+
+#endif /* FF_USE_LFN */
+
+
+
+#if FF_FS_EXFAT
+/*-----------------------------------------------------------------------*/
+/* exFAT: Checksum */
+/*-----------------------------------------------------------------------*/
+
+static WORD xdir_sum ( /* Get checksum of the directoly entry block */
+ const BYTE* dir /* Directory entry block to be calculated */
+)
+{
+ UINT i, szblk;
+ WORD sum;
+
+
+ szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; /* Number of bytes of the entry block */
+ for (i = sum = 0; i < szblk; i++) {
+ if (i == XDIR_SetSum) { /* Skip 2-byte sum field */
+ i++;
+ } else {
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i];
+ }
+ }
+ return sum;
+}
+
+
+
+static WORD xname_sum ( /* Get check sum (to be used as hash) of the file name */
+ const WCHAR* name /* File name to be calculated */
+)
+{
+ WCHAR chr;
+ WORD sum = 0;
+
+
+ while ((chr = *name++) != 0) {
+ chr = (WCHAR)ff_wtoupper(chr); /* File name needs to be up-case converted */
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF);
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8);
+ }
+ return sum;
+}
+
+
+#if !FF_FS_READONLY && FF_USE_MKFS
+static DWORD xsum32 ( /* Returns 32-bit checksum */
+ BYTE dat, /* Byte to be calculated (byte-by-byte processing) */
+ DWORD sum /* Previous sum value */
+)
+{
+ sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat;
+ return sum;
+}
+#endif
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2
+/*------------------------------------------------------*/
+/* exFAT: Get object information from a directory block */
+/*------------------------------------------------------*/
+
+static void get_xfileinfo (
+ BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */
+ FILINFO* fno /* Buffer to store the extracted file information */
+)
+{
+ WCHAR wc, hs;
+ UINT di, si, nc;
+
+ /* Get file name from the entry block */
+ si = SZDIRE * 2; /* 1st C1 entry */
+ nc = 0; hs = 0; di = 0;
+ while (nc < dirb[XDIR_NumName]) {
+ if (si >= MAXDIRB(FF_MAX_LFN)) { di = 0; break; } /* Truncated directory block? */
+ if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */
+ wc = ld_word(dirb + si); si += 2; nc++; /* Get a character */
+ if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */
+ hs = wc; continue; /* Get low surrogate */
+ }
+ wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in API encoding */
+ if (wc == 0) { di = 0; break; } /* Buffer overflow or wrong encoding? */
+ di += wc;
+ hs = 0;
+ }
+ if (hs != 0) di = 0; /* Broken surrogate pair? */
+ if (di == 0) fno->fname[di++] = '?'; /* Inaccessible object name? */
+ fno->fname[di] = 0; /* Terminate the name */
+ fno->altname[0] = 0; /* exFAT does not support SFN */
+
+ fno->fattrib = dirb[XDIR_Attr]; /* Attribute */
+ fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(dirb + XDIR_FileSize); /* Size */
+ fno->ftime = ld_word(dirb + XDIR_ModTime + 0); /* Time */
+ fno->fdate = ld_word(dirb + XDIR_ModTime + 2); /* Date */
+}
+
+#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */
+
+
+/*-----------------------------------*/
+/* exFAT: Get a directry entry block */
+/*-----------------------------------*/
+
+static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */
+ DIR* dp /* Reading direcotry object pointing top of the entry block to load */
+)
+{
+ FRESULT res;
+ UINT i, sz_ent;
+ BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */
+
+
+ /* Load file-directory entry */
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != ET_FILEDIR) return FR_INT_ERR; /* Invalid order */
+ mem_cpy(dirb + 0 * SZDIRE, dp->dir, SZDIRE);
+ sz_ent = (dirb[XDIR_NumSec] + 1) * SZDIRE;
+ if (sz_ent < 3 * SZDIRE || sz_ent > 19 * SZDIRE) return FR_INT_ERR;
+
+ /* Load stream-extension entry */
+ res = dir_next(dp, 0);
+ if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */
+ if (res != FR_OK) return res;
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != ET_STREAM) return FR_INT_ERR; /* Invalid order */
+ mem_cpy(dirb + 1 * SZDIRE, dp->dir, SZDIRE);
+ if (MAXDIRB(dirb[XDIR_NumName]) > sz_ent) return FR_INT_ERR;
+
+ /* Load file-name entries */
+ i = 2 * SZDIRE; /* Name offset to load */
+ do {
+ res = dir_next(dp, 0);
+ if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */
+ if (res != FR_OK) return res;
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != ET_FILENAME) return FR_INT_ERR; /* Invalid order */
+ if (i < MAXDIRB(FF_MAX_LFN)) mem_cpy(dirb + i, dp->dir, SZDIRE);
+ } while ((i += SZDIRE) < sz_ent);
+
+ /* Sanity check (do it for only accessible object) */
+ if (i <= MAXDIRB(FF_MAX_LFN)) {
+ if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR;
+ }
+ return FR_OK;
+}
+
+
+/*------------------------------------------------------------------*/
+/* exFAT: Initialize object allocation info with loaded entry block */
+/*------------------------------------------------------------------*/
+
+static void init_alloc_info (
+ FATFS* fs, /* Filesystem object */
+ FFOBJID* obj /* Object allocation information to be initialized */
+)
+{
+ obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Start cluster */
+ obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); /* Size */
+ obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; /* Allocation status */
+ obj->n_frag = 0; /* No last fragment info */
+}
+
+
+
+#if !FF_FS_READONLY || FF_FS_RPATH != 0
+/*------------------------------------------------*/
+/* exFAT: Load the object's directory entry block */
+/*------------------------------------------------*/
+
+static FRESULT load_obj_xdir (
+ DIR* dp, /* Blank directory object to be used to access containing direcotry */
+ const FFOBJID* obj /* Object with its containing directory information */
+)
+{
+ FRESULT res;
+
+ /* Open object containing directory */
+ dp->obj.fs = obj->fs;
+ dp->obj.sclust = obj->c_scl;
+ dp->obj.stat = (BYTE)obj->c_size;
+ dp->obj.objsize = obj->c_size & 0xFFFFFF00;
+ dp->obj.n_frag = 0;
+ dp->blk_ofs = obj->c_ofs;
+
+ res = dir_sdi(dp, dp->blk_ofs); /* Goto object's entry block */
+ if (res == FR_OK) {
+ res = load_xdir(dp); /* Load the object's entry block */
+ }
+ return res;
+}
+#endif
+
+
+#if !FF_FS_READONLY
+/*----------------------------------------*/
+/* exFAT: Store the directory entry block */
+/*----------------------------------------*/
+
+static FRESULT store_xdir (
+ DIR* dp /* Pointer to the direcotry object */
+)
+{
+ FRESULT res;
+ UINT nent;
+ BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */
+
+ /* Create set sum */
+ st_word(dirb + XDIR_SetSum, xdir_sum(dirb));
+ nent = dirb[XDIR_NumSec] + 1;
+
+ /* Store the direcotry entry block to the directory */
+ res = dir_sdi(dp, dp->blk_ofs);
+ while (res == FR_OK) {
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) break;
+ mem_cpy(dp->dir, dirb, SZDIRE);
+ dp->obj.fs->wflag = 1;
+ if (--nent == 0) break;
+ dirb += SZDIRE;
+ res = dir_next(dp, 0);
+ }
+ return (res == FR_OK || res == FR_DISK_ERR) ? res : FR_INT_ERR;
+}
+
+
+
+/*-------------------------------------------*/
+/* exFAT: Create a new directory enrty block */
+/*-------------------------------------------*/
+
+static void create_xdir (
+ BYTE* dirb, /* Pointer to the direcotry entry block buffer */
+ const WCHAR* lfn /* Pointer to the object name */
+)
+{
+ UINT i;
+ BYTE nc1, nlen;
+ WCHAR wc;
+
+
+ /* Create file-directory and stream-extension entry */
+ mem_set(dirb, 0, 2 * SZDIRE);
+ dirb[0 * SZDIRE + XDIR_Type] = ET_FILEDIR;
+ dirb[1 * SZDIRE + XDIR_Type] = ET_STREAM;
+
+ /* Create file-name entries */
+ i = SZDIRE * 2; /* Top of file_name entries */
+ nlen = nc1 = 0; wc = 1;
+ do {
+ dirb[i++] = ET_FILENAME; dirb[i++] = 0;
+ do { /* Fill name field */
+ if (wc != 0 && (wc = lfn[nlen]) != 0) nlen++; /* Get a character if exist */
+ st_word(dirb + i, wc); /* Store it */
+ i += 2;
+ } while (i % SZDIRE != 0);
+ nc1++;
+ } while (lfn[nlen]); /* Fill next entry if any char follows */
+
+ dirb[XDIR_NumName] = nlen; /* Set name length */
+ dirb[XDIR_NumSec] = 1 + nc1; /* Set secondary count (C0 + C1s) */
+ st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_FS_EXFAT */
+
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT
+/*-----------------------------------------------------------------------*/
+/* Read an object from the directory */
+/*-----------------------------------------------------------------------*/
+
+#define DIR_READ_FILE(dp) dir_read(dp, 0)
+#define DIR_READ_LABEL(dp) dir_read(dp, 1)
+
+static FRESULT dir_read (
+ DIR* dp, /* Pointer to the directory object */
+ int vol /* Filtered by 0:file/directory or 1:volume label */
+)
+{
+ FRESULT res = FR_NO_FILE;
+ FATFS *fs = dp->obj.fs;
+ BYTE attr, b;
+#if FF_USE_LFN
+ BYTE ord = 0xFF, sum = 0xFF;
+#endif
+
+ while (dp->sect) {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ b = dp->dir[DIR_Name]; /* Test for the entry type */
+ if (b == 0) {
+ res = FR_NO_FILE; break; /* Reached to end of the directory */
+ }
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ if (FF_USE_LABEL && vol) {
+ if (b == ET_VLABEL) break; /* Volume label entry? */
+ } else {
+ if (b == ET_FILEDIR) { /* Start of the file entry block? */
+ dp->blk_ofs = dp->dptr; /* Get location of the block */
+ res = load_xdir(dp); /* Load the entry block */
+ if (res == FR_OK) {
+ dp->obj.attr = fs->dirbuf[XDIR_Attr] & AM_MASK; /* Get attribute */
+ }
+ break;
+ }
+ }
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ dp->obj.attr = attr = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */
+#if FF_USE_LFN /* LFN configuration */
+ if (b == DDEM || b == '.' || (int)((attr & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */
+ ord = 0xFF;
+ } else {
+ if (attr == AM_LFN) { /* An LFN entry is found */
+ if (b & LLEF) { /* Is it start of an LFN sequence? */
+ sum = dp->dir[LDIR_Chksum];
+ b &= (BYTE)~LLEF; ord = b;
+ dp->blk_ofs = dp->dptr;
+ }
+ /* Check LFN validity and capture it */
+ ord = (b == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
+ } else { /* An SFN entry is found */
+ if (ord != 0 || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */
+ dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */
+ }
+ break;
+ }
+ }
+#else /* Non LFN configuration */
+ if (b != DDEM && b != '.' && attr != AM_LFN && (int)((attr & ~AM_ARC) == AM_VOL) == vol) { /* Is it a valid entry? */
+ break;
+ }
+#endif
+ }
+ res = dir_next(dp, 0); /* Next entry */
+ if (res != FR_OK) break;
+ }
+
+ if (res != FR_OK) dp->sect = 0; /* Terminate the read operation on error or EOT */
+ return res;
+}
+
+#endif /* FF_FS_MINIMIZE <= 1 || FF_USE_LABEL || FF_FS_RPATH >= 2 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Find an object in the directory */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp /* Pointer to the directory object with the file name */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+ BYTE c;
+#if FF_USE_LFN
+ BYTE a, ord, sum;
+#endif
+
+ res = dir_sdi(dp, 0); /* Rewind directory object */
+ if (res != FR_OK) return res;
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ BYTE nc;
+ UINT di, ni;
+ WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */
+
+ while ((res = DIR_READ_FILE(dp)) == FR_OK) { /* Read an item */
+#if FF_MAX_LFN < 255
+ if (fs->dirbuf[XDIR_NumName] > FF_MAX_LFN) continue; /* Skip comparison if inaccessible object name */
+#endif
+ if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */
+ for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */
+ if ((di % SZDIRE) == 0) di += 2;
+ if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break;
+ }
+ if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */
+ }
+ return res;
+ }
+#endif
+ /* On the FAT/FAT32 volume */
+#if FF_USE_LFN
+ ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+#endif
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ c = dp->dir[DIR_Name];
+ if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */
+#if FF_USE_LFN /* LFN configuration */
+ dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK;
+ if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */
+ ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+ } else {
+ if (a == AM_LFN) { /* An LFN entry is found */
+ if (!(dp->fn[NSFLAG] & NS_NOLFN)) {
+ if (c & LLEF) { /* Is it start of LFN sequence? */
+ sum = dp->dir[LDIR_Chksum];
+ c &= (BYTE)~LLEF; ord = c; /* LFN start order */
+ dp->blk_ofs = dp->dptr; /* Start offset of LFN */
+ }
+ /* Check validity of the LFN entry and compare it with given name */
+ ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
+ }
+ } else { /* An SFN entry is found */
+ if (ord == 0 && sum == sum_sfn(dp->dir)) break; /* LFN matched? */
+ if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */
+ ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+ }
+ }
+#else /* Non LFN configuration */
+ dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK;
+ if (!(dp->dir[DIR_Attr] & AM_VOL) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */
+#endif
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK);
+
+ return res;
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Register an object to the directory */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */
+ DIR* dp /* Target directory with object name to be created */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+#if FF_USE_LFN /* LFN configuration */
+ UINT n, nlen, nent;
+ BYTE sn[12], sum;
+
+
+ if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME; /* Check name validity */
+ for (nlen = 0; fs->lfnbuf[nlen]; nlen++) ; /* Get lfn length */
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ nent = (nlen + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */
+ res = dir_alloc(dp, nent); /* Allocate directory entries */
+ if (res != FR_OK) return res;
+ dp->blk_ofs = dp->dptr - SZDIRE * (nent - 1); /* Set the allocated entry block offset */
+
+ if (dp->obj.stat & 4) { /* Has the directory been stretched by new allocation? */
+ dp->obj.stat &= ~4;
+ res = fill_first_frag(&dp->obj); /* Fill the first fragment on the FAT if needed */
+ if (res != FR_OK) return res;
+ res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */
+ if (res != FR_OK) return res;
+ if (dp->obj.sclust != 0) { /* Is it a sub-directory? */
+ DIR dj;
+
+ res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */
+ if (res != FR_OK) return res;
+ dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase the directory size by cluster size */
+ st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize);
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize);
+ fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1; /* Update the allocation status */
+ res = store_xdir(&dj); /* Store the object status */
+ if (res != FR_OK) return res;
+ }
+ }
+
+ create_xdir(fs->dirbuf, fs->lfnbuf); /* Create on-memory directory block to be written later */
+ return FR_OK;
+ }
+#endif
+ /* On the FAT/FAT32 volume */
+ mem_cpy(sn, dp->fn, 12);
+ if (sn[NSFLAG] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */
+ dp->fn[NSFLAG] = NS_NOLFN; /* Find only SFN */
+ for (n = 1; n < 100; n++) {
+ gen_numname(dp->fn, sn, fs->lfnbuf, n); /* Generate a numbered name */
+ res = dir_find(dp); /* Check if the name collides with existing SFN */
+ if (res != FR_OK) break;
+ }
+ if (n == 100) return FR_DENIED; /* Abort if too many collisions */
+ if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */
+ dp->fn[NSFLAG] = sn[NSFLAG];
+ }
+
+ /* Create an SFN with/without LFNs. */
+ nent = (sn[NSFLAG] & NS_LFN) ? (nlen + 12) / 13 + 1 : 1; /* Number of entries to allocate */
+ res = dir_alloc(dp, nent); /* Allocate entries */
+ if (res == FR_OK && --nent) { /* Set LFN entry if needed */
+ res = dir_sdi(dp, dp->dptr - nent * SZDIRE);
+ if (res == FR_OK) {
+ sum = sum_sfn(dp->fn); /* Checksum value of the SFN tied to the LFN */
+ do { /* Store LFN entries in bottom first */
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ put_lfn(fs->lfnbuf, dp->dir, (BYTE)nent, sum);
+ fs->wflag = 1;
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK && --nent);
+ }
+ }
+
+#else /* Non LFN configuration */
+ res = dir_alloc(dp, 1); /* Allocate an entry for SFN */
+
+#endif
+
+ /* Set SFN entry */
+ if (res == FR_OK) {
+ res = move_window(fs, dp->sect);
+ if (res == FR_OK) {
+ mem_set(dp->dir, 0, SZDIRE); /* Clean the entry */
+ mem_cpy(dp->dir + DIR_Name, dp->fn, 11); /* Put SFN */
+#if FF_USE_LFN
+ dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */
+#endif
+ fs->wflag = 1;
+ }
+ }
+
+ return res;
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+#if !FF_FS_READONLY && FF_FS_MINIMIZE == 0
+/*-----------------------------------------------------------------------*/
+/* Remove an object from the directory */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */
+ DIR* dp /* Directory object pointing the entry to be removed */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+#if FF_USE_LFN /* LFN configuration */
+ DWORD last = dp->dptr;
+
+ res = (dp->blk_ofs == 0xFFFFFFFF) ? FR_OK : dir_sdi(dp, dp->blk_ofs); /* Goto top of the entry block if LFN is exist */
+ if (res == FR_OK) {
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ dp->dir[XDIR_Type] &= 0x7F; /* Clear the entry InUse flag. */
+ } else { /* On the FAT/FAT32 volume */
+ dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'. */
+ }
+ fs->wflag = 1;
+ if (dp->dptr >= last) break; /* If reached last entry then all entries of the object has been deleted. */
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK);
+ if (res == FR_NO_FILE) res = FR_INT_ERR;
+ }
+#else /* Non LFN configuration */
+
+ res = move_window(fs, dp->sect);
+ if (res == FR_OK) {
+ dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'.*/
+ fs->wflag = 1;
+ }
+#endif
+
+ return res;
+}
+
+#endif /* !FF_FS_READONLY && FF_FS_MINIMIZE == 0 */
+
+
+
+#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2
+/*-----------------------------------------------------------------------*/
+/* Get file information from directory entry */
+/*-----------------------------------------------------------------------*/
+
+static void get_fileinfo (
+ DIR* dp, /* Pointer to the directory object */
+ FILINFO* fno /* Pointer to the file information to be filled */
+)
+{
+ UINT si, di;
+#if FF_USE_LFN
+ BYTE lcf;
+ WCHAR wc, hs;
+ FATFS *fs = dp->obj.fs;
+#else
+ TCHAR c;
+#endif
+
+
+ fno->fname[0] = 0; /* Invaidate file info */
+ if (dp->sect == 0) return; /* Exit if read pointer has reached end of directory */
+
+#if FF_USE_LFN /* LFN configuration */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ get_xfileinfo(fs->dirbuf, fno);
+ return;
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */
+ si = di = hs = 0;
+ while (fs->lfnbuf[si] != 0) {
+ wc = fs->lfnbuf[si++]; /* Get an LFN character (UTF-16) */
+ if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */
+ hs = wc; continue; /* Get low surrogate */
+ }
+ wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in UTF-16 or UTF-8 encoding */
+ if (wc == 0) { di = 0; break; } /* Invalid char or buffer overflow? */
+ di += wc;
+ hs = 0;
+ }
+ if (hs != 0) di = 0; /* Broken surrogate pair? */
+ fno->fname[di] = 0; /* Terminate the LFN (null string means LFN is invalid) */
+ }
+ }
+
+ si = di = 0;
+ while (si < 11) { /* Get SFN from SFN entry */
+ wc = dp->dir[si++]; /* Get a char */
+ if (wc == ' ') continue; /* Skip padding spaces */
+ if (wc == RDDEM) wc = DDEM; /* Restore replaced DDEM character */
+ if (si == 9 && di < FF_SFN_BUF) fno->altname[di++] = '.'; /* Insert a . if extension is exist */
+#if FF_LFN_UNICODE >= 1 /* Unicode output */
+ if (dbc_1st((BYTE)wc) && si != 8 && si != 11 && dbc_2nd(dp->dir[si])) { /* Make a DBC if needed */
+ wc = wc << 8 | dp->dir[si++];
+ }
+ wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM -> Unicode */
+ if (wc == 0) { di = 0; break; } /* Wrong char in the current code page? */
+ wc = put_utf(wc, &fno->altname[di], FF_SFN_BUF - di); /* Store it in Unicode */
+ if (wc == 0) { di = 0; break; } /* Buffer overflow? */
+ di += wc;
+#else /* ANSI/OEM output */
+ fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */
+#endif
+ }
+ fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */
+
+ if (fno->fname[0] == 0) { /* If LFN is invalid, altname[] needs to be copied to fname[] */
+ if (di == 0) { /* If LFN and SFN both are invalid, this object is inaccesible */
+ fno->fname[di++] = '?';
+ } else {
+ for (si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++) { /* Copy altname[] to fname[] with case information */
+ wc = (WCHAR)fno->altname[si];
+ if (wc == '.') lcf = NS_EXT;
+ if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20;
+ fno->fname[di] = (TCHAR)wc;
+ }
+ }
+ fno->fname[di] = 0; /* Terminate the LFN */
+ if (!dp->dir[DIR_NTres]) fno->altname[0] = 0; /* Altname is not needed if neither LFN nor case info is exist. */
+ }
+
+#else /* Non-LFN configuration */
+ si = di = 0;
+ while (si < 11) { /* Copy name body and extension */
+ c = (TCHAR)dp->dir[si++];
+ if (c == ' ') continue; /* Skip padding spaces */
+ if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */
+ if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */
+ fno->fname[di++] = c;
+ }
+ fno->fname[di] = 0;
+#endif
+
+ fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */
+ fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */
+ fno->ftime = ld_word(dp->dir + DIR_ModTime + 0); /* Time */
+ fno->fdate = ld_word(dp->dir + DIR_ModTime + 2); /* Date */
+}
+
+#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */
+
+
+
+#if FF_USE_FIND && FF_FS_MINIMIZE <= 1
+/*-----------------------------------------------------------------------*/
+/* Pattern matching */
+/*-----------------------------------------------------------------------*/
+
+static DWORD get_achar ( /* Get a character and advances ptr */
+ const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */
+)
+{
+ DWORD chr;
+
+
+#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode input */
+ chr = tchar2uni(ptr);
+ if (chr == 0xFFFFFFFF) chr = 0; /* Wrong UTF encoding is recognized as end of the string */
+ chr = ff_wtoupper(chr);
+
+#else /* ANSI/OEM input */
+ chr = (BYTE)*(*ptr)++; /* Get a byte */
+ if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */
+#if FF_CODE_PAGE == 0
+ if (ExCvt && chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */
+#elif FF_CODE_PAGE < 900
+ if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */
+#endif
+#if FF_CODE_PAGE == 0 || FF_CODE_PAGE >= 900
+ if (dbc_1st((BYTE)chr)) { /* Get DBC 2nd byte if needed */
+ chr = dbc_2nd((BYTE)**ptr) ? chr << 8 | (BYTE)*(*ptr)++ : 0;
+ }
+#endif
+
+#endif
+ return chr;
+}
+
+
+static int pattern_matching ( /* 0:not matched, 1:matched */
+ const TCHAR* pat, /* Matching pattern */
+ const TCHAR* nam, /* String to be tested */
+ int skip, /* Number of pre-skip chars (number of ?s) */
+ int inf /* Infinite search (* specified) */
+)
+{
+ const TCHAR *pp, *np;
+ DWORD pc, nc;
+ int nm, nx;
+
+
+ while (skip--) { /* Pre-skip name chars */
+ if (!get_achar(&nam)) return 0; /* Branch mismatched if less name chars */
+ }
+ if (*pat == 0 && inf) return 1; /* (short circuit) */
+
+ do {
+ pp = pat; np = nam; /* Top of pattern and name to match */
+ for (;;) {
+ if (*pp == '?' || *pp == '*') { /* Wildcard? */
+ nm = nx = 0;
+ do { /* Analyze the wildcard block */
+ if (*pp++ == '?') nm++; else nx = 1;
+ } while (*pp == '?' || *pp == '*');
+ if (pattern_matching(pp, np, nm, nx)) return 1; /* Test new branch (recurs upto number of wildcard blocks in the pattern) */
+ nc = *np; break; /* Branch mismatched */
+ }
+ pc = get_achar(&pp); /* Get a pattern char */
+ nc = get_achar(&np); /* Get a name char */
+ if (pc != nc) break; /* Branch mismatched? */
+ if (pc == 0) return 1; /* Branch matched? (matched at end of both strings) */
+ }
+ get_achar(&nam); /* nam++ */
+ } while (inf && nc); /* Retry until end of name if infinite search is specified */
+
+ return 0;
+}
+
+#endif /* FF_USE_FIND && FF_FS_MINIMIZE <= 1 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Pick a top segment and create the object name in directory form */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */
+ DIR* dp, /* Pointer to the directory object */
+ const TCHAR** path /* Pointer to pointer to the segment in the path string */
+)
+{
+#if FF_USE_LFN /* LFN configuration */
+ BYTE b, cf;
+ WCHAR wc, *lfn;
+ DWORD uc;
+ UINT i, ni, si, di;
+ const TCHAR *p;
+
+
+ /* Create LFN into LFN working buffer */
+ p = *path; lfn = dp->obj.fs->lfnbuf; di = 0;
+ for (;;) {
+ uc = tchar2uni(&p); /* Get a character */
+ if (uc == 0xFFFFFFFF) return FR_INVALID_NAME; /* Invalid code or UTF decode error */
+ if (uc >= 0x10000) lfn[di++] = (WCHAR)(uc >> 16); /* Store high surrogate if needed */
+ wc = (WCHAR)uc;
+ if (wc < ' ' || wc == '/' || wc == '\\') break; /* Break if end of the path or a separator is found */
+ if (wc < 0x80 && chk_chr("\"*:<>\?|\x7F", wc)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */
+ if (di >= FF_MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */
+ lfn[di++] = wc; /* Store the Unicode character */
+ }
+ if (wc < ' ') { /* End of path? */
+ cf = NS_LAST; /* Set last segment flag */
+ } else {
+ cf = 0; /* Next segment follows */
+ while (*p == '/' || *p == '\\') p++; /* Skip duplicated separators if exist */
+ }
+ *path = p; /* Return pointer to the next segment */
+
+#if FF_FS_RPATH != 0
+ if ((di == 1 && lfn[di - 1] == '.') ||
+ (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.')) { /* Is this segment a dot name? */
+ lfn[di] = 0;
+ for (i = 0; i < 11; i++) { /* Create dot name for SFN entry */
+ dp->fn[i] = (i < di) ? '.' : ' ';
+ }
+ dp->fn[i] = cf | NS_DOT; /* This is a dot entry */
+ return FR_OK;
+ }
+#endif
+ while (di) { /* Snip off trailing spaces and dots if exist */
+ wc = lfn[di - 1];
+ if (wc != ' ' && wc != '.') break;
+ di--;
+ }
+ lfn[di] = 0; /* LFN is created into the working buffer */
+ if (di == 0) return FR_INVALID_NAME; /* Reject null name */
+
+ /* Create SFN in directory form */
+ for (si = 0; lfn[si] == ' '; si++) ; /* Remove leading spaces */
+ if (si > 0 || lfn[si] == '.') cf |= NS_LOSS | NS_LFN; /* Is there any leading space or dot? */
+ while (di > 0 && lfn[di - 1] != '.') di--; /* Find last dot (di<=si: no extension) */
+
+ mem_set(dp->fn, ' ', 11);
+ i = b = 0; ni = 8;
+ for (;;) {
+ wc = lfn[si++]; /* Get an LFN character */
+ if (wc == 0) break; /* Break on end of the LFN */
+ if (wc == ' ' || (wc == '.' && si != di)) { /* Remove embedded spaces and dots */
+ cf |= NS_LOSS | NS_LFN;
+ continue;
+ }
+
+ if (i >= ni || si == di) { /* End of field? */
+ if (ni == 11) { /* Name extension overflow? */
+ cf |= NS_LOSS | NS_LFN;
+ break;
+ }
+ if (si != di) cf |= NS_LOSS | NS_LFN; /* Name body overflow? */
+ if (si > di) break; /* No name extension? */
+ si = di; i = 8; ni = 11; b <<= 2; /* Enter name extension */
+ continue;
+ }
+
+ if (wc >= 0x80) { /* Is this a non-ASCII character? */
+ cf |= NS_LFN; /* LFN entry needs to be created */
+#if FF_CODE_PAGE == 0
+ if (ExCvt) { /* At SBCS */
+ wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */
+ if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */
+ } else { /* At DBCS */
+ wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */
+ }
+#elif FF_CODE_PAGE < 900 /* SBCS cfg */
+ wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */
+ if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */
+#else /* DBCS cfg */
+ wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */
+#endif
+ }
+
+ if (wc >= 0x100) { /* Is this a DBC? */
+ if (i >= ni - 1) { /* Field overflow? */
+ cf |= NS_LOSS | NS_LFN;
+ i = ni; continue; /* Next field */
+ }
+ dp->fn[i++] = (BYTE)(wc >> 8); /* Put 1st byte */
+ } else { /* SBC */
+ if (wc == 0 || chk_chr("+,;=[]", wc)) { /* Replace illegal characters for SFN if needed */
+ wc = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */
+ } else {
+ if (IsUpper(wc)) { /* ASCII upper case? */
+ b |= 2;
+ }
+ if (IsLower(wc)) { /* ASCII lower case? */
+ b |= 1; wc -= 0x20;
+ }
+ }
+ }
+ dp->fn[i++] = (BYTE)wc;
+ }
+
+ if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */
+
+ if (ni == 8) b <<= 2; /* Shift capital flags if no extension */
+ if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* LFN entry needs to be created if composite capitals */
+ if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */
+ if (b & 0x01) cf |= NS_EXT; /* NT flag (Extension has small capital letters only) */
+ if (b & 0x04) cf |= NS_BODY; /* NT flag (Body has small capital letters only) */
+ }
+
+ dp->fn[NSFLAG] = cf; /* SFN is created into dp->fn[] */
+
+ return FR_OK;
+
+
+#else /* FF_USE_LFN : Non-LFN configuration */
+ BYTE c, d, *sfn;
+ UINT ni, si, i;
+ const char *p;
+
+ /* Create file name in directory form */
+ p = *path; sfn = dp->fn;
+ mem_set(sfn, ' ', 11);
+ si = i = 0; ni = 8;
+#if FF_FS_RPATH != 0
+ if (p[si] == '.') { /* Is this a dot entry? */
+ for (;;) {
+ c = (BYTE)p[si++];
+ if (c != '.' || si >= 3) break;
+ sfn[i++] = c;
+ }
+ if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME;
+ *path = p + si; /* Return pointer to the next segment */
+ sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */
+ return FR_OK;
+ }
+#endif
+ for (;;) {
+ c = (BYTE)p[si++]; /* Get a byte */
+ if (c <= ' ') break; /* Break if end of the path name */
+ if (c == '/' || c == '\\') { /* Break if a separator is found */
+ while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */
+ break;
+ }
+ if (c == '.' || i >= ni) { /* End of body or field overflow? */
+ if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Field overflow or invalid dot? */
+ i = 8; ni = 11; /* Enter file extension field */
+ continue;
+ }
+#if FF_CODE_PAGE == 0
+ if (ExCvt && c >= 0x80) { /* Is SBC extended character? */
+ c = ExCvt[c & 0x7F]; /* To upper SBC extended character */
+ }
+#elif FF_CODE_PAGE < 900
+ if (c >= 0x80) { /* Is SBC extended character? */
+ c = ExCvt[c & 0x7F]; /* To upper SBC extended character */
+ }
+#endif
+ if (dbc_1st(c)) { /* Check if it is a DBC 1st byte */
+ d = (BYTE)p[si++]; /* Get 2nd byte */
+ if (!dbc_2nd(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */
+ sfn[i++] = c;
+ sfn[i++] = d;
+ } else { /* SBC */
+ if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */
+ if (IsLower(c)) c -= 0x20; /* To upper */
+ sfn[i++] = c;
+ }
+ }
+ *path = p + si; /* Return pointer to the next segment */
+ if (i == 0) return FR_INVALID_NAME; /* Reject nul string */
+
+ if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */
+ sfn[NSFLAG] = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */
+
+ return FR_OK;
+#endif /* FF_USE_LFN */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Follow a file path */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */
+ DIR* dp, /* Directory object to return last directory and found object */
+ const TCHAR* path /* Full-path string to find a file or directory */
+)
+{
+ FRESULT res;
+ BYTE ns;
+ FATFS *fs = dp->obj.fs;
+
+
+#if FF_FS_RPATH != 0
+ if (*path != '/' && *path != '\\') { /* Without heading separator */
+ dp->obj.sclust = fs->cdir; /* Start from current directory */
+ } else
+#endif
+ { /* With heading separator */
+ while (*path == '/' || *path == '\\') path++; /* Strip heading separator */
+ dp->obj.sclust = 0; /* Start from root directory */
+ }
+#if FF_FS_EXFAT
+ dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */
+#if FF_FS_RPATH != 0
+ if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */
+ DIR dj;
+
+ dp->obj.c_scl = fs->cdc_scl;
+ dp->obj.c_size = fs->cdc_size;
+ dp->obj.c_ofs = fs->cdc_ofs;
+ res = load_obj_xdir(&dj, &dp->obj);
+ if (res != FR_OK) return res;
+ dp->obj.objsize = ld_dword(fs->dirbuf + XDIR_FileSize);
+ dp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ }
+#endif
+#endif
+
+ if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */
+ dp->fn[NSFLAG] = NS_NONAME;
+ res = dir_sdi(dp, 0);
+
+ } else { /* Follow path */
+ for (;;) {
+ res = create_name(dp, &path); /* Get a segment name of the path */
+ if (res != FR_OK) break;
+ res = dir_find(dp); /* Find an object with the segment name */
+ ns = dp->fn[NSFLAG];
+ if (res != FR_OK) { /* Failed to find the object */
+ if (res == FR_NO_FILE) { /* Object is not found */
+ if (FF_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */
+ if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */
+ dp->fn[NSFLAG] = NS_NONAME;
+ res = FR_OK;
+ } else { /* Could not find the object */
+ if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */
+ }
+ }
+ break;
+ }
+ if (ns & NS_LAST) break; /* Last segment matched. Function completed. */
+ /* Get into the sub-directory */
+ if (!(dp->obj.attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */
+ res = FR_NO_PATH; break;
+ }
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* Save containing directory information for next dir */
+ dp->obj.c_scl = dp->obj.sclust;
+ dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat;
+ dp->obj.c_ofs = dp->blk_ofs;
+ init_alloc_info(fs, &dp->obj); /* Open next directory */
+ } else
+#endif
+ {
+ dp->obj.sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */
+ }
+ }
+ }
+
+ return res;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Get logical drive number from path name */
+/*-----------------------------------------------------------------------*/
+
+static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive number or null pointer) */
+ const TCHAR** path /* Pointer to pointer to the path name */
+)
+{
+ const TCHAR *tp, *tt;
+ TCHAR tc;
+ int i, vol = -1;
+#if FF_STR_VOLUME_ID /* Find string volume ID */
+ const char *sp;
+ char c;
+#endif
+
+ tt = tp = *path;
+ if (!tp) return vol; /* Invalid path name? */
+ do tc = *tt++; while ((UINT)tc >= (FF_USE_LFN ? ' ' : '!') && tc != ':'); /* Find a colon in the path */
+
+ if (tc == ':') { /* DOS/Windows style volume ID? */
+ i = FF_VOLUMES;
+ if (IsDigit(*tp) && tp + 2 == tt) { /* Is there a numeric volume ID + colon? */
+ i = (int)*tp - '0'; /* Get the LD number */
+ }
+#if FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */
+ else {
+ i = 0;
+ do {
+ sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */
+ do { /* Compare the volume ID with path name */
+ c = *sp++; tc = *tp++;
+ if (IsLower(c)) c -= 0x20;
+ if (IsLower(tc)) tc -= 0x20;
+ } while (c && (TCHAR)c == tc);
+ } while ((c || tp != tt) && ++i < FF_VOLUMES); /* Repeat for each id until pattern match */
+ }
+#endif
+ if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */
+ vol = i; /* Drive number */
+ *path = tt; /* Snip the drive prefix off */
+ }
+ return vol;
+ }
+#if FF_STR_VOLUME_ID == 2 /* Unix style volume ID is enabled */
+ if (*tp == '/') {
+ i = 0;
+ do {
+ sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */
+ do { /* Compare the volume ID with path name */
+ c = *sp++; tc = *(++tp);
+ if (IsLower(c)) c -= 0x20;
+ if (IsLower(tc)) tc -= 0x20;
+ } while (c && (TCHAR)c == tc);
+ } while ((c || (tc != '/' && (UINT)tc >= (FF_USE_LFN ? ' ' : '!'))) && ++i < FF_VOLUMES); /* Repeat for each ID until pattern match */
+ if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */
+ vol = i; /* Drive number */
+ *path = tp; /* Snip the drive prefix off */
+ return vol;
+ }
+ }
+#endif
+ /* No drive prefix is found */
+#if FF_FS_RPATH != 0
+ vol = CurrVol; /* Default drive is current drive */
+#else
+ vol = 0; /* Default drive is 0 */
+#endif
+ return vol; /* Return the default drive */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* GPT support functions */
+/*-----------------------------------------------------------------------*/
+
+#if FF_LBA64
+
+/* Calculate CRC32 in byte-by-byte */
+
+static DWORD crc32 ( /* Returns next CRC value */
+ DWORD crc, /* Current CRC value */
+ BYTE d /* A byte to be processed */
+)
+{
+ BYTE b;
+
+
+ for (b = 1; b; b <<= 1) {
+ crc ^= (d & b) ? 1 : 0;
+ crc = (crc & 1) ? crc >> 1 ^ 0xEDB88320 : crc >> 1;
+ }
+ return crc;
+}
+
+
+/* Check validity of GPT header */
+
+static int test_gpt_header ( /* 0:Invalid, 1:Valid */
+ const BYTE* gpth /* Pointer to the GPT header */
+)
+{
+ UINT i;
+ DWORD bcc;
+
+
+ if (mem_cmp(gpth + GPTH_Sign, "EFI PART" "\0\0\1\0" "\x5C\0\0", 16)) return 0; /* Check sign, version (1.0) and length (92) */
+ for (i = 0, bcc = 0xFFFFFFFF; i < 92; i++) { /* Check header BCC */
+ bcc = crc32(bcc, i - GPTH_Bcc < 4 ? 0 : gpth[i]);
+ }
+ if (~bcc != ld_dword(gpth + GPTH_Bcc)) return 0;
+ if (ld_dword(gpth + GPTH_PteSize) != SZ_GPTE) return 0; /* Table entry size (must be SZ_GPTE bytes) */
+ if (ld_dword(gpth + GPTH_PtNum) > 128) return 0; /* Table size (must be 128 entries or less) */
+
+ return 1;
+}
+
+#if !FF_FS_READONLY && FF_USE_MKFS
+
+/* Generate random value */
+static DWORD make_rand (
+ DWORD seed, /* Seed value */
+ BYTE* buff, /* Output buffer */
+ UINT n /* Data length */
+)
+{
+ UINT r;
+
+
+ if (seed == 0) seed = 1;
+ do {
+ for (r = 0; r < 8; r++) seed = seed & 1 ? seed >> 1 ^ 0xA3000000 : seed >> 1; /* Shift 8 bits the 32-bit LFSR */
+ *buff++ = (BYTE)seed;
+ } while (--n);
+ return seed;
+}
+
+#endif
+#endif
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Load a sector and check if it is an FAT VBR */
+/*-----------------------------------------------------------------------*/
+
+/* Check what the sector is */
+
+static UINT check_fs ( /* 0:FAT VBR, 1:exFAT VBR, 2:Not FAT and valid BS, 3:Not FAT and invalid BS, 4:Disk error */
+ FATFS* fs, /* Filesystem object */
+ LBA_t sect /* Sector to load and check if it is an FAT-VBR or not */
+)
+{
+ WORD w, sign;
+ BYTE b;
+
+
+ fs->wflag = 0; fs->winsect = (LBA_t)0 - 1; /* Invaidate window */
+ if (move_window(fs, sect) != FR_OK) return 4; /* Load the boot sector */
+ sign = ld_word(fs->win + BS_55AA);
+#if FF_FS_EXFAT
+ if (sign == 0xAA55 && !mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; /* It is an exFAT VBR */
+#endif
+ b = fs->win[BS_JmpBoot];
+ if (b == 0xEB || b == 0xE9 || b == 0xE8) { /* Valid JumpBoot code? (short jump, near jump or near call) */
+ if (sign == 0xAA55 && !mem_cmp(fs->win + BS_FilSysType32, "FAT32 ", 8)) return 0; /* It is an FAT32 VBR */
+ /* FAT volumes formatted with early MS-DOS lack boot signature and FAT string, so that we need to identify the FAT VBR without them. */
+ w = ld_word(fs->win + BPB_BytsPerSec);
+ if ((w & (w - 1)) == 0 && w >= FF_MIN_SS && w <= FF_MAX_SS) { /* Properness of sector size */
+ b = fs->win[BPB_SecPerClus];
+ if (b != 0 && (b & (b - 1)) == 0 /* Properness of cluster size */
+ && (fs->win[BPB_NumFATs] == 1 || fs->win[BPB_NumFATs] == 2) /* Properness of number of FATs */
+ && ld_word(fs->win + BPB_RootEntCnt) != 0 /* Properness of root entry count */
+ && ld_word(fs->win + BPB_FATSz16) != 0) { /* Properness of FAT size */
+ return 0; /* Sector can be presumed an FAT VBR */
+ }
+ }
+ }
+ return sign == 0xAA55 ? 2 : 3; /* Not an FAT VBR (valid or invalid BS) */
+}
+
+
+/* Find an FAT volume */
+/* (It supports only generic partitioning rules, MBR, GPT and SFD) */
+
+static UINT find_volume ( /* Returns BS status found in the hosting drive */
+ FATFS* fs, /* Filesystem object */
+ UINT part /* Partition to fined = 0:auto, 1..:forced */
+)
+{
+ UINT fmt, i;
+ DWORD mbr_pt[4];
+
+
+ fmt = check_fs(fs, 0); /* Load sector 0 and check if it is an FAT VBR as SFD */
+ if (fmt != 2 && (fmt >= 3 || part == 0)) return fmt; /* Returns if it is a FAT VBR as auto scan, not a BS or disk error */
+
+ /* Sector 0 is not an FAT VBR or forced partition number wants a partition */
+
+#if FF_LBA64
+ if (fs->win[MBR_Table + PTE_System] == 0xEE) { /* GPT protective MBR? */
+ DWORD n_ent, v_ent, ofs;
+ QWORD pt_lba;
+
+ if (move_window(fs, 1) != FR_OK) return 4; /* Load GPT header sector (next to MBR) */
+ if (!test_gpt_header(fs->win)) return 3; /* Check if GPT header is valid */
+ n_ent = ld_dword(fs->win + GPTH_PtNum); /* Number of entries */
+ pt_lba = ld_qword(fs->win + GPTH_PtOfs); /* Table location */
+ for (v_ent = i = 0; i < n_ent; i++) { /* Find FAT partition */
+ if (move_window(fs, pt_lba + i * SZ_GPTE / SS(fs)) != FR_OK) return 4; /* PT sector */
+ ofs = i * SZ_GPTE % SS(fs); /* Offset in the sector */
+ if (!mem_cmp(fs->win + ofs + GPTE_PtGuid, GUID_MS_Basic, 16)) { /* MS basic data partition? */
+ v_ent++;
+ fmt = check_fs(fs, ld_qword(fs->win + ofs + GPTE_FstLba)); /* Load VBR and check status */
+ if (part == 0 && fmt <= 1) return fmt; /* Auto search (valid FAT volume found first) */
+ if (part != 0 && v_ent == part) return fmt; /* Forced partition order (regardless of it is valid or not) */
+ }
+ }
+ return 3; /* Not found */
+ }
+#endif
+ if (FF_MULTI_PARTITION && part > 4) return 3; /* MBR has 4 partitions max */
+ for (i = 0; i < 4; i++) { /* Load partition offset in the MBR */
+ mbr_pt[i] = ld_dword(fs->win + MBR_Table + i * SZ_PTE + PTE_StLba);
+ }
+ i = part ? part - 1 : 0; /* Table index to find first */
+ do { /* Find an FAT volume */
+ fmt = mbr_pt[i] ? check_fs(fs, mbr_pt[i]) : 3; /* Check if the partition is FAT */
+ } while (part == 0 && fmt >= 2 && ++i < 4);
+ return fmt;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Determine logical drive number and mount the volume if needed */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */
+ const TCHAR** path, /* Pointer to pointer to the path name (drive number) */
+ FATFS** rfs, /* Pointer to pointer to the found filesystem object */
+ BYTE mode /* !=0: Check write protection for write access */
+)
+{
+ int vol;
+ DSTATUS stat;
+ LBA_t bsect;
+ DWORD tsect, sysect, fasize, nclst, szbfat;
+ WORD nrsv;
+ FATFS *fs;
+ UINT fmt;
+
+
+ /* Get logical drive number */
+ *rfs = 0;
+ vol = get_ldnumber(path);
+ if (vol < 0) return FR_INVALID_DRIVE;
+
+ /* Check if the filesystem object is valid or not */
+ fs = FatFs[vol]; /* Get pointer to the filesystem object */
+ if (!fs) return FR_NOT_ENABLED; /* Is the filesystem object available? */
+#if FF_FS_REENTRANT
+ if (!lock_fs(fs)) return FR_TIMEOUT; /* Lock the volume */
+#endif
+ *rfs = fs; /* Return pointer to the filesystem object */
+
+ mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */
+ if (fs->fs_type != 0) { /* If the volume has been mounted */
+ stat = disk_status(fs->pdrv);
+ if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */
+ if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */
+ return FR_WRITE_PROTECTED;
+ }
+ return FR_OK; /* The filesystem object is already valid */
+ }
+ }
+
+ /* The filesystem object is not valid. */
+ /* Following code attempts to mount the volume. (find a FAT volume, analyze the BPB and initialize the filesystem object) */
+
+ fs->fs_type = 0; /* Clear the filesystem object */
+ fs->pdrv = LD2PD(vol); /* Volume hosting physical drive */
+ stat = disk_initialize(fs->pdrv); /* Initialize the physical drive */
+ if (stat & STA_NOINIT) { /* Check if the initialization succeeded */
+ return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */
+ }
+ if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */
+ return FR_WRITE_PROTECTED;
+ }
+#if FF_MAX_SS != FF_MIN_SS /* Get sector size (multiple sector size cfg only) */
+ if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR;
+ if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR;
+#endif
+
+ /* Find an FAT volume on the drive */
+ fmt = find_volume(fs, LD2PT(vol));
+ if (fmt == 4) return FR_DISK_ERR; /* An error occured in the disk I/O layer */
+ if (fmt >= 2) return FR_NO_FILESYSTEM; /* No FAT volume is found */
+ bsect = fs->winsect; /* Volume location */
+
+ /* An FAT volume is found (bsect). Following code initializes the filesystem object */
+
+#if FF_FS_EXFAT
+ if (fmt == 1) {
+ QWORD maxlba;
+ DWORD so, cv, bcl, i;
+
+ for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */
+ if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM;
+
+ if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT version (must be version 1.0) */
+
+ if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) { /* (BPB_BytsPerSecEx must be equal to the physical sector size) */
+ return FR_NO_FILESYSTEM;
+ }
+
+ maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect; /* Last LBA + 1 of the volume */
+ if (!FF_LBA64 && maxlba >= 0x100000000) return FR_NO_FILESYSTEM; /* (It cannot be handled in 32-bit LBA) */
+
+ fs->fsize = ld_dword(fs->win + BPB_FatSzEx); /* Number of sectors per FAT */
+
+ fs->n_fats = fs->win[BPB_NumFATsEx]; /* Number of FATs */
+ if (fs->n_fats != 1) return FR_NO_FILESYSTEM; /* (Supports only 1 FAT) */
+
+ fs->csize = 1 << fs->win[BPB_SecPerClusEx]; /* Cluster size */
+ if (fs->csize == 0) return FR_NO_FILESYSTEM; /* (Must be 1..32768) */
+
+ nclst = ld_dword(fs->win + BPB_NumClusEx); /* Number of clusters */
+ if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM; /* (Too many clusters) */
+ fs->n_fatent = nclst + 2;
+
+ /* Boundaries and Limits */
+ fs->volbase = bsect;
+ fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx);
+ fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx);
+ if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM; /* (Volume size must not be smaller than the size requiered) */
+ fs->dirbase = ld_dword(fs->win + BPB_RootClusEx);
+
+ /* Get bitmap location and check if it is contiguous (implementation assumption) */
+ so = i = 0;
+ for (;;) { /* Find the bitmap entry in the root directory (in only first cluster) */
+ if (i == 0) {
+ if (so >= fs->csize) return FR_NO_FILESYSTEM; /* Not found? */
+ if (move_window(fs, clst2sect(fs, (DWORD)fs->dirbase) + so) != FR_OK) return FR_DISK_ERR;
+ so++;
+ }
+ if (fs->win[i] == ET_BITMAP) break; /* Is it a bitmap entry? */
+ i = (i + SZDIRE) % SS(fs); /* Next entry */
+ }
+ bcl = ld_dword(fs->win + i + 20); /* Bitmap cluster */
+ if (bcl < 2 || bcl >= fs->n_fatent) return FR_NO_FILESYSTEM;
+ fs->bitbase = fs->database + fs->csize * (bcl - 2); /* Bitmap sector */
+ for (;;) { /* Check if bitmap is contiguous */
+ if (move_window(fs, fs->fatbase + bcl / (SS(fs) / 4)) != FR_OK) return FR_DISK_ERR;
+ cv = ld_dword(fs->win + bcl % (SS(fs) / 4) * 4);
+ if (cv == 0xFFFFFFFF) break; /* Last link? */
+ if (cv != ++bcl) return FR_NO_FILESYSTEM; /* Fragmented? */
+ }
+
+#if !FF_FS_READONLY
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */
+#endif
+ fmt = FS_EXFAT; /* FAT sub-type */
+ } else
+#endif /* FF_FS_EXFAT */
+ {
+ if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */
+
+ fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */
+ if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32);
+ fs->fsize = fasize;
+
+ fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */
+ if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */
+ fasize *= fs->n_fats; /* Number of sectors for FAT area */
+
+ fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */
+ if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */
+
+ fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */
+ if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */
+
+ tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */
+ if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32);
+
+ nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */
+ if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */
+
+ /* Determine the FAT sub type */
+ sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */
+ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
+ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */
+ if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
+ fmt = 0;
+ if (nclst <= MAX_FAT32) fmt = FS_FAT32;
+ if (nclst <= MAX_FAT16) fmt = FS_FAT16;
+ if (nclst <= MAX_FAT12) fmt = FS_FAT12;
+ if (fmt == 0) return FR_NO_FILESYSTEM;
+
+ /* Boundaries and Limits */
+ fs->n_fatent = nclst + 2; /* Number of FAT entries */
+ fs->volbase = bsect; /* Volume start sector */
+ fs->fatbase = bsect + nrsv; /* FAT start sector */
+ fs->database = bsect + sysect; /* Data start sector */
+ if (fmt == FS_FAT32) {
+ if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */
+ if (fs->n_rootdir != 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */
+ fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */
+ szbfat = fs->n_fatent * 4; /* (Needed FAT size) */
+ } else {
+ if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */
+ fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */
+ szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */
+ fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1);
+ }
+ if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */
+
+#if !FF_FS_READONLY
+ /* Get FSInfo if available */
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */
+ fs->fsi_flag = 0x80;
+#if (FF_FS_NOFSINFO & 3) != 3
+ if (fmt == FS_FAT32 /* Allow to update FSInfo only if BPB_FSInfo32 == 1 */
+ && ld_word(fs->win + BPB_FSInfo32) == 1
+ && move_window(fs, bsect + 1) == FR_OK)
+ {
+ fs->fsi_flag = 0;
+ if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSInfo data if available */
+ && ld_dword(fs->win + FSI_LeadSig) == 0x41615252
+ && ld_dword(fs->win + FSI_StrucSig) == 0x61417272)
+ {
+#if (FF_FS_NOFSINFO & 1) == 0
+ fs->free_clst = ld_dword(fs->win + FSI_Free_Count);
+#endif
+#if (FF_FS_NOFSINFO & 2) == 0
+ fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free);
+#endif
+ }
+ }
+#endif /* (FF_FS_NOFSINFO & 3) != 3 */
+#endif /* !FF_FS_READONLY */
+ }
+
+ fs->fs_type = (BYTE)fmt;/* FAT sub-type */
+ fs->id = ++Fsid; /* Volume mount ID */
+#if FF_USE_LFN == 1
+ fs->lfnbuf = LfnBuf; /* Static LFN working buffer */
+#if FF_FS_EXFAT
+ fs->dirbuf = DirBuf; /* Static directory block scratchpad buuffer */
+#endif
+#endif
+#if FF_FS_RPATH != 0
+ fs->cdir = 0; /* Initialize current directory */
+#endif
+#if FF_FS_LOCK != 0 /* Clear file lock semaphores */
+ clear_lock(fs);
+#endif
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Check if the file/directory object is valid or not */
+/*-----------------------------------------------------------------------*/
+
+static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */
+ FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR object, to check validity */
+ FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */
+)
+{
+ FRESULT res = FR_INVALID_OBJECT;
+
+
+ if (obj && obj->fs && obj->fs->fs_type && obj->id == obj->fs->id) { /* Test if the object is valid */
+#if FF_FS_REENTRANT
+ if (lock_fs(obj->fs)) { /* Obtain the filesystem object */
+ if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */
+ res = FR_OK;
+ } else {
+ unlock_fs(obj->fs, FR_OK);
+ }
+ } else {
+ res = FR_TIMEOUT;
+ }
+#else
+ if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */
+ res = FR_OK;
+ }
+#endif
+ }
+ *rfs = (res == FR_OK) ? obj->fs : 0; /* Corresponding filesystem object */
+ return res;
+}
+
+
+
+
+/*---------------------------------------------------------------------------
+
+ Public Functions (FatFs API)
+
+----------------------------------------------------------------------------*/
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Mount/Unmount a Logical Drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mount (
+ FATFS* fs, /* Pointer to the filesystem object (NULL:unmount)*/
+ const TCHAR* path, /* Logical drive number to be mounted/unmounted */
+ BYTE opt /* Mode option 0:Do not mount (delayed mount), 1:Mount immediately */
+)
+{
+ FATFS *cfs;
+ int vol;
+ FRESULT res;
+ const TCHAR *rp = path;
+
+
+ /* Get logical drive number */
+ vol = get_ldnumber(&rp);
+ if (vol < 0) return FR_INVALID_DRIVE;
+ cfs = FatFs[vol]; /* Pointer to fs object */
+
+ if (cfs) {
+#if FF_FS_LOCK != 0
+ clear_lock(cfs);
+#endif
+#if FF_FS_REENTRANT /* Discard sync object of the current volume */
+ if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR;
+#endif
+ cfs->fs_type = 0; /* Clear old fs object */
+ }
+
+ if (fs) {
+ fs->fs_type = 0; /* Clear new fs object */
+#if FF_FS_REENTRANT /* Create sync object for the new volume */
+ if (!ff_cre_syncobj((BYTE)vol, &fs->sobj)) return FR_INT_ERR;
+#endif
+ }
+ FatFs[vol] = fs; /* Register new fs object */
+
+ if (opt == 0) return FR_OK; /* Do not mount now, it will be mounted later */
+
+ res = mount_volume(&path, &fs, 0); /* Force mounted the volume */
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Open or Create a File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_open (
+ FIL* fp, /* Pointer to the blank file object */
+ const TCHAR* path, /* Pointer to the file name */
+ BYTE mode /* Access mode and file open mode flags */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+#if !FF_FS_READONLY
+ DWORD cl, bcs, clst;
+ LBA_t sc;
+ FSIZE_t ofs;
+#endif
+ DEF_NAMBUF
+
+
+ if (!fp) return FR_INVALID_OBJECT;
+
+ /* Get logical drive number */
+ mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
+ res = mount_volume(&path, &fs, mode);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+#if !FF_FS_READONLY /* Read/Write configuration */
+ if (res == FR_OK) {
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */
+ res = FR_INVALID_NAME;
+ }
+#if FF_FS_LOCK != 0
+ else {
+ res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Check if the file can be used */
+ }
+#endif
+ }
+ /* Create or Open a file */
+ if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
+ if (res != FR_OK) { /* No file, create new */
+ if (res == FR_NO_FILE) { /* There is no file to open, create a new entry */
+#if FF_FS_LOCK != 0
+ res = enq_lock() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES;
+#else
+ res = dir_register(&dj);
+#endif
+ }
+ mode |= FA_CREATE_ALWAYS; /* File is created */
+ }
+ else { /* Any object with the same name is already existing */
+ if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */
+ res = FR_DENIED;
+ } else {
+ if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */
+ }
+ }
+ if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate the file if overwrite mode */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ /* Get current allocation info */
+ fp->obj.fs = fs;
+ init_alloc_info(fs, &fp->obj);
+ /* Set directory entry block initial state */
+ mem_set(fs->dirbuf + 2, 0, 30); /* Clear 85 entry except for NumSec */
+ mem_set(fs->dirbuf + 38, 0, 26); /* Clear C0 entry except for NumName and NameHash */
+ fs->dirbuf[XDIR_Attr] = AM_ARC;
+ st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME());
+ fs->dirbuf[XDIR_GenFlags] = 1;
+ res = store_xdir(&dj);
+ if (res == FR_OK && fp->obj.sclust != 0) { /* Remove the cluster chain if exist */
+ res = remove_chain(&fp->obj, fp->obj.sclust, 0);
+ fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */
+ }
+ } else
+#endif
+ {
+ /* Set directory entry initial state */
+ cl = ld_clust(fs, dj.dir); /* Get current cluster chain */
+ st_dword(dj.dir + DIR_CrtTime, GET_FATTIME()); /* Set created time */
+ dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */
+ st_clust(fs, dj.dir, 0); /* Reset file allocation info */
+ st_dword(dj.dir + DIR_FileSize, 0);
+ fs->wflag = 1;
+ if (cl != 0) { /* Remove the cluster chain if exist */
+ sc = fs->winsect;
+ res = remove_chain(&dj.obj, cl, 0);
+ if (res == FR_OK) {
+ res = move_window(fs, sc);
+ fs->last_clst = cl - 1; /* Reuse the cluster hole */
+ }
+ }
+ }
+ }
+ }
+ else { /* Open an existing file */
+ if (res == FR_OK) { /* Is the object exsiting? */
+ if (dj.obj.attr & AM_DIR) { /* File open against a directory */
+ res = FR_NO_FILE;
+ } else {
+ if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */
+ res = FR_DENIED;
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED; /* Set file change flag if created or overwritten */
+ fp->dir_sect = fs->winsect; /* Pointer to the directory entry */
+ fp->dir_ptr = dj.dir;
+#if FF_FS_LOCK != 0
+ fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Lock the file for this session */
+ if (fp->obj.lockid == 0) res = FR_INT_ERR;
+#endif
+ }
+#else /* R/O configuration */
+ if (res == FR_OK) {
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it origin directory itself? */
+ res = FR_INVALID_NAME;
+ } else {
+ if (dj.obj.attr & AM_DIR) { /* Is it a directory? */
+ res = FR_NO_FILE;
+ }
+ }
+ }
+#endif
+
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fp->obj.c_scl = dj.obj.sclust; /* Get containing directory info */
+ fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
+ fp->obj.c_ofs = dj.blk_ofs;
+ init_alloc_info(fs, &fp->obj);
+ } else
+#endif
+ {
+ fp->obj.sclust = ld_clust(fs, dj.dir); /* Get object allocation info */
+ fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize);
+ }
+#if FF_USE_FASTSEEK
+ fp->cltbl = 0; /* Disable fast seek mode */
+#endif
+ fp->obj.fs = fs; /* Validate the file object */
+ fp->obj.id = fs->id;
+ fp->flag = mode; /* Set file access mode */
+ fp->err = 0; /* Clear error flag */
+ fp->sect = 0; /* Invalidate current data sector */
+ fp->fptr = 0; /* Set file pointer top of the file */
+#if !FF_FS_READONLY
+#if !FF_FS_TINY
+ mem_set(fp->buf, 0, sizeof fp->buf); /* Clear sector buffer */
+#endif
+ if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */
+ fp->fptr = fp->obj.objsize; /* Offset to seek */
+ bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */
+ clst = fp->obj.sclust; /* Follow the cluster chain */
+ for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) {
+ clst = get_fat(&fp->obj, clst);
+ if (clst <= 1) res = FR_INT_ERR;
+ if (clst == 0xFFFFFFFF) res = FR_DISK_ERR;
+ }
+ fp->clust = clst;
+ if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */
+ sc = clst2sect(fs, clst);
+ if (sc == 0) {
+ res = FR_INT_ERR;
+ } else {
+ fp->sect = sc + (DWORD)(ofs / SS(fs));
+#if !FF_FS_TINY
+ if (disk_read(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR;
+#endif
+ }
+ }
+ }
+#endif
+ }
+
+ FREE_NAMBUF();
+ }
+
+ if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_read (
+ FIL* fp, /* Pointer to the file object */
+ void* buff, /* Pointer to data buffer */
+ UINT btr, /* Number of bytes to read */
+ UINT* br /* Pointer to number of bytes read */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst;
+ LBA_t sect;
+ FSIZE_t remain;
+ UINT rcnt, cc, csect;
+ BYTE *rbuff = (BYTE*)buff;
+
+
+ *br = 0; /* Clear read byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+ remain = fp->obj.objsize - fp->fptr;
+ if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */
+
+ for ( ; btr; /* Repeat until btr bytes read */
+ btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += rcnt) {
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
+ if (csect == 0) { /* On the cluster boundary? */
+ if (fp->fptr == 0) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow cluster chain from the origin */
+ } else { /* Middle or end of the file */
+#if FF_USE_FASTSEEK
+ if (fp->cltbl) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ } else
+#endif
+ {
+ clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */
+ }
+ }
+ if (clst < 2) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ }
+ sect = clst2sect(fs, fp->clust); /* Get current sector */
+ if (sect == 0) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+ cc = btr / SS(fs); /* When remaining bytes >= sector size, */
+ if (cc > 0) { /* Read maximum contiguous sectors directly */
+ if (csect + cc > fs->csize) { /* Clip at cluster boundary */
+ cc = fs->csize - csect;
+ }
+ if (disk_read(fs->pdrv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
+#if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */
+#if FF_FS_TINY
+ if (fs->wflag && fs->winsect - sect < cc) {
+ mem_cpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs));
+ }
+#else
+ if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) {
+ mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs));
+ }
+#endif
+#endif
+ rcnt = SS(fs) * cc; /* Number of bytes transferred */
+ continue;
+ }
+#if !FF_FS_TINY
+ if (fp->sect != sect) { /* Load data sector if not in cache */
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
+ }
+#endif
+ fp->sect = sect;
+ }
+ rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */
+ if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */
+#if FF_FS_TINY
+ if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
+ mem_cpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
+#else
+ mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
+#endif
+ }
+
+ LEAVE_FF(fs, FR_OK);
+}
+
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Write File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_write (
+ FIL* fp, /* Pointer to the file object */
+ const void* buff, /* Pointer to the data to be written */
+ UINT btw, /* Number of bytes to write */
+ UINT* bw /* Pointer to number of bytes written */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst;
+ LBA_t sect;
+ UINT wcnt, cc, csect;
+ const BYTE *wbuff = (const BYTE*)buff;
+
+
+ *bw = 0; /* Clear write byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ /* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */
+ if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) {
+ btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr);
+ }
+
+ for ( ; btw; /* Repeat until all data written */
+ btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize) {
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */
+ if (csect == 0) { /* On the cluster boundary? */
+ if (fp->fptr == 0) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow from the origin */
+ if (clst == 0) { /* If no cluster is allocated, */
+ clst = create_chain(&fp->obj, 0); /* create a new cluster chain */
+ }
+ } else { /* On the middle or end of the file */
+#if FF_USE_FASTSEEK
+ if (fp->cltbl) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ } else
+#endif
+ {
+ clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */
+ }
+ }
+ if (clst == 0) break; /* Could not allocate a new cluster (disk full) */
+ if (clst == 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */
+ }
+#if FF_FS_TINY
+ if (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */
+#else
+ if (fp->flag & FA_DIRTY) { /* Write-back sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ sect = clst2sect(fs, fp->clust); /* Get current sector */
+ if (sect == 0) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+ cc = btw / SS(fs); /* When remaining bytes >= sector size, */
+ if (cc > 0) { /* Write maximum contiguous sectors directly */
+ if (csect + cc > fs->csize) { /* Clip at cluster boundary */
+ cc = fs->csize - csect;
+ }
+ if (disk_write(fs->pdrv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
+#if FF_FS_MINIMIZE <= 2
+#if FF_FS_TINY
+ if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
+ mem_cpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs));
+ fs->wflag = 0;
+ }
+#else
+ if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
+ mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs));
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+#endif
+ wcnt = SS(fs) * cc; /* Number of bytes transferred */
+ continue;
+ }
+#if FF_FS_TINY
+ if (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */
+ if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR);
+ fs->winsect = sect;
+ }
+#else
+ if (fp->sect != sect && /* Fill sector cache with file data */
+ fp->fptr < fp->obj.objsize &&
+ disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) {
+ ABORT(fs, FR_DISK_ERR);
+ }
+#endif
+ fp->sect = sect;
+ }
+ wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */
+ if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */
+#if FF_FS_TINY
+ if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
+ mem_cpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */
+ fs->wflag = 1;
+#else
+ mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */
+ fp->flag |= FA_DIRTY;
+#endif
+ }
+
+ fp->flag |= FA_MODIFIED; /* Set file change flag */
+
+ LEAVE_FF(fs, FR_OK);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Synchronize the File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_sync (
+ FIL* fp /* Pointer to the file object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD tm;
+ BYTE *dir;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) {
+ if (fp->flag & FA_MODIFIED) { /* Is there any change to the file? */
+#if !FF_FS_TINY
+ if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) LEAVE_FF(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ /* Update the directory entry */
+ tm = GET_FATTIME(); /* Modified time */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ res = fill_first_frag(&fp->obj); /* Fill first fragment on the FAT if needed */
+ if (res == FR_OK) {
+ res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */
+ }
+ if (res == FR_OK) {
+ DIR dj;
+ DEF_NAMBUF
+
+ INIT_NAMBUF(fs);
+ res = load_obj_xdir(&dj, &fp->obj); /* Load directory entry block */
+ if (res == FR_OK) {
+ fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */
+ fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation information */
+ st_dword(fs->dirbuf + XDIR_FstClus, fp->obj.sclust); /* Update start cluster */
+ st_qword(fs->dirbuf + XDIR_FileSize, fp->obj.objsize); /* Update file size */
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, fp->obj.objsize); /* (FatFs does not support Valid File Size feature) */
+ st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Update modified time */
+ fs->dirbuf[XDIR_ModTime10] = 0;
+ st_dword(fs->dirbuf + XDIR_AccTime, 0);
+ res = store_xdir(&dj); /* Restore it to the directory */
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ fp->flag &= (BYTE)~FA_MODIFIED;
+ }
+ }
+ FREE_NAMBUF();
+ }
+ } else
+#endif
+ {
+ res = move_window(fs, fp->dir_sect);
+ if (res == FR_OK) {
+ dir = fp->dir_ptr;
+ dir[DIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */
+ st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation information */
+ st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */
+ st_dword(dir + DIR_ModTime, tm); /* Update modified time */
+ st_word(dir + DIR_LstAccDate, 0);
+ fs->wflag = 1;
+ res = sync_fs(fs); /* Restore it to the directory */
+ fp->flag &= (BYTE)~FA_MODIFIED;
+ }
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !FF_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Close File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_close (
+ FIL* fp /* Pointer to the file object to be closed */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+
+#if !FF_FS_READONLY
+ res = f_sync(fp); /* Flush cached data */
+ if (res == FR_OK)
+#endif
+ {
+ res = validate(&fp->obj, &fs); /* Lock volume */
+ if (res == FR_OK) {
+#if FF_FS_LOCK != 0
+ res = dec_lock(fp->obj.lockid); /* Decrement file open counter */
+ if (res == FR_OK) fp->obj.fs = 0; /* Invalidate file object */
+#else
+ fp->obj.fs = 0; /* Invalidate file object */
+#endif
+#if FF_FS_REENTRANT
+ unlock_fs(fs, FR_OK); /* Unlock volume */
+#endif
+ }
+ }
+ return res;
+}
+
+
+
+
+#if FF_FS_RPATH >= 1
+/*-----------------------------------------------------------------------*/
+/* Change Current Directory or Current Drive, Get Current Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_chdrive (
+ const TCHAR* path /* Drive number to set */
+)
+{
+ int vol;
+
+
+ /* Get logical drive number */
+ vol = get_ldnumber(&path);
+ if (vol < 0) return FR_INVALID_DRIVE;
+ CurrVol = (BYTE)vol; /* Set it as current volume */
+
+ return FR_OK;
+}
+
+
+
+FRESULT f_chdir (
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+#if FF_STR_VOLUME_ID == 2
+ UINT i;
+#endif
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = mount_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the path */
+ if (res == FR_OK) { /* Follow completed */
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it the start directory itself? */
+ fs->cdir = dj.obj.sclust;
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->cdc_scl = dj.obj.c_scl;
+ fs->cdc_size = dj.obj.c_size;
+ fs->cdc_ofs = dj.obj.c_ofs;
+ }
+#endif
+ } else {
+ if (dj.obj.attr & AM_DIR) { /* It is a sub-directory */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->cdir = ld_dword(fs->dirbuf + XDIR_FstClus); /* Sub-directory cluster */
+ fs->cdc_scl = dj.obj.sclust; /* Save containing directory information */
+ fs->cdc_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
+ fs->cdc_ofs = dj.blk_ofs;
+ } else
+#endif
+ {
+ fs->cdir = ld_clust(fs, dj.dir); /* Sub-directory cluster */
+ }
+ } else {
+ res = FR_NO_PATH; /* Reached but a file */
+ }
+ }
+ }
+ FREE_NAMBUF();
+ if (res == FR_NO_FILE) res = FR_NO_PATH;
+#if FF_STR_VOLUME_ID == 2 /* Also current drive is changed at Unix style volume ID */
+ if (res == FR_OK) {
+ for (i = FF_VOLUMES - 1; i && fs != FatFs[i]; i--) ; /* Set current drive */
+ CurrVol = (BYTE)i;
+ }
+#endif
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+#if FF_FS_RPATH >= 2
+FRESULT f_getcwd (
+ TCHAR* buff, /* Pointer to the directory path */
+ UINT len /* Size of buff in unit of TCHAR */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ UINT i, n;
+ DWORD ccl;
+ TCHAR *tp = buff;
+#if FF_VOLUMES >= 2
+ UINT vl;
+#if FF_STR_VOLUME_ID
+ const char *vp;
+#endif
+#endif
+ FILINFO fno;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ buff[0] = 0; /* Set null string to get current volume */
+ res = mount_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+
+ /* Follow parent directories and create the path */
+ i = len; /* Bottom of buffer (directory stack base) */
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* (Cannot do getcwd on exFAT and returns root path) */
+ dj.obj.sclust = fs->cdir; /* Start to follow upper directory from current directory */
+ while ((ccl = dj.obj.sclust) != 0) { /* Repeat while current directory is a sub-directory */
+ res = dir_sdi(&dj, 1 * SZDIRE); /* Get parent directory */
+ if (res != FR_OK) break;
+ res = move_window(fs, dj.sect);
+ if (res != FR_OK) break;
+ dj.obj.sclust = ld_clust(fs, dj.dir); /* Goto parent directory */
+ res = dir_sdi(&dj, 0);
+ if (res != FR_OK) break;
+ do { /* Find the entry links to the child directory */
+ res = DIR_READ_FILE(&dj);
+ if (res != FR_OK) break;
+ if (ccl == ld_clust(fs, dj.dir)) break; /* Found the entry */
+ res = dir_next(&dj, 0);
+ } while (res == FR_OK);
+ if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */
+ if (res != FR_OK) break;
+ get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */
+ for (n = 0; fno.fname[n]; n++) ; /* Name length */
+ if (i < n + 1) { /* Insufficient space to store the path name? */
+ res = FR_NOT_ENOUGH_CORE; break;
+ }
+ while (n) buff[--i] = fno.fname[--n]; /* Stack the name */
+ buff[--i] = '/';
+ }
+ }
+ if (res == FR_OK) {
+ if (i == len) buff[--i] = '/'; /* Is it the root-directory? */
+#if FF_VOLUMES >= 2 /* Put drive prefix */
+ vl = 0;
+#if FF_STR_VOLUME_ID >= 1 /* String volume ID */
+ for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ;
+ if (i >= n + 2) {
+ if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/';
+ for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ;
+ if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':';
+ vl++;
+ }
+#else /* Numeric volume ID */
+ if (i >= 3) {
+ *tp++ = (TCHAR)'0' + CurrVol;
+ *tp++ = (TCHAR)':';
+ vl = 2;
+ }
+#endif
+ if (vl == 0) res = FR_NOT_ENOUGH_CORE;
+#endif
+ /* Add current directory path */
+ if (res == FR_OK) {
+ do *tp++ = buff[i++]; while (i < len); /* Copy stacked path string */
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ *tp = 0;
+ LEAVE_FF(fs, res);
+}
+
+#endif /* FF_FS_RPATH >= 2 */
+#endif /* FF_FS_RPATH >= 1 */
+
+
+
+#if FF_FS_MINIMIZE <= 2
+/*-----------------------------------------------------------------------*/
+/* Seek File Read/Write Pointer */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_lseek (
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t ofs /* File pointer from top of file */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, bcs;
+ LBA_t nsect;
+ FSIZE_t ifptr;
+#if FF_USE_FASTSEEK
+ DWORD cl, pcl, ncl, tcl, tlen, ulen, *tbl;
+ LBA_t dsc;
+#endif
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) res = (FRESULT)fp->err;
+#if FF_FS_EXFAT && !FF_FS_READONLY
+ if (res == FR_OK && fs->fs_type == FS_EXFAT) {
+ res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */
+ }
+#endif
+ if (res != FR_OK) LEAVE_FF(fs, res);
+
+#if FF_USE_FASTSEEK
+ if (fp->cltbl) { /* Fast seek */
+ if (ofs == CREATE_LINKMAP) { /* Create CLMT */
+ tbl = fp->cltbl;
+ tlen = *tbl++; ulen = 2; /* Given table size and required table size */
+ cl = fp->obj.sclust; /* Origin of the chain */
+ if (cl != 0) {
+ do {
+ /* Get a fragment */
+ tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */
+ do {
+ pcl = cl; ncl++;
+ cl = get_fat(&fp->obj, cl);
+ if (cl <= 1) ABORT(fs, FR_INT_ERR);
+ if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ } while (cl == pcl + 1);
+ if (ulen <= tlen) { /* Store the length and top of the fragment */
+ *tbl++ = ncl; *tbl++ = tcl;
+ }
+ } while (cl < fs->n_fatent); /* Repeat until end of chain */
+ }
+ *fp->cltbl = ulen; /* Number of items used */
+ if (ulen <= tlen) {
+ *tbl = 0; /* Terminate table */
+ } else {
+ res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */
+ }
+ } else { /* Fast seek */
+ if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; /* Clip offset at the file size */
+ fp->fptr = ofs; /* Set file pointer */
+ if (ofs > 0) {
+ fp->clust = clmt_clust(fp, ofs - 1);
+ dsc = clst2sect(fs, fp->clust);
+ if (dsc == 0) ABORT(fs, FR_INT_ERR);
+ dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1);
+ if (fp->fptr % SS(fs) && dsc != fp->sect) { /* Refill sector cache if needed */
+#if !FF_FS_TINY
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */
+#endif
+ fp->sect = dsc;
+ }
+ }
+ }
+ } else
+#endif
+
+ /* Normal Seek */
+ {
+#if FF_FS_EXFAT
+ if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4 GiB - 1 if at FATxx */
+#endif
+ if (ofs > fp->obj.objsize && (FF_FS_READONLY || !(fp->flag & FA_WRITE))) { /* In read-only mode, clip offset with the file size */
+ ofs = fp->obj.objsize;
+ }
+ ifptr = fp->fptr;
+ fp->fptr = nsect = 0;
+ if (ofs > 0) {
+ bcs = (DWORD)fs->csize * SS(fs); /* Cluster size (byte) */
+ if (ifptr > 0 &&
+ (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */
+ fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); /* start from the current cluster */
+ ofs -= fp->fptr;
+ clst = fp->clust;
+ } else { /* When seek to back cluster, */
+ clst = fp->obj.sclust; /* start from the first cluster */
+#if !FF_FS_READONLY
+ if (clst == 0) { /* If no cluster chain, create a new chain */
+ clst = create_chain(&fp->obj, 0);
+ if (clst == 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->obj.sclust = clst;
+ }
+#endif
+ fp->clust = clst;
+ }
+ if (clst != 0) {
+ while (ofs > bcs) { /* Cluster following loop */
+ ofs -= bcs; fp->fptr += bcs;
+#if !FF_FS_READONLY
+ if (fp->flag & FA_WRITE) { /* Check if in write mode or not */
+ if (FF_FS_EXFAT && fp->fptr > fp->obj.objsize) { /* No FAT chain object needs correct objsize to generate FAT value */
+ fp->obj.objsize = fp->fptr;
+ fp->flag |= FA_MODIFIED;
+ }
+ clst = create_chain(&fp->obj, clst); /* Follow chain with forceed stretch */
+ if (clst == 0) { /* Clip file size in case of disk full */
+ ofs = 0; break;
+ }
+ } else
+#endif
+ {
+ clst = get_fat(&fp->obj, clst); /* Follow cluster chain if not in write mode */
+ }
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR);
+ fp->clust = clst;
+ }
+ fp->fptr += ofs;
+ if (ofs % SS(fs)) {
+ nsect = clst2sect(fs, clst); /* Current sector */
+ if (nsect == 0) ABORT(fs, FR_INT_ERR);
+ nsect += (DWORD)(ofs / SS(fs));
+ }
+ }
+ }
+ if (!FF_FS_READONLY && fp->fptr > fp->obj.objsize) { /* Set file change flag if the file size is extended */
+ fp->obj.objsize = fp->fptr;
+ fp->flag |= FA_MODIFIED;
+ }
+ if (fp->fptr % SS(fs) && nsect != fp->sect) { /* Fill sector cache if needed */
+#if !FF_FS_TINY
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
+#endif
+ fp->sect = nsect;
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if FF_FS_MINIMIZE <= 1
+/*-----------------------------------------------------------------------*/
+/* Create a Directory Object */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_opendir (
+ DIR* dp, /* Pointer to directory object to create */
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ if (!dp) return FR_INVALID_OBJECT;
+
+ /* Get logical drive */
+ res = mount_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ dp->obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(dp, path); /* Follow the path to the directory */
+ if (res == FR_OK) { /* Follow completed */
+ if (!(dp->fn[NSFLAG] & NS_NONAME)) { /* It is not the origin directory itself */
+ if (dp->obj.attr & AM_DIR) { /* This object is a sub-directory */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ dp->obj.c_scl = dp->obj.sclust; /* Get containing directory inforamation */
+ dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat;
+ dp->obj.c_ofs = dp->blk_ofs;
+ init_alloc_info(fs, &dp->obj); /* Get object allocation info */
+ } else
+#endif
+ {
+ dp->obj.sclust = ld_clust(fs, dp->dir); /* Get object allocation info */
+ }
+ } else { /* This object is a file */
+ res = FR_NO_PATH;
+ }
+ }
+ if (res == FR_OK) {
+ dp->obj.id = fs->id;
+ res = dir_sdi(dp, 0); /* Rewind directory */
+#if FF_FS_LOCK != 0
+ if (res == FR_OK) {
+ if (dp->obj.sclust != 0) {
+ dp->obj.lockid = inc_lock(dp, 0); /* Lock the sub directory */
+ if (!dp->obj.lockid) res = FR_TOO_MANY_OPEN_FILES;
+ } else {
+ dp->obj.lockid = 0; /* Root directory need not to be locked */
+ }
+ }
+#endif
+ }
+ }
+ FREE_NAMBUF();
+ if (res == FR_NO_FILE) res = FR_NO_PATH;
+ }
+ if (res != FR_OK) dp->obj.fs = 0; /* Invalidate the directory object if function faild */
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Close Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_closedir (
+ DIR *dp /* Pointer to the directory object to be closed */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+
+
+ res = validate(&dp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) {
+#if FF_FS_LOCK != 0
+ if (dp->obj.lockid) res = dec_lock(dp->obj.lockid); /* Decrement sub-directory open counter */
+ if (res == FR_OK) dp->obj.fs = 0; /* Invalidate directory object */
+#else
+ dp->obj.fs = 0; /* Invalidate directory object */
+#endif
+#if FF_FS_REENTRANT
+ unlock_fs(fs, FR_OK); /* Unlock volume */
+#endif
+ }
+ return res;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read Directory Entries in Sequence */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_readdir (
+ DIR* dp, /* Pointer to the open directory object */
+ FILINFO* fno /* Pointer to file information to return */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = validate(&dp->obj, &fs); /* Check validity of the directory object */
+ if (res == FR_OK) {
+ if (!fno) {
+ res = dir_sdi(dp, 0); /* Rewind the directory object */
+ } else {
+ INIT_NAMBUF(fs);
+ res = DIR_READ_FILE(dp); /* Read an item */
+ if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory */
+ if (res == FR_OK) { /* A valid entry is found */
+ get_fileinfo(dp, fno); /* Get the object information */
+ res = dir_next(dp, 0); /* Increment index for next */
+ if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory now */
+ }
+ FREE_NAMBUF();
+ }
+ }
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if FF_USE_FIND
+/*-----------------------------------------------------------------------*/
+/* Find Next File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_findnext (
+ DIR* dp, /* Pointer to the open directory object */
+ FILINFO* fno /* Pointer to the file information structure */
+)
+{
+ FRESULT res;
+
+
+ for (;;) {
+ res = f_readdir(dp, fno); /* Get a directory item */
+ if (res != FR_OK || !fno || !fno->fname[0]) break; /* Terminate if any error or end of directory */
+ if (pattern_matching(dp->pat, fno->fname, 0, 0)) break; /* Test for the file name */
+#if FF_USE_LFN && FF_USE_FIND == 2
+ if (pattern_matching(dp->pat, fno->altname, 0, 0)) break; /* Test for alternative name if exist */
+#endif
+ }
+ return res;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Find First File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_findfirst (
+ DIR* dp, /* Pointer to the blank directory object */
+ FILINFO* fno, /* Pointer to the file information structure */
+ const TCHAR* path, /* Pointer to the directory to open */
+ const TCHAR* pattern /* Pointer to the matching pattern */
+)
+{
+ FRESULT res;
+
+
+ dp->pat = pattern; /* Save pointer to pattern string */
+ res = f_opendir(dp, path); /* Open the target directory */
+ if (res == FR_OK) {
+ res = f_findnext(dp, fno); /* Find the first item */
+ }
+ return res;
+}
+
+#endif /* FF_USE_FIND */
+
+
+
+#if FF_FS_MINIMIZE == 0
+/*-----------------------------------------------------------------------*/
+/* Get File Status */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_stat (
+ const TCHAR* path, /* Pointer to the file path */
+ FILINFO* fno /* Pointer to file information to return */
+)
+{
+ FRESULT res;
+ DIR dj;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = mount_volume(&path, &dj.obj.fs, 0);
+ if (res == FR_OK) {
+ INIT_NAMBUF(dj.obj.fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK) { /* Follow completed */
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* It is origin directory */
+ res = FR_INVALID_NAME;
+ } else { /* Found an object */
+ if (fno) get_fileinfo(&dj, fno);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(dj.obj.fs, res);
+}
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Get Number of Free Clusters */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_getfree (
+ const TCHAR* path, /* Logical drive number */
+ DWORD* nclst, /* Pointer to a variable to return number of free clusters */
+ FATFS** fatfs /* Pointer to return pointer to corresponding filesystem object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD nfree, clst, stat;
+ LBA_t sect;
+ UINT i;
+ FFOBJID obj;
+
+
+ /* Get logical drive */
+ res = mount_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ *fatfs = fs; /* Return ptr to the fs object */
+ /* If free_clst is valid, return it without full FAT scan */
+ if (fs->free_clst <= fs->n_fatent - 2) {
+ *nclst = fs->free_clst;
+ } else {
+ /* Scan FAT to obtain number of free clusters */
+ nfree = 0;
+ if (fs->fs_type == FS_FAT12) { /* FAT12: Scan bit field FAT entries */
+ clst = 2; obj.fs = fs;
+ do {
+ stat = get_fat(&obj, clst);
+ if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
+ if (stat == 1) { res = FR_INT_ERR; break; }
+ if (stat == 0) nfree++;
+ } while (++clst < fs->n_fatent);
+ } else {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan allocation bitmap */
+ BYTE bm;
+ UINT b;
+
+ clst = fs->n_fatent - 2; /* Number of clusters */
+ sect = fs->bitbase; /* Bitmap sector */
+ i = 0; /* Offset in the sector */
+ do { /* Counts numbuer of bits with zero in the bitmap */
+ if (i == 0) {
+ res = move_window(fs, sect++);
+ if (res != FR_OK) break;
+ }
+ for (b = 8, bm = fs->win[i]; b && clst; b--, clst--) {
+ if (!(bm & 1)) nfree++;
+ bm >>= 1;
+ }
+ i = (i + 1) % SS(fs);
+ } while (clst);
+ } else
+#endif
+ { /* FAT16/32: Scan WORD/DWORD FAT entries */
+ clst = fs->n_fatent; /* Number of entries */
+ sect = fs->fatbase; /* Top of the FAT */
+ i = 0; /* Offset in the sector */
+ do { /* Counts numbuer of entries with zero in the FAT */
+ if (i == 0) {
+ res = move_window(fs, sect++);
+ if (res != FR_OK) break;
+ }
+ if (fs->fs_type == FS_FAT16) {
+ if (ld_word(fs->win + i) == 0) nfree++;
+ i += 2;
+ } else {
+ if ((ld_dword(fs->win + i) & 0x0FFFFFFF) == 0) nfree++;
+ i += 4;
+ }
+ i %= SS(fs);
+ } while (--clst);
+ }
+ }
+ *nclst = nfree; /* Return the free clusters */
+ fs->free_clst = nfree; /* Now free_clst is valid */
+ fs->fsi_flag |= 1; /* FAT32: FSInfo is to be updated */
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Truncate File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_truncate (
+ FIL* fp /* Pointer to the file object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD ncl;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ if (fp->fptr < fp->obj.objsize) { /* Process when fptr is not on the eof */
+ if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */
+ res = remove_chain(&fp->obj, fp->obj.sclust, 0);
+ fp->obj.sclust = 0;
+ } else { /* When truncate a part of the file, remove remaining clusters */
+ ncl = get_fat(&fp->obj, fp->clust);
+ res = FR_OK;
+ if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (ncl == 1) res = FR_INT_ERR;
+ if (res == FR_OK && ncl < fs->n_fatent) {
+ res = remove_chain(&fp->obj, ncl, fp->clust);
+ }
+ }
+ fp->obj.objsize = fp->fptr; /* Set file size to current read/write point */
+ fp->flag |= FA_MODIFIED;
+#if !FF_FS_TINY
+ if (res == FR_OK && (fp->flag & FA_DIRTY)) {
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) {
+ res = FR_DISK_ERR;
+ } else {
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+ }
+#endif
+ if (res != FR_OK) ABORT(fs, res);
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Delete a File/Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_unlink (
+ const TCHAR* path /* Pointer to the file or directory path */
+)
+{
+ FRESULT res;
+ DIR dj, sdj;
+ DWORD dclst = 0;
+ FATFS *fs;
+#if FF_FS_EXFAT
+ FFOBJID obj;
+#endif
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = mount_volume(&path, &fs, FA_WRITE);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (FF_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) {
+ res = FR_INVALID_NAME; /* Cannot remove dot entry */
+ }
+#if FF_FS_LOCK != 0
+ if (res == FR_OK) res = chk_lock(&dj, 2); /* Check if it is an open object */
+#endif
+ if (res == FR_OK) { /* The object is accessible */
+ if (dj.fn[NSFLAG] & NS_NONAME) {
+ res = FR_INVALID_NAME; /* Cannot remove the origin directory */
+ } else {
+ if (dj.obj.attr & AM_RDO) {
+ res = FR_DENIED; /* Cannot remove R/O object */
+ }
+ }
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ obj.fs = fs;
+ if (fs->fs_type == FS_EXFAT) {
+ init_alloc_info(fs, &obj);
+ dclst = obj.sclust;
+ } else
+#endif
+ {
+ dclst = ld_clust(fs, dj.dir);
+ }
+ if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory? */
+#if FF_FS_RPATH != 0
+ if (dclst == fs->cdir) { /* Is it the current directory? */
+ res = FR_DENIED;
+ } else
+#endif
+ {
+ sdj.obj.fs = fs; /* Open the sub-directory */
+ sdj.obj.sclust = dclst;
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ sdj.obj.objsize = obj.objsize;
+ sdj.obj.stat = obj.stat;
+ }
+#endif
+ res = dir_sdi(&sdj, 0);
+ if (res == FR_OK) {
+ res = DIR_READ_FILE(&sdj); /* Test if the directory is empty */
+ if (res == FR_OK) res = FR_DENIED; /* Not empty? */
+ if (res == FR_NO_FILE) res = FR_OK; /* Empty? */
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ res = dir_remove(&dj); /* Remove the directory entry */
+ if (res == FR_OK && dclst != 0) { /* Remove the cluster chain if exist */
+#if FF_FS_EXFAT
+ res = remove_chain(&obj, dclst, 0);
+#else
+ res = remove_chain(&dj.obj, dclst, 0);
+#endif
+ }
+ if (res == FR_OK) res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Create a Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mkdir (
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FFOBJID sobj;
+ FATFS *fs;
+ DWORD dcl, pcl, tm;
+ DEF_NAMBUF
+
+
+ res = mount_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK) res = FR_EXIST; /* Name collision? */
+ if (FF_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) { /* Invalid name? */
+ res = FR_INVALID_NAME;
+ }
+ if (res == FR_NO_FILE) { /* It is clear to create a new directory */
+ sobj.fs = fs; /* New object id to create a new chain */
+ dcl = create_chain(&sobj, 0); /* Allocate a cluster for the new directory */
+ res = FR_OK;
+ if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster? */
+ if (dcl == 1) res = FR_INT_ERR; /* Any insanity? */
+ if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; /* Disk error? */
+ tm = GET_FATTIME();
+ if (res == FR_OK) {
+ res = dir_clear(fs, dcl); /* Clean up the new table */
+ if (res == FR_OK) {
+ if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* Create dot entries (FAT only) */
+ mem_set(fs->win + DIR_Name, ' ', 11); /* Create "." entry */
+ fs->win[DIR_Name] = '.';
+ fs->win[DIR_Attr] = AM_DIR;
+ st_dword(fs->win + DIR_ModTime, tm);
+ st_clust(fs, fs->win, dcl);
+ mem_cpy(fs->win + SZDIRE, fs->win, SZDIRE); /* Create ".." entry */
+ fs->win[SZDIRE + 1] = '.'; pcl = dj.obj.sclust;
+ st_clust(fs, fs->win + SZDIRE, pcl);
+ fs->wflag = 1;
+ }
+ res = dir_register(&dj); /* Register the object to the parent directoy */
+ }
+ }
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* Initialize directory entry block */
+ st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Created time */
+ st_dword(fs->dirbuf + XDIR_FstClus, dcl); /* Table start cluster */
+ st_dword(fs->dirbuf + XDIR_FileSize, (DWORD)fs->csize * SS(fs)); /* Directory size needs to be valid */
+ st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)fs->csize * SS(fs));
+ fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag */
+ fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ st_dword(dj.dir + DIR_ModTime, tm); /* Created time */
+ st_clust(fs, dj.dir, dcl); /* Table start cluster */
+ dj.dir[DIR_Attr] = AM_DIR; /* Attribute */
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ } else {
+ remove_chain(&sobj, dcl, 0); /* Could not register, remove the allocated cluster */
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Rename a File/Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_rename (
+ const TCHAR* path_old, /* Pointer to the object name to be renamed */
+ const TCHAR* path_new /* Pointer to the new name */
+)
+{
+ FRESULT res;
+ DIR djo, djn;
+ FATFS *fs;
+ BYTE buf[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir;
+ LBA_t sect;
+ DEF_NAMBUF
+
+
+ get_ldnumber(&path_new); /* Snip the drive number of new name off */
+ res = mount_volume(&path_old, &fs, FA_WRITE); /* Get logical drive of the old object */
+ if (res == FR_OK) {
+ djo.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&djo, path_old); /* Check old object */
+ if (res == FR_OK && (djo.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check validity of name */
+#if FF_FS_LOCK != 0
+ if (res == FR_OK) {
+ res = chk_lock(&djo, 2);
+ }
+#endif
+ if (res == FR_OK) { /* Object to be renamed is found */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* At exFAT volume */
+ BYTE nf, nn;
+ WORD nh;
+
+ mem_cpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */
+ mem_cpy(&djn, &djo, sizeof djo);
+ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
+ if (res == FR_OK) { /* Is new name already in use by any other object? */
+ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
+ }
+ if (res == FR_NO_FILE) { /* It is a valid path and no name collision */
+ res = dir_register(&djn); /* Register the new entry */
+ if (res == FR_OK) {
+ nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName];
+ nh = ld_word(fs->dirbuf + XDIR_NameHash);
+ mem_cpy(fs->dirbuf, buf, SZDIRE * 2); /* Restore 85+C0 entry */
+ fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn;
+ st_word(fs->dirbuf + XDIR_NameHash, nh);
+ if (!(fs->dirbuf[XDIR_Attr] & AM_DIR)) fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */
+/* Start of critical section where an interruption can cause a cross-link */
+ res = store_xdir(&djn);
+ }
+ }
+ } else
+#endif
+ { /* At FAT/FAT32 volume */
+ mem_cpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */
+ mem_cpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */
+ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
+ if (res == FR_OK) { /* Is new name already in use by any other object? */
+ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
+ }
+ if (res == FR_NO_FILE) { /* It is a valid path and no name collision */
+ res = dir_register(&djn); /* Register the new entry */
+ if (res == FR_OK) {
+ dir = djn.dir; /* Copy directory entry of the object except name */
+ mem_cpy(dir + 13, buf + 13, SZDIRE - 13);
+ dir[DIR_Attr] = buf[DIR_Attr];
+ if (!(dir[DIR_Attr] & AM_DIR)) dir[DIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */
+ fs->wflag = 1;
+ if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */
+ sect = clst2sect(fs, ld_clust(fs, dir));
+ if (sect == 0) {
+ res = FR_INT_ERR;
+ } else {
+/* Start of critical section where an interruption can cause a cross-link */
+ res = move_window(fs, sect);
+ dir = fs->win + SZDIRE * 1; /* Ptr to .. entry */
+ if (res == FR_OK && dir[1] == '.') {
+ st_clust(fs, dir, djn.obj.sclust);
+ fs->wflag = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ res = dir_remove(&djo); /* Remove old entry */
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+/* End of the critical section */
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_FS_MINIMIZE == 0 */
+#endif /* FF_FS_MINIMIZE <= 1 */
+#endif /* FF_FS_MINIMIZE <= 2 */
+
+
+
+#if FF_USE_CHMOD && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Change Attribute */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_chmod (
+ const TCHAR* path, /* Pointer to the file path */
+ BYTE attr, /* Attribute bits */
+ BYTE mask /* Attribute mask to change */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = mount_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */
+ if (res == FR_OK) {
+ mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->dirbuf[XDIR_Attr] = (attr & mask) | (fs->dirbuf[XDIR_Attr] & (BYTE)~mask); /* Apply attribute change */
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ dj.dir[DIR_Attr] = (attr & mask) | (dj.dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Change Timestamp */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_utime (
+ const TCHAR* path, /* Pointer to the file/directory name */
+ const FILINFO* fno /* Pointer to the timestamp to be set */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = mount_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ st_dword(fs->dirbuf + XDIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime);
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ st_dword(dj.dir + DIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime);
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* FF_USE_CHMOD && !FF_FS_READONLY */
+
+
+
+#if FF_USE_LABEL
+/*-----------------------------------------------------------------------*/
+/* Get Volume Label */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_getlabel (
+ const TCHAR* path, /* Logical drive number */
+ TCHAR* label, /* Buffer to store the volume label */
+ DWORD* vsn /* Variable to store the volume serial number */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ UINT si, di;
+ WCHAR wc;
+
+ /* Get logical drive */
+ res = mount_volume(&path, &fs, 0);
+
+ /* Get volume label */
+ if (res == FR_OK && label) {
+ dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */
+ res = dir_sdi(&dj, 0);
+ if (res == FR_OK) {
+ res = DIR_READ_LABEL(&dj); /* Find a volume label entry */
+ if (res == FR_OK) {
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ WCHAR hs;
+
+ for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */
+ wc = ld_word(dj.dir + XDIR_Label + si * 2);
+ if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */
+ hs = wc; continue;
+ }
+ wc = put_utf((DWORD)hs << 16 | wc, &label[di], 4);
+ if (wc == 0) { di = 0; break; }
+ di += wc;
+ hs = 0;
+ }
+ if (hs != 0) di = 0; /* Broken surrogate pair? */
+ label[di] = 0;
+ } else
+#endif
+ {
+ si = di = 0; /* Extract volume label from AM_VOL entry */
+ while (si < 11) {
+ wc = dj.dir[si++];
+#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode output */
+ if (dbc_1st((BYTE)wc) && si < 11) wc = wc << 8 | dj.dir[si++]; /* Is it a DBC? */
+ wc = ff_oem2uni(wc, CODEPAGE); /* Convert it into Unicode */
+ if (wc != 0) wc = put_utf(wc, &label[di], 4); /* Put it in Unicode */
+ if (wc == 0) { di = 0; break; }
+ di += wc;
+#else /* ANSI/OEM output */
+ label[di++] = (TCHAR)wc;
+#endif
+ }
+ do { /* Truncate trailing spaces */
+ label[di] = 0;
+ if (di == 0) break;
+ } while (label[--di] == ' ');
+ }
+ }
+ }
+ if (res == FR_NO_FILE) { /* No label entry and return nul string */
+ label[0] = 0;
+ res = FR_OK;
+ }
+ }
+
+ /* Get volume serial number */
+ if (res == FR_OK && vsn) {
+ res = move_window(fs, fs->volbase);
+ if (res == FR_OK) {
+ switch (fs->fs_type) {
+ case FS_EXFAT:
+ di = BPB_VolIDEx; break;
+
+ case FS_FAT32:
+ di = BS_VolID32; break;
+
+ default:
+ di = BS_VolID;
+ }
+ *vsn = ld_dword(fs->win + di);
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Set Volume Label */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_setlabel (
+ const TCHAR* label /* Volume label to set with heading logical drive number */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ BYTE dirvn[22];
+ UINT di;
+ WCHAR wc;
+ static const char badchr[] = "+.,;=[]/\\\"*:<>\?|\x7F"; /* [0..] for FAT, [7..] for exFAT */
+#if FF_USE_LFN
+ DWORD dc;
+#endif
+
+ /* Get logical drive */
+ res = mount_volume(&label, &fs, FA_WRITE);
+ if (res != FR_OK) LEAVE_FF(fs, res);
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ mem_set(dirvn, 0, 22);
+ di = 0;
+ while ((UINT)*label >= ' ') { /* Create volume label */
+ dc = tchar2uni(&label); /* Get a Unicode character */
+ if (dc >= 0x10000) {
+ if (dc == 0xFFFFFFFF || di >= 10) { /* Wrong surrogate or buffer overflow */
+ dc = 0;
+ } else {
+ st_word(dirvn + di * 2, (WCHAR)(dc >> 16)); di++;
+ }
+ }
+ if (dc == 0 || chk_chr(badchr + 7, (int)dc) || di >= 11) { /* Check validity of the volume label */
+ LEAVE_FF(fs, FR_INVALID_NAME);
+ }
+ st_word(dirvn + di * 2, (WCHAR)dc); di++;
+ }
+ } else
+#endif
+ { /* On the FAT/FAT32 volume */
+ mem_set(dirvn, ' ', 11);
+ di = 0;
+ while ((UINT)*label >= ' ') { /* Create volume label */
+#if FF_USE_LFN
+ dc = tchar2uni(&label);
+ wc = (dc < 0x10000) ? ff_uni2oem(ff_wtoupper(dc), CODEPAGE) : 0;
+#else /* ANSI/OEM input */
+ wc = (BYTE)*label++;
+ if (dbc_1st((BYTE)wc)) wc = dbc_2nd((BYTE)*label) ? wc << 8 | (BYTE)*label++ : 0;
+ if (IsLower(wc)) wc -= 0x20; /* To upper ASCII characters */
+#if FF_CODE_PAGE == 0
+ if (ExCvt && wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */
+#elif FF_CODE_PAGE < 900
+ if (wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */
+#endif
+#endif
+ if (wc == 0 || chk_chr(badchr + 0, (int)wc) || di >= (UINT)((wc >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */
+ LEAVE_FF(fs, FR_INVALID_NAME);
+ }
+ if (wc >= 0x100) dirvn[di++] = (BYTE)(wc >> 8);
+ dirvn[di++] = (BYTE)wc;
+ }
+ if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */
+ while (di && dirvn[di - 1] == ' ') di--; /* Snip trailing spaces */
+ }
+
+ /* Set volume label */
+ dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */
+ res = dir_sdi(&dj, 0);
+ if (res == FR_OK) {
+ res = DIR_READ_LABEL(&dj); /* Get volume label entry */
+ if (res == FR_OK) {
+ if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) {
+ dj.dir[XDIR_NumLabel] = (BYTE)di; /* Change the volume label */
+ mem_cpy(dj.dir + XDIR_Label, dirvn, 22);
+ } else {
+ if (di != 0) {
+ mem_cpy(dj.dir, dirvn, 11); /* Change the volume label */
+ } else {
+ dj.dir[DIR_Name] = DDEM; /* Remove the volume label */
+ }
+ }
+ fs->wflag = 1;
+ res = sync_fs(fs);
+ } else { /* No volume label entry or an error */
+ if (res == FR_NO_FILE) {
+ res = FR_OK;
+ if (di != 0) { /* Create a volume label entry */
+ res = dir_alloc(&dj, 1); /* Allocate an entry */
+ if (res == FR_OK) {
+ mem_set(dj.dir, 0, SZDIRE); /* Clean the entry */
+ if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) {
+ dj.dir[XDIR_Type] = ET_VLABEL; /* Create volume label entry */
+ dj.dir[XDIR_NumLabel] = (BYTE)di;
+ mem_cpy(dj.dir + XDIR_Label, dirvn, 22);
+ } else {
+ dj.dir[DIR_Attr] = AM_VOL; /* Create volume label entry */
+ mem_cpy(dj.dir, dirvn, 11);
+ }
+ fs->wflag = 1;
+ res = sync_fs(fs);
+ }
+ }
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_USE_LABEL */
+
+
+
+#if FF_USE_EXPAND && !FF_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Allocate a Contiguous Blocks to the File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_expand (
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t fsz, /* File size to be expanded to */
+ BYTE opt /* Operation mode 0:Find and prepare or 1:Find and allocate */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD n, clst, stcl, scl, ncl, tcl, lclst;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (fsz == 0 || fp->obj.objsize != 0 || !(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED);
+#if FF_FS_EXFAT
+ if (fs->fs_type != FS_EXFAT && fsz >= 0x100000000) LEAVE_FF(fs, FR_DENIED); /* Check if in size limit */
+#endif
+ n = (DWORD)fs->csize * SS(fs); /* Cluster size */
+ tcl = (DWORD)(fsz / n) + ((fsz & (n - 1)) ? 1 : 0); /* Number of clusters required */
+ stcl = fs->last_clst; lclst = 0;
+ if (stcl < 2 || stcl >= fs->n_fatent) stcl = 2;
+
+#if FF_FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ scl = find_bitmap(fs, stcl, tcl); /* Find a contiguous cluster block */
+ if (scl == 0) res = FR_DENIED; /* No contiguous cluster block was found */
+ if (scl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (res == FR_OK) { /* A contiguous free area is found */
+ if (opt) { /* Allocate it now */
+ res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */
+ lclst = scl + tcl - 1;
+ } else { /* Set it as suggested point for next allocation */
+ lclst = scl - 1;
+ }
+ }
+ } else
+#endif
+ {
+ scl = clst = stcl; ncl = 0;
+ for (;;) { /* Find a contiguous cluster block */
+ n = get_fat(&fp->obj, clst);
+ if (++clst >= fs->n_fatent) clst = 2;
+ if (n == 1) { res = FR_INT_ERR; break; }
+ if (n == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
+ if (n == 0) { /* Is it a free cluster? */
+ if (++ncl == tcl) break; /* Break if a contiguous cluster block is found */
+ } else {
+ scl = clst; ncl = 0; /* Not a free cluster */
+ }
+ if (clst == stcl) { res = FR_DENIED; break; } /* No contiguous cluster? */
+ }
+ if (res == FR_OK) { /* A contiguous free area is found */
+ if (opt) { /* Allocate it now */
+ for (clst = scl, n = tcl; n; clst++, n--) { /* Create a cluster chain on the FAT */
+ res = put_fat(fs, clst, (n == 1) ? 0xFFFFFFFF : clst + 1);
+ if (res != FR_OK) break;
+ lclst = clst;
+ }
+ } else { /* Set it as suggested point for next allocation */
+ lclst = scl - 1;
+ }
+ }
+ }
+
+ if (res == FR_OK) {
+ fs->last_clst = lclst; /* Set suggested start cluster to start next */
+ if (opt) { /* Is it allocated now? */
+ fp->obj.sclust = scl; /* Update object allocation information */
+ fp->obj.objsize = fsz;
+ if (FF_FS_EXFAT) fp->obj.stat = 2; /* Set status 'contiguous chain' */
+ fp->flag |= FA_MODIFIED;
+ if (fs->free_clst <= fs->n_fatent - 2) { /* Update FSINFO */
+ fs->free_clst -= tcl;
+ fs->fsi_flag |= 1;
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* FF_USE_EXPAND && !FF_FS_READONLY */
+
+
+
+#if FF_USE_FORWARD
+/*-----------------------------------------------------------------------*/
+/* Forward Data to the Stream Directly */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_forward (
+ FIL* fp, /* Pointer to the file object */
+ UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */
+ UINT btf, /* Number of bytes to forward */
+ UINT* bf /* Pointer to number of bytes forwarded */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst;
+ LBA_t sect;
+ FSIZE_t remain;
+ UINT rcnt, csect;
+ BYTE *dbuf;
+
+
+ *bf = 0; /* Clear transfer byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ remain = fp->obj.objsize - fp->fptr;
+ if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */
+
+ for ( ; btf && (*func)(0, 0); /* Repeat until all data transferred or stream goes busy */
+ fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) {
+ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ if (csect == 0) { /* On the cluster boundary? */
+ clst = (fp->fptr == 0) ? /* On the top of the file? */
+ fp->obj.sclust : get_fat(&fp->obj, fp->clust);
+ if (clst <= 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ }
+ }
+ sect = clst2sect(fs, fp->clust); /* Get current data sector */
+ if (sect == 0) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+#if FF_FS_TINY
+ if (move_window(fs, sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window to the file data */
+ dbuf = fs->win;
+#else
+ if (fp->sect != sect) { /* Fill sector cache with file data */
+#if !FF_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ }
+ dbuf = fp->buf;
+#endif
+ fp->sect = sect;
+ rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */
+ if (rcnt > btf) rcnt = btf; /* Clip it by btr if needed */
+ rcnt = (*func)(dbuf + ((UINT)fp->fptr % SS(fs)), rcnt); /* Forward the file data */
+ if (rcnt == 0) ABORT(fs, FR_INT_ERR);
+ }
+
+ LEAVE_FF(fs, FR_OK);
+}
+#endif /* FF_USE_FORWARD */
+
+
+
+#if !FF_FS_READONLY && FF_USE_MKFS
+/*-----------------------------------------------------------------------*/
+/* Create an FAT/exFAT volume */
+/*-----------------------------------------------------------------------*/
+
+#define N_SEC_TRACK 63 /* Sectors per track for determination of drive CHS */
+#define GPT_ALIGN 0x100000 /* Alignment of partitions in GPT [byte] (>=128KB) */
+#define GPT_ITEMS 128 /* Number of GPT table size (>=128, sector aligned) */
+
+
+/* Create partitions on the physical drive */
+
+static FRESULT create_partition (
+ BYTE drv, /* Physical drive number */
+ const LBA_t plst[], /* Partition list */
+ UINT sys, /* System ID (for only MBR, temp setting) and bit8:GPT */
+ BYTE* buf /* Working buffer for a sector */
+)
+{
+ UINT i, cy;
+ LBA_t sz_drv;
+ DWORD sz_drv32, s_lba32, n_lba32;
+ BYTE *pte, hd, n_hd, sc, n_sc;
+
+ /* Get drive size */
+ if (disk_ioctl(drv, GET_SECTOR_COUNT, &sz_drv) != RES_OK) return FR_DISK_ERR;
+
+#if FF_LBA64
+ if (sz_drv >= FF_MIN_GPT) { /* Create partitions in GPT */
+ WORD ss;
+ UINT sz_pt, pi, si, ofs;
+ DWORD bcc, rnd, align;
+ QWORD s_lba64, n_lba64, sz_pool, s_bpt;
+ static const BYTE gpt_mbr[16] = {0x00, 0x00, 0x02, 0x00, 0xEE, 0xFE, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
+
+#if FF_MAX_SS != FF_MIN_SS
+ if (disk_ioctl(drv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR; /* Get sector size */
+ if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR;
+#else
+ ss = FF_MAX_SS;
+#endif
+ rnd = GET_FATTIME(); /* Random seed */
+ align = GPT_ALIGN / ss; /* Partition alignment [sector] */
+ sz_pt = GPT_ITEMS * SZ_GPTE / ss; /* Size of PT [sector] */
+ s_bpt = sz_drv - sz_pt - 1; /* Backup PT start sector */
+ s_lba64 = 2 + sz_pt; /* First allocatable sector */
+ sz_pool = s_bpt - s_lba64; /* Size of allocatable area */
+ bcc = 0xFFFFFFFF; n_lba64 = 1;
+ pi = si = 0; /* partition table index, size table index */
+ do {
+ if (pi * SZ_GPTE % ss == 0) mem_set(buf, 0, ss); /* Clean the buffer if needed */
+ if (n_lba64 != 0) { /* Is the size table not termintated? */
+ s_lba64 = (s_lba64 + align - 1) & ((QWORD)0 - align); /* Align partition start */
+ n_lba64 = plst[si++]; /* Get a partition size */
+ if (n_lba64 <= 100) { /* Is the size in percentage? */
+ n_lba64 = sz_pool * n_lba64 / 100;
+ n_lba64 = (n_lba64 + align - 1) & ((QWORD)0 - align); /* Align partition end (only if in percentage) */
+ }
+ if (s_lba64 + n_lba64 > s_bpt) { /* Clip at end of the pool */
+ n_lba64 = (s_lba64 < s_bpt) ? s_bpt - s_lba64 : 0;
+ }
+ }
+ if (n_lba64 != 0) { /* Add a partition? */
+ ofs = pi * SZ_GPTE % ss;
+ mem_cpy(buf + ofs + GPTE_PtGuid, GUID_MS_Basic, 16); /* Partition GUID (Microsoft Basic Data) */
+ rnd = make_rand(rnd, buf + ofs + GPTE_UpGuid, 16); /* Unique partition GUID */
+ st_qword(buf + ofs + GPTE_FstLba, s_lba64); /* Partition start LBA */
+ st_qword(buf + ofs + GPTE_LstLba, s_lba64 + n_lba64 - 1); /* Partition end LBA */
+ s_lba64 += n_lba64; /* Next partition LBA */
+ }
+ if ((pi + 1) * SZ_GPTE % ss == 0) { /* Write the buffer if it is filled up */
+ for (i = 0; i < ss; bcc = crc32(bcc, buf[i++])) ; /* Calculate table check sum */
+ if (disk_write(drv, buf, 2 + pi * SZ_GPTE / ss, 1) != RES_OK) return FR_DISK_ERR; /* Primary table */
+ if (disk_write(drv, buf, s_bpt + pi * SZ_GPTE / ss, 1) != RES_OK) return FR_DISK_ERR; /* Secondary table */
+ }
+ } while (++pi < GPT_ITEMS);
+
+ /* Create primary GPT header */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + GPTH_Sign, "EFI PART" "\0\0\1\0" "\x5C\0\0", 16); /* Signature, version (1.0) and size (92) */
+ st_dword(buf + GPTH_PtBcc, ~bcc); /* Table check sum */
+ st_qword(buf + GPTH_CurLba, 1); /* LBA of this header */
+ st_qword(buf + GPTH_BakLba, sz_drv - 1); /* LBA of another header */
+ st_qword(buf + GPTH_FstLba, 2 + sz_pt); /* LBA of first allocatable sector */
+ st_qword(buf + GPTH_LstLba, s_bpt - 1); /* LBA of last allocatable sector */
+ st_dword(buf + GPTH_PteSize, SZ_GPTE); /* Size of a table entry */
+ st_dword(buf + GPTH_PtNum, GPT_ITEMS); /* Number of table entries */
+ st_dword(buf + GPTH_PtOfs, 2); /* LBA of this table */
+ rnd = make_rand(rnd, buf + GPTH_DskGuid, 16); /* Disk GUID */
+ for (i = 0, bcc= 0xFFFFFFFF; i < 92; bcc = crc32(bcc, buf[i++])) ; /* Calculate header check sum */
+ st_dword(buf + GPTH_Bcc, ~bcc); /* Header check sum */
+ if (disk_write(drv, buf, 1, 1) != RES_OK) return FR_DISK_ERR;
+
+ /* Create secondary GPT header */
+ st_qword(buf + GPTH_CurLba, sz_drv - 1); /* LBA of this header */
+ st_qword(buf + GPTH_BakLba, 1); /* LBA of another header */
+ st_qword(buf + GPTH_PtOfs, s_bpt); /* LBA of this table */
+ st_dword(buf + GPTH_Bcc, 0);
+ for (i = 0, bcc= 0xFFFFFFFF; i < 92; bcc = crc32(bcc, buf[i++])) ; /* Calculate header check sum */
+ st_dword(buf + GPTH_Bcc, ~bcc); /* Header check sum */
+ if (disk_write(drv, buf, sz_drv - 1, 1) != RES_OK) return FR_DISK_ERR;
+
+ /* Create protective MBR */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + MBR_Table, gpt_mbr, 16); /* Create a GPT partition */
+ st_word(buf + BS_55AA, 0xAA55);
+ if (disk_write(drv, buf, 0, 1) != RES_OK) return FR_DISK_ERR;
+
+ } else
+#endif
+ { /* Create partitions in MBR */
+ sz_drv32 = (DWORD)sz_drv;
+ n_sc = N_SEC_TRACK; /* Determine drive CHS without any consideration of the drive geometry */
+ for (n_hd = 8; n_hd != 0 && sz_drv32 / n_hd / n_sc > 1024; n_hd *= 2) ;
+ if (n_hd == 0) n_hd = 255; /* Number of heads needs to be <256 */
+
+ mem_set(buf, 0, FF_MAX_SS); /* Clear MBR */
+ pte = buf + MBR_Table; /* Partition table in the MBR */
+ for (i = 0, s_lba32 = n_sc; i < 4 && s_lba32 != 0 && s_lba32 < sz_drv32; i++, s_lba32 += n_lba32) {
+ n_lba32 = (DWORD)plst[i]; /* Get partition size */
+ if (n_lba32 <= 100) n_lba32 = (n_lba32 == 100) ? sz_drv32 : sz_drv32 / 100 * n_lba32; /* Size in percentage? */
+ if (s_lba32 + n_lba32 > sz_drv32 || s_lba32 + n_lba32 < s_lba32) n_lba32 = sz_drv32 - s_lba32; /* Clip at drive size */
+ if (n_lba32 == 0) break; /* End of table or no sector to allocate? */
+
+ st_dword(pte + PTE_StLba, s_lba32); /* Start LBA */
+ st_dword(pte + PTE_SizLba, n_lba32); /* Number of sectors */
+ pte[PTE_System] = (BYTE)sys; /* System type */
+
+ cy = (UINT)(s_lba32 / n_sc / n_hd); /* Start cylinder */
+ hd = (BYTE)(s_lba32 / n_sc % n_hd); /* Start head */
+ sc = (BYTE)(s_lba32 % n_sc + 1); /* Start sector */
+ pte[PTE_StHead] = hd;
+ pte[PTE_StSec] = (BYTE)((cy >> 2 & 0xC0) | sc);
+ pte[PTE_StCyl] = (BYTE)cy;
+
+ cy = (UINT)((s_lba32 + n_lba32 - 1) / n_sc / n_hd); /* End cylinder */
+ hd = (BYTE)((s_lba32 + n_lba32 - 1) / n_sc % n_hd); /* End head */
+ sc = (BYTE)((s_lba32 + n_lba32 - 1) % n_sc + 1); /* End sector */
+ pte[PTE_EdHead] = hd;
+ pte[PTE_EdSec] = (BYTE)((cy >> 2 & 0xC0) | sc);
+ pte[PTE_EdCyl] = (BYTE)cy;
+
+ pte += SZ_PTE; /* Next entry */
+ }
+
+ st_word(buf + BS_55AA, 0xAA55); /* MBR signature */
+ if (disk_write(drv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the MBR */
+ }
+
+ return FR_OK;
+}
+
+
+
+FRESULT f_mkfs (
+ const TCHAR* path, /* Logical drive number */
+ const MKFS_PARM* opt, /* Format options */
+ void* work, /* Pointer to working buffer (null: use heap memory) */
+ UINT len /* Size of working buffer [byte] */
+)
+{
+ static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */
+ static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */
+ static const MKFS_PARM defopt = {FM_ANY, 0, 0, 0, 0}; /* Default parameter */
+ BYTE fsopt, fsty, sys, *buf, *pte, pdrv, ipart;
+ WORD ss; /* Sector size */
+ DWORD sz_buf, sz_blk, n_clst, pau, nsect, n;
+ LBA_t sz_vol, b_vol, b_fat, b_data; /* Size of volume, Base LBA of volume, fat, data */
+ LBA_t sect, lba[2];
+ DWORD sz_rsv, sz_fat, sz_dir, sz_au; /* Size of reserved, fat, dir, data, cluster */
+ UINT n_fat, n_root, i; /* Index, Number of FATs and Number of roor dir entries */
+ int vol;
+ DSTATUS ds;
+ FRESULT fr;
+
+
+ /* Check mounted drive and clear work area */
+ vol = get_ldnumber(&path); /* Get target logical drive */
+ if (vol < 0) return FR_INVALID_DRIVE;
+ if (FatFs[vol]) FatFs[vol]->fs_type = 0; /* Clear the fs object if mounted */
+ pdrv = LD2PD(vol); /* Physical drive */
+ ipart = LD2PT(vol); /* Partition (0:create as new, 1..:get from partition table) */
+ if (!opt) opt = &defopt; /* Use default parameter if it is not given */
+
+ /* Get physical drive status (sz_drv, sz_blk, ss) */
+ ds = disk_initialize(pdrv);
+ if (ds & STA_NOINIT) return FR_NOT_READY;
+ if (ds & STA_PROTECT) return FR_WRITE_PROTECTED;
+ sz_blk = opt->align;
+ if (sz_blk == 0 && disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK) sz_blk = 1;
+ if (sz_blk == 0 || sz_blk > 0x8000 || (sz_blk & (sz_blk - 1))) sz_blk = 1;
+#if FF_MAX_SS != FF_MIN_SS
+ if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR;
+ if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR;
+#else
+ ss = FF_MAX_SS;
+#endif
+ /* Options for FAT sub-type and FAT parameters */
+ fsopt = opt->fmt & (FM_ANY | FM_SFD);
+ n_fat = (opt->n_fat >= 1 && opt->n_fat <= 2) ? opt->n_fat : 1;
+ n_root = (opt->n_root >= 1 && opt->n_root <= 32768 && (opt->n_root % (ss / SZDIRE)) == 0) ? opt->n_root : 512;
+ sz_au = (opt->au_size <= 0x1000000 && (opt->au_size & (opt->au_size - 1)) == 0) ? opt->au_size : 0;
+ sz_au /= ss; /* Byte --> Sector */
+
+ /* Get working buffer */
+ sz_buf = len / ss; /* Size of working buffer [sector] */
+ if (sz_buf == 0) return FR_NOT_ENOUGH_CORE;
+ buf = (BYTE*)work; /* Working buffer */
+#if FF_USE_LFN == 3
+ if (!buf) buf = ff_memalloc(sz_buf * ss); /* Use heap memory for working buffer */
+#endif
+ if (!buf) return FR_NOT_ENOUGH_CORE;
+
+ /* Determine where the volume to be located (b_vol, sz_vol) */
+ b_vol = sz_vol = 0;
+ if (FF_MULTI_PARTITION && ipart != 0) { /* Is the volume associated with any specific partition? */
+ /* Get partition location from the existing partition table */
+ if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load MBR */
+ if (ld_word(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */
+#if FF_LBA64
+ if (buf[MBR_Table + PTE_System] == 0xEE) { /* GPT protective MBR? */
+ DWORD n_ent, ofs;
+ QWORD pt_lba;
+
+ /* Get the partition location from GPT */
+ if (disk_read(pdrv, buf, 1, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load GPT header sector (next to MBR) */
+ if (!test_gpt_header(buf)) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if GPT header is valid */
+ n_ent = ld_dword(buf + GPTH_PtNum); /* Number of entries */
+ pt_lba = ld_qword(buf + GPTH_PtOfs); /* Table start sector */
+ ofs = i = 0;
+ while (n_ent) { /* Find MS Basic partition with order of ipart */
+ if (ofs == 0 && disk_read(pdrv, buf, pt_lba++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Get PT sector */
+ if (!mem_cmp(buf + ofs + GPTE_PtGuid, GUID_MS_Basic, 16) && ++i == ipart) { /* MS basic data partition? */
+ b_vol = ld_qword(buf + ofs + GPTE_FstLba);
+ sz_vol = ld_qword(buf + ofs + GPTE_LstLba) - b_vol + 1;
+ break;
+ }
+ n_ent--; ofs = (ofs + SZ_GPTE) % ss; /* Next entry */
+ }
+ if (n_ent == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* Partition not found */
+ fsopt |= 0x80; /* Partitioning is in GPT */
+ } else
+#endif
+ { /* Get the partition location from MBR partition table */
+ pte = buf + (MBR_Table + (ipart - 1) * SZ_PTE);
+ if (ipart > 4 || pte[PTE_System] == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* No partition? */
+ b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */
+ sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */
+ }
+ } else { /* The volume is associated with a physical drive */
+ if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ if (!(fsopt & FM_SFD)) { /* To be partitioned? */
+ /* Create a single-partition on the drive in this function */
+#if FF_LBA64
+ if (sz_vol >= FF_MIN_GPT) { /* Which partition type to create, MBR or GPT? */
+ fsopt |= 0x80; /* Partitioning is in GPT */
+ b_vol = GPT_ALIGN / ss; sz_vol -= b_vol + GPT_ITEMS * SZ_GPTE / ss + 1; /* Estimated partition offset and size */
+ } else
+#endif
+ { /* Partitioning is in MBR */
+ if (sz_vol > N_SEC_TRACK) {
+ b_vol = N_SEC_TRACK; sz_vol -= b_vol; /* Estimated partition offset and size */
+ }
+ }
+ }
+ }
+ if (sz_vol < 128) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=128s */
+
+ /* Now start to create a FAT volume at b_vol and sz_vol */
+
+ do { /* Pre-determine the FAT type */
+ if (FF_FS_EXFAT && (fsopt & FM_EXFAT)) { /* exFAT possible? */
+ if ((fsopt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || sz_au > 128) { /* exFAT only, vol >= 64MS or sz_au > 128S ? */
+ fsty = FS_EXFAT; break;
+ }
+ }
+#if FF_LBA64
+ if (sz_vol >= 0x100000000) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too large volume for FAT/FAT32 */
+#endif
+ if (sz_au > 128) sz_au = 128; /* Invalid AU for FAT/FAT32? */
+ if (fsopt & FM_FAT32) { /* FAT32 possible? */
+ if (!(fsopt & FM_FAT)) { /* no-FAT? */
+ fsty = FS_FAT32; break;
+ }
+ }
+ if (!(fsopt & FM_FAT)) LEAVE_MKFS(FR_INVALID_PARAMETER); /* no-FAT? */
+ fsty = FS_FAT16;
+ } while (0);
+
+#if FF_FS_EXFAT
+ if (fsty == FS_EXFAT) { /* Create an exFAT volume */
+ DWORD szb_bit, szb_case, sum, nb, cl, tbl[3];
+ WCHAR ch, si;
+ UINT j, st;
+ BYTE b;
+
+ if (sz_vol < 0x1000) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume for exFAT? */
+#if FF_USE_TRIM
+ lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */
+ disk_ioctl(pdrv, CTRL_TRIM, lba);
+#endif
+ /* Determine FAT location, data location and number of clusters */
+ if (sz_au == 0) { /* AU auto-selection */
+ sz_au = 8;
+ if (sz_vol >= 0x80000) sz_au = 64; /* >= 512Ks */
+ if (sz_vol >= 0x4000000) sz_au = 256; /* >= 64Ms */
+ }
+ b_fat = b_vol + 32; /* FAT start at offset 32 */
+ sz_fat = (DWORD)((sz_vol / sz_au + 2) * 4 + ss - 1) / ss; /* Number of FAT sectors */
+ b_data = (b_fat + sz_fat + sz_blk - 1) & ~((LBA_t)sz_blk - 1); /* Align data area to the erase block boundary */
+ if (b_data - b_vol >= sz_vol / 2) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */
+ n_clst = (DWORD)(sz_vol - (b_data - b_vol)) / sz_au; /* Number of clusters */
+ if (n_clst <16) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too few clusters? */
+ if (n_clst > MAX_EXFAT) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters? */
+
+ szb_bit = (n_clst + 7) / 8; /* Size of allocation bitmap */
+ tbl[0] = (szb_bit + sz_au * ss - 1) / (sz_au * ss); /* Number of allocation bitmap clusters */
+
+ /* Create a compressed up-case table */
+ sect = b_data + sz_au * tbl[0]; /* Table start sector */
+ sum = 0; /* Table checksum to be stored in the 82 entry */
+ st = 0; si = 0; i = 0; j = 0; szb_case = 0;
+ do {
+ switch (st) {
+ case 0:
+ ch = (WCHAR)ff_wtoupper(si); /* Get an up-case char */
+ if (ch != si) {
+ si++; break; /* Store the up-case char if exist */
+ }
+ for (j = 1; (WCHAR)(si + j) && (WCHAR)(si + j) == ff_wtoupper((WCHAR)(si + j)); j++) ; /* Get run length of no-case block */
+ if (j >= 128) {
+ ch = 0xFFFF; st = 2; break; /* Compress the no-case block if run is >= 128 */
+ }
+ st = 1; /* Do not compress short run */
+ /* go to next case */
+ case 1:
+ ch = si++; /* Fill the short run */
+ if (--j == 0) st = 0;
+ break;
+
+ default:
+ ch = (WCHAR)j; si += (WCHAR)j; /* Number of chars to skip */
+ st = 0;
+ }
+ sum = xsum32(buf[i + 0] = (BYTE)ch, sum); /* Put it into the write buffer */
+ sum = xsum32(buf[i + 1] = (BYTE)(ch >> 8), sum);
+ i += 2; szb_case += 2;
+ if (si == 0 || i == sz_buf * ss) { /* Write buffered data when buffer full or end of process */
+ n = (i + ss - 1) / ss;
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; i = 0;
+ }
+ } while (si);
+ tbl[1] = (szb_case + sz_au * ss - 1) / (sz_au * ss); /* Number of up-case table clusters */
+ tbl[2] = 1; /* Number of root dir clusters */
+
+ /* Initialize the allocation bitmap */
+ sect = b_data; nsect = (szb_bit + ss - 1) / ss; /* Start of bitmap and number of sectors */
+ nb = tbl[0] + tbl[1] + tbl[2]; /* Number of clusters in-use by system */
+ do {
+ mem_set(buf, 0, sz_buf * ss);
+ for (i = 0; nb >= 8 && i < sz_buf * ss; buf[i++] = 0xFF, nb -= 8) ;
+ for (b = 1; nb != 0 && i < sz_buf * ss; buf[i] |= b, b <<= 1, nb--) ;
+ n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Initialize the FAT */
+ sect = b_fat; nsect = sz_fat; /* Start of FAT and number of FAT sectors */
+ j = nb = cl = 0;
+ do {
+ mem_set(buf, 0, sz_buf * ss); i = 0; /* Clear work area and reset write index */
+ if (cl == 0) { /* Set FAT [0] and FAT[1] */
+ st_dword(buf + i, 0xFFFFFFF8); i += 4; cl++;
+ st_dword(buf + i, 0xFFFFFFFF); i += 4; cl++;
+ }
+ do { /* Create chains of bitmap, up-case and root dir */
+ while (nb != 0 && i < sz_buf * ss) { /* Create a chain */
+ st_dword(buf + i, (nb > 1) ? cl + 1 : 0xFFFFFFFF);
+ i += 4; cl++; nb--;
+ }
+ if (nb == 0 && j < 3) nb = tbl[j++]; /* Next chain */
+ } while (nb != 0 && i < sz_buf * ss);
+ n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Initialize the root directory */
+ mem_set(buf, 0, sz_buf * ss);
+ buf[SZDIRE * 0 + 0] = ET_VLABEL; /* Volume label entry (no label) */
+ buf[SZDIRE * 1 + 0] = ET_BITMAP; /* Bitmap entry */
+ st_dword(buf + SZDIRE * 1 + 20, 2); /* cluster */
+ st_dword(buf + SZDIRE * 1 + 24, szb_bit); /* size */
+ buf[SZDIRE * 2 + 0] = ET_UPCASE; /* Up-case table entry */
+ st_dword(buf + SZDIRE * 2 + 4, sum); /* sum */
+ st_dword(buf + SZDIRE * 2 + 20, 2 + tbl[0]); /* cluster */
+ st_dword(buf + SZDIRE * 2 + 24, szb_case); /* size */
+ sect = b_data + sz_au * (tbl[0] + tbl[1]); nsect = sz_au; /* Start of the root directory and number of sectors */
+ do { /* Fill root directory sectors */
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ mem_set(buf, 0, ss);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Create two set of the exFAT VBR blocks */
+ sect = b_vol;
+ for (n = 0; n < 2; n++) {
+ /* Main record (+0) */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11); /* Boot jump code (x86), OEM name */
+ st_qword(buf + BPB_VolOfsEx, b_vol); /* Volume offset in the physical drive [sector] */
+ st_qword(buf + BPB_TotSecEx, sz_vol); /* Volume size [sector] */
+ st_dword(buf + BPB_FatOfsEx, (DWORD)(b_fat - b_vol)); /* FAT offset [sector] */
+ st_dword(buf + BPB_FatSzEx, sz_fat); /* FAT size [sector] */
+ st_dword(buf + BPB_DataOfsEx, (DWORD)(b_data - b_vol)); /* Data offset [sector] */
+ st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */
+ st_dword(buf + BPB_RootClusEx, 2 + tbl[0] + tbl[1]); /* Root dir cluster # */
+ st_dword(buf + BPB_VolIDEx, GET_FATTIME()); /* VSN */
+ st_word(buf + BPB_FSVerEx, 0x100); /* Filesystem version (1.00) */
+ for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */
+ for (buf[BPB_SecPerClusEx] = 0, i = sz_au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */
+ buf[BPB_NumFATsEx] = 1; /* Number of FATs */
+ buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */
+ st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */
+ st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */
+ for (i = sum = 0; i < ss; i++) { /* VBR checksum */
+ if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum);
+ }
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ /* Extended bootstrap record (+1..+8) */
+ mem_set(buf, 0, ss);
+ st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */
+ for (j = 1; j < 9; j++) {
+ for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ }
+ /* OEM/Reserved record (+9..+10) */
+ mem_set(buf, 0, ss);
+ for ( ; j < 11; j++) {
+ for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ }
+ /* Sum record (+11) */
+ for (i = 0; i < ss; i += 4) st_dword(buf + i, sum); /* Fill with checksum value */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ }
+
+ } else
+#endif /* FF_FS_EXFAT */
+ { /* Create an FAT/FAT32 volume */
+ do {
+ pau = sz_au;
+ /* Pre-determine number of clusters and FAT sub-type */
+ if (fsty == FS_FAT32) { /* FAT32 volume */
+ if (pau == 0) { /* AU auto-selection */
+ n = (DWORD)sz_vol / 0x20000; /* Volume size in unit of 128KS */
+ for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */
+ }
+ n_clst = (DWORD)sz_vol / pau; /* Number of clusters */
+ sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* FAT size [sector] */
+ sz_rsv = 32; /* Number of reserved sectors */
+ sz_dir = 0; /* No static directory */
+ if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) LEAVE_MKFS(FR_MKFS_ABORTED);
+ } else { /* FAT volume */
+ if (pau == 0) { /* au auto-selection */
+ n = (DWORD)sz_vol / 0x1000; /* Volume size in unit of 4KS */
+ for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */
+ }
+ n_clst = (DWORD)sz_vol / pau;
+ if (n_clst > MAX_FAT12) {
+ n = n_clst * 2 + 4; /* FAT size [byte] */
+ } else {
+ fsty = FS_FAT12;
+ n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */
+ }
+ sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */
+ sz_rsv = 1; /* Number of reserved sectors */
+ sz_dir = (DWORD)n_root * SZDIRE / ss; /* Root dir size [sector] */
+ }
+ b_fat = b_vol + sz_rsv; /* FAT base */
+ b_data = b_fat + sz_fat * n_fat + sz_dir; /* Data base */
+
+ /* Align data area to erase block boundary (for flash memory media) */
+ n = (DWORD)(((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data); /* Sectors to next nearest from current data base */
+ if (fsty == FS_FAT32) { /* FAT32: Move FAT */
+ sz_rsv += n; b_fat += n;
+ } else { /* FAT: Expand FAT */
+ if (n % n_fat) { /* Adjust fractional error if needed */
+ n--; sz_rsv++; b_fat++;
+ }
+ sz_fat += n / n_fat;
+ }
+
+ /* Determine number of clusters and final check of validity of the FAT sub-type */
+ if (sz_vol < b_data + pau * 16 - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */
+ n_clst = ((DWORD)sz_vol - sz_rsv - sz_fat * n_fat - sz_dir) / pau;
+ if (fsty == FS_FAT32) {
+ if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32? */
+ if (sz_au == 0 && (sz_au = pau / 2) != 0) continue; /* Adjust cluster size and retry */
+ LEAVE_MKFS(FR_MKFS_ABORTED);
+ }
+ }
+ if (fsty == FS_FAT16) {
+ if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */
+ if (sz_au == 0 && (pau * 2) <= 64) {
+ sz_au = pau * 2; continue; /* Adjust cluster size and retry */
+ }
+ if ((fsopt & FM_FAT32)) {
+ fsty = FS_FAT32; continue; /* Switch type to FAT32 and retry */
+ }
+ if (sz_au == 0 && (sz_au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */
+ LEAVE_MKFS(FR_MKFS_ABORTED);
+ }
+ if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */
+ if (sz_au == 0 && (sz_au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */
+ LEAVE_MKFS(FR_MKFS_ABORTED);
+ }
+ }
+ if (fsty == FS_FAT12 && n_clst > MAX_FAT12) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters for FAT12 */
+
+ /* Ok, it is the valid cluster configuration */
+ break;
+ } while (1);
+
+#if FF_USE_TRIM
+ lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */
+ disk_ioctl(pdrv, CTRL_TRIM, lba);
+#endif
+ /* Create FAT VBR */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */
+ st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */
+ buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */
+ st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */
+ buf[BPB_NumFATs] = (BYTE)n_fat; /* Number of FATs */
+ st_word(buf + BPB_RootEntCnt, (WORD)((fsty == FS_FAT32) ? 0 : n_root)); /* Number of root directory entries */
+ if (sz_vol < 0x10000) {
+ st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */
+ } else {
+ st_dword(buf + BPB_TotSec32, (DWORD)sz_vol); /* Volume size in 32-bit LBA */
+ }
+ buf[BPB_Media] = 0xF8; /* Media descriptor byte */
+ st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */
+ st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */
+ st_dword(buf + BPB_HiddSec, (DWORD)b_vol); /* Volume offset in the physical drive [sector] */
+ if (fsty == FS_FAT32) {
+ st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */
+ st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */
+ st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */
+ st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */
+ st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */
+ buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */
+ buf[BS_BootSig32] = 0x29; /* Extended boot signature */
+ mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */
+ } else {
+ st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */
+ st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */
+ buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */
+ buf[BS_BootSig] = 0x29; /* Extended boot signature */
+ mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */
+ }
+ st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */
+ if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the VBR sector */
+
+ /* Create FSINFO record if needed */
+ if (fsty == FS_FAT32) {
+ disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */
+ mem_set(buf, 0, ss);
+ st_dword(buf + FSI_LeadSig, 0x41615252);
+ st_dword(buf + FSI_StrucSig, 0x61417272);
+ st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */
+ st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */
+ st_word(buf + BS_55AA, 0xAA55);
+ disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */
+ disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */
+ }
+
+ /* Initialize FAT area */
+ mem_set(buf, 0, sz_buf * ss);
+ sect = b_fat; /* FAT start sector */
+ for (i = 0; i < n_fat; i++) { /* Initialize FATs each */
+ if (fsty == FS_FAT32) {
+ st_dword(buf + 0, 0xFFFFFFF8); /* FAT[0] */
+ st_dword(buf + 4, 0xFFFFFFFF); /* FAT[1] */
+ st_dword(buf + 8, 0x0FFFFFFF); /* FAT[2] (root directory) */
+ } else {
+ st_dword(buf + 0, (fsty == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* FAT[0] and FAT[1] */
+ }
+ nsect = sz_fat; /* Number of FAT sectors */
+ do { /* Fill FAT sectors */
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ mem_set(buf, 0, ss); /* Rest of FAT all are cleared */
+ sect += n; nsect -= n;
+ } while (nsect);
+ }
+
+ /* Initialize root directory (fill with zero) */
+ nsect = (fsty == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */
+ do {
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+ sect += n; nsect -= n;
+ } while (nsect);
+ }
+
+ /* A FAT volume has been created here */
+
+ /* Determine system ID in the MBR partition table */
+ if (FF_FS_EXFAT && fsty == FS_EXFAT) {
+ sys = 0x07; /* exFAT */
+ } else {
+ if (fsty == FS_FAT32) {
+ sys = 0x0C; /* FAT32X */
+ } else {
+ if (sz_vol >= 0x10000) {
+ sys = 0x06; /* FAT12/16 (large) */
+ } else {
+ sys = (fsty == FS_FAT16) ? 0x04 : 0x01; /* FAT16 : FAT12 */
+ }
+ }
+ }
+
+ /* Update partition information */
+ if (FF_MULTI_PARTITION && ipart != 0) { /* Volume is in the existing partition */
+ if (!FF_LBA64 || !(fsopt & 0x80)) {
+ /* Update system ID in the partition table */
+ if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Read the MBR */
+ buf[MBR_Table + (ipart - 1) * SZ_PTE + PTE_System] = sys; /* Set system ID */
+ if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it back to the MBR */
+ }
+ } else { /* Volume as a new single partition */
+ if (!(fsopt & FM_SFD)) { /* Create partition table if not in SFD */
+ lba[0] = sz_vol, lba[1] = 0;
+ fr = create_partition(pdrv, lba, sys, buf);
+ if (fr != FR_OK) LEAVE_MKFS(fr);
+ }
+ }
+
+ if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) LEAVE_MKFS(FR_DISK_ERR);
+
+ LEAVE_MKFS(FR_OK);
+}
+
+
+
+
+#if FF_MULTI_PARTITION
+/*-----------------------------------------------------------------------*/
+/* Create Partition Table on the Physical Drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_fdisk (
+ BYTE pdrv, /* Physical drive number */
+ const LBA_t ptbl[], /* Pointer to the size table for each partitions */
+ void* work /* Pointer to the working buffer (null: use heap memory) */
+)
+{
+ BYTE *buf = (BYTE*)work;
+ DSTATUS stat;
+
+
+ stat = disk_initialize(pdrv);
+ if (stat & STA_NOINIT) return FR_NOT_READY;
+ if (stat & STA_PROTECT) return FR_WRITE_PROTECTED;
+#if FF_USE_LFN == 3
+ if (!buf) buf = ff_memalloc(FF_MAX_SS); /* Use heap memory for working buffer */
+#endif
+ if (!buf) return FR_NOT_ENOUGH_CORE;
+
+ LEAVE_MKFS(create_partition(pdrv, ptbl, 0x07, buf));
+}
+
+#endif /* FF_MULTI_PARTITION */
+#endif /* !FF_FS_READONLY && FF_USE_MKFS */
+
+
+
+
+#if FF_USE_STRFUNC
+#if FF_USE_LFN && FF_LFN_UNICODE && (FF_STRF_ENCODE < 0 || FF_STRF_ENCODE > 3)
+#error Wrong FF_STRF_ENCODE setting
+#endif
+/*-----------------------------------------------------------------------*/
+/* Get a String from the File */
+/*-----------------------------------------------------------------------*/
+
+TCHAR* f_gets (
+ TCHAR* buff, /* Pointer to the buffer to store read string */
+ int len, /* Size of string buffer (items) */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ int nc = 0;
+ TCHAR *p = buff;
+ BYTE s[4];
+ UINT rc;
+ DWORD dc;
+#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE <= 2
+ WCHAR wc;
+#endif
+#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE == 3
+ UINT ct;
+#endif
+
+#if FF_USE_LFN && FF_LFN_UNICODE /* With code conversion (Unicode API) */
+ /* Make a room for the character and terminator */
+ if (FF_LFN_UNICODE == 1) len -= (FF_STRF_ENCODE == 0) ? 1 : 2;
+ if (FF_LFN_UNICODE == 2) len -= (FF_STRF_ENCODE == 0) ? 3 : 4;
+ if (FF_LFN_UNICODE == 3) len -= 1;
+ while (nc < len) {
+#if FF_STRF_ENCODE == 0 /* Read a character in ANSI/OEM */
+ f_read(fp, s, 1, &rc); /* Get a code unit */
+ if (rc != 1) break; /* EOF? */
+ wc = s[0];
+ if (dbc_1st((BYTE)wc)) { /* DBC 1st byte? */
+ f_read(fp, s, 1, &rc); /* Get DBC 2nd byte */
+ if (rc != 1 || !dbc_2nd(s[0])) continue; /* Wrong code? */
+ wc = wc << 8 | s[0];
+ }
+ dc = ff_oem2uni(wc, CODEPAGE); /* OEM --> */
+ if (dc == 0) continue;
+#elif FF_STRF_ENCODE == 1 || FF_STRF_ENCODE == 2 /* Read a character in UTF-16LE/BE */
+ f_read(fp, s, 2, &rc); /* Get a code unit */
+ if (rc != 2) break; /* EOF? */
+ dc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1];
+ if (IsSurrogateL(dc)) continue; /* Broken surrogate pair? */
+ if (IsSurrogateH(dc)) { /* High surrogate? */
+ f_read(fp, s, 2, &rc); /* Get low surrogate */
+ if (rc != 2) break; /* EOF? */
+ wc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1];
+ if (!IsSurrogateL(wc)) continue; /* Broken surrogate pair? */
+ dc = ((dc & 0x3FF) + 0x40) << 10 | (wc & 0x3FF); /* Merge surrogate pair */
+ }
+#else /* Read a character in UTF-8 */
+ f_read(fp, s, 1, &rc); /* Get a code unit */
+ if (rc != 1) break; /* EOF? */
+ dc = s[0];
+ if (dc >= 0x80) { /* Multi-byte sequence? */
+ ct = 0;
+ if ((dc & 0xE0) == 0xC0) { dc &= 0x1F; ct = 1; } /* 2-byte sequence? */
+ if ((dc & 0xF0) == 0xE0) { dc &= 0x0F; ct = 2; } /* 3-byte sequence? */
+ if ((dc & 0xF8) == 0xF0) { dc &= 0x07; ct = 3; } /* 4-byte sequence? */
+ if (ct == 0) continue;
+ f_read(fp, s, ct, &rc); /* Get trailing bytes */
+ if (rc != ct) break;
+ rc = 0;
+ do { /* Merge the byte sequence */
+ if ((s[rc] & 0xC0) != 0x80) break;
+ dc = dc << 6 | (s[rc] & 0x3F);
+ } while (++rc < ct);
+ if (rc != ct || dc < 0x80 || IsSurrogate(dc) || dc >= 0x110000) continue; /* Wrong encoding? */
+ }
+#endif
+ /* A code point is avaialble in dc to be output */
+
+ if (FF_USE_STRFUNC == 2 && dc == '\r') continue; /* Strip \r off if needed */
+#if FF_LFN_UNICODE == 1 || FF_LFN_UNICODE == 3 /* Output it in UTF-16/32 encoding */
+ if (FF_LFN_UNICODE == 1 && dc >= 0x10000) { /* Out of BMP at UTF-16? */
+ *p++ = (TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */
+ dc = 0xDC00 | (dc & 0x3FF); /* Make low surrogate */
+ }
+ *p++ = (TCHAR)dc; nc++;
+ if (dc == '\n') break; /* End of line? */
+#elif FF_LFN_UNICODE == 2 /* Output it in UTF-8 encoding */
+ if (dc < 0x80) { /* Single byte? */
+ *p++ = (TCHAR)dc;
+ nc++;
+ if (dc == '\n') break; /* End of line? */
+ } else {
+ if (dc < 0x800) { /* 2-byte sequence? */
+ *p++ = (TCHAR)(0xC0 | (dc >> 6 & 0x1F));
+ *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F));
+ nc += 2;
+ } else {
+ if (dc < 0x10000) { /* 3-byte sequence? */
+ *p++ = (TCHAR)(0xE0 | (dc >> 12 & 0x0F));
+ *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F));
+ *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F));
+ nc += 3;
+ } else { /* 4-byte sequence? */
+ *p++ = (TCHAR)(0xF0 | (dc >> 18 & 0x07));
+ *p++ = (TCHAR)(0x80 | (dc >> 12 & 0x3F));
+ *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F));
+ *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F));
+ nc += 4;
+ }
+ }
+ }
+#endif
+ }
+
+#else /* Byte-by-byte read without any conversion (ANSI/OEM API) */
+ len -= 1; /* Make a room for the terminator */
+ while (nc < len) {
+ f_read(fp, s, 1, &rc); /* Get a byte */
+ if (rc != 1) break; /* EOF? */
+ dc = s[0];
+ if (FF_USE_STRFUNC == 2 && dc == '\r') continue;
+ *p++ = (TCHAR)dc; nc++;
+ if (dc == '\n') break;
+ }
+#endif
+
+ *p = 0; /* Terminate the string */
+ return nc ? buff : 0; /* When no data read due to EOF or error, return with error. */
+}
+
+
+
+
+#if !FF_FS_READONLY
+#include
+/*-----------------------------------------------------------------------*/
+/* Put a Character to the File (sub-functions) */
+/*-----------------------------------------------------------------------*/
+
+/* Putchar output buffer and work area */
+
+typedef struct {
+ FIL *fp; /* Ptr to the writing file */
+ int idx, nchr; /* Write index of buf[] (-1:error), number of encoding units written */
+#if FF_USE_LFN && FF_LFN_UNICODE == 1
+ WCHAR hs;
+#elif FF_USE_LFN && FF_LFN_UNICODE == 2
+ BYTE bs[4];
+ UINT wi, ct;
+#endif
+ BYTE buf[64]; /* Write buffer */
+} putbuff;
+
+
+/* Buffered write with code conversion */
+
+static void putc_bfd (putbuff* pb, TCHAR c)
+{
+ UINT n;
+ int i, nc;
+#if FF_USE_LFN && FF_LFN_UNICODE
+ WCHAR hs, wc;
+#if FF_LFN_UNICODE == 2
+ DWORD dc;
+ TCHAR *tp;
+#endif
+#endif
+
+ if (FF_USE_STRFUNC == 2 && c == '\n') { /* LF -> CRLF conversion */
+ putc_bfd(pb, '\r');
+ }
+
+ i = pb->idx; /* Write index of pb->buf[] */
+ if (i < 0) return;
+ nc = pb->nchr; /* Write unit counter */
+
+#if FF_USE_LFN && FF_LFN_UNICODE
+#if FF_LFN_UNICODE == 1 /* UTF-16 input */
+ if (IsSurrogateH(c)) { /* High surrogate? */
+ pb->hs = c; return; /* Save it for next */
+ }
+ hs = pb->hs; pb->hs = 0;
+ if (hs != 0) { /* There is a leading high surrogate */
+ if (!IsSurrogateL(c)) hs = 0; /* Discard high surrogate if not a surrogate pair */
+ } else {
+ if (IsSurrogateL(c)) return; /* Discard stray low surrogate */
+ }
+ wc = c;
+#elif FF_LFN_UNICODE == 2 /* UTF-8 input */
+ for (;;) {
+ if (pb->ct == 0) { /* Out of multi-byte sequence? */
+ pb->bs[pb->wi = 0] = (BYTE)c; /* Save 1st byte */
+ if ((BYTE)c < 0x80) break; /* Single byte? */
+ if (((BYTE)c & 0xE0) == 0xC0) pb->ct = 1; /* 2-byte sequence? */
+ if (((BYTE)c & 0xF0) == 0xE0) pb->ct = 2; /* 3-byte sequence? */
+ if (((BYTE)c & 0xF1) == 0xF0) pb->ct = 3; /* 4-byte sequence? */
+ return;
+ } else { /* In the multi-byte sequence */
+ if (((BYTE)c & 0xC0) != 0x80) { /* Broken sequence? */
+ pb->ct = 0; continue;
+ }
+ pb->bs[++pb->wi] = (BYTE)c; /* Save the trailing byte */
+ if (--pb->ct == 0) break; /* End of multi-byte sequence? */
+ return;
+ }
+ }
+ tp = (TCHAR*)pb->bs;
+ dc = tchar2uni(&tp); /* UTF-8 ==> UTF-16 */
+ if (dc == 0xFFFFFFFF) return; /* Wrong code? */
+ wc = (WCHAR)dc;
+ hs = (WCHAR)(dc >> 16);
+#elif FF_LFN_UNICODE == 3 /* UTF-32 input */
+ if (IsSurrogate(c) || c >= 0x110000) return; /* Discard invalid code */
+ if (c >= 0x10000) { /* Out of BMP? */
+ hs = (WCHAR)(0xD800 | ((c >> 10) - 0x40)); /* Make high surrogate */
+ wc = 0xDC00 | (c & 0x3FF); /* Make low surrogate */
+ } else {
+ hs = 0;
+ wc = (WCHAR)c;
+ }
+#endif
+ /* A code point in UTF-16 is available in hs and wc */
+
+#if FF_STRF_ENCODE == 1 /* Write a code point in UTF-16LE */
+ if (hs != 0) { /* Surrogate pair? */
+ st_word(&pb->buf[i], hs);
+ i += 2;
+ nc++;
+ }
+ st_word(&pb->buf[i], wc);
+ i += 2;
+#elif FF_STRF_ENCODE == 2 /* Write a code point in UTF-16BE */
+ if (hs != 0) { /* Surrogate pair? */
+ pb->buf[i++] = (BYTE)(hs >> 8);
+ pb->buf[i++] = (BYTE)hs;
+ nc++;
+ }
+ pb->buf[i++] = (BYTE)(wc >> 8);
+ pb->buf[i++] = (BYTE)wc;
+#elif FF_STRF_ENCODE == 3 /* Write a code point in UTF-8 */
+ if (hs != 0) { /* 4-byte sequence? */
+ nc += 3;
+ hs = (hs & 0x3FF) + 0x40;
+ pb->buf[i++] = (BYTE)(0xF0 | hs >> 8);
+ pb->buf[i++] = (BYTE)(0x80 | (hs >> 2 & 0x3F));
+ pb->buf[i++] = (BYTE)(0x80 | (hs & 3) << 4 | (wc >> 6 & 0x0F));
+ pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F));
+ } else {
+ if (wc < 0x80) { /* Single byte? */
+ pb->buf[i++] = (BYTE)wc;
+ } else {
+ if (wc < 0x800) { /* 2-byte sequence? */
+ nc += 1;
+ pb->buf[i++] = (BYTE)(0xC0 | wc >> 6);
+ } else { /* 3-byte sequence */
+ nc += 2;
+ pb->buf[i++] = (BYTE)(0xE0 | wc >> 12);
+ pb->buf[i++] = (BYTE)(0x80 | (wc >> 6 & 0x3F));
+ }
+ pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F));
+ }
+ }
+#else /* Write a code point in ANSI/OEM */
+ if (hs != 0) return;
+ wc = ff_uni2oem(wc, CODEPAGE); /* UTF-16 ==> ANSI/OEM */
+ if (wc == 0) return;
+ if (wc >= 0x100) {
+ pb->buf[i++] = (BYTE)(wc >> 8); nc++;
+ }
+ pb->buf[i++] = (BYTE)wc;
+#endif
+
+#else /* ANSI/OEM input (without re-encoding) */
+ pb->buf[i++] = (BYTE)c;
+#endif
+
+ if (i >= (int)(sizeof pb->buf) - 4) { /* Write buffered characters to the file */
+ f_write(pb->fp, pb->buf, (UINT)i, &n);
+ i = (n == (UINT)i) ? 0 : -1;
+ }
+ pb->idx = i;
+ pb->nchr = nc + 1;
+}
+
+
+/* Flush remaining characters in the buffer */
+
+static int putc_flush (putbuff* pb)
+{
+ UINT nw;
+
+ if ( pb->idx >= 0 /* Flush buffered characters to the file */
+ && f_write(pb->fp, pb->buf, (UINT)pb->idx, &nw) == FR_OK
+ && (UINT)pb->idx == nw) return pb->nchr;
+ return EOF;
+}
+
+
+/* Initialize write buffer */
+
+static void putc_init (putbuff* pb, FIL* fp)
+{
+ mem_set(pb, 0, sizeof (putbuff));
+ pb->fp = fp;
+}
+
+
+
+int f_putc (
+ TCHAR c, /* A character to be output */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ putbuff pb;
+
+
+ putc_init(&pb, fp);
+ putc_bfd(&pb, c); /* Put the character */
+ return putc_flush(&pb);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Put a String to the File */
+/*-----------------------------------------------------------------------*/
+
+int f_puts (
+ const TCHAR* str, /* Pointer to the string to be output */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ putbuff pb;
+
+
+ putc_init(&pb, fp);
+ while (*str) putc_bfd(&pb, *str++); /* Put the string */
+ return putc_flush(&pb);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Put a Formatted String to the File */
+/*-----------------------------------------------------------------------*/
+
+int f_printf (
+ FIL* fp, /* Pointer to the file object */
+ const TCHAR* fmt, /* Pointer to the format string */
+ ... /* Optional arguments... */
+)
+{
+ va_list arp;
+ putbuff pb;
+ BYTE f, r;
+ UINT i, j, w;
+ DWORD v;
+ TCHAR c, d, str[32], *p;
+
+
+ putc_init(&pb, fp);
+
+ va_start(arp, fmt);
+
+ for (;;) {
+ c = *fmt++;
+ if (c == 0) break; /* End of string */
+ if (c != '%') { /* Non escape character */
+ putc_bfd(&pb, c);
+ continue;
+ }
+ w = f = 0;
+ c = *fmt++;
+ if (c == '0') { /* Flag: '0' padding */
+ f = 1; c = *fmt++;
+ } else {
+ if (c == '-') { /* Flag: left justified */
+ f = 2; c = *fmt++;
+ }
+ }
+ if (c == '*') { /* Minimum width by argument */
+ w = va_arg(arp, int);
+ c = *fmt++;
+ } else {
+ while (IsDigit(c)) { /* Minimum width */
+ w = w * 10 + c - '0';
+ c = *fmt++;
+ }
+ }
+ if (c == 'l' || c == 'L') { /* Type prefix: Size is long int */
+ f |= 4; c = *fmt++;
+ }
+ if (c == 0) break;
+ d = c;
+ if (IsLower(d)) d -= 0x20;
+ switch (d) { /* Atgument type is... */
+ case 'S' : /* String */
+ p = va_arg(arp, TCHAR*);
+ for (j = 0; p[j]; j++) ;
+ if (!(f & 2)) { /* Right padded */
+ while (j++ < w) putc_bfd(&pb, ' ') ;
+ }
+ while (*p) putc_bfd(&pb, *p++) ; /* String body */
+ while (j++ < w) putc_bfd(&pb, ' ') ; /* Left padded */
+ continue;
+
+ case 'C' : /* Character */
+ putc_bfd(&pb, (TCHAR)va_arg(arp, int)); continue;
+
+ case 'B' : /* Unsigned binary */
+ r = 2; break;
+
+ case 'O' : /* Unsigned octal */
+ r = 8; break;
+
+ case 'D' : /* Signed decimal */
+ case 'U' : /* Unsigned decimal */
+ r = 10; break;
+
+ case 'X' : /* Unsigned hexdecimal */
+ r = 16; break;
+
+ default: /* Unknown type (pass-through) */
+ putc_bfd(&pb, c); continue;
+ }
+
+ /* Get an argument and put it in numeral */
+ v = (f & 4) ? (DWORD)va_arg(arp, long) : ((d == 'D') ? (DWORD)(long)va_arg(arp, int) : (DWORD)va_arg(arp, unsigned int));
+ if (d == 'D' && (v & 0x80000000)) {
+ v = 0 - v;
+ f |= 8;
+ }
+ i = 0;
+ do {
+ d = (TCHAR)(v % r); v /= r;
+ if (d > 9) d += (c == 'x') ? 0x27 : 0x07;
+ str[i++] = d + '0';
+ } while (v && i < sizeof str / sizeof *str);
+ if (f & 8) str[i++] = '-';
+ j = i; d = (f & 1) ? '0' : ' ';
+ if (!(f & 2)) {
+ while (j++ < w) putc_bfd(&pb, d); /* Right pad */
+ }
+ do {
+ putc_bfd(&pb, str[--i]); /* Number body */
+ } while (i);
+ while (j++ < w) putc_bfd(&pb, d); /* Left pad */
+ }
+
+ va_end(arp);
+
+ return putc_flush(&pb);
+}
+
+#endif /* !FF_FS_READONLY */
+#endif /* FF_USE_STRFUNC */
+
+
+
+#if FF_CODE_PAGE == 0
+/*-----------------------------------------------------------------------*/
+/* Set Active Codepage for the Path Name */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_setcp (
+ WORD cp /* Value to be set as active code page */
+)
+{
+ static const WORD validcp[] = { 437, 720, 737, 771, 775, 850, 852, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0};
+ static const BYTE* const tables[] = {Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0};
+ UINT i;
+
+
+ for (i = 0; validcp[i] != 0 && validcp[i] != cp; i++) ; /* Find the code page */
+ if (validcp[i] != cp) return FR_INVALID_PARAMETER; /* Not found? */
+
+ CodePage = cp;
+ if (cp >= 900) { /* DBCS */
+ ExCvt = 0;
+ DbcTbl = tables[i];
+ } else { /* SBCS */
+ ExCvt = tables[i];
+ DbcTbl = 0;
+ }
+ return FR_OK;
+}
+#endif /* FF_CODE_PAGE == 0 */
+
diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ff.h b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ff.h
new file mode 100644
index 00000000..d4b9e9fc
--- /dev/null
+++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ff.h
@@ -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
+typedef unsigned __int64 QWORD;
+#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
+#define FF_INTDEF 2
+#include